From 1ff9c0f2b9e7edeb4a5dd0437c1afbb875962ca3 Mon Sep 17 00:00:00 2001
From: Nguyen Tran <88808276+ngtr6788@users.noreply.github.com>
Date: Tue, 9 Jan 2024 04:24:50 -0500
Subject: [PATCH 01/12] fix: support destructurings containing await (#9962)
Adds a traversion mechanism to found out if destructured expressions contain await
Fixes #9686
Fixes #9312
Fixes #9982
---
.changeset/nasty-yaks-peel.md | 5 +
.../phases/3-transform/client/utils.js | 128 ++++++++++++++++--
.../svelte/src/compiler/utils/builders.js | 18 +++
.../destructure-async-assignments/_config.js | 75 ++++++++++
.../destructure-async-assignments/main.svelte | 89 ++++++++++++
5 files changed, 305 insertions(+), 10 deletions(-)
create mode 100644 .changeset/nasty-yaks-peel.md
create mode 100644 packages/svelte/tests/runtime-runes/samples/destructure-async-assignments/_config.js
create mode 100644 packages/svelte/tests/runtime-runes/samples/destructure-async-assignments/main.svelte
diff --git a/.changeset/nasty-yaks-peel.md b/.changeset/nasty-yaks-peel.md
new file mode 100644
index 0000000000..267e4458a5
--- /dev/null
+++ b/.changeset/nasty-yaks-peel.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: support async/await in destructuring assignments
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/utils.js b/packages/svelte/src/compiler/phases/3-transform/client/utils.js
index ca652801a7..2ecec2eeb3 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/utils.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/utils.js
@@ -115,6 +115,103 @@ export function serialize_get_binding(node, state) {
return node;
}
+/**
+ * @param {import('estree').Expression | import('estree').Pattern} expression
+ * @returns {boolean}
+ */
+function is_expression_async(expression) {
+ switch (expression.type) {
+ case 'AwaitExpression': {
+ return true;
+ }
+ case 'ArrayPattern': {
+ return expression.elements.some((element) => element && is_expression_async(element));
+ }
+ case 'ArrayExpression': {
+ return expression.elements.some((element) => {
+ if (!element) {
+ return false;
+ } else if (element.type === 'SpreadElement') {
+ return is_expression_async(element.argument);
+ } else {
+ return is_expression_async(element);
+ }
+ });
+ }
+ case 'AssignmentPattern':
+ case 'AssignmentExpression':
+ case 'BinaryExpression':
+ case 'LogicalExpression': {
+ return is_expression_async(expression.left) || is_expression_async(expression.right);
+ }
+ case 'CallExpression':
+ case 'NewExpression': {
+ return (
+ (expression.callee.type !== 'Super' && is_expression_async(expression.callee)) ||
+ expression.arguments.some((element) => {
+ if (element.type === 'SpreadElement') {
+ return is_expression_async(element.argument);
+ } else {
+ return is_expression_async(element);
+ }
+ })
+ );
+ }
+ case 'ChainExpression': {
+ return is_expression_async(expression.expression);
+ }
+ case 'ConditionalExpression': {
+ return (
+ is_expression_async(expression.test) ||
+ is_expression_async(expression.alternate) ||
+ is_expression_async(expression.consequent)
+ );
+ }
+ case 'ImportExpression': {
+ return is_expression_async(expression.source);
+ }
+ case 'MemberExpression': {
+ return (
+ (expression.object.type !== 'Super' && is_expression_async(expression.object)) ||
+ (expression.property.type !== 'PrivateIdentifier' &&
+ is_expression_async(expression.property))
+ );
+ }
+ case 'ObjectPattern':
+ case 'ObjectExpression': {
+ return expression.properties.some((property) => {
+ if (property.type === 'SpreadElement') {
+ return is_expression_async(property.argument);
+ } else if (property.type === 'Property') {
+ return (
+ (property.key.type !== 'PrivateIdentifier' && is_expression_async(property.key)) ||
+ is_expression_async(property.value)
+ );
+ }
+ });
+ }
+ case 'RestElement': {
+ return is_expression_async(expression.argument);
+ }
+ case 'SequenceExpression':
+ case 'TemplateLiteral': {
+ return expression.expressions.some((subexpression) => is_expression_async(subexpression));
+ }
+ case 'TaggedTemplateExpression': {
+ return is_expression_async(expression.tag) || is_expression_async(expression.quasi);
+ }
+ case 'UnaryExpression':
+ case 'UpdateExpression': {
+ return is_expression_async(expression.argument);
+ }
+ case 'YieldExpression': {
+ return expression.argument ? is_expression_async(expression.argument) : false;
+ }
+ default:
+ return false;
+ }
+}
+
/**
* @template {import('./types').ClientTransformState} State
* @param {import('estree').AssignmentExpression} node
@@ -153,17 +250,28 @@ export function serialize_set_binding(node, context, fallback) {
return fallback();
}
- return b.call(
- b.thunk(
- b.block([
- b.const(tmp_id, /** @type {import('estree').Expression} */ (visit(node.right))),
- b.stmt(b.sequence(assignments)),
- // return because it could be used in a nested expression where the value is needed.
- // example: { foo: ({ bar } = { bar: 1 })}
- b.return(b.id(tmp_id))
- ])
- )
+ const rhs_expression = /** @type {import('estree').Expression} */ (visit(node.right));
+
+ const iife_is_async =
+ is_expression_async(rhs_expression) ||
+ assignments.some((assignment) => is_expression_async(assignment));
+
+ const iife = b.arrow(
+ [],
+ b.block([
+ b.const(tmp_id, rhs_expression),
+ b.stmt(b.sequence(assignments)),
+ // return because it could be used in a nested expression where the value is needed.
+ // example: { foo: ({ bar } = { bar: 1 })}
+ b.return(b.id(tmp_id))
+ ])
);
+
+ if (iife_is_async) {
+ return b.await(b.call(b.async(iife)));
+ } else {
+ return b.call(iife);
+ }
}
if (node.left.type !== 'Identifier' && node.left.type !== 'MemberExpression') {
diff --git a/packages/svelte/src/compiler/utils/builders.js b/packages/svelte/src/compiler/utils/builders.js
index f0907f293f..1595f79879 100644
--- a/packages/svelte/src/compiler/utils/builders.js
+++ b/packages/svelte/src/compiler/utils/builders.js
@@ -44,6 +44,23 @@ export function assignment(operator, left, right) {
return { type: 'AssignmentExpression', operator, left, right };
}
+/**
+ * @template T
+ * @param {T & import('estree').BaseFunction} func
+ * @returns {T & import('estree').BaseFunction}
+ */
+export function async(func) {
+ return { ...func, async: true };
+}
+
+/**
+ * @param {import('estree').Expression} argument
+ * @returns {import('estree').AwaitExpression}
+ */
+export function await_builder(argument) {
+ return { type: 'AwaitExpression', argument };
+}
+
/**
* @param {import('estree').BinaryOperator} operator
* @param {import('estree').Expression} left
@@ -573,6 +590,7 @@ export function throw_error(str) {
}
export {
+ await_builder as await,
new_builder as new,
let_builder as let,
const_builder as const,
diff --git a/packages/svelte/tests/runtime-runes/samples/destructure-async-assignments/_config.js b/packages/svelte/tests/runtime-runes/samples/destructure-async-assignments/_config.js
new file mode 100644
index 0000000000..e7d3c80cfd
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/destructure-async-assignments/_config.js
@@ -0,0 +1,75 @@
+import { test } from '../../test';
+
+export default test({
+ html: `
+
+
0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ `,
+
+ async test({ assert, target, window }) {
+ const btn = target.querySelector('button');
+ const clickEvent = new window.Event('click', { bubbles: true });
+ await btn?.dispatchEvent(clickEvent);
+ for (let i = 1; i <= 42; i += 1) {
+ await Promise.resolve();
+ }
+
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ `
+ );
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/destructure-async-assignments/main.svelte b/packages/svelte/tests/runtime-runes/samples/destructure-async-assignments/main.svelte
new file mode 100644
index 0000000000..2d2a6538d5
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/destructure-async-assignments/main.svelte
@@ -0,0 +1,89 @@
+
+
+
+{a}
+{b}
+{c}
+{d}
+{e}
+{f}
+{g}
+{h}
+{i}
+{j}
+{k}
+{l}
+{m}
+{n}
+{o}
+{p}
+{q}
+{r}
+{s}
+{t}
+{u}
+{v}
+{w}
+{x}
+{y}
+{z}
\ No newline at end of file
From b6238904391cac97f28637595a9d2abbc8962828 Mon Sep 17 00:00:00 2001
From: gtmnayan <50981692+gtm-nayan@users.noreply.github.com>
Date: Tue, 9 Jan 2024 15:11:40 +0545
Subject: [PATCH 02/12] chore(repl): hide globals (#10125)
---
.../src/lib/Output/Viewer.svelte | 38 ++++++++++---------
1 file changed, 21 insertions(+), 17 deletions(-)
diff --git a/sites/svelte-5-preview/src/lib/Output/Viewer.svelte b/sites/svelte-5-preview/src/lib/Output/Viewer.svelte
index 7ae1d4d05d..214846f62e 100644
--- a/sites/svelte-5-preview/src/lib/Output/Viewer.svelte
+++ b/sites/svelte-5-preview/src/lib/Output/Viewer.svelte
@@ -111,26 +111,30 @@
${styles}
- const styles = document.querySelectorAll('style[id^=svelte-]');
-
- let i = styles.length;
- while (i--) styles[i].parentNode.removeChild(styles[i]);
-
- if (window.unmount) {
- try {
- window.unmount();
- } catch (err) {
- console.error(err);
+ {
+ const styles = document.querySelectorAll('style[id^=svelte-]');
+
+ let i = styles.length;
+ while (i--) styles[i].parentNode.removeChild(styles[i]);
+
+ if (window.__unmount_previous) {
+ try {
+ window.__unmount_previous();
+ } catch (err) {
+ console.error(err);
+ }
}
- }
- document.body.innerHTML = '';
- window._svelteTransitionManager = null;
-
- const { mount, App } = ${$bundle.client?.code};
- const [, destroy] = mount(App, { target: document.body });
+ document.body.innerHTML = '';
+ window._svelteTransitionManager = null;
+ }
- window.unmount = destroy;
+ const __repl_exports = ${$bundle.client?.code};
+ {
+ const { mount, App } = __repl_exports;
+ const [, destroy] = mount(App, { target: document.body });
+ window.__unmount_previous = destroy;
+ }
//# sourceURL=playground:output
`);
error = null;
From 3624a4c2a00cda7e18ddf6331015995dabd30932 Mon Sep 17 00:00:00 2001
From: Simon H <5968653+dummdidumm@users.noreply.github.com>
Date: Tue, 9 Jan 2024 10:29:23 +0100
Subject: [PATCH 03/12] feat: allow modifiying derived props (#10080)
It's an unnecessary restruction because it can be worked around (hide it behind a getter/setter), already works for bind:x and prevents valid use cases
---
.changeset/hungry-trees-travel.md | 5 +++++
.../src/compiler/phases/2-analyze/validation.js | 14 +++++++-------
.../runes-no-derived-assignment/main.svelte | 4 ++--
.../samples/runes-no-derived-update/main.svelte | 4 ++--
.../samples/derived-proxy/_config.js | 15 +++++++++++++++
.../samples/derived-proxy/main.svelte | 7 +++++++
6 files changed, 38 insertions(+), 11 deletions(-)
create mode 100644 .changeset/hungry-trees-travel.md
create mode 100644 packages/svelte/tests/runtime-runes/samples/derived-proxy/_config.js
create mode 100644 packages/svelte/tests/runtime-runes/samples/derived-proxy/main.svelte
diff --git a/.changeset/hungry-trees-travel.md b/.changeset/hungry-trees-travel.md
new file mode 100644
index 0000000000..d90a448daa
--- /dev/null
+++ b/.changeset/hungry-trees-travel.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+feat: allow modifiying derived props
diff --git a/packages/svelte/src/compiler/phases/2-analyze/validation.js b/packages/svelte/src/compiler/phases/2-analyze/validation.js
index 40244f97d0..f64b0268ab 100644
--- a/packages/svelte/src/compiler/phases/2-analyze/validation.js
+++ b/packages/svelte/src/compiler/phases/2-analyze/validation.js
@@ -854,6 +854,13 @@ function validate_assignment(node, argument, state) {
let left = /** @type {import('estree').Expression | import('estree').Super} */ (argument);
+ if (left.type === 'Identifier') {
+ const binding = state.scope.get(left.name);
+ if (binding?.kind === 'derived') {
+ error(node, 'invalid-derived-assignment');
+ }
+ }
+
/** @type {import('estree').Expression | import('estree').PrivateIdentifier | null} */
let property = null;
@@ -862,13 +869,6 @@ function validate_assignment(node, argument, state) {
left = left.object;
}
- if (left.type === 'Identifier') {
- const binding = state.scope.get(left.name);
- if (binding?.kind === 'derived') {
- error(node, 'invalid-derived-assignment');
- }
- }
-
if (left.type === 'ThisExpression' && property?.type === 'PrivateIdentifier') {
if (state.private_derived_state.includes(property.name)) {
error(node, 'invalid-derived-assignment');
diff --git a/packages/svelte/tests/compiler-errors/samples/runes-no-derived-assignment/main.svelte b/packages/svelte/tests/compiler-errors/samples/runes-no-derived-assignment/main.svelte
index f89cc7b6ba..3bf836f6c5 100644
--- a/packages/svelte/tests/compiler-errors/samples/runes-no-derived-assignment/main.svelte
+++ b/packages/svelte/tests/compiler-errors/samples/runes-no-derived-assignment/main.svelte
@@ -1,5 +1,5 @@
diff --git a/packages/svelte/tests/compiler-errors/samples/runes-no-derived-update/main.svelte b/packages/svelte/tests/compiler-errors/samples/runes-no-derived-update/main.svelte
index a4aca48089..d266c95bb8 100644
--- a/packages/svelte/tests/compiler-errors/samples/runes-no-derived-update/main.svelte
+++ b/packages/svelte/tests/compiler-errors/samples/runes-no-derived-update/main.svelte
@@ -1,5 +1,5 @@
diff --git a/packages/svelte/tests/runtime-runes/samples/derived-proxy/_config.js b/packages/svelte/tests/runtime-runes/samples/derived-proxy/_config.js
new file mode 100644
index 0000000000..32340d018a
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/derived-proxy/_config.js
@@ -0,0 +1,15 @@
+import { test } from '../../test';
+
+export default test({
+ html: ``,
+
+ async test({ assert, target }) {
+ const [btn1, btn2] = target.querySelectorAll('button');
+
+ await btn1?.click();
+ assert.htmlEqual(target.innerHTML, ``);
+
+ await btn2?.click();
+ assert.htmlEqual(target.innerHTML, ``);
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/derived-proxy/main.svelte b/packages/svelte/tests/runtime-runes/samples/derived-proxy/main.svelte
new file mode 100644
index 0000000000..ddbc56aa7b
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/derived-proxy/main.svelte
@@ -0,0 +1,7 @@
+
+
+
+
From f5dc562ee76fdd1c4dcd7ae7683cc0f4b44c7fa5 Mon Sep 17 00:00:00 2001
From: Dominic Gannaway
Date: Tue, 9 Jan 2024 10:11:49 +0000
Subject: [PATCH 04/12] fix: ensure nested blocks are inert during outro
transitions (#10126)
* fix: ensure nested blocks are inert during outro transitions
* lint
---
.changeset/spotty-pens-agree.md | 5 ++++
.../svelte/src/internal/client/runtime.js | 23 +++++++++++++++++++
.../samples/if-transition-inert/_config.js | 16 +++++++++++++
.../samples/if-transition-inert/main.svelte | 17 ++++++++++++++
4 files changed, 61 insertions(+)
create mode 100644 .changeset/spotty-pens-agree.md
create mode 100644 packages/svelte/tests/runtime-runes/samples/if-transition-inert/_config.js
create mode 100644 packages/svelte/tests/runtime-runes/samples/if-transition-inert/main.svelte
diff --git a/.changeset/spotty-pens-agree.md b/.changeset/spotty-pens-agree.md
new file mode 100644
index 0000000000..2b7f76d696
--- /dev/null
+++ b/.changeset/spotty-pens-agree.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: ensure nested blocks are inert during outro transitions
diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js
index 8631529e62..38c7e3d12b 100644
--- a/packages/svelte/src/internal/client/runtime.js
+++ b/packages/svelte/src/internal/client/runtime.js
@@ -10,6 +10,7 @@ import {
} from '../../constants.js';
import { readonly } from './proxy/readonly.js';
import { READONLY_SYMBOL, STATE_SYMBOL, proxy, unstate } from './proxy/proxy.js';
+import { EACH_BLOCK, IF_BLOCK } from './block.js';
export const SOURCE = 1;
export const DERIVED = 1 << 1;
@@ -1019,6 +1020,28 @@ export function mark_subtree_inert(signal, inert) {
if (!inert && (flags & IS_EFFECT) !== 0 && (flags & CLEAN) === 0) {
schedule_effect(/** @type {import('./types.js').EffectSignal} */ (signal), false);
}
+ // Nested if block effects
+ const block = signal.b;
+ if (block !== null) {
+ const type = block.t;
+ if (type === IF_BLOCK) {
+ const consequent_effect = block.ce;
+ if (consequent_effect !== null) {
+ mark_subtree_inert(consequent_effect, inert);
+ }
+ const alternate_effect = block.ae;
+ if (alternate_effect !== null) {
+ mark_subtree_inert(alternate_effect, inert);
+ }
+ } else if (type === EACH_BLOCK) {
+ const items = block.v;
+ for (let { e: each_item_effect } of items) {
+ if (each_item_effect !== null) {
+ mark_subtree_inert(each_item_effect, inert);
+ }
+ }
+ }
+ }
}
const references = signal.r;
if (references !== null) {
diff --git a/packages/svelte/tests/runtime-runes/samples/if-transition-inert/_config.js b/packages/svelte/tests/runtime-runes/samples/if-transition-inert/_config.js
new file mode 100644
index 0000000000..7a1673786c
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/if-transition-inert/_config.js
@@ -0,0 +1,16 @@
+import { flushSync } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+ html: `hello
`,
+
+ async test({ assert, target }) {
+ const [btn1, btn2] = target.querySelectorAll('button');
+
+ flushSync(() => {
+ btn1.click();
+ });
+
+ assert.htmlEqual(target.innerHTML, `hello
`);
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/if-transition-inert/main.svelte b/packages/svelte/tests/runtime-runes/samples/if-transition-inert/main.svelte
new file mode 100644
index 0000000000..0a0cc2bdf6
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/if-transition-inert/main.svelte
@@ -0,0 +1,17 @@
+
+
+
+
+{#if state}
+
+ {#if true}
+ {state}
+ {/if}
+
+{/if}
+
+
From cd2263fdabd97dcb5e7d99b891580a0757359e3b Mon Sep 17 00:00:00 2001
From: navorite
Date: Tue, 9 Jan 2024 12:50:12 +0200
Subject: [PATCH 05/12] fix: infer `svg` namespace correctly (#10027)
Add recursive check for logic blocks, ignore things such as ConstTags and Comments
closes #10025
---------
Co-authored-by: Simon Holthausen
---
.changeset/dull-roses-relate.md | 5 ++
.../src/compiler/phases/3-transform/utils.js | 76 +++++++++++++++----
.../destructure-async-assignments/main.svelte | 6 +-
.../svg-namespace-infer/Wrapper.svelte | 13 ++++
.../samples/svg-namespace-infer/_config.js | 26 +++++++
.../samples/svg-namespace-infer/main.svelte | 7 ++
6 files changed, 116 insertions(+), 17 deletions(-)
create mode 100644 .changeset/dull-roses-relate.md
create mode 100644 packages/svelte/tests/runtime-runes/samples/svg-namespace-infer/Wrapper.svelte
create mode 100644 packages/svelte/tests/runtime-runes/samples/svg-namespace-infer/_config.js
create mode 100644 packages/svelte/tests/runtime-runes/samples/svg-namespace-infer/main.svelte
diff --git a/.changeset/dull-roses-relate.md b/.changeset/dull-roses-relate.md
new file mode 100644
index 0000000000..b0da71fe59
--- /dev/null
+++ b/.changeset/dull-roses-relate.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: infer `svg` namespace correctly
diff --git a/packages/svelte/src/compiler/phases/3-transform/utils.js b/packages/svelte/src/compiler/phases/3-transform/utils.js
index b7592f6f81..8ce6c0fd1b 100644
--- a/packages/svelte/src/compiler/phases/3-transform/utils.js
+++ b/packages/svelte/src/compiler/phases/3-transform/utils.js
@@ -211,23 +211,67 @@ export function infer_namespace(namespace, parent, nodes, path) {
parent_node.type === 'SvelteFragment' ||
parent_node.type === 'SnippetBlock')
) {
- // Heuristic: Keep current namespace, unless we find a regular element,
- // in which case we always want html, or we only find svg nodes,
- // in which case we assume svg.
- let only_svg = true;
- for (const node of nodes) {
- if (node.type === 'RegularElement') {
- if (!node.metadata.svg) {
- namespace = 'html';
- only_svg = false;
- break;
- }
- } else if (node.type !== 'Text' || node.data.trim() !== '') {
- only_svg = false;
- }
+ const new_namespace = check_nodes_for_namespace(nodes, 'keep');
+ if (new_namespace !== 'keep' && new_namespace !== 'maybe_html') {
+ namespace = new_namespace;
}
- if (only_svg) {
- namespace = 'svg';
+ }
+
+ return namespace;
+}
+
+/**
+ * Heuristic: Keep current namespace, unless we find a regular element,
+ * in which case we always want html, or we only find svg nodes,
+ * in which case we assume svg.
+ * @param {import('#compiler').SvelteNode[]} nodes
+ * @param {import('#compiler').Namespace | 'keep' | 'maybe_html'} namespace
+ */
+function check_nodes_for_namespace(nodes, namespace) {
+ for (const node of nodes) {
+ if (node.type === 'RegularElement') {
+ if (!node.metadata.svg) {
+ namespace = 'html';
+ break;
+ } else if (namespace === 'keep') {
+ namespace = 'svg';
+ }
+ } else if (
+ (node.type === 'Text' && node.data.trim() !== '') ||
+ node.type === 'HtmlTag' ||
+ node.type === 'RenderTag'
+ ) {
+ namespace = 'maybe_html';
+ } else if (node.type === 'EachBlock') {
+ namespace = check_nodes_for_namespace(node.body.nodes, namespace);
+ if (namespace === 'html') break;
+ if (node.fallback) {
+ namespace = check_nodes_for_namespace(node.fallback.nodes, namespace);
+ if (namespace === 'html') break;
+ }
+ } else if (node.type === 'IfBlock') {
+ namespace = check_nodes_for_namespace(node.consequent.nodes, namespace);
+ if (namespace === 'html') break;
+ if (node.alternate) {
+ namespace = check_nodes_for_namespace(node.alternate.nodes, namespace);
+ if (namespace === 'html') break;
+ }
+ } else if (node.type === 'AwaitBlock') {
+ if (node.pending) {
+ namespace = check_nodes_for_namespace(node.pending.nodes, namespace);
+ if (namespace === 'html') break;
+ }
+ if (node.then) {
+ namespace = check_nodes_for_namespace(node.then.nodes, namespace);
+ if (namespace === 'html') break;
+ }
+ if (node.catch) {
+ namespace = check_nodes_for_namespace(node.catch.nodes, namespace);
+ if (namespace === 'html') break;
+ }
+ } else if (node.type === 'KeyBlock') {
+ namespace = check_nodes_for_namespace(node.fragment.nodes, namespace);
+ if (namespace === 'html') break;
}
}
diff --git a/packages/svelte/tests/runtime-runes/samples/destructure-async-assignments/main.svelte b/packages/svelte/tests/runtime-runes/samples/destructure-async-assignments/main.svelte
index 2d2a6538d5..0e3eec9d9e 100644
--- a/packages/svelte/tests/runtime-runes/samples/destructure-async-assignments/main.svelte
+++ b/packages/svelte/tests/runtime-runes/samples/destructure-async-assignments/main.svelte
@@ -34,6 +34,10 @@
return Promise.resolve([24, 25]);
}
+ const some = {
+ fn: () => {}
+ }
+
const update = async () => {
[a, b] = [1, await Promise.resolve(2)];
({ c = await Promise.resolve(3), d } = { d: 4 });
@@ -49,7 +53,7 @@
[l = obj[await Promise.resolve("prop")] + 1] = [];
[m] = [`${1}${await Promise.resolve("3")}`];
[n] = [-(await Promise.resolve(-14))];
- [o] = [(console.log(15), await Promise.resolve(15))];
+ [o] = [(some.fn(), await Promise.resolve(15))];
({ anotherprop: p = await Promise.resolve(16) } = obj);
let val1, val2;
({ val1 = (async function (x) { return await x; })(Promise.resolve(18)), r = await val1 }
diff --git a/packages/svelte/tests/runtime-runes/samples/svg-namespace-infer/Wrapper.svelte b/packages/svelte/tests/runtime-runes/samples/svg-namespace-infer/Wrapper.svelte
new file mode 100644
index 0000000000..41f3f38fa8
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/svg-namespace-infer/Wrapper.svelte
@@ -0,0 +1,13 @@
+outside
+
+{#if true}
+ true
+{:else}
+ false
+{/if}
+
+{#each Array(3).fill(0) as item, idx}
+ {idx}
+{/each}
+
+
diff --git a/packages/svelte/tests/runtime-runes/samples/svg-namespace-infer/_config.js b/packages/svelte/tests/runtime-runes/samples/svg-namespace-infer/_config.js
new file mode 100644
index 0000000000..46118026af
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/svg-namespace-infer/_config.js
@@ -0,0 +1,26 @@
+import { test, ok } from '../../test';
+
+export default test({
+ html: `
+
+`,
+ test({ assert, target }) {
+ const svg = target.querySelector('svg');
+ ok(svg);
+
+ assert.equal(svg.namespaceURI, 'http://www.w3.org/2000/svg');
+
+ const text_elements = target.querySelectorAll('text');
+
+ assert.equal(text_elements.length, 5);
+
+ for (const { namespaceURI } of text_elements)
+ assert.equal(namespaceURI, 'http://www.w3.org/2000/svg');
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/svg-namespace-infer/main.svelte b/packages/svelte/tests/runtime-runes/samples/svg-namespace-infer/main.svelte
new file mode 100644
index 0000000000..7c1253ea7e
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/svg-namespace-infer/main.svelte
@@ -0,0 +1,7 @@
+
+
+
From 901cfc9f15561500c6d78b5060266d648f118270 Mon Sep 17 00:00:00 2001
From: Dominic Gannaway
Date: Tue, 9 Jan 2024 10:54:29 +0000
Subject: [PATCH 06/12] fix: improve ssr template literal generation (#10127)
---
.changeset/wicked-hairs-cheer.md | 5 +++++
.../phases/3-transform/server/transform-server.js | 2 +-
.../runtime-runes/samples/attribute-parts/_config.js | 6 ++++++
.../runtime-runes/samples/attribute-parts/main.svelte | 9 +++++++++
4 files changed, 21 insertions(+), 1 deletion(-)
create mode 100644 .changeset/wicked-hairs-cheer.md
create mode 100644 packages/svelte/tests/runtime-runes/samples/attribute-parts/_config.js
create mode 100644 packages/svelte/tests/runtime-runes/samples/attribute-parts/main.svelte
diff --git a/.changeset/wicked-hairs-cheer.md b/.changeset/wicked-hairs-cheer.md
new file mode 100644
index 0000000000..532d81260b
--- /dev/null
+++ b/.changeset/wicked-hairs-cheer.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: improve ssr template literal generation
diff --git a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js
index f00091a20b..5cb479b91a 100644
--- a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js
+++ b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js
@@ -705,7 +705,7 @@ function serialize_attribute_value(
/** @type {import('estree').Expression} */ (context.visit(node.expression))
)
);
- if (i === attribute_value.length) {
+ if (i === attribute_value.length || attribute_value[i]?.type !== 'Text') {
quasis.push(b.quasi('', true));
}
}
diff --git a/packages/svelte/tests/runtime-runes/samples/attribute-parts/_config.js b/packages/svelte/tests/runtime-runes/samples/attribute-parts/_config.js
new file mode 100644
index 0000000000..dabe420686
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/attribute-parts/_config.js
@@ -0,0 +1,6 @@
+import { flushSync } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+ html: `
`
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/attribute-parts/main.svelte b/packages/svelte/tests/runtime-runes/samples/attribute-parts/main.svelte
new file mode 100644
index 0000000000..3bbd04cb7a
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/attribute-parts/main.svelte
@@ -0,0 +1,9 @@
+
+
+
+
+
From aa5a62396a6dfda896051535dfd5d2a2c55d8fd2 Mon Sep 17 00:00:00 2001
From: Simon H <5968653+dummdidumm@users.noreply.github.com>
Date: Tue, 9 Jan 2024 15:25:29 +0100
Subject: [PATCH 07/12] fix: legacy reactive dependencies tweak (#10128)
take into account member expressions when determining legacy reactive dependencies
fixes #9954
---
.changeset/quiet-crabs-nail.md | 5 +++++
.../src/compiler/phases/2-analyze/index.js | 21 ++++++++++++++++---
.../_config.js | 5 +++++
.../main.svelte | 12 +++++++++++
4 files changed, 40 insertions(+), 3 deletions(-)
create mode 100644 .changeset/quiet-crabs-nail.md
create mode 100644 packages/svelte/tests/runtime-legacy/samples/reactive-value-assign-properties/_config.js
create mode 100644 packages/svelte/tests/runtime-legacy/samples/reactive-value-assign-properties/main.svelte
diff --git a/.changeset/quiet-crabs-nail.md b/.changeset/quiet-crabs-nail.md
new file mode 100644
index 0000000000..78ea5ba14e
--- /dev/null
+++ b/.changeset/quiet-crabs-nail.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: take into account member expressions when determining legacy reactive dependencies
diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js
index dc98fb31e6..781ccdfc60 100644
--- a/packages/svelte/src/compiler/phases/2-analyze/index.js
+++ b/packages/svelte/src/compiler/phases/2-analyze/index.js
@@ -500,7 +500,15 @@ const legacy_scope_tweaker = {
node.body.type === 'ExpressionStatement' &&
node.body.expression.type === 'AssignmentExpression'
) {
- for (const id of extract_identifiers(node.body.expression.left)) {
+ let ids = extract_identifiers(node.body.expression.left);
+ if (node.body.expression.left.type === 'MemberExpression') {
+ const id = object(node.body.expression.left);
+ if (id !== null) {
+ ids = [id];
+ }
+ }
+
+ for (const id of ids) {
const binding = state.scope.get(id.name);
if (binding?.kind === 'legacy_reactive') {
// TODO does this include `let double; $: double = x * 2`?
@@ -511,8 +519,15 @@ const legacy_scope_tweaker = {
},
AssignmentExpression(node, { state, next }) {
if (state.reactive_statement && node.operator === '=') {
- for (const id of extract_identifiers(node.left)) {
- state.reactive_statement.assignments.add(id);
+ if (node.left.type === 'MemberExpression') {
+ const id = object(node.left);
+ if (id !== null) {
+ state.reactive_statement.assignments.add(id);
+ }
+ } else {
+ for (const id of extract_identifiers(node.left)) {
+ state.reactive_statement.assignments.add(id);
+ }
}
}
diff --git a/packages/svelte/tests/runtime-legacy/samples/reactive-value-assign-properties/_config.js b/packages/svelte/tests/runtime-legacy/samples/reactive-value-assign-properties/_config.js
new file mode 100644
index 0000000000..9c66e2e6db
--- /dev/null
+++ b/packages/svelte/tests/runtime-legacy/samples/reactive-value-assign-properties/_config.js
@@ -0,0 +1,5 @@
+import { test } from '../../test';
+
+export default test({
+ html: `1 1`
+});
diff --git a/packages/svelte/tests/runtime-legacy/samples/reactive-value-assign-properties/main.svelte b/packages/svelte/tests/runtime-legacy/samples/reactive-value-assign-properties/main.svelte
new file mode 100644
index 0000000000..6dabf61707
--- /dev/null
+++ b/packages/svelte/tests/runtime-legacy/samples/reactive-value-assign-properties/main.svelte
@@ -0,0 +1,12 @@
+
+
+{button.title} {button.label}
From 14dbc1be1720ff69e6f3c407e43c9c0765b0c140 Mon Sep 17 00:00:00 2001
From: Simon Holthausen
Date: Tue, 9 Jan 2024 15:33:25 +0100
Subject: [PATCH 08/12] fix: make `ComponentType` generic optional
fixes #9975
---
.changeset/red-doors-own.md | 5 +++++
packages/svelte/src/main/public.d.ts | 2 +-
packages/svelte/types/index.d.ts | 2 +-
3 files changed, 7 insertions(+), 2 deletions(-)
create mode 100644 .changeset/red-doors-own.md
diff --git a/.changeset/red-doors-own.md b/.changeset/red-doors-own.md
new file mode 100644
index 0000000000..4cd8d80791
--- /dev/null
+++ b/.changeset/red-doors-own.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: make `ComponentType` generic optional
diff --git a/packages/svelte/src/main/public.d.ts b/packages/svelte/src/main/public.d.ts
index 5849835ea2..2e8f7a6f00 100644
--- a/packages/svelte/src/main/public.d.ts
+++ b/packages/svelte/src/main/public.d.ts
@@ -177,7 +177,7 @@ export type ComponentProps = Comp extends SvelteCo
*
* ```
*/
-export type ComponentType = (new (
+export type ComponentType = (new (
options: ComponentConstructorOptions<
Comp extends SvelteComponent ? Props : Record
>
diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts
index 7baeab4189..c2a22a6e9b 100644
--- a/packages/svelte/types/index.d.ts
+++ b/packages/svelte/types/index.d.ts
@@ -178,7 +178,7 @@ declare module 'svelte' {
*
* ```
*/
- export type ComponentType = (new (
+ export type ComponentType = (new (
options: ComponentConstructorOptions<
Comp extends SvelteComponent ? Props : Record
>
From d171a39b0ad97e2a05de1f38bc76a3d345e2b3d5 Mon Sep 17 00:00:00 2001
From: Simon Holthausen
Date: Tue, 9 Jan 2024 16:15:16 +0100
Subject: [PATCH 09/12] fix: keep intermediate number value representations
fixes #9959
---
.changeset/fast-weeks-clean.md | 5 +++
packages/svelte/src/internal/client/render.js | 36 ++++++++++++++-----
2 files changed, 32 insertions(+), 9 deletions(-)
create mode 100644 .changeset/fast-weeks-clean.md
diff --git a/.changeset/fast-weeks-clean.md b/.changeset/fast-weeks-clean.md
new file mode 100644
index 0000000000..4ce7f286b3
--- /dev/null
+++ b/.changeset/fast-weeks-clean.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: keep intermediate number value representations
diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js
index 5670ef582b..30da7f35bb 100644
--- a/packages/svelte/src/internal/client/render.js
+++ b/packages/svelte/src/internal/client/render.js
@@ -926,32 +926,50 @@ export function selected(dom) {
}
/**
- * @param {Element} dom
+ * @param {HTMLInputElement} dom
* @param {() => unknown} get_value
* @param {(value: unknown) => void} update
* @returns {void}
*/
export function bind_value(dom, get_value, update) {
dom.addEventListener('input', () => {
- // @ts-ignore
+ /** @type {any} */
let value = dom.value;
- // @ts-ignore
- const type = dom.type;
- if (type === 'number' || type === 'range') {
- value = value === '' ? null : +value;
+ if (is_numberlike_input(dom)) {
+ value = to_number(value);
}
update(value);
});
+
render_effect(() => {
const value = get_value();
- const coerced_value = value == null ? null : value + '';
- // @ts-ignore
- dom.value = coerced_value;
// @ts-ignore
dom.__value = value;
+
+ if (is_numberlike_input(dom) && value === to_number(dom.value)) {
+ // handles 0 vs 00 case (see https://github.com/sveltejs/svelte/issues/9959)
+ return;
+ }
+
+ dom.value = stringify(value);
});
}
+/**
+ * @param {HTMLInputElement} dom
+ */
+function is_numberlike_input(dom) {
+ const type = dom.type;
+ return type === 'number' || type === 'range';
+}
+
+/**
+ * @param {string} value
+ */
+function to_number(value) {
+ return value === '' ? null : +value;
+}
+
/**
* @param {HTMLSelectElement} dom
* @param {() => unknown} get_value
From dda4ad510f1907a114a16227c3412eb00bd21738 Mon Sep 17 00:00:00 2001
From: Simon Holthausen
Date: Tue, 9 Jan 2024 16:54:45 +0100
Subject: [PATCH 10/12] fix: silence false positive state warning
the continue was essentially a noop because it targeted the wrong for loop
---
.changeset/short-buses-camp.md | 5 +++++
packages/svelte/src/compiler/phases/2-analyze/index.js | 4 ++--
.../validator/samples/runes-referenced-nonstate/input.svelte | 2 ++
3 files changed, 9 insertions(+), 2 deletions(-)
create mode 100644 .changeset/short-buses-camp.md
diff --git a/.changeset/short-buses-camp.md b/.changeset/short-buses-camp.md
new file mode 100644
index 0000000000..b7c0976bc8
--- /dev/null
+++ b/.changeset/short-buses-camp.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: silence false positive state warning
diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js
index 781ccdfc60..3a4e323a77 100644
--- a/packages/svelte/src/compiler/phases/2-analyze/index.js
+++ b/packages/svelte/src/compiler/phases/2-analyze/index.js
@@ -428,7 +428,7 @@ export function analyze_component(root, options) {
for (const scope of [module.scope, instance.scope]) {
outer: for (const [name, binding] of scope.declarations) {
if (binding.kind === 'normal' && binding.reassigned) {
- for (const { path } of binding.references) {
+ inner: for (const { path } of binding.references) {
if (path[0].type !== 'Fragment') continue;
for (let i = 1; i < path.length; i += 1) {
const type = path[i].type;
@@ -437,7 +437,7 @@ export function analyze_component(root, options) {
type === 'FunctionExpression' ||
type === 'ArrowFunctionExpression'
) {
- continue;
+ continue inner;
}
}
diff --git a/packages/svelte/tests/validator/samples/runes-referenced-nonstate/input.svelte b/packages/svelte/tests/validator/samples/runes-referenced-nonstate/input.svelte
index fd9d6c3173..c3a2b38fa7 100644
--- a/packages/svelte/tests/validator/samples/runes-referenced-nonstate/input.svelte
+++ b/packages/svelte/tests/validator/samples/runes-referenced-nonstate/input.svelte
@@ -2,9 +2,11 @@
let a = $state(1);
let b = 2;
let c = 3;
+ let d = 4;
+
{a} + {b} + {c} = {a + b + c}
From c05e94f26e771f5d0ff43df3de9a42a02f96f25e Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Tue, 9 Jan 2024 17:12:15 +0100
Subject: [PATCH 11/12] Version Packages (next) (#10122)
Co-authored-by: github-actions[bot]
---
.changeset/pre.json | 10 ++++++++++
packages/svelte/CHANGELOG.md | 24 ++++++++++++++++++++++++
packages/svelte/package.json | 2 +-
packages/svelte/src/version.js | 2 +-
4 files changed, 36 insertions(+), 2 deletions(-)
diff --git a/.changeset/pre.json b/.changeset/pre.json
index 0b32a281d3..ea45a1e6e1 100644
--- a/.changeset/pre.json
+++ b/.changeset/pre.json
@@ -34,12 +34,14 @@
"dry-eggs-play",
"dry-eggs-retire",
"dull-mangos-wave",
+ "dull-roses-relate",
"early-ads-tie",
"eight-steaks-shout",
"eighty-bikes-camp",
"empty-crabs-think",
"fair-crabs-check",
"famous-knives-sneeze",
+ "fast-weeks-clean",
"few-mugs-fail",
"fifty-rice-wait",
"fifty-steaks-float",
@@ -64,6 +66,7 @@
"honest-icons-change",
"hungry-dots-fry",
"hungry-tips-unite",
+ "hungry-trees-travel",
"itchy-beans-melt",
"itchy-kings-deliver",
"itchy-lions-wash",
@@ -83,6 +86,7 @@
"light-pens-watch",
"long-buckets-lay",
"long-crews-return",
+ "loud-cheetahs-flow",
"lovely-carpets-lick",
"lovely-items-turn",
"lovely-rules-eat",
@@ -90,6 +94,7 @@
"moody-frogs-exist",
"moody-owls-cry",
"nasty-lions-double",
+ "nasty-yaks-peel",
"neat-dingos-clap",
"new-boats-wait",
"ninety-dingos-walk",
@@ -111,8 +116,10 @@
"pretty-ties-help",
"purple-dragons-peel",
"quiet-camels-mate",
+ "quiet-crabs-nail",
"rare-pears-whisper",
"real-guests-do",
+ "red-doors-own",
"rich-sheep-burn",
"rich-tables-sing",
"rotten-bags-type",
@@ -127,6 +134,7 @@
"sharp-tomatoes-learn",
"shiny-baboons-play",
"shiny-shrimps-march",
+ "short-buses-camp",
"slimy-clouds-talk",
"slimy-walls-draw",
"slow-chefs-dream",
@@ -138,6 +146,7 @@
"sour-forks-stare",
"sour-rules-march",
"spicy-plums-admire",
+ "spotty-pens-agree",
"stale-books-perform",
"stale-comics-look",
"strong-gifts-smoke",
@@ -172,6 +181,7 @@
"wet-games-fly",
"wicked-clouds-exercise",
"wicked-doors-train",
+ "wicked-hairs-cheer",
"wild-foxes-wonder",
"wise-dancers-hang",
"wise-donkeys-marry",
diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md
index 184030c083..82098fc762 100644
--- a/packages/svelte/CHANGELOG.md
+++ b/packages/svelte/CHANGELOG.md
@@ -1,5 +1,29 @@
# svelte
+## 5.0.0-next.31
+
+### Patch Changes
+
+- fix: infer `svg` namespace correctly ([#10027](https://github.com/sveltejs/svelte/pull/10027))
+
+- fix: keep intermediate number value representations ([`d171a39b0`](https://github.com/sveltejs/svelte/commit/d171a39b0ad97e2a05de1f38bc76a3d345e2b3d5))
+
+- feat: allow modifiying derived props ([#10080](https://github.com/sveltejs/svelte/pull/10080))
+
+- fix: improve signal consumer tracking behavior ([#10121](https://github.com/sveltejs/svelte/pull/10121))
+
+- fix: support async/await in destructuring assignments ([#9962](https://github.com/sveltejs/svelte/pull/9962))
+
+- fix: take into account member expressions when determining legacy reactive dependencies ([#10128](https://github.com/sveltejs/svelte/pull/10128))
+
+- fix: make `ComponentType` generic optional ([`14dbc1be1`](https://github.com/sveltejs/svelte/commit/14dbc1be1720ff69e6f3c407e43c9c0765b0c140))
+
+- fix: silence false positive state warning ([`dda4ad510`](https://github.com/sveltejs/svelte/commit/dda4ad510f1907a114a16227c3412eb00bd21738))
+
+- fix: ensure nested blocks are inert during outro transitions ([#10126](https://github.com/sveltejs/svelte/pull/10126))
+
+- fix: improve ssr template literal generation ([#10127](https://github.com/sveltejs/svelte/pull/10127))
+
## 5.0.0-next.30
### Patch Changes
diff --git a/packages/svelte/package.json b/packages/svelte/package.json
index 736fdc7e45..d2c29459bc 100644
--- a/packages/svelte/package.json
+++ b/packages/svelte/package.json
@@ -2,7 +2,7 @@
"name": "svelte",
"description": "Cybernetically enhanced web apps",
"license": "MIT",
- "version": "5.0.0-next.30",
+ "version": "5.0.0-next.31",
"type": "module",
"types": "./types/index.d.ts",
"engines": {
diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js
index 74d15d26b8..17bb8b9464 100644
--- a/packages/svelte/src/version.js
+++ b/packages/svelte/src/version.js
@@ -6,5 +6,5 @@
* https://svelte.dev/docs/svelte-compiler#svelte-version
* @type {string}
*/
-export const VERSION = '5.0.0-next.30';
+export const VERSION = '5.0.0-next.31';
export const PUBLIC_VERSION = '5';
From f3265c580c1f15d4adabe8bb4ac58729c130adf3 Mon Sep 17 00:00:00 2001
From: Simon Holthausen
Date: Tue, 9 Jan 2024 17:22:48 +0100
Subject: [PATCH 12/12] chore: better test case
closes #10129
---
.../_config.js | 34 ++++++++++++++++++-
.../main.svelte | 21 +++++++-----
2 files changed, 45 insertions(+), 10 deletions(-)
diff --git a/packages/svelte/tests/runtime-legacy/samples/reactive-value-assign-properties/_config.js b/packages/svelte/tests/runtime-legacy/samples/reactive-value-assign-properties/_config.js
index 9c66e2e6db..fed7568b62 100644
--- a/packages/svelte/tests/runtime-legacy/samples/reactive-value-assign-properties/_config.js
+++ b/packages/svelte/tests/runtime-legacy/samples/reactive-value-assign-properties/_config.js
@@ -1,5 +1,37 @@
+import { flushSync } from 'svelte';
import { test } from '../../test';
export default test({
- html: `1 1`
+ html: ``,
+ test({ assert, target }) {
+ const [button1, button2, button3, button4] = target.querySelectorAll('button');
+
+ button1.click();
+ flushSync();
+ assert.htmlEqual(
+ target.innerHTML,
+ ``
+ );
+
+ button2.click();
+ flushSync();
+ assert.htmlEqual(
+ target.innerHTML,
+ ``
+ );
+
+ button3.click();
+ flushSync();
+ assert.htmlEqual(
+ target.innerHTML,
+ ``
+ );
+
+ button4.click();
+ flushSync();
+ assert.htmlEqual(
+ target.innerHTML,
+ ``
+ );
+ }
});
diff --git a/packages/svelte/tests/runtime-legacy/samples/reactive-value-assign-properties/main.svelte b/packages/svelte/tests/runtime-legacy/samples/reactive-value-assign-properties/main.svelte
index 6dabf61707..2ff764892e 100644
--- a/packages/svelte/tests/runtime-legacy/samples/reactive-value-assign-properties/main.svelte
+++ b/packages/svelte/tests/runtime-legacy/samples/reactive-value-assign-properties/main.svelte
@@ -1,12 +1,15 @@
-{button.title} {button.label}
+{#each array as row, i}
+ {#each row as item, j}
+
+ {/each}
+{/each}