diff --git a/CHANGELOG.md b/CHANGELOG.md index d153f3da8501..3e40b439754d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ * Treat slots as if they don't exist when using CSS adjacent and general sibling combinators ([#8284](https://github.com/sveltejs/svelte/issues/8284)) * Fix transitions so that they don't require a `style-src 'unsafe-inline'` Content Security Policy (CSP) ([#6662](https://github.com/sveltejs/svelte/issues/6662)). * Explicitly disallow `var` declarations extending the reactive statement scope ([#6800](https://github.com/sveltejs/svelte/pull/6800)) +* Allow `#each` to iterate over iterables like `Set`, `Map` etc ([#7425](https://github.com/sveltejs/svelte/issues/7425)) * Warn about `:` in attributes and props to prevent ambiguity with Svelte directives ([#6823](https://github.com/sveltejs/svelte/issues/6823)) ## 3.59.1 diff --git a/site/content/docs/03-template-syntax.md b/site/content/docs/03-template-syntax.md index 7c195ee78765..01ad2add6f64 100644 --- a/site/content/docs/03-template-syntax.md +++ b/site/content/docs/03-template-syntax.md @@ -296,6 +296,8 @@ An each block can also have an `{:else}` clause, which is rendered if the list i {/each} ``` +It is possible to iterate over iterables like `Map` or `Set`. Iterables need to be finite and static (they shouldn't change while being iterated over). Under the hood, they are transformed to an array using `Array.from` before being passed off to rendering. If you're writing performance-sensitive code, try to avoid iterables and use regular arrays as they are more performant. + ### {#await ...} diff --git a/src/compiler/compile/internal_exports.js b/src/compiler/compile/internal_exports.js index 8785bbf3a86a..d2b735621429 100644 --- a/src/compiler/compile/internal_exports.js +++ b/src/compiler/compile/internal_exports.js @@ -1,2 +1,2 @@ // This file is automatically generated -export default new Set(["HtmlTag","HtmlTagHydration","ResizeObserverSingleton","SvelteComponent","SvelteComponentDev","SvelteComponentTyped","SvelteElement","action_destroyer","add_attribute","add_classes","add_flush_callback","add_iframe_resize_listener","add_location","add_render_callback","add_styles","add_transform","afterUpdate","append","append_dev","append_empty_stylesheet","append_hydration","append_hydration_dev","append_styles","assign","attr","attr_dev","attribute_to_object","beforeUpdate","bind","binding_callbacks","blank_object","bubble","check_outros","children","claim_comment","claim_component","claim_element","claim_html_tag","claim_space","claim_svg_element","claim_text","clear_loops","comment","component_subscribe","compute_rest_props","compute_slots","construct_svelte_component","construct_svelte_component_dev","contenteditable_truthy_values","createEventDispatcher","create_animation","create_bidirectional_transition","create_component","create_custom_element","create_in_transition","create_out_transition","create_slot","create_ssr_component","current_component","custom_event","dataset_dev","debug","destroy_block","destroy_component","destroy_each","detach","detach_after_dev","detach_before_dev","detach_between_dev","detach_dev","dirty_components","dispatch_dev","each","element","element_is","empty","end_hydrating","escape","escape_attribute_value","escape_object","exclude_internal_props","fix_and_destroy_block","fix_and_outro_and_destroy_block","fix_position","flush","flush_render_callbacks","getAllContexts","getContext","get_all_dirty_from_scope","get_binding_group_value","get_current_component","get_custom_elements_slots","get_root_for_style","get_slot_changes","get_spread_object","get_spread_update","get_store_value","get_svelte_dataset","globals","group_outros","handle_promise","hasContext","has_prop","head_selector","identity","init","init_binding_group","init_binding_group_dynamic","insert","insert_dev","insert_hydration","insert_hydration_dev","intros","invalid_attribute_name_character","is_client","is_crossorigin","is_empty","is_function","is_promise","is_void","listen","listen_dev","loop","loop_guard","merge_ssr_styles","missing_component","mount_component","noop","not_equal","now","null_to_empty","object_without_properties","onDestroy","onMount","once","outro_and_destroy_block","prevent_default","prop_dev","query_selector_all","raf","resize_observer_border_box","resize_observer_content_box","resize_observer_device_pixel_content_box","run","run_all","safe_not_equal","schedule_update","select_multiple_value","select_option","select_options","select_value","self","setContext","set_attributes","set_current_component","set_custom_element_data","set_custom_element_data_map","set_data","set_data_contenteditable","set_data_contenteditable_dev","set_data_dev","set_data_maybe_contenteditable","set_data_maybe_contenteditable_dev","set_dynamic_element_data","set_input_type","set_input_value","set_now","set_raf","set_store_value","set_style","set_svg_attributes","space","split_css_unit","spread","src_url_equal","start_hydrating","stop_immediate_propagation","stop_propagation","subscribe","svg_element","text","tick","time_ranges_to_array","to_number","toggle_class","transition_in","transition_out","trusted","update_await_block_branch","update_keyed_each","update_slot","update_slot_base","validate_component","validate_dynamic_element","validate_each_argument","validate_each_keys","validate_slots","validate_store","validate_void_dynamic_element","xlink_attr"]); \ No newline at end of file +export default new Set(["HtmlTag","HtmlTagHydration","ResizeObserverSingleton","SvelteComponent","SvelteComponentDev","SvelteComponentTyped","SvelteElement","action_destroyer","add_attribute","add_classes","add_flush_callback","add_iframe_resize_listener","add_location","add_render_callback","add_styles","add_transform","afterUpdate","append","append_dev","append_empty_stylesheet","append_hydration","append_hydration_dev","append_styles","assign","attr","attr_dev","attribute_to_object","beforeUpdate","bind","binding_callbacks","blank_object","bubble","check_outros","children","claim_comment","claim_component","claim_element","claim_html_tag","claim_space","claim_svg_element","claim_text","clear_loops","comment","component_subscribe","compute_rest_props","compute_slots","construct_svelte_component","construct_svelte_component_dev","contenteditable_truthy_values","createEventDispatcher","create_animation","create_bidirectional_transition","create_component","create_custom_element","create_in_transition","create_out_transition","create_slot","create_ssr_component","current_component","custom_event","dataset_dev","debug","destroy_block","destroy_component","destroy_each","detach","detach_after_dev","detach_before_dev","detach_between_dev","detach_dev","dirty_components","dispatch_dev","each","element","element_is","empty","end_hydrating","ensure_array_like","ensure_array_like_dev","escape","escape_attribute_value","escape_object","exclude_internal_props","fix_and_destroy_block","fix_and_outro_and_destroy_block","fix_position","flush","flush_render_callbacks","getAllContexts","getContext","get_all_dirty_from_scope","get_binding_group_value","get_current_component","get_custom_elements_slots","get_root_for_style","get_slot_changes","get_spread_object","get_spread_update","get_store_value","get_svelte_dataset","globals","group_outros","handle_promise","hasContext","has_prop","head_selector","identity","init","init_binding_group","init_binding_group_dynamic","insert","insert_dev","insert_hydration","insert_hydration_dev","intros","invalid_attribute_name_character","is_client","is_crossorigin","is_empty","is_function","is_promise","is_void","listen","listen_dev","loop","loop_guard","merge_ssr_styles","missing_component","mount_component","noop","not_equal","now","null_to_empty","object_without_properties","onDestroy","onMount","once","outro_and_destroy_block","prevent_default","prop_dev","query_selector_all","raf","resize_observer_border_box","resize_observer_content_box","resize_observer_device_pixel_content_box","run","run_all","safe_not_equal","schedule_update","select_multiple_value","select_option","select_options","select_value","self","setContext","set_attributes","set_current_component","set_custom_element_data","set_custom_element_data_map","set_data","set_data_contenteditable","set_data_contenteditable_dev","set_data_dev","set_data_maybe_contenteditable","set_data_maybe_contenteditable_dev","set_dynamic_element_data","set_input_type","set_input_value","set_now","set_raf","set_store_value","set_style","set_svg_attributes","space","split_css_unit","spread","src_url_equal","start_hydrating","stop_immediate_propagation","stop_propagation","subscribe","svg_element","text","tick","time_ranges_to_array","to_number","toggle_class","transition_in","transition_out","trusted","update_await_block_branch","update_keyed_each","update_slot","update_slot_base","validate_component","validate_dynamic_element","validate_each_keys","validate_slots","validate_store","validate_void_dynamic_element","xlink_attr"]); \ No newline at end of file diff --git a/src/compiler/compile/render_dom/wrappers/EachBlock.js b/src/compiler/compile/render_dom/wrappers/EachBlock.js index 34fbbac16e9c..7e1d1393623e 100644 --- a/src/compiler/compile/render_dom/wrappers/EachBlock.js +++ b/src/compiler/compile/render_dom/wrappers/EachBlock.js @@ -206,11 +206,8 @@ export default class EachBlockWrapper extends Wrapper { const needs_anchor = this.next ? !this.next.is_dom_node() : !parent_node || !this.parent.is_dom_node(); - const snippet = this.node.expression.manipulate(block); + const snippet = x`@ensure_array_like(${this.node.expression.manipulate(block)})`; block.chunks.init.push(b`let ${this.vars.each_block_value} = ${snippet};`); - if (this.renderer.options.dev) { - block.chunks.init.push(b`@validate_each_argument(${this.vars.each_block_value});`); - } /** @type {import('estree').Identifier} */ const initial_anchor_node = { @@ -480,7 +477,6 @@ export default class EachBlockWrapper extends Wrapper { this.block.maintain_context = true; this.updates.push(b` ${this.vars.each_block_value} = ${snippet}; - ${this.renderer.options.dev && b`@validate_each_argument(${this.vars.each_block_value});`} ${this.block.has_outros && b`@group_outros();`} ${ @@ -628,7 +624,6 @@ export default class EachBlockWrapper extends Wrapper { const update = b` ${!this.block.has_update_method && b`const #old_length = ${this.vars.each_block_value}.length;`} ${this.vars.each_block_value} = ${snippet}; - ${this.renderer.options.dev && b`@validate_each_argument(${this.vars.each_block_value});`} let #i; for (#i = ${start}; #i < ${data_length}; #i += 1) { diff --git a/src/runtime/internal/dev.js b/src/runtime/internal/dev.js index 0a7d1eeb2b45..494c276260f9 100644 --- a/src/runtime/internal/dev.js +++ b/src/runtime/internal/dev.js @@ -12,6 +12,7 @@ import { SvelteComponent } from './Component.js'; import { is_void } from '../../shared/utils/names.js'; import { VERSION } from '../../shared/version.js'; import { contenteditable_truthy_values } from './utils.js'; +import { ensure_array_like } from './each.js'; /** * @template T @@ -208,16 +209,15 @@ export function set_data_maybe_contenteditable_dev(text, data, attr_value) { } } -/** - * @returns {void} */ -export function validate_each_argument(arg) { - if (typeof arg !== 'string' && !(arg && typeof arg === 'object' && 'length' in arg)) { - let msg = '{#each} only iterates over array-like objects.'; - if (typeof Symbol === 'function' && arg && Symbol.iterator in arg) { - msg += ' You can use a spread to convert this iterable into an array.'; - } - throw new Error(msg); +export function ensure_array_like_dev(arg) { + if ( + typeof arg !== 'string' && + !(arg && typeof arg === 'object' && 'length' in arg) && + !(typeof Symbol === 'function' && arg && Symbol.iterator in arg) + ) { + throw new Error('{#each} only works with iterable values.'); } + return ensure_array_like(arg); } /** diff --git a/src/runtime/internal/keyed_each.js b/src/runtime/internal/each.js similarity index 92% rename from src/runtime/internal/keyed_each.js rename to src/runtime/internal/each.js index 3bbc042149c2..2641658658cf 100644 --- a/src/runtime/internal/keyed_each.js +++ b/src/runtime/internal/each.js @@ -1,6 +1,16 @@ import { transition_in, transition_out } from './transitions.js'; import { run_all } from './utils.js'; +// general each functions: + +export function ensure_array_like(array_like_or_iterator) { + return array_like_or_iterator?.length !== undefined + ? array_like_or_iterator + : Array.from(array_like_or_iterator); +} + +// keyed each functions: + /** @returns {void} */ export function destroy_block(block, lookup) { block.d(1); diff --git a/src/runtime/internal/index.js b/src/runtime/internal/index.js index dcf925a54231..ca57ae1414cd 100644 --- a/src/runtime/internal/index.js +++ b/src/runtime/internal/index.js @@ -3,7 +3,7 @@ export * from './await_block.js'; export * from './dom.js'; export * from './environment.js'; export * from './globals.js'; -export * from './keyed_each.js'; +export * from './each.js'; export * from './lifecycle.js'; export * from './loop.js'; export * from './scheduler.js'; diff --git a/src/runtime/internal/ssr.js b/src/runtime/internal/ssr.js index 0bba8adfeece..85d6f443df44 100644 --- a/src/runtime/internal/ssr.js +++ b/src/runtime/internal/ssr.js @@ -1,6 +1,7 @@ import { set_current_component, current_component } from './lifecycle.js'; import { run_all, blank_object } from './utils.js'; import { boolean_attributes } from '../../shared/boolean_attributes.js'; +import { ensure_array_like } from './each.js'; export { is_void } from '../../shared/utils/names.js'; export const invalid_attribute_name_character = @@ -107,6 +108,7 @@ export function escape_object(obj) { /** @returns {string} */ export function each(items, fn) { + items = ensure_array_like(items); let str = ''; for (let i = 0; i < items.length; i += 1) { str += fn(items[i], i); diff --git a/test/js/samples/debug-foo-bar-baz-things/expected.js b/test/js/samples/debug-foo-bar-baz-things/expected.js index 0b0a41abe234..360df103e469 100644 --- a/test/js/samples/debug-foo-bar-baz-things/expected.js +++ b/test/js/samples/debug-foo-bar-baz-things/expected.js @@ -7,6 +7,7 @@ import { detach_dev, dispatch_dev, element, + ensure_array_like_dev, init, insert_dev, noop, @@ -14,7 +15,6 @@ import { set_data_dev, space, text, - validate_each_argument, validate_slots } from "svelte/internal"; @@ -89,8 +89,7 @@ function create_fragment(ctx) { let p; let t1; let t2; - let each_value = /*things*/ ctx[0]; - validate_each_argument(each_value); + let each_value = ensure_array_like_dev(/*things*/ ctx[0]); let each_blocks = []; for (let i = 0; i < each_value.length; i += 1) { @@ -126,8 +125,7 @@ function create_fragment(ctx) { }, p: function update(ctx, [dirty]) { if (dirty & /*things*/ 1) { - each_value = /*things*/ ctx[0]; - validate_each_argument(each_value); + each_value = ensure_array_like_dev(/*things*/ ctx[0]); let i; for (i = 0; i < each_value.length; i += 1) { diff --git a/test/js/samples/debug-foo/expected.js b/test/js/samples/debug-foo/expected.js index 637a229a979c..e4925d9ef0ce 100644 --- a/test/js/samples/debug-foo/expected.js +++ b/test/js/samples/debug-foo/expected.js @@ -7,6 +7,7 @@ import { detach_dev, dispatch_dev, element, + ensure_array_like_dev, init, insert_dev, noop, @@ -14,7 +15,6 @@ import { set_data_dev, space, text, - validate_each_argument, validate_slots } from "svelte/internal"; @@ -83,8 +83,7 @@ function create_fragment(ctx) { let p; let t1; let t2; - let each_value = /*things*/ ctx[0]; - validate_each_argument(each_value); + let each_value = ensure_array_like_dev(/*things*/ ctx[0]); let each_blocks = []; for (let i = 0; i < each_value.length; i += 1) { @@ -120,8 +119,7 @@ function create_fragment(ctx) { }, p: function update(ctx, [dirty]) { if (dirty & /*things*/ 1) { - each_value = /*things*/ ctx[0]; - validate_each_argument(each_value); + each_value = ensure_array_like_dev(/*things*/ ctx[0]); let i; for (i = 0; i < each_value.length; i += 1) { diff --git a/test/js/samples/debug-no-dependencies/expected.js b/test/js/samples/debug-no-dependencies/expected.js index e9e4d34a11bd..8e3ae1c43aa8 100644 --- a/test/js/samples/debug-no-dependencies/expected.js +++ b/test/js/samples/debug-no-dependencies/expected.js @@ -5,13 +5,13 @@ import { detach_dev, dispatch_dev, empty, + ensure_array_like_dev, init, insert_dev, noop, safe_not_equal, space, text, - validate_each_argument, validate_slots } from "svelte/internal"; @@ -65,8 +65,7 @@ function create_each_block(ctx) { function create_fragment(ctx) { let each_1_anchor; - let each_value = things; - validate_each_argument(each_value); + let each_value = ensure_array_like_dev(things); let each_blocks = []; for (let i = 0; i < each_value.length; i += 1) { @@ -95,8 +94,7 @@ function create_fragment(ctx) { }, p: function update(ctx, [dirty]) { if (dirty & /*things*/ 0) { - each_value = things; - validate_each_argument(each_value); + each_value = ensure_array_like_dev(things); let i; for (i = 0; i < each_value.length; i += 1) { diff --git a/test/js/samples/deconflict-builtins/expected.js b/test/js/samples/deconflict-builtins/expected.js index 7e5263e8b34e..e61d466acf34 100644 --- a/test/js/samples/deconflict-builtins/expected.js +++ b/test/js/samples/deconflict-builtins/expected.js @@ -6,6 +6,7 @@ import { detach, element, empty, + ensure_array_like, init, insert, noop, @@ -46,7 +47,7 @@ function create_each_block(ctx) { function create_fragment(ctx) { let each_1_anchor; - let each_value = /*createElement*/ ctx[0]; + let each_value = ensure_array_like(/*createElement*/ ctx[0]); let each_blocks = []; for (let i = 0; i < each_value.length; i += 1) { @@ -72,7 +73,7 @@ function create_fragment(ctx) { }, p(ctx, [dirty]) { if (dirty & /*createElement*/ 1) { - each_value = /*createElement*/ ctx[0]; + each_value = ensure_array_like(/*createElement*/ ctx[0]); let i; for (i = 0; i < each_value.length; i += 1) { diff --git a/test/js/samples/each-block-array-literal/expected.js b/test/js/samples/each-block-array-literal/expected.js index 575cf1bba701..6620abc0fa6a 100644 --- a/test/js/samples/each-block-array-literal/expected.js +++ b/test/js/samples/each-block-array-literal/expected.js @@ -6,6 +6,7 @@ import { detach, element, empty, + ensure_array_like, init, insert, noop, @@ -46,7 +47,7 @@ function create_each_block(ctx) { function create_fragment(ctx) { let each_1_anchor; - let each_value = [/*a*/ ctx[0], /*b*/ ctx[1], /*c*/ ctx[2], /*d*/ ctx[3], /*e*/ ctx[4]]; + let each_value = ensure_array_like([/*a*/ ctx[0], /*b*/ ctx[1], /*c*/ ctx[2], /*d*/ ctx[3], /*e*/ ctx[4]]); let each_blocks = []; for (let i = 0; i < 5; i += 1) { @@ -72,7 +73,7 @@ function create_fragment(ctx) { }, p(ctx, [dirty]) { if (dirty & /*a, b, c, d, e*/ 31) { - each_value = [/*a*/ ctx[0], /*b*/ ctx[1], /*c*/ ctx[2], /*d*/ ctx[3], /*e*/ ctx[4]]; + each_value = ensure_array_like([/*a*/ ctx[0], /*b*/ ctx[1], /*c*/ ctx[2], /*d*/ ctx[3], /*e*/ ctx[4]]); let i; for (i = 0; i < 5; i += 1) { diff --git a/test/js/samples/each-block-changed-check/expected.js b/test/js/samples/each-block-changed-check/expected.js index 5451f3699067..6d03010c2689 100644 --- a/test/js/samples/each-block-changed-check/expected.js +++ b/test/js/samples/each-block-changed-check/expected.js @@ -7,6 +7,7 @@ import { destroy_each, detach, element, + ensure_array_like, init, insert, noop, @@ -83,7 +84,7 @@ function create_fragment(ctx) { let t0; let p; let t1; - let each_value = /*comments*/ ctx[0]; + let each_value = ensure_array_like(/*comments*/ ctx[0]); let each_blocks = []; for (let i = 0; i < each_value.length; i += 1) { @@ -113,7 +114,7 @@ function create_fragment(ctx) { }, p(ctx, [dirty]) { if (dirty & /*comments, elapsed, time*/ 7) { - each_value = /*comments*/ ctx[0]; + each_value = ensure_array_like(/*comments*/ ctx[0]); let i; for (i = 0; i < each_value.length; i += 1) { diff --git a/test/js/samples/each-block-keyed-animated/expected.js b/test/js/samples/each-block-keyed-animated/expected.js index 7edfa77b1810..d8aa7a750181 100644 --- a/test/js/samples/each-block-keyed-animated/expected.js +++ b/test/js/samples/each-block-keyed-animated/expected.js @@ -6,6 +6,7 @@ import { detach, element, empty, + ensure_array_like, fix_and_destroy_block, fix_position, init, @@ -68,7 +69,7 @@ function create_fragment(ctx) { let each_blocks = []; let each_1_lookup = new Map(); let each_1_anchor; - let each_value = /*things*/ ctx[0]; + let each_value = ensure_array_like(/*things*/ ctx[0]); const get_key = ctx => /*thing*/ ctx[1].id; for (let i = 0; i < each_value.length; i += 1) { @@ -96,7 +97,7 @@ function create_fragment(ctx) { }, p(ctx, [dirty]) { if (dirty & /*things*/ 1) { - each_value = /*things*/ ctx[0]; + each_value = ensure_array_like(/*things*/ ctx[0]); for (let i = 0; i < each_blocks.length; i += 1) each_blocks[i].r(); each_blocks = update_keyed_each(each_blocks, dirty, get_key, 1, ctx, each_value, each_1_lookup, each_1_anchor.parentNode, fix_and_destroy_block, create_each_block, each_1_anchor, get_each_context); for (let i = 0; i < each_blocks.length; i += 1) each_blocks[i].a(); diff --git a/test/js/samples/each-block-keyed/expected.js b/test/js/samples/each-block-keyed/expected.js index a6357cbf77e6..10c41ee15983 100644 --- a/test/js/samples/each-block-keyed/expected.js +++ b/test/js/samples/each-block-keyed/expected.js @@ -6,6 +6,7 @@ import { detach, element, empty, + ensure_array_like, init, insert, noop, @@ -53,7 +54,7 @@ function create_fragment(ctx) { let each_blocks = []; let each_1_lookup = new Map(); let each_1_anchor; - let each_value = /*things*/ ctx[0]; + let each_value = ensure_array_like(/*things*/ ctx[0]); const get_key = ctx => /*thing*/ ctx[1].id; for (let i = 0; i < each_value.length; i += 1) { @@ -81,7 +82,7 @@ function create_fragment(ctx) { }, p(ctx, [dirty]) { if (dirty & /*things*/ 1) { - each_value = /*things*/ ctx[0]; + each_value = ensure_array_like(/*things*/ ctx[0]); each_blocks = update_keyed_each(each_blocks, dirty, get_key, 1, ctx, each_value, each_1_lookup, each_1_anchor.parentNode, destroy_block, create_each_block, each_1_anchor, get_each_context); } }, diff --git a/test/runtime/runtime.shared.js b/test/runtime/runtime.shared.js index 0d317fb36005..b02dfb81ffda 100644 --- a/test/runtime/runtime.shared.js +++ b/test/runtime/runtime.shared.js @@ -214,7 +214,7 @@ async function run_test(dir) { const dir = `${cwd}/_output/${hydrate ? 'hydratable' : 'normal'}`; const out = `${dir}/${file.replace(/\.svelte$/, '.js')}`; - mkdirp(dir); + mkdirp(path.dirname(out)); // file could be in subdirectory, therefore don't use dir const { js } = compile(fs.readFileSync(`${cwd}/${file}`, 'utf-8').replace(/\r/g, ''), { ...compileOptions, diff --git a/test/runtime/samples/dev-warning-each-block-no-sets-maps/_config.js b/test/runtime/samples/dev-warning-each-block-no-sets-maps/_config.js deleted file mode 100644 index 3d6c11993232..000000000000 --- a/test/runtime/samples/dev-warning-each-block-no-sets-maps/_config.js +++ /dev/null @@ -1,7 +0,0 @@ -export default { - compileOptions: { - dev: true - }, - error: - '{#each} only iterates over array-like objects. You can use a spread to convert this iterable into an array.' -}; diff --git a/test/runtime/samples/dev-warning-each-block-no-sets-maps/main.svelte b/test/runtime/samples/dev-warning-each-block-no-sets-maps/main.svelte deleted file mode 100644 index e1aae26d129a..000000000000 --- a/test/runtime/samples/dev-warning-each-block-no-sets-maps/main.svelte +++ /dev/null @@ -1,7 +0,0 @@ - - -{#each foo as item} -
{item}
-{/each} \ No newline at end of file diff --git a/test/runtime/samples/dev-warning-each-block-require-arraylike/_config.js b/test/runtime/samples/dev-warning-each-block-require-arraylike/_config.js index ff6ef8060709..ecf16743a02a 100644 --- a/test/runtime/samples/dev-warning-each-block-require-arraylike/_config.js +++ b/test/runtime/samples/dev-warning-each-block-require-arraylike/_config.js @@ -2,5 +2,5 @@ export default { compileOptions: { dev: true }, - error: '{#each} only iterates over array-like objects.' + error: '{#each} only works with iterable values.' }; diff --git a/test/runtime/samples/each-block-with-iterable/_config.js b/test/runtime/samples/each-block-with-iterable/_config.js new file mode 100644 index 000000000000..b1ba63754c72 --- /dev/null +++ b/test/runtime/samples/each-block-with-iterable/_config.js @@ -0,0 +1,29 @@ +export default { + html: ` +

1

+

2

+ +

1 0

+

2 1

+ +

1 0

+

2 1

+ `, + + test({ assert, component, target }) { + component.numbers = new Set([2, 3]); + assert.htmlEqual( + target.innerHTML, + ` +

2

+

3

+ +

2 0

+

3 1

+ +

2 0

+

3 1

+ ` + ); + } +}; diff --git a/test/runtime/samples/each-block-with-iterable/main.svelte b/test/runtime/samples/each-block-with-iterable/main.svelte new file mode 100644 index 000000000000..b69e5ec3fa70 --- /dev/null +++ b/test/runtime/samples/each-block-with-iterable/main.svelte @@ -0,0 +1,15 @@ + + +{#each numbers as i} +

{i}

+{/each} + +{#each numbers as i, index} +

{i} {index}

+{/each} + +{#each numbers as i, index (i)} +

{i} {index}

+{/each}