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