mirror of https://github.com/sveltejs/svelte
commit
c930e89e42
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'svelte': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix: keep creating state sources for property checks during component init
|
||||||
@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'svelte': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
fix: flush anything pending before invoking flushSync callback function
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'svelte': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
fix: don't overwrite an unchanged spread `value`, preserving incomplete number input
|
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'svelte': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix: track missing state property checks in unowned deriveds
|
||||||
@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'svelte': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
fix: read batch-local array on each-block commit
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'svelte': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
fix: prevent hydration mismatch recovery from being intercepted by error boundaries
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'svelte': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
fix: preserve dynamic element connections during hydration
|
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
import { test } from '../../test';
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
error: {
|
||||||
|
code: 'block_duplicate_clause',
|
||||||
|
message: '{:else} cannot appear more than once within a block',
|
||||||
|
position: [42, 42]
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
{#each items as item}
|
||||||
|
{item}
|
||||||
|
{:else}
|
||||||
|
A
|
||||||
|
{:else}
|
||||||
|
B
|
||||||
|
{/each}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
import { test } from '../../test';
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
error: {
|
||||||
|
code: 'block_duplicate_clause',
|
||||||
|
message: '{:else} cannot appear more than once within a block',
|
||||||
|
position: [23, 23]
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
{#if a}
|
||||||
|
A
|
||||||
|
{:else}
|
||||||
|
B
|
||||||
|
{:else if b}
|
||||||
|
C
|
||||||
|
{/if}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
import { test } from '../../test';
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
error: {
|
||||||
|
code: 'let_directive_snippet_conflict',
|
||||||
|
message:
|
||||||
|
'Cannot use `let:` directives on a component that has a `children` snippet. Use snippet parameters instead (e.g. `{#snippet children({ item: x })}`)',
|
||||||
|
position: [62, 74]
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
<script>
|
||||||
|
import Child from './irrelevant';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Child let:item={x}>
|
||||||
|
{#snippet children()}{x}{/snippet}
|
||||||
|
</Child>
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
import { test } from '../../test';
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
error: {
|
||||||
|
code: 'let_directive_snippet_conflict',
|
||||||
|
message:
|
||||||
|
'Cannot use `let:` directives on a component that has a `children` snippet. Use snippet parameters instead (e.g. `{#snippet children({ value })}`)',
|
||||||
|
position: [359, 368]
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
<script>
|
||||||
|
import Child from './irrelevant';
|
||||||
|
import Parent from './irrelevant';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- ok -->
|
||||||
|
<Child let:value>
|
||||||
|
{value}
|
||||||
|
{#snippet foo()}x{/snippet}
|
||||||
|
</Child>
|
||||||
|
<Child>
|
||||||
|
{#snippet children({ value })}{value}{/snippet}
|
||||||
|
</Child>
|
||||||
|
<Parent>
|
||||||
|
<Child slot="foo" let:value>
|
||||||
|
{#snippet children()}{value}{/snippet}
|
||||||
|
</Child>
|
||||||
|
</Parent>
|
||||||
|
|
||||||
|
<!-- invalid -->
|
||||||
|
<Child let:value>
|
||||||
|
{#snippet children()}Value is: {value}{/snippet}
|
||||||
|
</Child>
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
import { test } from '../../test';
|
||||||
|
|
||||||
|
// https://github.com/sveltejs/svelte/issues/18924
|
||||||
|
export default test({
|
||||||
|
expect_hydration_error: true
|
||||||
|
});
|
||||||
@ -0,0 +1 @@
|
|||||||
|
<div><p>inner</p></div>
|
||||||
@ -0,0 +1 @@
|
|||||||
|
<!--[--><!--[--><div><span></span><!--[--><p>inner</p><!--]--></div><!--]--><!--]-->
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
<svelte:boundary>
|
||||||
|
<div>
|
||||||
|
<svelte:boundary>
|
||||||
|
<p>inner</p>
|
||||||
|
{#snippet failed()}
|
||||||
|
<p>inner failed</p>
|
||||||
|
{/snippet}
|
||||||
|
</svelte:boundary>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#snippet failed()}
|
||||||
|
<p>outer failed</p>
|
||||||
|
{/snippet}
|
||||||
|
</svelte:boundary>
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
<script>
|
||||||
|
$: count = 7;
|
||||||
|
$: other = 8;
|
||||||
|
let obj = {};
|
||||||
|
$: obj.x = 1;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<p>{count} {other} {obj.x}</p>
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
<script>
|
||||||
|
import { run } from 'svelte/legacy';
|
||||||
|
|
||||||
|
let count = $state(7);
|
||||||
|
|
||||||
|
let other = $state(8);
|
||||||
|
|
||||||
|
let obj = $state({});
|
||||||
|
run(() => {
|
||||||
|
obj.x = 1;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<p>{count} {other} {obj.x}</p>
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
<script>export let answer</script>
|
||||||
|
<p>{answer}</p>
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
<script>
|
||||||
|
let { answer } = $props();</script>
|
||||||
|
<p>{answer}</p>
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
import { test, ok } from '../../assert';
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
mode: ['client'],
|
||||||
|
async test({ assert, target }) {
|
||||||
|
/** @type {string[]} */
|
||||||
|
const errors = [];
|
||||||
|
/** @param {ErrorEvent} event */
|
||||||
|
const on_error = (event) => {
|
||||||
|
errors.push(event.message);
|
||||||
|
event.preventDefault();
|
||||||
|
};
|
||||||
|
window.addEventListener('error', on_error);
|
||||||
|
|
||||||
|
const button = target.querySelector('button');
|
||||||
|
ok(button);
|
||||||
|
|
||||||
|
button.focus();
|
||||||
|
button.click();
|
||||||
|
await new Promise((r) => setTimeout(r, 0));
|
||||||
|
|
||||||
|
window.removeEventListener('error', on_error);
|
||||||
|
|
||||||
|
assert.equal(errors.length, 0, errors.join('\n'));
|
||||||
|
assert.equal(target.querySelector('button'), null);
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
<script>
|
||||||
|
let activeElement = $state();
|
||||||
|
let show = $state(true);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:document bind:activeElement />
|
||||||
|
|
||||||
|
{#if show}
|
||||||
|
<button onclick={() => (show = false)}>hide</button>
|
||||||
|
{/if}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
<script>
|
||||||
|
let { promise } = $props();
|
||||||
|
|
||||||
|
const value = await promise;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<p>{await Promise.resolve(value)}</p>
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
import { tick } from 'svelte';
|
||||||
|
import { test } from '../../test';
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
async test({ assert, target }) {
|
||||||
|
const [hide, resolve] = target.querySelectorAll('button');
|
||||||
|
|
||||||
|
assert.htmlEqual(
|
||||||
|
target.innerHTML,
|
||||||
|
`
|
||||||
|
<button>hide</button>
|
||||||
|
<button>resolve</button>
|
||||||
|
<p>loading...</p>
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
hide.click();
|
||||||
|
await tick();
|
||||||
|
|
||||||
|
resolve.click();
|
||||||
|
await tick();
|
||||||
|
|
||||||
|
assert.htmlEqual(
|
||||||
|
target.innerHTML,
|
||||||
|
`
|
||||||
|
<button>hide</button>
|
||||||
|
<button>resolve</button>
|
||||||
|
`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
<script>
|
||||||
|
import Child from './Child.svelte';
|
||||||
|
|
||||||
|
const deferred = Promise.withResolvers();
|
||||||
|
|
||||||
|
let show = $state(true);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button onclick={() => (show = false)}>hide</button>
|
||||||
|
<button onclick={() => deferred.resolve(1)}>resolve</button>
|
||||||
|
|
||||||
|
<svelte:boundary>
|
||||||
|
{#if show}
|
||||||
|
<Child promise={deferred.promise} />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#snippet pending()}
|
||||||
|
<p>loading...</p>
|
||||||
|
{/snippet}
|
||||||
|
</svelte:boundary>
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
import { tick } from 'svelte';
|
||||||
|
import { test } from '../../test';
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
mode: ['client', 'hydrate'],
|
||||||
|
async test({ assert, target, window, logs }) {
|
||||||
|
await new Promise((r) => setTimeout(r));
|
||||||
|
await tick();
|
||||||
|
|
||||||
|
const button = target.querySelector('button');
|
||||||
|
const div = target.querySelector('div');
|
||||||
|
|
||||||
|
button?.click();
|
||||||
|
div?.dispatchEvent(new window.MouseEvent('mouseenter'));
|
||||||
|
window.dispatchEvent(new window.Event('resize'));
|
||||||
|
|
||||||
|
assert.deepEqual(logs, ['click', 'mouseenter', 'resize']);
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
<script>
|
||||||
|
// Wait a macrotask to make sure the handlers aren't read before the microtask-Promise.resolve() resolves, masking a bug
|
||||||
|
await new Promise((r) => setTimeout(r));
|
||||||
|
|
||||||
|
function create_handler(name) {
|
||||||
|
return () => {
|
||||||
|
console.log(name);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const onclick = create_handler('click');
|
||||||
|
const onmouseenter = create_handler('mouseenter');
|
||||||
|
const onresize = create_handler('resize');
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:window {onresize} />
|
||||||
|
|
||||||
|
<button {onclick}>click</button>
|
||||||
|
<div {onmouseenter}>hover</div>
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
import { tick } from 'svelte';
|
||||||
|
import { test } from '../../test';
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
async test({ assert, target, logs }) {
|
||||||
|
await tick();
|
||||||
|
|
||||||
|
const [rerun, discard] = target.querySelectorAll('button');
|
||||||
|
|
||||||
|
rerun.click();
|
||||||
|
await tick();
|
||||||
|
|
||||||
|
discard.click();
|
||||||
|
await tick();
|
||||||
|
|
||||||
|
assert.deepEqual(logs, [1, false, 1, 1, 1]);
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -0,0 +1,49 @@
|
|||||||
|
<script>
|
||||||
|
import { fork } from 'svelte';
|
||||||
|
|
||||||
|
const init = $state({ x: 1 });
|
||||||
|
const init_missing = $state({});
|
||||||
|
const root = $state({ x: 1 });
|
||||||
|
const teardown = $state({ x: 1 });
|
||||||
|
const after_await = $state({ x: 1 });
|
||||||
|
|
||||||
|
let count = $state(0);
|
||||||
|
|
||||||
|
// each object is first checked with `in` while an effect is active but no reaction is
|
||||||
|
'x' in init;
|
||||||
|
'y' in init_missing;
|
||||||
|
|
||||||
|
$effect.root(() => {
|
||||||
|
'x' in root;
|
||||||
|
});
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
count;
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
'x' in teardown;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.resolve();
|
||||||
|
|
||||||
|
'x' in after_await;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button onclick={() => count++}>rerun</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onclick={() => {
|
||||||
|
fork(() => {
|
||||||
|
init.x = 2;
|
||||||
|
init_missing.y = 1;
|
||||||
|
root.x = 2;
|
||||||
|
teardown.x = 2;
|
||||||
|
after_await.x = 2;
|
||||||
|
}).discard();
|
||||||
|
|
||||||
|
console.log(init.x, 'y' in init_missing, root.x, teardown.x, after_await.x);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
discard
|
||||||
|
</button>
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
import { flushSync } from 'svelte';
|
||||||
|
import { test } from '../../test';
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
async test({ assert, target }) {
|
||||||
|
const [button] = target.querySelectorAll('button');
|
||||||
|
|
||||||
|
button.click();
|
||||||
|
flushSync();
|
||||||
|
|
||||||
|
assert.htmlEqual(target.innerHTML, '<button>Reproduce</button><p>Deferred outro</p>');
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
<script>
|
||||||
|
import { crossfade } from 'svelte/transition';
|
||||||
|
|
||||||
|
let visible = $state(true);
|
||||||
|
let restore = $state(false);
|
||||||
|
const [send] = crossfade({});
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (restore) visible = true;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onclick={() => {
|
||||||
|
visible = false;
|
||||||
|
restore = true;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Reproduce
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{#if visible}
|
||||||
|
<p out:send={{ key: 'item' }}>Deferred outro</p>
|
||||||
|
{/if}
|
||||||
@ -0,0 +1,50 @@
|
|||||||
|
import { tick } from 'svelte';
|
||||||
|
import { test } from '../../test';
|
||||||
|
|
||||||
|
/** @type {Array<() => void>} */
|
||||||
|
const resolvers = [];
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
transformError: (error) => new Promise((resolve) => resolvers.push(() => resolve(error))),
|
||||||
|
|
||||||
|
async test({ assert, target, logs }) {
|
||||||
|
const [error, toggle, reset, destroy] = target.querySelectorAll('button');
|
||||||
|
const paragraph = /** @type {HTMLParagraphElement} */ (target.querySelector('p'));
|
||||||
|
|
||||||
|
error.click();
|
||||||
|
await tick();
|
||||||
|
resolvers.shift()?.();
|
||||||
|
await tick();
|
||||||
|
assert.htmlEqual(paragraph.innerHTML, 'boom');
|
||||||
|
|
||||||
|
// A retained reset is inert after its boundary has been destroyed
|
||||||
|
toggle.click();
|
||||||
|
await tick();
|
||||||
|
reset.click();
|
||||||
|
await tick();
|
||||||
|
assert.htmlEqual(paragraph.innerHTML, 'boom');
|
||||||
|
|
||||||
|
// Resolving an error transform cannot resume a destroyed boundary
|
||||||
|
toggle.click();
|
||||||
|
await tick();
|
||||||
|
error.click();
|
||||||
|
await tick();
|
||||||
|
toggle.click();
|
||||||
|
await tick();
|
||||||
|
resolvers.shift()?.();
|
||||||
|
await tick();
|
||||||
|
assert.htmlEqual(paragraph.innerHTML, 'boom');
|
||||||
|
|
||||||
|
// A failed snippet's reset is also inert while the boundary is being destroyed
|
||||||
|
toggle.click();
|
||||||
|
await tick();
|
||||||
|
error.click();
|
||||||
|
await tick();
|
||||||
|
resolvers.shift()?.();
|
||||||
|
await tick();
|
||||||
|
destroy.click();
|
||||||
|
await tick();
|
||||||
|
assert.htmlEqual(paragraph.innerHTML, 'boom,boom');
|
||||||
|
assert.deepEqual(logs, ['render', 'render', 'render']);
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
<script>
|
||||||
|
let show = $state(true);
|
||||||
|
let must_throw = $state(false);
|
||||||
|
let reset;
|
||||||
|
let element;
|
||||||
|
let errors = $state([]);
|
||||||
|
let reset_during_cleanup = false;
|
||||||
|
|
||||||
|
function throw_error() {
|
||||||
|
throw new Error('boom');
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggle() {
|
||||||
|
must_throw = false;
|
||||||
|
show = !show;
|
||||||
|
}
|
||||||
|
|
||||||
|
function reset_on_cleanup(_, reset_boundary) {
|
||||||
|
return {
|
||||||
|
destroy() {
|
||||||
|
if (reset_during_cleanup) reset_boundary();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function track_render() {
|
||||||
|
console.log('render');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button onclick={() => (must_throw = true)}>error</button>
|
||||||
|
<button onclick={toggle}>toggle</button>
|
||||||
|
<button onclick={() => reset()}>reset</button>
|
||||||
|
<button onclick={() => { reset_during_cleanup = true; toggle(); }}>destroy</button>
|
||||||
|
<p>{errors.join(',')}</p>
|
||||||
|
|
||||||
|
{#if show}
|
||||||
|
<svelte:boundary onerror={(error, fn) => { errors.push(error.message); reset = fn; }}>
|
||||||
|
{track_render()}
|
||||||
|
<input bind:this={element} />
|
||||||
|
{must_throw ? throw_error() : ''}
|
||||||
|
|
||||||
|
{#snippet failed(_, failed_reset)}
|
||||||
|
<input bind:this={element} use:reset_on_cleanup={failed_reset} />
|
||||||
|
{/snippet}
|
||||||
|
</svelte:boundary>
|
||||||
|
{/if}
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
import { tick } from 'svelte';
|
||||||
|
import { test } from '../../test';
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
mode: ['hydrate'],
|
||||||
|
|
||||||
|
async test({ assert, target }) {
|
||||||
|
await tick();
|
||||||
|
assert.htmlEqual(target.innerHTML, '<p>failed: setter error</p><p>after</p>');
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
<script module>
|
||||||
|
if (!customElements.get('throwing-element')) {
|
||||||
|
customElements.define(
|
||||||
|
'throwing-element',
|
||||||
|
class extends HTMLElement {
|
||||||
|
set value(_) {
|
||||||
|
throw new Error('setter error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:boundary>
|
||||||
|
<throwing-element {...{ value: 'boom' }}></throwing-element>
|
||||||
|
|
||||||
|
{#snippet failed(error)}
|
||||||
|
<p>failed: {error.message}</p>
|
||||||
|
{/snippet}
|
||||||
|
</svelte:boundary>
|
||||||
|
|
||||||
|
<p>after</p>
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"code": "declaration_duplicate",
|
||||||
|
"message": "`item` has already been declared",
|
||||||
|
"start": {
|
||||||
|
"line": 13,
|
||||||
|
"column": 8
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 13,
|
||||||
|
"column": 12
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
<!-- this is ok -->
|
||||||
|
{#snippet row1(item)}
|
||||||
|
{#if item}
|
||||||
|
{const item = 1}
|
||||||
|
<p>{item}</p>
|
||||||
|
{/if}
|
||||||
|
{/snippet}
|
||||||
|
|
||||||
|
{@render row1(5)}
|
||||||
|
|
||||||
|
<!-- this is not -->
|
||||||
|
{#snippet row2(item)}
|
||||||
|
{const item = 1}
|
||||||
|
<p>{item}</p>
|
||||||
|
{/snippet}
|
||||||
|
|
||||||
|
{@render row2(5)}
|
||||||
Loading…
Reference in new issue