diff --git a/.changeset/ninety-olives-report.md b/.changeset/ninety-olives-report.md new file mode 100644 index 0000000000..3e66a41d02 --- /dev/null +++ b/.changeset/ninety-olives-report.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: replace `undefined` with `void(0)` in CallExpressions diff --git a/.changeset/wise-schools-report.md b/.changeset/wise-schools-report.md new file mode 100644 index 0000000000..47ec887256 --- /dev/null +++ b/.changeset/wise-schools-report.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: place store setup inside async body diff --git a/documentation/docs/07-misc/04-custom-elements.md b/documentation/docs/07-misc/04-custom-elements.md index 7e6a17b947..4e5afff7d2 100644 --- a/documentation/docs/07-misc/04-custom-elements.md +++ b/documentation/docs/07-misc/04-custom-elements.md @@ -4,7 +4,7 @@ title: Custom elements -Svelte components can also be compiled to custom elements (aka web components) using the `customElement: true` compiler option. You should specify a tag name for the component using the `` [element](svelte-options). +Svelte components can also be compiled to custom elements (aka web components) using the `customElement: true` compiler option. You should specify a tag name for the component using the `` [element](svelte-options). Within the custom element you can access the host element via the [`$host`](https://svelte.dev/docs/svelte/$host) rune. ```svelte diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index fb6b20c489..de94eb1897 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,31 @@ # svelte +## 5.38.6 + +### Patch Changes + +- fix: don't fail on `flushSync` while flushing effects ([#16674](https://github.com/sveltejs/svelte/pull/16674)) + +## 5.38.5 + +### Patch Changes + +- fix: ensure async deriveds always get dependencies from thennable ([#16672](https://github.com/sveltejs/svelte/pull/16672)) + +## 5.38.4 + +### Patch Changes + +- fix: place instance-level snippets inside async body ([#16666](https://github.com/sveltejs/svelte/pull/16666)) + +- fix: Add check for builtin custom elements in `set_custom_element_data` ([#16592](https://github.com/sveltejs/svelte/pull/16592)) + +- fix: restore batch along with effect context ([#16668](https://github.com/sveltejs/svelte/pull/16668)) + +- fix: wait until changes propagate before updating input selection state ([#16649](https://github.com/sveltejs/svelte/pull/16649)) + +- fix: add "Accept-CH" as valid value for `http-equiv` ([#16671](https://github.com/sveltejs/svelte/pull/16671)) + ## 5.38.3 ### Patch Changes diff --git a/packages/svelte/elements.d.ts b/packages/svelte/elements.d.ts index f63a31a96b..b0c2fae2de 100644 --- a/packages/svelte/elements.d.ts +++ b/packages/svelte/elements.d.ts @@ -1268,6 +1268,7 @@ export interface HTMLMetaAttributes extends HTMLAttributes { charset?: string | undefined | null; content?: string | undefined | null; 'http-equiv'?: + | 'accept-ch' | 'content-security-policy' | 'content-type' | 'default-style' diff --git a/packages/svelte/package.json b/packages/svelte/package.json index b7effe35bd..fe42603184 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.38.3", + "version": "5.38.6", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js index b7cfd30b7c..35f54a57cb 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js @@ -362,22 +362,41 @@ export function client_component(analysis, options) { let component_block = b.block([ store_init, - ...store_setup, ...legacy_reactive_declarations, - ...group_binding_declarations, - ...state.instance_level_snippets + ...group_binding_declarations ]); + const should_inject_context = + dev || + analysis.needs_context || + analysis.reactive_statements.size > 0 || + component_returned_object.length > 0; + if (analysis.instance.has_await) { + if (should_inject_context && component_returned_object.length > 0) { + component_block.body.push(b.var('$$exports')); + } const body = b.block([ + ...store_setup, + ...state.instance_level_snippets, .../** @type {ESTree.Statement[]} */ (instance.body), + ...(should_inject_context && component_returned_object.length > 0 + ? [b.stmt(b.assignment('=', b.id('$$exports'), b.object(component_returned_object)))] + : []), b.if(b.call('$.aborted'), b.return()), .../** @type {ESTree.Statement[]} */ (template.body) ]); component_block.body.push(b.stmt(b.call(`$.async_body`, b.arrow([], body, true)))); } else { - component_block.body.push(.../** @type {ESTree.Statement[]} */ (instance.body)); + component_block.body.push( + ...state.instance_level_snippets, + .../** @type {ESTree.Statement[]} */ (instance.body) + ); + if (should_inject_context && component_returned_object.length > 0) { + component_block.body.push(b.var('$$exports', b.object(component_returned_object))); + } + component_block.body.unshift(...store_setup); if (!analysis.runes && analysis.needs_context) { component_block.body.push(b.stmt(b.call('$.init', analysis.immutable ? b.true : undefined))); @@ -392,12 +411,6 @@ export function client_component(analysis, options) { ); } - const should_inject_context = - dev || - analysis.needs_context || - analysis.reactive_statements.size > 0 || - component_returned_object.length > 0; - let should_inject_props = should_inject_context || analysis.needs_props || @@ -444,7 +457,7 @@ export function client_component(analysis, options) { let to_push; if (component_returned_object.length > 0) { - let pop_call = b.call('$.pop', b.object(component_returned_object)); + let pop_call = b.call('$.pop', b.id('$$exports')); to_push = needs_store_cleanup ? b.var('$$pop', pop_call) : b.return(pop_call); } else { to_push = b.stmt(b.call('$.pop')); @@ -455,6 +468,7 @@ export function client_component(analysis, options) { if (needs_store_cleanup) { component_block.body.push(b.stmt(b.call('$$cleanup'))); + if (component_returned_object.length > 0) { component_block.body.push(b.return(b.id('$$pop'))); } diff --git a/packages/svelte/src/compiler/utils/builders.js b/packages/svelte/src/compiler/utils/builders.js index 56a5f31ffe..03a946ff9c 100644 --- a/packages/svelte/src/compiler/utils/builders.js +++ b/packages/svelte/src/compiler/utils/builders.js @@ -100,7 +100,7 @@ export function call(callee, ...args) { if (typeof callee === 'string') callee = id(callee); args = args.slice(); - // replacing missing arguments with `undefined`, unless they're at the end in which case remove them + // replacing missing arguments with `void(0)`, unless they're at the end in which case remove them let i = args.length; let popping = true; while (i--) { @@ -108,7 +108,7 @@ export function call(callee, ...args) { if (popping) { args.pop(); } else { - args[i] = id('undefined'); + args[i] = void0; } } else { popping = false; diff --git a/packages/svelte/src/internal/client/dom/blocks/boundary.js b/packages/svelte/src/internal/client/dom/blocks/boundary.js index 1866931ef2..60fe2b7d3c 100644 --- a/packages/svelte/src/internal/client/dom/blocks/boundary.js +++ b/packages/svelte/src/internal/client/dom/blocks/boundary.js @@ -1,10 +1,5 @@ /** @import { Effect, Source, TemplateNode, } from '#client' */ -import { - BOUNDARY_EFFECT, - EFFECT_PRESERVED, - EFFECT_RAN, - EFFECT_TRANSPARENT -} from '#client/constants'; +import { BOUNDARY_EFFECT, EFFECT_PRESERVED, EFFECT_TRANSPARENT } from '#client/constants'; import { component_context, set_component_context } from '../../context.js'; import { handle_error, invoke_error_boundary } from '../../error-handling.js'; import { block, branch, destroy_effect, pause_effect } from '../../reactivity/effects.js'; diff --git a/packages/svelte/src/internal/client/dom/elements/attributes.js b/packages/svelte/src/internal/client/dom/elements/attributes.js index 2fa5d4541c..a5b7140f25 100644 --- a/packages/svelte/src/internal/client/dom/elements/attributes.js +++ b/packages/svelte/src/internal/client/dom/elements/attributes.js @@ -238,10 +238,10 @@ export function set_custom_element_data(node, prop, value) { // Don't compute setters for custom elements while they aren't registered yet, // because during their upgrade/instantiation they might add more setters. // Instead, fall back to a simple "an object, then set as property" heuristic. - (setters_cache.has(node.nodeName) || + (setters_cache.has(node.getAttribute('is') || node.nodeName) || // customElements may not be available in browser extension contexts !customElements || - customElements.get(node.tagName.toLowerCase()) + customElements.get(node.getAttribute('is') || node.tagName.toLowerCase()) ? get_setters(node).includes(prop) : value && typeof value === 'object') ) { @@ -546,9 +546,10 @@ var setters_cache = new Map(); /** @param {Element} element */ function get_setters(element) { - var setters = setters_cache.get(element.nodeName); + var cache_key = element.getAttribute('is') || element.nodeName; + var setters = setters_cache.get(cache_key); if (setters) return setters; - setters_cache.set(element.nodeName, (setters = [])); + setters_cache.set(cache_key, (setters = [])); var descriptors; var proto = element; // In the case of custom elements there might be setters on the instance diff --git a/packages/svelte/src/internal/client/dom/elements/bindings/input.js b/packages/svelte/src/internal/client/dom/elements/bindings/input.js index 67e6ff1dd2..815acde7c5 100644 --- a/packages/svelte/src/internal/client/dom/elements/bindings/input.js +++ b/packages/svelte/src/internal/client/dom/elements/bindings/input.js @@ -6,7 +6,7 @@ import * as e from '../../../errors.js'; import { is } from '../../../proxy.js'; import { queue_micro_task } from '../../task.js'; import { hydrating } from '../../hydration.js'; -import { untrack } from '../../../runtime.js'; +import { tick, untrack } from '../../../runtime.js'; import { is_runes } from '../../../context.js'; import { current_batch, previous_batch } from '../../../reactivity/batch.js'; @@ -17,11 +17,9 @@ import { current_batch, previous_batch } from '../../../reactivity/batch.js'; * @returns {void} */ export function bind_value(input, get, set = get) { - var runes = is_runes(); - var batches = new WeakSet(); - listen_to_event_and_reset_event(input, 'input', (is_reset) => { + listen_to_event_and_reset_event(input, 'input', async (is_reset) => { if (DEV && input.type === 'checkbox') { // TODO should this happen in prod too? e.bind_invalid_checkbox_value(); @@ -36,9 +34,13 @@ export function bind_value(input, get, set = get) { batches.add(current_batch); } - // In runes mode, respect any validation in accessors (doesn't apply in legacy mode, - // because we use mutable state which ensures the render effect always runs) - if (runes && value !== (value = get())) { + // Because `{#each ...}` blocks work by updating sources inside the flush, + // we need to wait a tick before checking to see if we should forcibly + // update the input and reset the selection state + await tick(); + + // Respect any validation in accessors + if (value !== (value = get())) { var start = input.selectionStart; var end = input.selectionEnd; diff --git a/packages/svelte/src/internal/client/reactivity/async.js b/packages/svelte/src/internal/client/reactivity/async.js index 0b404920b8..6dc1dce543 100644 --- a/packages/svelte/src/internal/client/reactivity/async.js +++ b/packages/svelte/src/internal/client/reactivity/async.js @@ -73,11 +73,13 @@ function capture() { var previous_effect = active_effect; var previous_reaction = active_reaction; var previous_component_context = component_context; + var previous_batch = current_batch; return function restore() { set_active_effect(previous_effect); set_active_reaction(previous_reaction); set_component_context(previous_component_context); + previous_batch?.activate(); if (DEV) { set_from_async_derived(null); @@ -176,8 +178,8 @@ export function unset_context() { * @param {() => Promise} fn */ export async function async_body(fn) { - const unsuspend = suspend(); - const active = /** @type {Effect} */ (active_effect); + var unsuspend = suspend(); + var active = /** @type {Effect} */ (active_effect); try { await fn(); diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index 60fa03c56c..82f1de67a9 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -76,8 +76,8 @@ let queued_root_effects = []; let last_scheduled_effect = null; let is_flushing = false; - let is_flushing_sync = false; + export class Batch { /** * The current values of any sources that are updated in this batch @@ -187,7 +187,7 @@ export class Batch { // if there are multiple batches, we are 'time travelling' — // we need to undo the changes belonging to any batch // other than the current one - if (batches.size > 1) { + if (async_mode_flag && batches.size > 1) { current_values = new Map(); batch_deriveds = new Map(); @@ -484,6 +484,7 @@ export class Batch { */ export function flushSync(fn) { if (async_mode_flag && active_effect !== null) { + // We disallow this because it creates super-hard to reason about stack trace and because it's generally a bad idea e.flush_sync_in_effect(); } @@ -622,7 +623,9 @@ function flush_queued_effects(effects) { } } - if (eager_block_effects.length > 0) { + // If update_effect() has a flushSync() in it, we may have flushed another flush_queued_effects(), + // which already handled this logic and did set eager_block_effects to null. + if (eager_block_effects?.length > 0) { // TODO this feels incorrect! it gets the tests passing old_values.clear(); @@ -678,6 +681,8 @@ export function suspend() { if (!pending) { batch.activate(); batch.decrement(); + } else { + batch.deactivate(); } unset_context(); diff --git a/packages/svelte/src/internal/client/reactivity/deriveds.js b/packages/svelte/src/internal/client/reactivity/deriveds.js index 7f730e365e..31dc267960 100644 --- a/packages/svelte/src/internal/client/reactivity/deriveds.js +++ b/packages/svelte/src/internal/client/reactivity/deriveds.js @@ -120,6 +120,9 @@ export function async_derived(fn, location) { try { var p = fn(); + // Make sure to always access the then property to read any signals + // it might access, so that we track them as dependencies. + if (prev) Promise.resolve(p).catch(() => {}); // avoid unhandled rejection } catch (error) { p = Promise.reject(error); } diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index 815b25bf1f..67c586790f 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.38.3'; +export const VERSION = '5.38.6'; export const PUBLIC_VERSION = '5'; diff --git a/packages/svelte/tests/runtime-runes/samples/async-derived-reverse-order/_config.js b/packages/svelte/tests/runtime-runes/samples/async-derived-reverse-order/_config.js new file mode 100644 index 0000000000..bd0dd753c2 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-derived-reverse-order/_config.js @@ -0,0 +1,41 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target }) { + const [increment, pop] = target.querySelectorAll('button'); + + increment.click(); + await tick(); + + pop.click(); + await tick(); + + pop.click(); + await tick(); + + assert.htmlEqual( + target.innerHTML, + ` + + +

1

+ ` + ); + + increment.click(); + await tick(); + + pop.click(); + await tick(); + + assert.htmlEqual( + target.innerHTML, + ` + + +

2

+ ` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-derived-reverse-order/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-derived-reverse-order/main.svelte new file mode 100644 index 0000000000..b9f6c26c2a --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-derived-reverse-order/main.svelte @@ -0,0 +1,35 @@ + + + + + + + +

{await push()}

+ + {#snippet pending()} +

loading...

+ {/snippet} +
diff --git a/packages/svelte/tests/runtime-runes/samples/async-nested-top-level/Bar.svelte b/packages/svelte/tests/runtime-runes/samples/async-nested-top-level/Bar.svelte new file mode 100644 index 0000000000..f1ac9ab760 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-nested-top-level/Bar.svelte @@ -0,0 +1,7 @@ + + +

bar: {bar}

diff --git a/packages/svelte/tests/runtime-runes/samples/async-nested-top-level/Foo.svelte b/packages/svelte/tests/runtime-runes/samples/async-nested-top-level/Foo.svelte new file mode 100644 index 0000000000..e2029a3033 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-nested-top-level/Foo.svelte @@ -0,0 +1,10 @@ + + +

foo: {foo}

+ + diff --git a/packages/svelte/tests/runtime-runes/samples/async-nested-top-level/_config.js b/packages/svelte/tests/runtime-runes/samples/async-nested-top-level/_config.js new file mode 100644 index 0000000000..ca7965bf79 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-nested-top-level/_config.js @@ -0,0 +1,42 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target }) { + const [show, resolve] = target.querySelectorAll('button'); + + show.click(); + await tick(); + assert.htmlEqual( + target.innerHTML, + ` + + +

pending...

+ ` + ); + + resolve.click(); + await tick(); + assert.htmlEqual( + target.innerHTML, + ` + + +

pending...

+ ` + ); + + resolve.click(); + await tick(); + assert.htmlEqual( + target.innerHTML, + ` + + +

foo: foo

+

bar: bar

+ ` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-nested-top-level/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-nested-top-level/main.svelte new file mode 100644 index 0000000000..bd0efaa4f8 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-nested-top-level/main.svelte @@ -0,0 +1,31 @@ + + + + + + + + + + {#if show} + + {/if} + + {#if $effect.pending()} +

pending...

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

initializing...

+ {/snippet} +
diff --git a/packages/svelte/tests/runtime-runes/samples/async-redirect/_config.js b/packages/svelte/tests/runtime-runes/samples/async-redirect/_config.js index ebbe642860..fe92977c21 100644 --- a/packages/svelte/tests/runtime-runes/samples/async-redirect/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/async-redirect/_config.js @@ -29,6 +29,7 @@ export default test({

c

+

b or c

` ); @@ -46,6 +47,7 @@ export default test({

b

+

b or c

` ); } diff --git a/packages/svelte/tests/runtime-runes/samples/async-redirect/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-redirect/main.svelte index bf5fdf9ed3..aead1b00e5 100644 --- a/packages/svelte/tests/runtime-runes/samples/async-redirect/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/async-redirect/main.svelte @@ -33,6 +33,10 @@

c

{/if} + {#if route === 'b' || route === 'c'} +

b or c

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

pending...

{/snippet} diff --git a/packages/svelte/tests/runtime-runes/samples/async-reference-in-snippet/_config.js b/packages/svelte/tests/runtime-runes/samples/async-reference-in-snippet/_config.js new file mode 100644 index 0000000000..c6903c3eed --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-reference-in-snippet/_config.js @@ -0,0 +1,9 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target }) { + await tick(); + assert.htmlEqual(target.innerHTML, 'value'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-reference-in-snippet/app.svelte b/packages/svelte/tests/runtime-runes/samples/async-reference-in-snippet/app.svelte new file mode 100644 index 0000000000..27b29cfe50 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-reference-in-snippet/app.svelte @@ -0,0 +1,9 @@ + + +{#snippet valueSnippet()} + {value} +{/snippet} + +{@render valueSnippet()} \ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/async-reference-in-snippet/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-reference-in-snippet/main.svelte new file mode 100644 index 0000000000..c251a5645b --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-reference-in-snippet/main.svelte @@ -0,0 +1,8 @@ + + + {#snippet pending()} + {/snippet} + + \ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/async-top-level-deriveds/Foo.svelte b/packages/svelte/tests/runtime-runes/samples/async-top-level-deriveds/Foo.svelte new file mode 100644 index 0000000000..e8a7c84137 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-top-level-deriveds/Foo.svelte @@ -0,0 +1,8 @@ + + +

{foo} {bar}

diff --git a/packages/svelte/tests/runtime-runes/samples/async-top-level-deriveds/_config.js b/packages/svelte/tests/runtime-runes/samples/async-top-level-deriveds/_config.js new file mode 100644 index 0000000000..2c7ffd3952 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-top-level-deriveds/_config.js @@ -0,0 +1,41 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target }) { + const [show, resolve] = target.querySelectorAll('button'); + + show.click(); + await tick(); + assert.htmlEqual( + target.innerHTML, + ` + + +

pending...

+ ` + ); + + resolve.click(); + await tick(); + assert.htmlEqual( + target.innerHTML, + ` + + +

pending...

+ ` + ); + + resolve.click(); + await tick(); + assert.htmlEqual( + target.innerHTML, + ` + + +

foo bar

+ ` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-top-level-deriveds/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-top-level-deriveds/main.svelte new file mode 100644 index 0000000000..bd0efaa4f8 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-top-level-deriveds/main.svelte @@ -0,0 +1,31 @@ + + + + + + + + + + {#if show} + + {/if} + + {#if $effect.pending()} +

pending...

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

initializing...

+ {/snippet} +
diff --git a/packages/svelte/tests/runtime-runes/samples/binding-update-in-each/_config.js b/packages/svelte/tests/runtime-runes/samples/binding-update-in-each/_config.js new file mode 100644 index 0000000000..b6371ce11c --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/binding-update-in-each/_config.js @@ -0,0 +1,28 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + mode: ['client', 'hydrate'], + + html: `

a`, + + async test({ assert, target }) { + const [input] = target.querySelectorAll('input'); + + input.focus(); + input.value = 'ab'; + input.dispatchEvent(new InputEvent('input', { bubbles: true })); + flushSync(); + + assert.htmlEqual(target.innerHTML, `

ab`); + assert.equal(input.value, 'ab'); + + input.focus(); + input.value = 'abc'; + input.dispatchEvent(new InputEvent('input', { bubbles: true })); + flushSync(); + + assert.htmlEqual(target.innerHTML, `

abc`); + assert.equal(input.value, 'abc'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/binding-update-in-each/main.svelte b/packages/svelte/tests/runtime-runes/samples/binding-update-in-each/main.svelte new file mode 100644 index 0000000000..7925195ee1 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/binding-update-in-each/main.svelte @@ -0,0 +1,8 @@ + + +{#each array as obj} + obj.value, (value) => array = [{ value }]} /> +

{obj.value}

+{/each} diff --git a/packages/svelte/tests/runtime-runes/samples/binding-update-while-focused-3/_config.js b/packages/svelte/tests/runtime-runes/samples/binding-update-while-focused-3/_config.js new file mode 100644 index 0000000000..0909dee7a8 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/binding-update-while-focused-3/_config.js @@ -0,0 +1,30 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + mode: ['client', 'hydrate'], + + async test({ assert, target }) { + const [input] = target.querySelectorAll('input'); + + input.focus(); + input.value = 'Ab'; + input.dispatchEvent(new InputEvent('input', { bubbles: true })); + + await tick(); + await tick(); + + assert.equal(input.value, 'AB'); + assert.htmlEqual(target.innerHTML, `

AB

`); + + input.focus(); + input.value = 'ABc'; + input.dispatchEvent(new InputEvent('input', { bubbles: true })); + + await tick(); + await tick(); + + assert.equal(input.value, 'ABC'); + assert.htmlEqual(target.innerHTML, `

ABC

`); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/binding-update-while-focused-3/main.svelte b/packages/svelte/tests/runtime-runes/samples/binding-update-while-focused-3/main.svelte new file mode 100644 index 0000000000..b61bfe4e67 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/binding-update-while-focused-3/main.svelte @@ -0,0 +1,6 @@ + + + text, (v) => text = v.toUpperCase()} /> +

{text}

diff --git a/packages/svelte/tests/runtime-runes/samples/custom-element-attributes/_config.js b/packages/svelte/tests/runtime-runes/samples/custom-element-attributes/_config.js index 7f406d8f0d..3d8917c147 100644 --- a/packages/svelte/tests/runtime-runes/samples/custom-element-attributes/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/custom-element-attributes/_config.js @@ -20,5 +20,8 @@ export default test({ const [value1, value2] = target.querySelectorAll('value-element'); assert.equal(value1.shadowRoot?.innerHTML, 'test'); assert.equal(value2.shadowRoot?.innerHTML, 'test'); + + const value_builtin = target.querySelector('div'); + assert.equal(value_builtin?.shadowRoot?.innerHTML, 'test'); } }); diff --git a/packages/svelte/tests/runtime-runes/samples/custom-element-attributes/main.svelte b/packages/svelte/tests/runtime-runes/samples/custom-element-attributes/main.svelte index 82774f160d..badb8f96c7 100644 --- a/packages/svelte/tests/runtime-runes/samples/custom-element-attributes/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/custom-element-attributes/main.svelte @@ -15,6 +15,24 @@ } }); } + if(!customElements.get('value-builtin')) { + customElements.define('value-builtin', class extends HTMLDivElement { + + constructor() { + super(); + this.attachShadow({ mode: 'open' }); + } + + set value(v) { + if (this.__value !== v) { + this.__value = v; + this.shadowRoot.innerHTML = `${v}`; + } + } + }, { + extends: 'div' + }); + } @@ -22,3 +40,4 @@ +
diff --git a/packages/svelte/tests/runtime-runes/samples/flush-sync-inside-attachment/Child.svelte b/packages/svelte/tests/runtime-runes/samples/flush-sync-inside-attachment/Child.svelte new file mode 100644 index 0000000000..44447e4f36 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/flush-sync-inside-attachment/Child.svelte @@ -0,0 +1,7 @@ + + +{text} diff --git a/packages/svelte/tests/runtime-runes/samples/flush-sync-inside-attachment/_config.js b/packages/svelte/tests/runtime-runes/samples/flush-sync-inside-attachment/_config.js new file mode 100644 index 0000000000..ec8858b2c6 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/flush-sync-inside-attachment/_config.js @@ -0,0 +1,12 @@ +import { async_mode } from '../../../helpers'; +import { test } from '../../test'; + +export default test({ + // In legacy mode this succeeds and logs 'hello' + // In async mode this throws an error because flushSync is called inside an effect + async test({ assert, target, logs }) { + assert.htmlEqual(target.innerHTML, `
hello
`); + assert.deepEqual(logs, ['hello']); + }, + runtime_error: async_mode ? 'flush_sync_in_effect' : undefined +}); diff --git a/packages/svelte/tests/runtime-runes/samples/flush-sync-inside-attachment/main.svelte b/packages/svelte/tests/runtime-runes/samples/flush-sync-inside-attachment/main.svelte new file mode 100644 index 0000000000..bef820376b --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/flush-sync-inside-attachment/main.svelte @@ -0,0 +1,13 @@ + + + + +
{ + mount(Child, { target, props: { text: 'hello' } }); + flushSync(); +}}>