From fdd009b60d7efe478b499c3730220052a6ded7d1 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Wed, 25 Jun 2025 22:04:07 +0200 Subject: [PATCH] make validate_each_keys async-aware --- .../3-transform/client/visitors/EachBlock.js | 29 +++++++----- .../samples/async-each-keyed/_config.js | 44 +++++++++++++++++++ .../samples/async-each-keyed/main.svelte | 13 ++++++ 3 files changed, 75 insertions(+), 11 deletions(-) create mode 100644 packages/svelte/tests/runtime-runes/samples/async-each-keyed/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/async-each-keyed/main.svelte diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/EachBlock.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/EachBlock.js index f5758893b2..d61d9f6ede 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/EachBlock.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/EachBlock.js @@ -312,14 +312,7 @@ export function EachBlock(node, context) { declarations.push(b.let(node.index, index)); } - if (dev && node.metadata.keyed) { - context.state.init.push( - b.stmt(b.call('$.validate_each_keys', b.thunk(collection), key_function)) - ); - } - const { has_await } = node.metadata.expression; - const thunk = b.thunk(collection, has_await); const render_args = [b.id('$$anchor'), item]; @@ -342,20 +335,34 @@ export function EachBlock(node, context) { } if (has_await) { + const statements = [b.stmt(b.call('$.each', ...args))]; + if (dev && node.metadata.keyed) { + statements.unshift( + b.stmt( + b.call( + '$.validate_each_keys', + b.thunk(b.call('$.get', b.id('$$collection'))), + key_function + ) + ) + ); + } context.state.init.push( b.stmt( b.call( '$.async', context.state.node, b.array([thunk]), - b.arrow( - [context.state.node, b.id('$$collection')], - b.block([b.stmt(b.call('$.each', ...args))]) - ) + b.arrow([context.state.node, b.id('$$collection')], b.block(statements)) ) ) ); } else { + if (dev && node.metadata.keyed) { + context.state.init.push( + b.stmt(b.call('$.validate_each_keys', b.thunk(collection), key_function)) + ); + } context.state.init.push(b.stmt(b.call('$.each', ...args))); } } diff --git a/packages/svelte/tests/runtime-runes/samples/async-each-keyed/_config.js b/packages/svelte/tests/runtime-runes/samples/async-each-keyed/_config.js new file mode 100644 index 0000000000..7a9c0760bb --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-each-keyed/_config.js @@ -0,0 +1,44 @@ +import { tick } from 'svelte'; +import { deferred } from '../../../../src/internal/shared/utils.js'; +import { test } from '../../test'; + +/** @type {ReturnType} */ +let d; + +export default test({ + compileOptions: { + dev: true + }, + html: `

pending

`, + + get props() { + d = deferred(); + + return { + promise: d.promise + }; + }, + + async test({ assert, target, component }) { + d.resolve(['a', 'b', 'c']); + await tick(); + assert.htmlEqual(target.innerHTML, '

a

b

c

'); + + d = deferred(); + component.promise = d.promise; + await tick(); + assert.htmlEqual(target.innerHTML, '

a

b

c

'); + + d.resolve(['d', 'e', 'f', 'g']); + await tick(); + assert.htmlEqual(target.innerHTML, '

d

e

f

g

'); + + d = deferred(); + component.promise = d.promise; + d.resolve(['d', 'e', 'f', 'd']); + await tick(); + assert.fail('should not allow duplicate keys'); + }, + + runtime_error: 'each_key_duplicate' +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-each-keyed/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-each-keyed/main.svelte new file mode 100644 index 0000000000..07e4f17c53 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-each-keyed/main.svelte @@ -0,0 +1,13 @@ + + + + {#each await promise as item (item)} +

{item}

+ {/each} + + {#snippet pending()} +

pending

+ {/snippet} +