diff --git a/.changeset/four-beers-like.md b/.changeset/four-beers-like.md
new file mode 100644
index 0000000000..967d76af66
--- /dev/null
+++ b/.changeset/four-beers-like.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: prevent hydration error on async `{@html ...}`
diff --git a/.changeset/spicy-teeth-tan.md b/.changeset/spicy-teeth-tan.md
deleted file mode 100644
index c7efa65130..0000000000
--- a/.changeset/spicy-teeth-tan.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: defer batch resolution until earlier intersecting batches have committed
diff --git a/.changeset/tasty-carrots-tie.md b/.changeset/tasty-carrots-tie.md
deleted file mode 100644
index 5d6cb8025d..0000000000
--- a/.changeset/tasty-carrots-tie.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: properly invoke `iterator.return()` during reactivity loss check
diff --git a/documentation/docs/02-runes/03-$derived.md b/documentation/docs/02-runes/03-$derived.md
index 35cb6c1912..f85ba90baa 100644
--- a/documentation/docs/02-runes/03-$derived.md
+++ b/documentation/docs/02-runes/03-$derived.md
@@ -51,6 +51,17 @@ In essence, `$derived(expression)` is equivalent to `$derived.by(() => expressio
Anything read synchronously inside the `$derived` expression (or `$derived.by` function body) is considered a _dependency_ of the derived state. When the state changes, the derived will be marked as _dirty_ and recalculated when it is next read.
+In addition, if an expression contains an [`await`](await-expressions), Svelte transforms it such that any state _after_ the `await` is also tracked — in other words, in a case like this...
+
+```js
+let a = Promise.resolve(1);
+let b = 2;
+// ---cut---
+let total = $derived(await a + b);
+```
+
+...both `a` and `b` are tracked, even though `b` is only read once `a` has resolved, after the initial execution. (This does not apply to `await` in functions that are called by the expression, only the expression itself.)
+
To exempt a piece of state from being treated as a dependency, use [`untrack`](svelte#untrack).
## Overriding derived values
diff --git a/documentation/docs/02-runes/04-$effect.md b/documentation/docs/02-runes/04-$effect.md
index d41c5b8e6a..a13fc7bc46 100644
--- a/documentation/docs/02-runes/04-$effect.md
+++ b/documentation/docs/02-runes/04-$effect.md
@@ -41,9 +41,11 @@ You can use `$effect` anywhere, not just at the top level of a component, as lon
> [!NOTE] Svelte uses effects internally to represent logic and expressions in your template — this is how `
hello {name}! ` updates when `name` changes.
-An effect can return a _teardown function_ which will run immediately before the effect re-runs ([demo](/playground/untitled#H4sIAAAAAAAAE42SQVODMBCF_8pOxkPRKq3HCsx49K4n64xpskjGkDDJ0tph-O8uINo6HjxB3u7HvrehE07WKDbiyZEhi1osRWksRrF57gQdm6E2CKx_dd43zU3co6VB28mIf-nKO0JH_BmRRRVMQ8XWbXkAgfKtI8jhIpIkXKySu7lSG2tNRGZ1_GlYr1ZTD3ddYFmiosUigbyAbpC2lKbwWJkIB8ZhhxBQBWRSw6FCh3sM8GrYTthL-wqqku4N44TyqEgwF3lmRHr4Op0PGXoH31c5rO8mqV-eOZ49bikgtcHBL55tmhIkEMqg_cFB2TpFxjtg703we6NRL8HQFCS07oSUCZi6Rm04lz1yytIHBKoQpo1w6Gsm4gmyS8b8Y5PydeMdX8gwS2Ok4I-ov5NZtvQde95GMsccn_1wzNKfu3RZtS66cSl9lvL7qO1aIk7knbJGvefdtIOzi73M4bYvovUHDFk6AcX_0HRESxnpBOW_jfCDxIZCi_1L_wm4xGQ60wIAAA==)).
+An effect can return a _teardown function_ which will run immediately before the effect re-runs:
+
```svelte
+
+
a++}>a++
b++}>b++
@@ -236,6 +254,7 @@ When using [`await`](await-expressions) in components, the `$effect.pending()` r
pending promises: {$effect.pending()}
{/if}
```
+
## `$effect.root`
@@ -285,9 +304,11 @@ In general, `$effect` is best considered something of an escape hatch — useful
If you're using an effect because you want to be able to reassign the derived value (to build an optimistic UI, for example) note that [deriveds can be directly overridden]($derived#Overriding-derived-values) as of Svelte 5.25.
-You might be tempted to do something convoluted with effects to link one value to another. The following example shows two inputs for "money spent" and "money left" that are connected to each other. If you update one, the other should update accordingly. Don't use effects for this ([demo](/playground/untitled#H4sIAAAAAAAAE5WRTWrDMBCFryKGLBJoY3fRjWIHeoiu6i6UZBwEY0VE49TB-O6VxrFTSih0qe_Ne_OjHpxpEDS8O7ZMeIAnqC1hAP3RA1990hKI_Fb55v06XJA4sZ0J-IjvT47RcYyBIuzP1vO2chVHHFjxiQ2pUr3k-SZRQlbBx_LIFoEN4zJfzQph_UMQr4hRXmBd456Xy5Uqt6pPKHmkfmzyPAZL2PCnbRpg8qWYu63I7lu4gswOSRYqrPNt3CgeqqzgbNwRK1A76w76YqjFspfcQTWmK3vJHlQm1puSTVSeqdOc_r9GaeCHfUSY26TXry6Br4RSK3C6yMEGT-aqVU3YbUZ2NF6rfP2KzXgbuYzY46czdgyazy0On_FlLH3F-UDXhgIO35UGlA1rAgAA)):
+You might be tempted to do something convoluted with effects to link one value to another. The following example shows two inputs for "money spent" and "money left" that are connected to each other. If you update one, the other should update accordingly. Instead of using effects for this...
+
```svelte
+
+
+
+```
```svelte
@@ -163,6 +179,7 @@ The fallback value of a prop not declared with `$bindable` is left untouched —
clicks: {object.count}
```
+
In summary: don't mutate props. Either use callback props to communicate changes, or — if parent and child should share the same object — use the [`$bindable`]($bindable) rune.
diff --git a/documentation/docs/02-runes/07-$inspect.md b/documentation/docs/02-runes/07-$inspect.md
index f67e250b45..00857f3ef4 100644
--- a/documentation/docs/02-runes/07-$inspect.md
+++ b/documentation/docs/02-runes/07-$inspect.md
@@ -5,9 +5,11 @@ tags: rune-inspect
> [!NOTE] `$inspect` only works during development. In a production build it becomes a noop.
-The `$inspect` rune is roughly equivalent to `console.log`, with the exception that it will re-run whenever its argument changes. `$inspect` tracks reactive state deeply, meaning that updating something inside an object or array using fine-grained reactivity will cause it to re-fire ([demo](/playground/untitled#H4sIAAAAAAAACkWQ0YqDQAxFfyUMhSotdZ-tCvu431AXtGOqQ2NmmMm0LOK_r7Utfby5JzeXTOpiCIPKT5PidkSVq2_n1F7Jn3uIcEMSXHSw0evHpAjaGydVzbUQCmgbWaCETZBWMPlKj29nxBDaHj_edkAiu12JhdkYDg61JGvE_s2nR8gyuBuiJZuDJTyQ7eE-IEOzog1YD80Lb0APLfdYc5F9qnFxjiKWwbImo6_llKRQVs-2u91c_bD2OCJLkT3JZasw7KLA2XCX31qKWE6vIzNk1fKE0XbmYrBTufiI8-_8D2cUWBA_AQAA)):
+The `$inspect` rune is roughly equivalent to `console.log`, with the exception that it will re-run whenever its argument changes. `$inspect` tracks reactive state deeply, meaning that updating something inside an object or array using fine-grained reactivity will cause it to re-fire:
+
```svelte
+
@@ -71,6 +73,7 @@ Snippets can be declared anywhere inside your component. They can reference valu
{@render hello('alice')}
{@render hello('bob')}
```
+
...and they are 'visible' to everything in the same lexical scope (i.e. siblings, and children of those siblings):
@@ -91,9 +94,11 @@ Snippets can be declared anywhere inside your component. They can reference valu
{@render x()}
```
-Snippets can reference themselves and each other ([demo](/playground/untitled#H4sIAAAAAAAAE2WPTQqDMBCFrxLiRqH1Zysi7TlqF1YnENBJSGJLCYGeo5tesUeosfYH3c2bee_jjaWMd6BpfrAU6x5oTvdS0g01V-mFPkNnYNRaDKrxGxto5FKCIaeu1kYwFkauwsoUWtZYPh_3W5FMY4U2mb3egL9kIwY0rbhgiO-sDTgjSEqSTvIDs-jiOP7i_MHuFGAL6p9BtiSbOTl0GtzCuihqE87cqtyam6WRGz_vRcsZh5bmRg3gju4Fptq_kzQBAAA=)):
+Snippets can reference themselves and each other:
+
```svelte
+
{#snippet blastoff()}
🚀
{/snippet}
@@ -109,14 +114,17 @@ Snippets can reference themselves and each other ([demo](/playground/untitled#H4
{@render countdown(10)}
```
+
## Passing snippets to components
### Explicit props
-Within the template, snippets are values just like any other. As such, they can be passed to components as props ([demo](/playground/untitled#H4sIAAAAAAAAE3VS247aMBD9lZGpBGwDASRegonaPvQL2qdlH5zYEKvBNvbQLbL875VzAcKyj3PmzJnLGU8UOwqSkd8KJdaCk4TsZS0cyV49wYuJuQiQpGd-N2bu_ooaI1YwJ57hpVYoFDqSEepKKw3mO7VDeTTaIvxiRS1gb_URxvO0ibrS8WanIrHUyiHs7Vmigy28RmyHHmKvDMbMmFq4cQInvGSwTsBYWYoMVhCSB2rBFFPsyl0uruTlR3JZCWvlTXl1Yy_mawiR_rbZKZrellJ-5JQ0RiBUgnFhJ9OGR7HKmwVoilXeIye8DOJGfYCgRlZ3iE876TBsZPX7hPdteO75PC4QaIo8vwNPePmANQ2fMeEFHrLD7rR1jTNkW986E8C3KwfwVr8HSHOSEBT_kGRozyIkn_zQveXDL3rIfPJHtUDwzShJd_Qk3gQCbOGLsdq4yfTRJopRuin3I7nv6kL7ARRjmLdBDG3uv1mhuLA3V2mKtqNEf_oCn8p9aN-WYqH5peP4kWBl1UwJzAEPT9U7K--0fRrrWnPTXpCm1_EVdXjpNmlA8G1hPPyM1fKgMqjFHjctXGjLhZ05w0qpDhksGrybuNEHtJnCalZWsuaTlfq6nPaaBSv_HKw-K57BjzOiVj9ZKQYKzQjZodYFqydYTRN4gPhVzTDO2xnma3HsVWjaLjT8nbfwHy7Q5f2dBAAA)):
+Within the template, snippets are values just like any other. As such, they can be passed to components as props:
+
```svelte
+
+
+
+ {#if header}
+
+ {@render header()}
+
+ {/if}
+
+
+ {#each data as d}
+ {@render row(d)}
+ {/each}
+
+
+
+
```
+
Think about it like passing content instead of data to a component. The concept is similar to slots in web components.
### Implicit props
-As an authoring convenience, snippets declared directly _inside_ a component implicitly become props _on_ the component ([demo](/playground/untitled#H4sIAAAAAAAAE3VSTa_aMBD8Kyu_SkAbCA-JSzBR20N_QXt6vIMTO8SqsY29tI2s_PcqTiB8vaPHs7MzuxuIZgdBMvJLo0QlOElIJZXwJHsLBBvb_XUASc7Mb9Yu_B-hsMMK5sUzvDQahUZPMkJ96aTFfKd3KA_WOISfrFACKmcOMFmk8TWUTjY73RFLoz1C5U4SPWzhrcN2GKDrlcGEWauEnyRwxCaDdQLWyVJksII2uaMWTDPNLtzX5YX8-kgua-GcHJVXI3u5WEPb0d83O03TMZSmfRzOkG1Db7mNacOL19JagVALxoWbztq-H8U6j0SaYp2P2BGbOyQ2v8PQIFMXLKRDk177pq0zf6d8bMrzwBdd0pamyPMb-IjNEzS2f86Gz_Dwf-2F9nvNSUJQ_EOSoTuJNvngqK5v4Pas7n4-OCwlEEJcQTIMO-nSQwtb-GSdsX46e9gbRoP9yGQ11I0rEuycunu6PHx1QnPhxm3SFN15MOlYEFJZtf0dUywMbwZOeBGsrKNLYB54-1R9WNqVdki7usim6VmQphf7mnpshiQRhNAXdoOfMyX3OgMlKtz0cGEcF27uLSul3mewjPjgOOoDukxjPS9rqfh0pb-8zs6aBSt_7505aZ7B9xOi0T9YKW4UooVsr0zB1BTrWQJ3EL-oWcZ572GxFoezCk37QLe3897-B2i2U62uBAAA)):
+As an authoring convenience, snippets declared directly _inside_ a component implicitly become props _on_ the component:
+
```svelte
-
+
+
+
{#snippet header()}
fruit
@@ -169,12 +225,54 @@ As an authoring convenience, snippets declared directly _inside_ a component imp
```
+```svelte
+
+
+
+
+ {#if header}
+
+ {@render header()}
+
+ {/if}
+
+
+ {#each data as d}
+ {@render row(d)}
+ {/each}
+
+
+
+
+```
+
+
### Implicit `children` snippet
-Any content inside the component tags that is _not_ a snippet declaration implicitly becomes part of the `children` snippet ([demo](/playground/untitled#H4sIAAAAAAAAE3WOQQrCMBBFrzIMggql3ddY1Du4si5sOmIwnYRkFKX07lKqglqX8_7_w2uRDw1hjlsWI5ZqTPBoLEXMdy3K3fdZDzB5Ndfep_FKVnpWHSKNce1YiCVijirqYLwUJQOYxrsgsLmIOIZjcA1M02w4n-PpomSVvTclqyEutDX6DA2pZ7_ABIVugrmEC3XJH92P55_G39GodCmWBFrQJ2PrQAwdLGHig_NxNv9xrQa1dhWIawrv1Wzeqawa8953D-8QOmaEAQAA)):
+Any content inside the component tags that is _not_ a snippet declaration implicitly becomes part of the `children` snippet:
+
```svelte
+
+
click me
```
@@ -187,6 +285,7 @@ Any content inside the component tags that is _not_ a snippet declaration implic
{@render children()}
```
+
> [!NOTE] Note that you cannot have a prop called `children` if you also have content inside the component — for this reason, you should avoid having props with that name
@@ -256,9 +355,21 @@ We can tighten things up further by declaring a generic, so that `data` and `row
## Exporting snippets
-Snippets declared at the top level of a `.svelte` file can be exported from a `
+
+{@render add(1, 2)}
+
+```
```svelte
+
@@ -267,6 +378,7 @@ Snippets declared at the top level of a `.svelte` file can be exported from a `<
{a} + {b} = {a + b}
{/snippet}
```
+
> [!NOTE]
> This requires Svelte 5.5.0 or newer
diff --git a/documentation/docs/03-template-syntax/12-bind.md b/documentation/docs/03-template-syntax/12-bind.md
index be84969b87..e8164149db 100644
--- a/documentation/docs/03-template-syntax/12-bind.md
+++ b/documentation/docs/03-template-syntax/12-bind.md
@@ -54,9 +54,11 @@ A `bind:value` directive on an ` ` element binds the input's `value` prope
{message}
```
-In the case of a numeric input (`type="number"` or `type="range"`), the value will be coerced to a number ([demo](/playground/untitled#H4sIAAAAAAAAE6WPwYoCMQxAfyWEPeyiOOqx2w74Hds9pBql0IllmhGXYf5dKqwiyILsLXnwwsuI-5i4oPkaUX8yo7kCnKNQV7dNzoty4qSVBSr8jG-Poixa0KAt2z5mbb14TaxA4OCtKCm_rz4-f2m403WltrlrYhMFTtcLNkoeFGqZ8yhDF7j3CCHKzpwoDexGmqCL4jwuPUJHZ-dxVcfmyYGe5MAv-La5pbxYFf5Z9Zf_UJXb-sEMquFgJJhBmGyTW5yj8lnRaD_w9D1dAKSSj7zqAQAA)):
+In the case of a numeric input (`type="number"` or `type="range"`), the value will be coerced to a number:
+
```svelte
+
+Customize your burrito
+
Plain
Whole wheat
@@ -165,7 +171,17 @@ Inputs that work together can use `bind:group` ([demo](/playground/untitled#H4sI
Beans
Cheese
Guac (extra)
+
+Tortilla: {tortilla}
+Fillings: {fillings.join(', ') || 'None'}
+
+
```
+
> [!NOTE] `bind:group` only works if the inputs are in the same Svelte component.
diff --git a/documentation/docs/03-template-syntax/19-await-expressions.md b/documentation/docs/03-template-syntax/19-await-expressions.md
index 2f73f6a47c..04437f5ee3 100644
--- a/documentation/docs/03-template-syntax/19-await-expressions.md
+++ b/documentation/docs/03-template-syntax/19-await-expressions.md
@@ -25,9 +25,11 @@ The experimental flag will be removed in Svelte 6.
## Synchronized updates
-When an `await` expression depends on a particular piece of state, changes to that state will not be reflected in the UI until the asynchronous work has completed, so that the UI is not left in an inconsistent state. In other words, in an example like [this](/playground/untitled#H4sIAAAAAAAAE42QsWrDQBBEf2VZUkhYRE4gjSwJ0qVMkS6XYk9awcFpJe5Wdoy4fw-ycdykSPt2dpiZFYVGxgrf2PsJTlPwPWTcO-U-xwIH5zli9bminudNtwEsbl-v8_wYj-x1Y5Yi_8W7SZRFI1ZYxy64WVsjRj0rEDTwEJWUs6f8cKP2Tp8vVIxSPEsHwyKdukmA-j6jAmwO63Y1SidyCsIneA_T6CJn2ZBD00Jk_XAjT4tmQwEv-32eH6AsgYK6wXWOPPTs6Xy1CaxLECDYgb3kSUbq8p5aaifzorCt0RiUZbQcDIJ10ldH8gs3K6X2Xzqbro5zu1KCHaw2QQPrtclvwVSXc2sEC1T-Vqw0LJy-ClRy_uSkx2ogHzn9ADZ1CubKAQAA)...
+When an `await` expression depends on a particular piece of state, changes to that state will not be reflected in the UI until the asynchronous work has completed, so that the UI is not left in an inconsistent state. In other words, in an example like this...
+
```svelte
+
+
+
+
+
+```
+
+```svelte
+
+
+
+{@render children()}
+```
+
+```svelte
+
+
+
+hello {user.name}, inside Child.svelte
+```
+
+```ts
+/// file: context.ts
+import { createContext } from 'svelte';
+
+interface User {
+ name: string;
+}
+
+export const [getUserContext, setUserContext] = createContext();
+```
+
+
+> [!NOTE] `createContext` was added in version 5.40. If you are using an earlier version of Svelte, you must use `setContext` and `getContext` instead.
+
+This is particularly useful when `Parent.svelte` is not directly aware of `Child.svelte`, but instead renders it as part of a `children` [snippet](snippet) as shown above.
+
+## `setContext` and `getContext`
+
+As an alternative to `createContext`, you can use `setContext` and `getContext` directly. The parent component sets context with `setContext(key, value)`...
```svelte
@@ -26,32 +85,28 @@ Context allows components to access values owned by parent components without pa
{message}, inside Child.svelte
```
-This is particularly useful when `Parent.svelte` is not directly aware of `Child.svelte`, but instead renders it as part of a `children` [snippet](snippet) ([demo](/playground/untitled#H4sIAAAAAAAAE42Q3W6DMAyFX8WyJgESK-oto6hTX2D3YxcM3IIUQpR40yqUd58CrCXsp7tL7HNsf2dAWXaEKR56yfTBGOOxFWQwfR6Qz8q1XAHjL-GjUhvzToJd7bU09FO9ctMkG0wxM5VuFeeFLLjtVK8ZnkpNkuGo-w6CTTJ9Z3PwsBAemlbUF934W8iy5DpaZtOUcU02-ZLcaS51jHEkTFm_kY1_wfOO8QnXrb8hBzDEc6pgZ4gFoyz4KgiD7nxfTe8ghqAhIfrJ46cTzVZBbkPlODVJsLCDO6V7ZcJoncyw1yRr0hd1GNn_ZbEM3I9i1bmVxOlWElUvDUNHxpQngt3C4CXzjS1rtvkw22wMrTRtTbC8Lkuabe7jvthPPe3DofYCAAA=)):
-
-```svelte
-
-
-
-```
-
The key (`'my-context'`, in the example above) and the context itself can be any JavaScript value.
+> [!NOTE] `createContext` is preferred since it provides better type safety and makes it unnecessary to use keys.
+
In addition to [`setContext`](svelte#setContext) and [`getContext`](svelte#getContext), Svelte exposes [`hasContext`](svelte#hasContext) and [`getAllContexts`](svelte#getAllContexts) functions.
## Using context with state
-You can store reactive state in context ([demo](/playground/untitled#H4sIAAAAAAAAE41R0W6DMAz8FSuaBNUQdK8MkKZ-wh7HHihzu6hgosRMm1D-fUpSVNq12x4iEvvOx_kmQU2PIhfP3DCCJGgHYvxkkYid7NCI_GUS_KUcxhVEMjOelErNB3bsatvG4LW6n0ZsRC4K02qpuKqpZtmrQTNMYJA3QRAs7PTQQxS40eMCt3mX3duxnWb-lS5h7nTI0A4jMWoo4c44P_Hku-zrOazdy64chWo-ScfRkRgl8wgHKrLTH1OxHZkHgoHaTraHcopXUFYzPPVfuC_hwQaD1GrskdiNCdQwJljJqlvXfyqVsA5CGg0uRUQifHw56xFtciO75QrP07vo_JXf_tf8yK2ezDKY_ZWt_1y2qqYzv7bI1IW1V_sN19m-07wCAAA=))...
+You can store reactive state in context...
+
```svelte
+
counter.count += 1}>
@@ -61,12 +116,39 @@ You can store reactive state in context ([demo](/playground/untitled#H4sIAAAAAAA
+
+ counter.count = 0}>
+ reset
+
+```
+
+```svelte
+
+
+
+{counter.count}
+```
+
+```ts
+/// file: context.ts
+import { createContext } from 'svelte';
+
+interface Counter {
+ count: number;
+}
+
+export const [getCounter, setCounter] = createContext();
```
+
...though note that if you _reassign_ `counter` instead of updating it, you will 'break the link' — in other words instead of this...
```svelte
- counter = { count: 0 }}>
+ counter = { count: 0 } }>
reset
```
@@ -81,21 +163,7 @@ You can store reactive state in context ([demo](/playground/untitled#H4sIAAAAAAA
Svelte will warn you if you get it wrong.
-## Type-safe context
-
-As an alternative to using `setContext` and `getContext` directly, you can use them via `createContext`. This gives you type safety and makes it unnecessary to use a key:
-
-```ts
-/// file: context.ts
-// @filename: ambient.d.ts
-interface User {}
-
-// @filename: index.ts
-// ---cut---
-import { createContext } from 'svelte';
-
-export const [getUserContext, setUserContext] = createContext();
-```
+## Component testing
When writing [component tests](testing#Unit-and-component-tests-with-Vitest-Component-testing), it can be useful to create a wrapper component that sets the context in order to check the behaviour of a component that uses it. As of version 5.49, you can do this sort of thing:
@@ -140,7 +208,7 @@ export const myGlobalState = $state({
In many cases this is perfectly fine, but there is a risk: if you mutate the state during server-side rendering (which is discouraged, but entirely possible!)...
```svelte
-
+
* ```
*/
- static of(fn: () => U, options?: SpringOpts): Spring;
+ static of(fn: () => U, options?: SpringOptions): Spring;
/**
* Sets `spring.target` to `value` and returns a `Promise` that resolves if and when `spring.current` catches up to it.
@@ -63,7 +96,7 @@ export class Spring {
* If `options.preserveMomentum` is provided, the spring will continue on its current trajectory for
* the specified number of milliseconds. This is useful for things like 'fling' gestures.
*/
- set(value: T, options?: SpringUpdateOpts): Promise;
+ set(value: T, options?: SpringUpdateOptions): Promise;
damping: number;
precision: number;
@@ -81,8 +114,8 @@ export class Spring {
}
export interface Tweened extends Readable {
- set(value: T, opts?: TweenedOptions): Promise;
- update(updater: Updater, opts?: TweenedOptions): Promise;
+ set(value: T, opts?: TweenOptions): Promise;
+ update(updater: Updater, opts?: TweenOptions): Promise;
}
export { prefersReducedMotion, spring, tweened, Tween } from './index.js';
diff --git a/packages/svelte/src/motion/spring.js b/packages/svelte/src/motion/spring.js
index 44be1a501b..7198d60127 100644
--- a/packages/svelte/src/motion/spring.js
+++ b/packages/svelte/src/motion/spring.js
@@ -1,6 +1,6 @@
/** @import { Task } from '#client' */
-/** @import { SpringOpts, SpringUpdateOpts, TickContext } from './private.js' */
-/** @import { Spring as SpringStore } from './public.js' */
+/** @import { TickContext } from './private.js' */
+/** @import { Spring as SpringStore, SpringOptions, SpringUpdateOptions } from './public.js' */
import { writable } from '../store/shared/index.js';
import { loop } from '../internal/client/loop.js';
import { raf } from '../internal/client/timing.js';
@@ -62,7 +62,7 @@ function tick_spring(ctx, last_value, current_value, target_value) {
* @deprecated Use [`Spring`](https://svelte.dev/docs/svelte/svelte-motion#Spring) instead
* @template [T=any]
* @param {T} [value]
- * @param {SpringOpts} [opts]
+ * @param {SpringOptions} [opts]
* @returns {SpringStore}
*/
export function spring(value, opts = {}) {
@@ -83,7 +83,7 @@ export function spring(value, opts = {}) {
let cancel_task = false;
/**
* @param {T} new_value
- * @param {SpringUpdateOpts} opts
+ * @param {SpringUpdateOptions} opts
* @returns {Promise}
*/
function set(new_value, opts = {}) {
@@ -191,7 +191,7 @@ export class Spring {
/**
* @param {T} value
- * @param {SpringOpts} [options]
+ * @param {SpringOptions} [options]
*/
constructor(value, options = {}) {
this.#current = DEV ? tag(state(value), 'Spring.current') : state(value);
@@ -225,7 +225,7 @@ export class Spring {
* ```
* @template U
* @param {() => U} fn
- * @param {SpringOpts} [options]
+ * @param {SpringOptions} [options]
*/
static of(fn, options) {
const spring = new Spring(fn(), options);
@@ -293,7 +293,7 @@ export class Spring {
* the specified number of milliseconds. This is useful for things like 'fling' gestures.
*
* @param {T} value
- * @param {SpringUpdateOpts} [options]
+ * @param {SpringUpdateOptions} [options]
*/
set(value, options) {
this.#deferred?.reject(new Error('Aborted'));
diff --git a/packages/svelte/src/motion/tweened.js b/packages/svelte/src/motion/tweened.js
index 437c22ec3b..a24148d075 100644
--- a/packages/svelte/src/motion/tweened.js
+++ b/packages/svelte/src/motion/tweened.js
@@ -1,6 +1,5 @@
/** @import { Task } from '../internal/client/types' */
-/** @import { Tweened } from './public' */
-/** @import { TweenedOptions } from './private' */
+/** @import { Tweened, TweenOptions } from './public' */
import { writable } from '../store/shared/index.js';
import { raf } from '../internal/client/timing.js';
import { loop } from '../internal/client/loop.js';
@@ -84,7 +83,7 @@ function get_interpolator(a, b) {
* @deprecated Use [`Tween`](https://svelte.dev/docs/svelte/svelte-motion#Tween) instead
* @template T
* @param {T} [value]
- * @param {TweenedOptions} [defaults]
+ * @param {TweenOptions} [defaults]
* @returns {Tweened}
*/
export function tweened(value, defaults = {}) {
@@ -94,7 +93,7 @@ export function tweened(value, defaults = {}) {
let target_value = value;
/**
* @param {T} new_value
- * @param {TweenedOptions} [opts]
+ * @param {TweenOptions} [opts]
*/
function set(new_value, opts) {
target_value = new_value;
@@ -180,7 +179,7 @@ export class Tween {
#current;
#target;
- /** @type {TweenedOptions} */
+ /** @type {TweenOptions} */
#defaults;
/** @type {import('../internal/client/types').Task | null} */
@@ -188,7 +187,7 @@ export class Tween {
/**
* @param {T} value
- * @param {TweenedOptions} options
+ * @param {TweenOptions} options
*/
constructor(value, options = {}) {
this.#current = state(value);
@@ -216,7 +215,7 @@ export class Tween {
* ```
* @template U
* @param {() => U} fn
- * @param {TweenedOptions} [options]
+ * @param {TweenOptions} [options]
*/
static of(fn, options) {
const tween = new Tween(fn(), options);
@@ -233,7 +232,7 @@ export class Tween {
*
* If `options` are provided, they will override the tween's defaults.
* @param {T} value
- * @param {TweenedOptions} [options]
+ * @param {TweenOptions} [options]
* @returns
*/
set(value, options) {
diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js
index 191d53016a..ff540e3962 100644
--- a/packages/svelte/src/version.js
+++ b/packages/svelte/src/version.js
@@ -4,5 +4,5 @@
* The current version, as set in package.json.
* @type {string}
*/
-export const VERSION = '5.54.0';
+export const VERSION = '5.55.0';
export const PUBLIC_VERSION = '5';
diff --git a/packages/svelte/tests/hydration/samples/css-props-hmr/Component.svelte b/packages/svelte/tests/hydration/samples/css-props-hmr/Component.svelte
new file mode 100644
index 0000000000..ac73501cca
--- /dev/null
+++ b/packages/svelte/tests/hydration/samples/css-props-hmr/Component.svelte
@@ -0,0 +1,7 @@
+Hello
+
+
\ No newline at end of file
diff --git a/packages/svelte/tests/hydration/samples/css-props-hmr/_config.js b/packages/svelte/tests/hydration/samples/css-props-hmr/_config.js
new file mode 100644
index 0000000000..2cd696d584
--- /dev/null
+++ b/packages/svelte/tests/hydration/samples/css-props-hmr/_config.js
@@ -0,0 +1,7 @@
+import { test } from '../../test';
+
+export default test({
+ compileOptions: {
+ hmr: true
+ }
+});
diff --git a/packages/svelte/tests/hydration/samples/css-props-hmr/main.svelte b/packages/svelte/tests/hydration/samples/css-props-hmr/main.svelte
new file mode 100644
index 0000000000..eed9b57caf
--- /dev/null
+++ b/packages/svelte/tests/hydration/samples/css-props-hmr/main.svelte
@@ -0,0 +1,5 @@
+
+
+
\ No newline at end of file
diff --git a/packages/svelte/tests/print/samples/const-tag/output.svelte b/packages/svelte/tests/print/samples/const-tag/output.svelte
index d9d8addcbb..dded998a84 100644
--- a/packages/svelte/tests/print/samples/const-tag/output.svelte
+++ b/packages/svelte/tests/print/samples/const-tag/output.svelte
@@ -3,6 +3,6 @@
{#each boxes as box}
- {@const area = box.width * box.height;}
+ {@const area = box.width * box.height}
{box.width} * {box.height} = {area}
{/each}
diff --git a/packages/svelte/tests/runtime-runes/samples/async-hydrate-html-tag/_config.js b/packages/svelte/tests/runtime-runes/samples/async-hydrate-html-tag/_config.js
new file mode 100644
index 0000000000..cb73ab1833
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-hydrate-html-tag/_config.js
@@ -0,0 +1,10 @@
+import { tick } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+ mode: ['hydrate'],
+ async test({ assert, target }) {
+ await tick();
+ assert.htmlEqual(target.innerHTML, ``);
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/async-hydrate-html-tag/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-hydrate-html-tag/main.svelte
new file mode 100644
index 0000000000..cd34a1a16e
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-hydrate-html-tag/main.svelte
@@ -0,0 +1,14 @@
+
+
+
+
{@html await firstTest()}
+ {await otherTest()}
+
diff --git a/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-1/_config.js b/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-1/_config.js
new file mode 100644
index 0000000000..03d8e49b6f
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-1/_config.js
@@ -0,0 +1,107 @@
+import { tick } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+ async test({ assert, target }) {
+ await tick();
+ const [a_b, a_c, b_d, shift, pop] = target.querySelectorAll('button');
+
+ a_b.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ a 0 | b 0 | c 0 | d 0
+ a and b
+ a and c
+ b and d
+ shift
+ pop
+ `
+ );
+
+ a_c.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ a 0 | b 0 | c 0 | d 0
+ a and b
+ a and c
+ b and d
+ shift
+ pop
+ `
+ );
+
+ b_d.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ a 0 | b 0 | c 0 | d 0
+ a and b
+ a and c
+ b and d
+ shift
+ pop
+ `
+ );
+
+ shift.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ a 0 | b 0 | c 0 | d 0
+ a and b
+ a and c
+ b and d
+ shift
+ pop
+ `
+ );
+
+ shift.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ a 1 | b 1 | c 0 | d 0
+ a and b
+ a and c
+ b and d
+ shift
+ pop
+ `
+ );
+
+ shift.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ a 2 | b 1 | c 1 | d 0
+ a and b
+ a and c
+ b and d
+ shift
+ pop
+ `
+ );
+
+ shift.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ a 2 | b 2 | c 1 | d 1
+ a and b
+ a and c
+ b and d
+ shift
+ pop
+ `
+ );
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-1/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-1/main.svelte
new file mode 100644
index 0000000000..25dbe586ca
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-1/main.svelte
@@ -0,0 +1,26 @@
+
+
+a {await delay(a)} | b {await delay(b)} | c {c} | d {d}
+ {a++;b++;}}>
+ a and b
+
+ {a++;c++;}}>
+ a and c
+
+ {b++;d++;}}>
+ b and d
+
+ deferred.shift()?.()}>shift
+ deferred.pop()?.()}>pop
diff --git a/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-2/_config.js b/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-2/_config.js
new file mode 100644
index 0000000000..f4aff83aad
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-2/_config.js
@@ -0,0 +1,82 @@
+import { tick } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+ skip: true, // TODO works on https://github.com/sveltejs/svelte/pull/17971
+ async test({ assert, target }) {
+ await tick();
+ const [a_b, a_c, b_d, shift, pop] = target.querySelectorAll('button');
+
+ a_b.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ a 0 | b 0 | c 0 | d 0
+ a and b
+ a and c
+ b and d
+ shift
+ pop
+ `
+ );
+
+ a_c.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ a 0 | b 0 | c 0 | d 0
+ a and b
+ a and c
+ b and d
+ shift
+ pop
+ `
+ );
+
+ b_d.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ a 0 | b 0 | c 0 | d 0
+ a and b
+ a and c
+ b and d
+ shift
+ pop
+ `
+ );
+
+ pop.click(); // second b resolved, blocked on first batch because a still pending
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ a 0 | b 0 | c 0 | d 0
+ a and b
+ a and c
+ b and d
+ shift
+ pop
+ `
+ );
+
+ for (let i = 0; i < 3; i++) {
+ pop.click(); // second a resolved, first a/b now obsolete; empty queue
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ a 2 | b 2 | c 1 | d 1
+ a and b
+ a and c
+ b and d
+ shift
+ pop
+ `
+ );
+ }
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-2/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-2/main.svelte
new file mode 100644
index 0000000000..25dbe586ca
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-2/main.svelte
@@ -0,0 +1,26 @@
+
+
+a {await delay(a)} | b {await delay(b)} | c {c} | d {d}
+ {a++;b++;}}>
+ a and b
+
+ {a++;c++;}}>
+ a and c
+
+ {b++;d++;}}>
+ b and d
+
+ deferred.shift()?.()}>shift
+ deferred.pop()?.()}>pop
diff --git a/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-3/_config.js b/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-3/_config.js
new file mode 100644
index 0000000000..c9e7513b22
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-3/_config.js
@@ -0,0 +1,108 @@
+import { tick } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+ skip: true, // TODO works on https://github.com/sveltejs/svelte/pull/17971
+ async test({ assert, target }) {
+ await tick();
+ const [a_b, a_c, b_d, shift, pop] = target.querySelectorAll('button');
+
+ a_b.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ a 0 | b 0 | c 0 | d 0
+ a and b
+ a and c
+ b and d
+ shift
+ pop
+ `
+ );
+
+ a_c.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ a 0 | b 0 | c 0 | d 0
+ a and b
+ a and c
+ b and d
+ shift
+ pop
+ `
+ );
+
+ b_d.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ a 0 | b 0 | c 0 | d 0
+ a and b
+ a and c
+ b and d
+ shift
+ pop
+ `
+ );
+
+ shift.click(); // first a resolved, still pending: [b, a, b]
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ a 0 | b 0 | c 0 | d 0
+ a and b
+ a and c
+ b and d
+ shift
+ pop
+ `
+ );
+
+ pop.click(); // second b resolved, still pending: [b, a]
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ a 0 | b 0 | c 0 | d 0
+ a and b
+ a and c
+ b and d
+ shift
+ pop
+ `
+ );
+
+ shift.click(); // first b resolved, first + last batch settled, still pending: [a]
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ a 1 | b 2 | c 0 | d 1
+ a and b
+ a and c
+ b and d
+ shift
+ pop
+ `
+ );
+
+ shift.click(); // all resolved
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ a 2 | b 2 | c 1 | d 1
+ a and b
+ a and c
+ b and d
+ shift
+ pop
+ `
+ );
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-3/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-3/main.svelte
new file mode 100644
index 0000000000..25dbe586ca
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-3/main.svelte
@@ -0,0 +1,26 @@
+
+
+a {await delay(a)} | b {await delay(b)} | c {c} | d {d}
+ {a++;b++;}}>
+ a and b
+
+ {a++;c++;}}>
+ a and c
+
+ {b++;d++;}}>
+ b and d
+
+ deferred.shift()?.()}>shift
+ deferred.pop()?.()}>pop
diff --git a/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-4/_config.js b/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-4/_config.js
new file mode 100644
index 0000000000..472a3ebcaf
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-4/_config.js
@@ -0,0 +1,110 @@
+import { tick } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+ skip: true, // TODO works on https://github.com/sveltejs/svelte/pull/17971
+ async test({ assert, target }) {
+ await tick();
+ const [a_b, a_c, b_d, shift, pop] = target.querySelectorAll('button');
+
+ a_b.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ a 0 | b 0 | c 0 | d 0
+ a and b
+ a and c
+ b and d
+ shift
+ pop
+ `
+ );
+
+ a_c.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ a 0 | b 0 | c 0 | d 0
+ a and b
+ a and c
+ b and d
+ shift
+ pop
+ `
+ );
+
+ b_d.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ a 0 | b 0 | c 0 | d 0
+ a and b
+ a and c
+ b and d
+ shift
+ pop
+ `
+ );
+
+ shift.click(); // first a resolved, still pending: [b, a, b]
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ a 0 | b 0 | c 0 | d 0
+ a and b
+ a and c
+ b and d
+ shift
+ pop
+ `
+ );
+
+ pop.click(); // second b resolved, still pending: [b, a]
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ a 0 | b 0 | c 0 | d 0
+ a and b
+ a and c
+ b and d
+ shift
+ pop
+ `
+ );
+
+ pop.click(); // second a resolved, first a/b now obsolete
+ // TODO would be nice to show final result here already, right now it doesn't because
+ // we have no handle on the already resolved first a anymore
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ a 0 | b 0 | c 0 | d 0
+ a and b
+ a and c
+ b and d
+ shift
+ pop
+ `
+ );
+
+ shift.click(); // queue empty
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ a 2 | b 2 | c 1 | d 1
+ a and b
+ a and c
+ b and d
+ shift
+ pop
+ `
+ );
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-4/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-4/main.svelte
new file mode 100644
index 0000000000..25dbe586ca
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-4/main.svelte
@@ -0,0 +1,26 @@
+
+
+a {await delay(a)} | b {await delay(b)} | c {c} | d {d}
+ {a++;b++;}}>
+ a and b
+
+ {a++;c++;}}>
+ a and c
+
+ {b++;d++;}}>
+ b and d
+
+ deferred.shift()?.()}>shift
+ deferred.pop()?.()}>pop
diff --git a/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-5/_config.js b/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-5/_config.js
new file mode 100644
index 0000000000..d03f3cfbb8
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-5/_config.js
@@ -0,0 +1,118 @@
+import { tick } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+ async test({ assert, target }) {
+ await tick();
+ const [a, c, shift, pop] = target.querySelectorAll('button');
+
+ a.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ a 0 | b 0 | c 0 | d 0
+ a++
+ c++
+ shift
+ pop
+ `
+ );
+
+ c.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ a 0 | b 0 | c 0 | d 0
+ a++
+ c++
+ shift
+ pop
+ `
+ );
+
+ shift.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ a 0 | b 0 | c 0 | d 0
+ a++
+ c++
+ shift
+ pop
+ `
+ );
+
+ // how it's on main
+
+ shift.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ a 0 | b 0 | c 1 | d 1
+ a++
+ c++
+ shift
+ pop
+ `
+ );
+
+ shift.click();
+ await tick();
+ shift.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ a 1 | b 2 | c 1 | d 3
+ a++
+ c++
+ shift
+ pop
+ `
+ );
+
+ // how it's on https://github.com/sveltejs/svelte/pull/17971
+ // shift.click();
+ // await tick();
+ // assert.htmlEqual(
+ // target.innerHTML,
+ // `
+ // a 0 | b 0 | c 0 | d 0
+ // a++
+ // c++
+ // shift
+ // pop
+ // `
+ // );
+
+ // shift.click();
+ // await tick();
+ // assert.htmlEqual(
+ // target.innerHTML,
+ // `
+ // a 1 | b 2 | c 0 | d 2
+ // a++
+ // c++
+ // shift
+ // pop
+ // `
+ // );
+
+ // shift.click();
+ // await tick();
+ // assert.htmlEqual(
+ // target.innerHTML,
+ // `
+ // a 1 | b 2 | c 1 | d 3
+ // a++
+ // c++
+ // shift
+ // pop
+ // `
+ // );
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-5/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-5/main.svelte
new file mode 100644
index 0000000000..cf028718c2
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-5/main.svelte
@@ -0,0 +1,23 @@
+
+
+a {a} | b {b} | c {c} | d {d}
+ {a++;}}>
+ a++
+
+ {c++;}}>
+ c++
+
+ deferred.shift()?.()}>shift
+ deferred.pop()?.()}>pop
\ No newline at end of file
diff --git a/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-6/_config.js b/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-6/_config.js
new file mode 100644
index 0000000000..27152889a2
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-6/_config.js
@@ -0,0 +1,76 @@
+import { tick } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+ async test({ assert, target }) {
+ await tick();
+ const [a, c, shift, pop] = target.querySelectorAll('button');
+
+ a.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ a 0 | b 0 | c 0 | d 0
+ a++
+ c++
+ shift
+ pop
+ `
+ );
+
+ c.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ a 0 | b 0 | c 0 | d 0
+ a++
+ c++
+ shift
+ pop
+ `
+ );
+
+ // Although the second batch is eventually connected to the first one, we can't see that
+ // at this point yet and so the second one flushes right away.
+ pop.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ a 0 | b 0 | c 1 | d 1
+ a++
+ c++
+ shift
+ pop
+ `
+ );
+
+ pop.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ a 0 | b 0 | c 1 | d 1
+ a++
+ c++
+ shift
+ pop
+ `
+ );
+
+ pop.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ a 1 | b 2 | c 1 | d 3
+ a++
+ c++
+ shift
+ pop
+ `
+ );
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-6/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-6/main.svelte
new file mode 100644
index 0000000000..cf028718c2
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-6/main.svelte
@@ -0,0 +1,23 @@
+
+
+a {a} | b {b} | c {c} | d {d}
+ {a++;}}>
+ a++
+
+ {c++;}}>
+ c++
+
+ deferred.shift()?.()}>shift
+ deferred.pop()?.()}>pop
\ No newline at end of file
diff --git a/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-7/_config.js b/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-7/_config.js
new file mode 100644
index 0000000000..b1bf53a2fc
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-7/_config.js
@@ -0,0 +1,129 @@
+import { tick } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+ async test({ assert, target }) {
+ await tick();
+ const [a, c, shift, pop] = target.querySelectorAll('button');
+
+ a.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ a 0 | b 0 | c 0 | d 0
+ a++
+ c++
+ shift
+ pop
+ `
+ );
+
+ c.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ a 0 | b 0 | c 0 | d 0
+ a++
+ c++
+ shift
+ pop
+ `
+ );
+
+ shift.click(); // schedules second step of first batch and schedules rerun of second batch
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ a 0 | b 0 | c 0 | d 0
+ a++
+ c++
+ shift
+ pop
+ `
+ );
+
+ // how it's on main
+
+ pop.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ a 1 | b 2 | c 0 | d 2
+ a++
+ c++
+ shift
+ pop
+ `
+ );
+
+ shift.click(); // obsolete second batch promise (already rejected)
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ a 1 | b 2 | c 0 | d 2
+ a++
+ c++
+ shift
+ pop
+ `
+ );
+
+ shift.click(); // first batch resolves
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ a 1 | b 2 | c 1 | d 3
+ a++
+ c++
+ shift
+ pop
+ `
+ );
+
+ // how it's on https://github.com/sveltejs/svelte/pull/17971
+ // pop.click(); // second batch resolves but knows it needs to wait on first batch
+ // await tick();
+ // assert.htmlEqual(
+ // target.innerHTML,
+ // `
+ // a 0 | b 0 | c 0 | d 0
+ // a++
+ // c++
+ // shift
+ // pop
+ // `
+ // );
+
+ // shift.click(); // obsolete second batch promise (already rejected)
+ // await tick();
+ // assert.htmlEqual(
+ // target.innerHTML,
+ // `
+ // a 0 | b 0 | c 0 | d 0
+ // a++
+ // c++
+ // shift
+ // pop
+ // `
+ // );
+
+ // shift.click(); // first batch resolves, with it second can now resolve as well
+ // await tick();
+ // assert.htmlEqual(
+ // target.innerHTML,
+ // `
+ // a 1 | b 2 | c 1 | d 3
+ // a++
+ // c++
+ // shift
+ // pop
+ // `
+ // );
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-7/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-7/main.svelte
new file mode 100644
index 0000000000..cf028718c2
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-7/main.svelte
@@ -0,0 +1,23 @@
+
+
+a {a} | b {b} | c {c} | d {d}
+ {a++;}}>
+ a++
+
+ {c++;}}>
+ c++
+
+ deferred.shift()?.()}>shift
+ deferred.pop()?.()}>pop
\ No newline at end of file
diff --git a/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-fork-1/_config.js b/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-fork-1/_config.js
new file mode 100644
index 0000000000..07ce7e340f
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-fork-1/_config.js
@@ -0,0 +1,34 @@
+import { tick } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+ async test({ assert, target }) {
+ await tick();
+ const [a_b_fork, a_c, shift, pop, commit] = target.querySelectorAll('button');
+ const [p] = target.querySelectorAll('p');
+
+ a_b_fork.click();
+ await tick();
+ assert.htmlEqual(p.innerHTML, 'a 0 | b 0 | c 0');
+
+ a_c.click();
+ await tick();
+ assert.htmlEqual(p.innerHTML, 'a 0 | b 0 | c 0');
+
+ pop.click();
+ await tick();
+ assert.htmlEqual(p.innerHTML, 'a 1 | b 0 | c 1');
+
+ shift.click();
+ await tick();
+ assert.htmlEqual(p.innerHTML, 'a 1 | b 0 | c 1');
+
+ shift.click();
+ await tick();
+ assert.htmlEqual(p.innerHTML, 'a 1 | b 0 | c 1');
+
+ commit.click();
+ await tick();
+ assert.htmlEqual(p.innerHTML, 'a 1 | b 1 | c 1');
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-fork-1/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-fork-1/main.svelte
new file mode 100644
index 0000000000..60ba74fb8e
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-fork-1/main.svelte
@@ -0,0 +1,27 @@
+
+
+a {await delay(a)} | b {await delay(b)} | c {c}
+
+ {f = fork(() => {a++;b++;});}}>
+ a and b (fork)
+
+ {a++;c++;}}>
+ a and c
+
+ deferred.shift()?.()}>shift
+ deferred.pop()?.()}>pop
+ f.commit()}>commit fork
diff --git a/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-fork-2/_config.js b/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-fork-2/_config.js
new file mode 100644
index 0000000000..180a190dae
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-fork-2/_config.js
@@ -0,0 +1,42 @@
+import { tick } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+ async test({ assert, target }) {
+ await tick();
+ const [a_b_fork, a_c, b_d, shift, pop, commit] = target.querySelectorAll('button');
+ const [p] = target.querySelectorAll('p');
+
+ a_b_fork.click();
+ await tick();
+ assert.htmlEqual(p.innerHTML, 'a 0 | b 0 | c 0 | d 0');
+
+ a_c.click();
+ await tick();
+ assert.htmlEqual(p.innerHTML, 'a 0 | b 0 | c 0 | d 0');
+
+ b_d.click();
+ await tick();
+ assert.htmlEqual(p.innerHTML, 'a 0 | b 0 | c 0 | d 0');
+
+ pop.click();
+ await tick();
+ assert.htmlEqual(p.innerHTML, 'a 0 | b 1 | c 0 | d 1');
+
+ pop.click();
+ await tick();
+ assert.htmlEqual(p.innerHTML, 'a 1 | b 1 | c 1 | d 1');
+
+ shift.click();
+ await tick();
+ assert.htmlEqual(p.innerHTML, 'a 1 | b 1 | c 1 | d 1');
+
+ shift.click();
+ await tick();
+ assert.htmlEqual(p.innerHTML, 'a 1 | b 1 | c 1 | d 1');
+
+ commit.click();
+ await tick();
+ assert.htmlEqual(p.innerHTML, 'a 1 | b 1 | c 1 | d 1');
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-fork-2/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-fork-2/main.svelte
new file mode 100644
index 0000000000..6d1c4ab418
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-fork-2/main.svelte
@@ -0,0 +1,31 @@
+
+
+a {await delay(a)} | b {await delay(b)} | c {c} | d {d}
+
+ {f = fork(() => {a++;b++;});}}>
+ a and b (fork)
+
+ {a++;c++;}}>
+ a and c
+
+ {b++;d++;}}>
+ b and d
+
+ deferred.shift()?.()}>shift
+ deferred.pop()?.()}>pop
+ f.commit()}>commit fork
diff --git a/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-fork-3/_config.js b/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-fork-3/_config.js
new file mode 100644
index 0000000000..61360d8dc9
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-fork-3/_config.js
@@ -0,0 +1,43 @@
+import { tick } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+ skip: true,
+ async test({ assert, target }) {
+ await tick();
+ const [a_b_fork, a_c, b_d, shift, pop, commit] = target.querySelectorAll('button');
+ const [p] = target.querySelectorAll('p');
+
+ a_b_fork.click();
+ await tick();
+ assert.htmlEqual(p.innerHTML, 'a 0 | b 0 | c 0 | d 0');
+
+ a_c.click();
+ await tick();
+ assert.htmlEqual(p.innerHTML, 'a 0 | b 0 | c 0 | d 0');
+
+ b_d.click();
+ await tick();
+ assert.htmlEqual(p.innerHTML, 'a 0 | b 0 | c 0 | d 0');
+
+ shift.click();
+ await tick();
+ assert.htmlEqual(p.innerHTML, 'a 0 | b 0 | c 0 | d 0');
+
+ shift.click();
+ await tick();
+ assert.htmlEqual(p.innerHTML, 'a 0 | b 0 | c 0 | d 0');
+
+ shift.click();
+ await tick();
+ assert.htmlEqual(p.innerHTML, 'a 1 | b 0 | c 1 | d 0');
+
+ shift.click();
+ await tick();
+ assert.htmlEqual(p.innerHTML, 'a 1 | b 1 | c 1 | d 1');
+
+ commit.click();
+ await tick();
+ assert.htmlEqual(p.innerHTML, 'a 1 | b 1 | c 1 | d 1');
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-fork-3/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-fork-3/main.svelte
new file mode 100644
index 0000000000..6d1c4ab418
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-overlap-multiple-fork-3/main.svelte
@@ -0,0 +1,31 @@
+
+
+a {await delay(a)} | b {await delay(b)} | c {c} | d {d}
+
+ {f = fork(() => {a++;b++;});}}>
+ a and b (fork)
+
+ {a++;c++;}}>
+ a and c
+
+ {b++;d++;}}>
+ b and d
+
+ deferred.shift()?.()}>shift
+ deferred.pop()?.()}>pop
+ f.commit()}>commit fork
diff --git a/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-1/Child.svelte b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-1/Child.svelte
new file mode 100644
index 0000000000..6b765526c8
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-1/Child.svelte
@@ -0,0 +1,7 @@
+
+
+{x}
diff --git a/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-1/_config.js b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-1/_config.js
new file mode 100644
index 0000000000..0af275009c
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-1/_config.js
@@ -0,0 +1,38 @@
+import { tick } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+ skip: true, // TODO works on https://github.com/sveltejs/svelte/pull/17971
+ async test({ assert, target, logs }) {
+ const [x, y, resolve] = target.querySelectorAll('button');
+
+ x.click();
+ await tick();
+
+ y.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ x
+ y++
+ resolve
+ ` // if this shows world world - that would also be ok
+ );
+
+ resolve.click();
+ await tick();
+ assert.deepEqual(logs, ['universe', 'universe', '$effect: universe', '$effect: universe']);
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ x
+ y++
+ resolve
+ universe
+ universe
+ universe
+ `
+ );
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-1/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-1/main.svelte
new file mode 100644
index 0000000000..8edc718de2
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-1/main.svelte
@@ -0,0 +1,28 @@
+
+
+ x = 'universe'}>x
+
+ y++}>y++
+
+ deferred.shift()()}>resolve
+
+{#if x === 'universe'}
+ {await delay(x)}
+
+{/if}
+
+{#if y > 0}
+
+{/if}
diff --git a/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-2/Child.svelte b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-2/Child.svelte
new file mode 100644
index 0000000000..f8c01e9efd
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-2/Child.svelte
@@ -0,0 +1,11 @@
+
+
+
+{x}
+{JSON.stringify(x)}
+{#if x === 'universe'}universe{:else}world{/if}
+{#if JSON.stringify(x) === '"universe"'}universe{:else}world{/if}
+{await Promise.resolve(x)}
+{await Promise.resolve(JSON.stringify(x))}
\ No newline at end of file
diff --git a/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-2/_config.js b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-2/_config.js
new file mode 100644
index 0000000000..035616dfb6
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-2/_config.js
@@ -0,0 +1,49 @@
+import { tick } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+ skip: true, // TODO works on https://github.com/sveltejs/svelte/pull/17971
+ async test({ assert, target }) {
+ const [x, y, resolve] = target.querySelectorAll('button');
+
+ x.click();
+ await tick();
+
+ y.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ x
+ y++
+ resolve
+
+ ` // if this shows world world "world" world world world "world" - then this would also be ok
+ );
+
+ resolve.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ x
+ y++
+ resolve
+ universe
+ universe
+ "universe"
+ universe
+ universe
+ universe
+ "universe"
+
+ universe
+ "universe"
+ universe
+ universe
+ universe
+ "universe"
+ `
+ );
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-2/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-2/main.svelte
new file mode 100644
index 0000000000..11c4bdf5d1
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-2/main.svelte
@@ -0,0 +1,30 @@
+
+
+ x = 'universe'}>x
+
+ y++}>y++
+
+ deferred.shift()()}>resolve
+
+{#if x === 'universe'}
+ {await delay(x)}
+
+{/if}
+
+
+
+{#if y > 0}
+
+{/if}
diff --git a/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-3/Child.svelte b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-3/Child.svelte
new file mode 100644
index 0000000000..f8c01e9efd
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-3/Child.svelte
@@ -0,0 +1,11 @@
+
+
+
+{x}
+{JSON.stringify(x)}
+{#if x === 'universe'}universe{:else}world{/if}
+{#if JSON.stringify(x) === '"universe"'}universe{:else}world{/if}
+{await Promise.resolve(x)}
+{await Promise.resolve(JSON.stringify(x))}
\ No newline at end of file
diff --git a/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-3/_config.js b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-3/_config.js
new file mode 100644
index 0000000000..a2d615b6e5
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-3/_config.js
@@ -0,0 +1,61 @@
+import { tick } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+ skip: true, // TODO works on https://github.com/sveltejs/svelte/pull/17971
+ async test({ assert, target }) {
+ const [x, y, resolve] = target.querySelectorAll('button');
+
+ x.click();
+ await tick();
+
+ y.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ x
+ y++
+ resolve
+
+ `
+ );
+
+ resolve.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ x
+ y++
+ resolve
+
+ ` // if this shows world world "world" world world world "world" - then this would also be ok
+ );
+
+ resolve.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ x
+ y++
+ resolve
+ universe
+ universe
+ "universe"
+ universe
+ universe
+ universe
+ "universe"
+
+ universe
+ "universe"
+ universe
+ universe
+ universe
+ "universe"
+ `
+ );
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-3/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-3/main.svelte
new file mode 100644
index 0000000000..b02ab20995
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-3/main.svelte
@@ -0,0 +1,34 @@
+
+
+ (x = 'universe')}>x
+
+ y++}>y++
+ deferred.pop()?.()}>resolve
+
+{#if x === 'universe'}
+ {await delay(x)}
+
+{/if}
+
+
+
+{#if y > 0}
+
+{/if}
+
\ No newline at end of file
diff --git a/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-all-combinations/Child.svelte b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-all-combinations/Child.svelte
new file mode 100644
index 0000000000..f8c01e9efd
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-all-combinations/Child.svelte
@@ -0,0 +1,11 @@
+
+
+
+{x}
+{JSON.stringify(x)}
+{#if x === 'universe'}universe{:else}world{/if}
+{#if JSON.stringify(x) === '"universe"'}universe{:else}world{/if}
+{await Promise.resolve(x)}
+{await Promise.resolve(JSON.stringify(x))}
\ No newline at end of file
diff --git a/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-all-combinations/_config.js b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-all-combinations/_config.js
new file mode 100644
index 0000000000..8b7e7784de
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-all-combinations/_config.js
@@ -0,0 +1,171 @@
+import { tick } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+ skip: true, // TODO more combinations pass on https://github.com/sveltejs/svelte/pull/17971
+ timeout: 20_000,
+ async test({ assert, target }) {
+ const [x, fork_x, y, fork_y, shift, pop, commit_x, commit_y, reset] =
+ target.querySelectorAll('button');
+
+ const initial = `
+ x
+ x (fork)
+ y++
+ y++ (fork)
+ shift
+ pop
+ commit x
+ commit y
+ reset
+
+ `;
+
+ const final = `
+ x
+ x (fork)
+ y++
+ y++ (fork)
+ shift
+ pop
+ commit x
+ commit y
+ reset
+ universe
+ universe
+ "universe"
+ universe
+ universe
+ universe
+ "universe"
+
+ universe
+ "universe"
+ universe
+ universe
+ universe
+ "universe"
+ `;
+
+ /** @param {HTMLElement} button */
+ async function click(button) {
+ button.click();
+ await tick();
+ }
+
+ /**
+ * Generate all permutations of an array.
+ * @param {HTMLElement[]} actions
+ * @returns {HTMLElement[][]}
+ */
+ function permutations(actions) {
+ if (actions.length <= 1) return [actions];
+
+ /** @type {HTMLElement[][]} */
+ const result = [];
+
+ for (let i = 0; i < actions.length; i++) {
+ const head = actions[i];
+ const rest = actions.slice(0, i).concat(actions.slice(i + 1));
+ for (const tail of permutations(rest)) {
+ result.push([head, ...tail]);
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Keep only valid orders where fork commits happen after their fork action.
+ * @param {HTMLElement[]} order
+ */
+ function is_valid_order(order) {
+ const x_fork_index = order.indexOf(fork_x);
+ const commit_x_index = order.indexOf(commit_x);
+ if (commit_x_index !== -1 && (x_fork_index === -1 || commit_x_index < x_fork_index)) {
+ return false;
+ }
+
+ const y_fork_index = order.indexOf(fork_y);
+ const commit_y_index = order.indexOf(commit_y);
+ if (commit_y_index !== -1 && (y_fork_index === -1 || commit_y_index < y_fork_index)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Four control scenarios:
+ * - x direct, y direct
+ * - x direct, y via fork+commit
+ * - x via fork+commit, y direct
+ * - x via fork+commit, y via fork+commit
+ */
+ const control_scenarios = [
+ [x, y],
+ [x, fork_y, commit_y],
+ [fork_x, commit_x, y],
+ [fork_x, commit_x, fork_y, commit_y]
+ ];
+
+ const control_orders = control_scenarios.flatMap((scenario) =>
+ permutations(scenario).filter(is_valid_order)
+ );
+
+ /**
+ * All shift/pop combinations for draining async work.
+ * We click three times because this scenario can queue up to 3 deferred resolutions.
+ */
+ const resolve_orders = [
+ [shift, shift, shift],
+ [shift, pop, pop],
+ [pop, shift, shift],
+ [pop, pop, pop]
+ ];
+
+ for (const controls of control_orders) {
+ for (const resolves of resolve_orders) {
+ for (const action of controls) {
+ await click(action);
+ }
+
+ for (const action of resolves) {
+ await click(action);
+ }
+
+ const failure_msg = `Failed for: ${controls
+ .map((btn) => btn.textContent)
+ .concat(...resolves.map((btn) => btn.textContent))
+ .join(', ')}`;
+ assert.htmlEqual(target.innerHTML, final, failure_msg);
+
+ await click(reset);
+ assert.htmlEqual(target.innerHTML, initial, failure_msg);
+ }
+ }
+
+ const other_scenarios = [
+ [x, shift, y, shift, shift],
+ [x, shift, y, pop, pop],
+ [fork_x, shift, y, shift, commit_x, shift],
+ [fork_x, shift, y, pop, commit_x, pop],
+ [y, shift, x, shift, shift],
+ [y, shift, x, pop, pop],
+ [fork_y, shift, x, shift, commit_y, shift],
+ [fork_y, shift, x, pop, commit_y, pop]
+ ];
+
+ for (const scenario of other_scenarios) {
+ for (const action of scenario) {
+ await click(action);
+ }
+
+ const failure_msg = `Failed for: ${scenario.map((btn) => btn.textContent).join(', ')}`;
+ assert.htmlEqual(target.innerHTML, final, failure_msg);
+
+ await click(reset);
+ assert.htmlEqual(target.innerHTML, initial, failure_msg);
+ }
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-all-combinations/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-all-combinations/main.svelte
new file mode 100644
index 0000000000..bb821d9b0f
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-all-combinations/main.svelte
@@ -0,0 +1,41 @@
+
+
+ (x = 'universe')}>x
+ (fx = fork(() => {x = 'universe';}))}>x (fork)
+ y++}>y++
+ (fy = fork(() => {y++;}))}>y++ (fork)
+ deferred.shift()?.()}>shift
+ deferred.pop()?.()}>pop
+ fx.commit()}>commit x
+ fy.commit()}>commit y
+ {x = 'world'; y = 0;}}>reset
+
+{#if x === 'universe'}
+ {await delay(x)}
+
+{/if}
+
+
+
+{#if y > 0}
+
+{/if}
diff --git a/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-1/Child.svelte b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-1/Child.svelte
new file mode 100644
index 0000000000..f8c01e9efd
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-1/Child.svelte
@@ -0,0 +1,11 @@
+
+
+
+{x}
+{JSON.stringify(x)}
+{#if x === 'universe'}universe{:else}world{/if}
+{#if JSON.stringify(x) === '"universe"'}universe{:else}world{/if}
+{await Promise.resolve(x)}
+{await Promise.resolve(JSON.stringify(x))}
\ No newline at end of file
diff --git a/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-1/_config.js b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-1/_config.js
new file mode 100644
index 0000000000..737169cb91
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-1/_config.js
@@ -0,0 +1,90 @@
+import { tick } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+ skip: true, // TODO works on https://github.com/sveltejs/svelte/pull/17971
+ async test({ assert, target }) {
+ const [x, y, shift, pop, commit] = target.querySelectorAll('button');
+
+ x.click();
+ await tick();
+
+ y.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ x
+ y++
+ shift
+ pop
+ commit
+
+ `
+ );
+
+ commit.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ x
+ y++
+ shift
+ pop
+ commit
+
+ `
+ );
+
+ shift.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ x
+ y++
+ shift
+ pop
+ commit
+ universe
+ universe
+ "universe"
+ universe
+ universe
+ universe
+ "universe"
+
+ `
+ );
+
+ shift.click();
+ await tick();
+ shift.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ x
+ y++
+ shift
+ pop
+ commit
+ universe
+ universe
+ "universe"
+ universe
+ universe
+ universe
+ "universe"
+
+ universe
+ "universe"
+ universe
+ universe
+ universe
+ "universe"
+ `
+ );
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-1/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-1/main.svelte
new file mode 100644
index 0000000000..5f8d5efa13
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-1/main.svelte
@@ -0,0 +1,36 @@
+
+
+ (f = fork(() => {x = 'universe';}))}>x
+ y++}>y++
+ deferred.shift()?.()}>shift
+ deferred.pop()?.()}>pop
+ f.commit()}>commit
+
+{#if x === 'universe'}
+ {await delay(x)}
+
+{/if}
+
+
+
+{#if y > 0}
+
+{/if}
diff --git a/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-2/Child.svelte b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-2/Child.svelte
new file mode 100644
index 0000000000..f8c01e9efd
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-2/Child.svelte
@@ -0,0 +1,11 @@
+
+
+
+{x}
+{JSON.stringify(x)}
+{#if x === 'universe'}universe{:else}world{/if}
+{#if JSON.stringify(x) === '"universe"'}universe{:else}world{/if}
+{await Promise.resolve(x)}
+{await Promise.resolve(JSON.stringify(x))}
\ No newline at end of file
diff --git a/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-2/_config.js b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-2/_config.js
new file mode 100644
index 0000000000..a712e70630
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-2/_config.js
@@ -0,0 +1,71 @@
+import { tick } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+ skip: true, // TODO works on https://github.com/sveltejs/svelte/pull/17971
+ async test({ assert, target }) {
+ const [x, y, shift, pop, commit] = target.querySelectorAll('button');
+
+ y.click();
+ await tick();
+
+ x.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ x
+ y++
+ shift
+ pop
+ commit
+
+ `
+ );
+
+ commit.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ x
+ y++
+ shift
+ pop
+ commit
+
+ `
+ );
+
+ shift.click();
+ await tick();
+ shift.click();
+ await tick();
+ shift.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ x
+ y++
+ shift
+ pop
+ commit
+ universe
+ universe
+ "universe"
+ universe
+ universe
+ universe
+ "universe"
+
+ universe
+ "universe"
+ universe
+ universe
+ universe
+ "universe"
+ `
+ );
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-2/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-2/main.svelte
new file mode 100644
index 0000000000..5f8d5efa13
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-2/main.svelte
@@ -0,0 +1,36 @@
+
+
+ (f = fork(() => {x = 'universe';}))}>x
+ y++}>y++
+ deferred.shift()?.()}>shift
+ deferred.pop()?.()}>pop
+ f.commit()}>commit
+
+{#if x === 'universe'}
+ {await delay(x)}
+
+{/if}
+
+
+
+{#if y > 0}
+
+{/if}
diff --git a/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-3/Child.svelte b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-3/Child.svelte
new file mode 100644
index 0000000000..f8c01e9efd
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-3/Child.svelte
@@ -0,0 +1,11 @@
+
+
+
+{x}
+{JSON.stringify(x)}
+{#if x === 'universe'}universe{:else}world{/if}
+{#if JSON.stringify(x) === '"universe"'}universe{:else}world{/if}
+{await Promise.resolve(x)}
+{await Promise.resolve(JSON.stringify(x))}
\ No newline at end of file
diff --git a/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-3/_config.js b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-3/_config.js
new file mode 100644
index 0000000000..905e76511b
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-3/_config.js
@@ -0,0 +1,70 @@
+import { tick } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+ async test({ assert, target }) {
+ const [x, y, shift, pop, commit] = target.querySelectorAll('button');
+
+ y.click();
+ await tick();
+
+ x.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ x
+ y++
+ shift
+ pop
+ commit
+
+ `
+ );
+
+ commit.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ x
+ y++
+ shift
+ pop
+ commit
+
+ `
+ );
+
+ pop.click();
+ await tick();
+ pop.click();
+ await tick();
+ pop.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ x
+ y++
+ shift
+ pop
+ commit
+ universe
+ universe
+ "universe"
+ universe
+ universe
+ universe
+ "universe"
+
+ universe
+ "universe"
+ universe
+ universe
+ universe
+ "universe"
+ `
+ );
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-3/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-3/main.svelte
new file mode 100644
index 0000000000..5f8d5efa13
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-3/main.svelte
@@ -0,0 +1,36 @@
+
+
+ (f = fork(() => {x = 'universe';}))}>x
+ y++}>y++
+ deferred.shift()?.()}>shift
+ deferred.pop()?.()}>pop
+ f.commit()}>commit
+
+{#if x === 'universe'}
+ {await delay(x)}
+
+{/if}
+
+
+
+{#if y > 0}
+
+{/if}
diff --git a/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-4/Child.svelte b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-4/Child.svelte
new file mode 100644
index 0000000000..f8c01e9efd
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-4/Child.svelte
@@ -0,0 +1,11 @@
+
+
+
+{x}
+{JSON.stringify(x)}
+{#if x === 'universe'}universe{:else}world{/if}
+{#if JSON.stringify(x) === '"universe"'}universe{:else}world{/if}
+{await Promise.resolve(x)}
+{await Promise.resolve(JSON.stringify(x))}
\ No newline at end of file
diff --git a/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-4/_config.js b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-4/_config.js
new file mode 100644
index 0000000000..6a6399a19e
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-4/_config.js
@@ -0,0 +1,76 @@
+import { tick } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+ skip: true, // TODO works on https://github.com/sveltejs/svelte/pull/17971
+ async test({ assert, target }) {
+ const [x, y, resolve, commit] = target.querySelectorAll('button');
+
+ x.click();
+ await tick();
+
+ y.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ x
+ y++
+ resolve
+ commit
+
+ world
+ "world"
+ world
+ world
+ world
+ "world"
+ `
+ );
+
+ commit.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ x
+ y++
+ resolve
+ commit
+
+ world
+ "world"
+ world
+ world
+ world
+ "world"
+ `
+ );
+
+ resolve.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ x
+ y++
+ resolve
+ commit
+ universe
+ universe
+ "universe"
+ universe
+ universe
+ universe
+ "universe"
+
+ universe
+ "universe"
+ universe
+ universe
+ universe
+ "universe"
+ `
+ );
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-4/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-4/main.svelte
new file mode 100644
index 0000000000..5f00fc4dec
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-4/main.svelte
@@ -0,0 +1,32 @@
+
+
+ (f = fork(() => {x = 'universe';}))}>x
+
+ y++}>y++
+ deferred.pop()?.()}>resolve
+ f.commit()}>commit
+
+{#if x === 'universe'}
+ {await delay(x)}
+
+{/if}
+
+
+
+{#if y > 0}
+
+{/if}
diff --git a/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-5/Child.svelte b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-5/Child.svelte
new file mode 100644
index 0000000000..f8c01e9efd
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-5/Child.svelte
@@ -0,0 +1,11 @@
+
+
+
+{x}
+{JSON.stringify(x)}
+{#if x === 'universe'}universe{:else}world{/if}
+{#if JSON.stringify(x) === '"universe"'}universe{:else}world{/if}
+{await Promise.resolve(x)}
+{await Promise.resolve(JSON.stringify(x))}
\ No newline at end of file
diff --git a/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-5/_config.js b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-5/_config.js
new file mode 100644
index 0000000000..9221a96c2e
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-5/_config.js
@@ -0,0 +1,85 @@
+import { tick } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+ skip: true, // TODO works on https://github.com/sveltejs/svelte/pull/17971
+ async test({ assert, target }) {
+ const [x, y, resolve, commit] = target.querySelectorAll('button');
+
+ x.click();
+ await tick();
+
+ y.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ x
+ y++
+ resolve
+ commit
+
+ `
+ );
+
+ commit.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ x
+ y++
+ resolve
+ commit
+
+ `
+ );
+
+ resolve.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ x
+ y++
+ resolve
+ commit
+
+ world
+ "world"
+ world
+ world
+ world
+ "world"
+ `
+ );
+
+ resolve.click();
+ await tick();
+ resolve.click();
+ await tick();
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ x
+ y++
+ resolve
+ commit
+ universe
+ universe
+ "universe"
+ universe
+ universe
+ universe
+ "universe"
+
+ universe
+ "universe"
+ universe
+ universe
+ universe
+ "universe"
+ `
+ );
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-5/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-5/main.svelte
new file mode 100644
index 0000000000..5575e3cbd4
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-state-new-branch-fork-5/main.svelte
@@ -0,0 +1,36 @@
+
+
+ (f = fork(() => {x = 'universe';}))}>x
+
+ y++}>y++
+ deferred.pop()?.()}>resolve
+ f.commit()}>commit
+
+{#if x === 'universe'}
+ {await delay(x)}
+
+{/if}
+
+
+
+{#if y > 0}
+
+{/if}
diff --git a/packages/svelte/tests/snapshot/samples/async-in-derived/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/async-in-derived/_expected/client/index.svelte.js
index a8b1a43495..4f06d9ddbf 100644
--- a/packages/svelte/tests/snapshot/samples/async-in-derived/_expected/client/index.svelte.js
+++ b/packages/svelte/tests/snapshot/samples/async-in-derived/_expected/client/index.svelte.js
@@ -10,13 +10,15 @@ export default function Async_in_derived($$anchor, $$props) {
var $$promises = $.run([
async () => yes1 = await $.async_derived(() => 1),
async () => yes2 = await $.async_derived(async () => foo(await 1)),
- () => no1 = $.derived(async () => {
- return await 1;
- }),
-
- () => no2 = $.derived(() => async () => {
- return await 1;
- })
+ () => {
+ no1 = $.derived(async () => {
+ return await 1;
+ });
+
+ no2 = $.derived(() => async () => {
+ return await 1;
+ });
+ }
]);
var fragment = $.comment();
diff --git a/packages/svelte/tests/snapshot/samples/async-in-derived/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/async-in-derived/_expected/server/index.svelte.js
index 1697e3adc6..3a53475944 100644
--- a/packages/svelte/tests/snapshot/samples/async-in-derived/_expected/server/index.svelte.js
+++ b/packages/svelte/tests/snapshot/samples/async-in-derived/_expected/server/index.svelte.js
@@ -8,13 +8,15 @@ export default function Async_in_derived($$renderer, $$props) {
var $$promises = $$renderer.run([
async () => yes1 = await $.async_derived(() => 1),
async () => yes2 = await $.async_derived(async () => foo(await 1)),
- () => no1 = $.derived(async () => {
- return await 1;
- }),
-
- () => no2 = $.derived(() => async () => {
- return await 1;
- })
+ () => {
+ no1 = $.derived(async () => {
+ return await 1;
+ });
+
+ no2 = $.derived(() => async () => {
+ return await 1;
+ });
+ }
]);
if (true) {
diff --git a/packages/svelte/tests/snapshot/samples/async-top-level-group-sync-run/_config.js b/packages/svelte/tests/snapshot/samples/async-top-level-group-sync-run/_config.js
new file mode 100644
index 0000000000..2e30bbeb16
--- /dev/null
+++ b/packages/svelte/tests/snapshot/samples/async-top-level-group-sync-run/_config.js
@@ -0,0 +1,3 @@
+import { test } from '../../test';
+
+export default test({ compileOptions: { experimental: { async: true } } });
diff --git a/packages/svelte/tests/snapshot/samples/async-top-level-group-sync-run/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/async-top-level-group-sync-run/_expected/client/index.svelte.js
new file mode 100644
index 0000000000..8fb09fadd2
--- /dev/null
+++ b/packages/svelte/tests/snapshot/samples/async-top-level-group-sync-run/_expected/client/index.svelte.js
@@ -0,0 +1,26 @@
+import 'svelte/internal/disclose-version';
+import 'svelte/internal/flags/async';
+import * as $ from 'svelte/internal/client';
+
+export default function Async_top_level_group_sync_run($$anchor) {
+ var a,
+ // these should be grouped into one, having an async tick inbetween
+ // would change how the code runs and could introduce subtle timing bugs
+ b,
+ c;
+
+ var $$promises = $.run([
+ async () => a = await Promise.resolve(1),
+ () => {
+ b = a + 1;
+ c = b + 1;
+ }
+ ]);
+
+ $.next();
+
+ var text = $.text();
+
+ $.template_effect(() => $.set_text(text, c), void 0, void 0, [$$promises[1]]);
+ $.append($$anchor, text);
+}
\ No newline at end of file
diff --git a/packages/svelte/tests/snapshot/samples/async-top-level-group-sync-run/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/async-top-level-group-sync-run/_expected/server/index.svelte.js
new file mode 100644
index 0000000000..43db129746
--- /dev/null
+++ b/packages/svelte/tests/snapshot/samples/async-top-level-group-sync-run/_expected/server/index.svelte.js
@@ -0,0 +1,21 @@
+import 'svelte/internal/flags/async';
+import * as $ from 'svelte/internal/server';
+
+export default function Async_top_level_group_sync_run($$renderer) {
+ var a,
+ // these should be grouped into one, having an async tick inbetween
+ // would change how the code runs and could introduce subtle timing bugs
+ b,
+ c;
+
+ var $$promises = $$renderer.run([
+ async () => a = await Promise.resolve(1),
+ () => {
+ b = a + 1;
+ c = b + 1;
+ }
+ ]);
+
+ $$renderer.push(``);
+ $$renderer.async([$$promises[1]], ($$renderer) => $$renderer.push(() => $.escape(c)));
+}
\ No newline at end of file
diff --git a/packages/svelte/tests/snapshot/samples/async-top-level-group-sync-run/index.svelte b/packages/svelte/tests/snapshot/samples/async-top-level-group-sync-run/index.svelte
new file mode 100644
index 0000000000..98f602312e
--- /dev/null
+++ b/packages/svelte/tests/snapshot/samples/async-top-level-group-sync-run/index.svelte
@@ -0,0 +1,9 @@
+
+
+{c}
diff --git a/packages/svelte/tests/suite.ts b/packages/svelte/tests/suite.ts
index bbd252b8e1..af36f16e29 100644
--- a/packages/svelte/tests/suite.ts
+++ b/packages/svelte/tests/suite.ts
@@ -4,6 +4,7 @@ import { it } from 'vitest';
export interface BaseTest {
skip?: boolean;
solo?: boolean;
+ timeout?: number;
}
/**
@@ -30,7 +31,7 @@ export function suite(fn: (config: Test, test_dir: string
await for_each_dir(cwd, samples_dir, (config, dir) => {
let it_fn = config.skip ? it.skip : config.solo ? it.only : it;
- it_fn(dir, () => fn(config, `${cwd}/${samples_dir}/${dir}`));
+ it_fn(dir, { timeout: config.timeout }, () => fn(config, `${cwd}/${samples_dir}/${dir}`));
});
}
};
@@ -57,7 +58,7 @@ export function suite_with_variants {
+ it_fn(`${dir} (${variant})`, { timeout: config.timeout }, async () => {
if (!called_common) {
called_common = true;
common = await common_setup(config, `${cwd}/${samples_dir}/${dir}`);
diff --git a/packages/svelte/tests/types/motion.ts b/packages/svelte/tests/types/motion.ts
new file mode 100644
index 0000000000..0ba5d1d9c6
--- /dev/null
+++ b/packages/svelte/tests/types/motion.ts
@@ -0,0 +1,22 @@
+import {
+ type TweenOptions,
+ type SpringOptions,
+ type SpringUpdateOptions,
+ type Updater
+} from 'svelte/motion';
+
+let tweenOptions: TweenOptions = {
+ delay: 100,
+ duration: 400
+};
+
+let springOptions: SpringOptions = {
+ stiffness: 0.1,
+ damping: 0.5
+};
+
+let springUpdateOptions: SpringUpdateOptions = {
+ instant: true
+};
+
+let updater: Updater = (target, value) => target + value;
diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts
index f2d0549430..51b9d079f4 100644
--- a/packages/svelte/types/index.d.ts
+++ b/packages/svelte/types/index.d.ts
@@ -2010,16 +2010,50 @@ declare module 'svelte/legacy' {
declare module 'svelte/motion' {
import type { MediaQuery } from 'svelte/reactivity';
+ export interface SpringOptions {
+ stiffness?: number;
+ damping?: number;
+ precision?: number;
+ }
+
+ export interface SpringUpdateOptions {
+ /**
+ * @deprecated Only use this for the spring store; does nothing when set on the Spring class
+ */
+ hard?: any;
+ /**
+ * @deprecated Only use this for the spring store; does nothing when set on the Spring class
+ */
+ soft?: string | number | boolean;
+ /**
+ * Only use this for the Spring class; does nothing when set on the spring store
+ */
+ instant?: boolean;
+ /**
+ * Only use this for the Spring class; does nothing when set on the spring store
+ */
+ preserveMomentum?: number;
+ }
+
+ export type Updater = (target_value: T, value: T) => T;
+
+ export interface TweenOptions {
+ delay?: number;
+ duration?: number | ((from: T, to: T) => number);
+ easing?: (t: number) => number;
+ interpolate?: (a: T, b: T) => (t: number) => T;
+ }
+
// TODO we do declaration merging here in order to not have a breaking change (renaming the Spring interface)
// this means both the Spring class and the Spring interface are merged into one with some things only
// existing on one side. In Svelte 6, remove the type definition and move the jsdoc onto the class in spring.js
export interface Spring extends Readable {
- set(new_value: T, opts?: SpringUpdateOpts): Promise;
+ set(new_value: T, opts?: SpringUpdateOptions): Promise;
/**
* @deprecated Only exists on the legacy `spring` store, not the `Spring` class
*/
- update: (fn: Updater, opts?: SpringUpdateOpts) => Promise;
+ update: (fn: Updater, opts?: SpringUpdateOptions) => Promise;
/**
* @deprecated Only exists on the legacy `spring` store, not the `Spring` class
*/
@@ -2046,7 +2080,7 @@ declare module 'svelte/motion' {
* @since 5.8.0
*/
export class Spring {
- constructor(value: T, options?: SpringOpts);
+ constructor(value: T, options?: SpringOptions);
/**
* Create a spring whose value is bound to the return value of `fn`. This must be called
@@ -2062,7 +2096,7 @@ declare module 'svelte/motion' {
*
* ```
*/
- static of(fn: () => U, options?: SpringOpts): Spring;
+ static of(fn: () => U, options?: SpringOptions): Spring;
/**
* Sets `spring.target` to `value` and returns a `Promise` that resolves if and when `spring.current` catches up to it.
@@ -2072,7 +2106,7 @@ declare module 'svelte/motion' {
* If `options.preserveMomentum` is provided, the spring will continue on its current trajectory for
* the specified number of milliseconds. This is useful for things like 'fling' gestures.
*/
- set(value: T, options?: SpringUpdateOpts): Promise;
+ set(value: T, options?: SpringUpdateOptions): Promise;
damping: number;
precision: number;
@@ -2090,8 +2124,8 @@ declare module 'svelte/motion' {
}
export interface Tweened extends Readable {
- set(value: T, opts?: TweenedOptions): Promise;
- update(updater: Updater, opts?: TweenedOptions): Promise;
+ set(value: T, opts?: TweenOptions): Promise;
+ update(updater: Updater, opts?: TweenOptions): Promise;
}
/** Callback to inform of a value updates. */
type Subscriber = (value: T) => void;
@@ -2108,39 +2142,6 @@ declare module 'svelte/motion' {
*/
subscribe(this: void, run: Subscriber, invalidate?: () => void): Unsubscriber;
}
- interface SpringOpts {
- stiffness?: number;
- damping?: number;
- precision?: number;
- }
-
- interface SpringUpdateOpts {
- /**
- * @deprecated Only use this for the spring store; does nothing when set on the Spring class
- */
- hard?: any;
- /**
- * @deprecated Only use this for the spring store; does nothing when set on the Spring class
- */
- soft?: string | number | boolean;
- /**
- * Only use this for the Spring class; does nothing when set on the spring store
- */
- instant?: boolean;
- /**
- * Only use this for the Spring class; does nothing when set on the spring store
- */
- preserveMomentum?: number;
- }
-
- type Updater = (target_value: T, value: T) => T;
-
- interface TweenedOptions {
- delay?: number;
- duration?: number | ((from: T, to: T) => number);
- easing?: (t: number) => number;
- interpolate?: (a: T, b: T) => (t: number) => T;
- }
/**
* A [media query](https://svelte.dev/docs/svelte/svelte-reactivity#MediaQuery) that matches if the user [prefers reduced motion](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion).
*
@@ -2170,13 +2171,13 @@ declare module 'svelte/motion' {
*
* @deprecated Use [`Spring`](https://svelte.dev/docs/svelte/svelte-motion#Spring) instead
* */
- export function spring(value?: T | undefined, opts?: SpringOpts | undefined): Spring;
+ export function spring(value?: T | undefined, opts?: SpringOptions | undefined): Spring;
/**
* A tweened store in Svelte is a special type of store that provides smooth transitions between state values over time.
*
* @deprecated Use [`Tween`](https://svelte.dev/docs/svelte/svelte-motion#Tween) instead
* */
- export function tweened(value?: T | undefined, defaults?: TweenedOptions | undefined): Tweened;
+ export function tweened(value?: T | undefined, defaults?: TweenOptions | undefined): Tweened;
/**
* A wrapper for a value that tweens smoothly to its target value. Changes to `tween.target` will cause `tween.current` to
* move towards it over time, taking account of the `delay`, `duration` and `easing` options.
@@ -2209,15 +2210,15 @@ declare module 'svelte/motion' {
* ```
*
*/
- static of(fn: () => U, options?: TweenedOptions | undefined): Tween;
+ static of(fn: () => U, options?: TweenOptions | undefined): Tween;
- constructor(value: T, options?: TweenedOptions);
+ constructor(value: T, options?: TweenOptions);
/**
* Sets `tween.target` to `value` and returns a `Promise` that resolves if and when `tween.current` catches up to it.
*
* If `options` are provided, they will override the tween's defaults.
* */
- set(value: T, options?: TweenedOptions | undefined): Promise;
+ set(value: T, options?: TweenOptions | undefined): Promise;
get current(): T;
set target(v: T);
get target(): T;