mirror of https://github.com/sveltejs/svelte
pull/16762/head
commit
380f45725b
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'svelte': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix: send `$effect.pending` count to the correct boundary
|
@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'svelte': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
fix: properly catch top level await errors
|
|
@ -0,0 +1,10 @@
|
|||||||
|
import { test } from '../../test';
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
async: true,
|
||||||
|
error: {
|
||||||
|
code: 'const_tag_invalid_reference',
|
||||||
|
message: 'The `{@const foo = ...}` declaration is not available in this snippet ',
|
||||||
|
position: [376, 379]
|
||||||
|
}
|
||||||
|
});
|
@ -0,0 +1,32 @@
|
|||||||
|
<svelte:options runes />
|
||||||
|
|
||||||
|
<!-- ok -->
|
||||||
|
<svelte:boundary>
|
||||||
|
{@const foo = 'bar'}
|
||||||
|
|
||||||
|
{#snippet other()}
|
||||||
|
{foo}
|
||||||
|
{/snippet}
|
||||||
|
|
||||||
|
{foo}
|
||||||
|
|
||||||
|
<svelte:boundary>
|
||||||
|
{#snippet failed()}
|
||||||
|
{foo}
|
||||||
|
{/snippet}
|
||||||
|
</svelte:boundary>
|
||||||
|
|
||||||
|
{#snippet failed()}
|
||||||
|
{@const foo = 'bar'}
|
||||||
|
{foo}
|
||||||
|
{/snippet}
|
||||||
|
</svelte:boundary>
|
||||||
|
|
||||||
|
<!-- error -->
|
||||||
|
<svelte:boundary>
|
||||||
|
{@const foo = 'bar'}
|
||||||
|
|
||||||
|
{#snippet failed()}
|
||||||
|
{foo}
|
||||||
|
{/snippet}
|
||||||
|
</svelte:boundary>
|
@ -0,0 +1,10 @@
|
|||||||
|
import { test } from '../../test';
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
async: true,
|
||||||
|
error: {
|
||||||
|
code: 'const_tag_invalid_reference',
|
||||||
|
message: 'The `{@const foo = ...}` declaration is not available in this snippet ',
|
||||||
|
position: [298, 301]
|
||||||
|
}
|
||||||
|
});
|
@ -0,0 +1,27 @@
|
|||||||
|
<svelte:options runes />
|
||||||
|
|
||||||
|
<!-- ok -->
|
||||||
|
<Component>
|
||||||
|
{@const foo = 'bar'}
|
||||||
|
{foo}
|
||||||
|
|
||||||
|
<Component>
|
||||||
|
{#snippet prop()}
|
||||||
|
{foo}
|
||||||
|
{/snippet}
|
||||||
|
</Component>
|
||||||
|
|
||||||
|
{#snippet prop()}
|
||||||
|
{@const foo = 'bar'}
|
||||||
|
{foo}
|
||||||
|
{/snippet}
|
||||||
|
</Component>
|
||||||
|
|
||||||
|
<!-- error -->
|
||||||
|
<Component>
|
||||||
|
{@const foo = 'bar'}
|
||||||
|
|
||||||
|
{#snippet prop()}
|
||||||
|
{foo}
|
||||||
|
{/snippet}
|
||||||
|
</Component>
|
@ -0,0 +1,15 @@
|
|||||||
|
<script>
|
||||||
|
async function c(a) {
|
||||||
|
await Promise.resolve()
|
||||||
|
if (a) {
|
||||||
|
throw new Error('error');
|
||||||
|
} else {
|
||||||
|
return 'ok';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let a = $state();
|
||||||
|
let b = $derived(await c(a));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button onclick={() => a = 1}>{b}</button>
|
@ -0,0 +1,19 @@
|
|||||||
|
import { tick } from 'svelte';
|
||||||
|
import { test } from '../../test';
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
async test({ assert, target }) {
|
||||||
|
await tick();
|
||||||
|
let [btn] = target.querySelectorAll('button');
|
||||||
|
btn.click();
|
||||||
|
await tick();
|
||||||
|
|
||||||
|
assert.htmlEqual(target.innerHTML, '<button>reset</button>');
|
||||||
|
|
||||||
|
[btn] = target.querySelectorAll('button');
|
||||||
|
btn.click();
|
||||||
|
await tick();
|
||||||
|
|
||||||
|
assert.htmlEqual(target.innerHTML, '<button>ok</button>');
|
||||||
|
}
|
||||||
|
});
|
@ -0,0 +1,12 @@
|
|||||||
|
<script>
|
||||||
|
import Test from './Test.svelte';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:boundary>
|
||||||
|
<Test />
|
||||||
|
|
||||||
|
{#snippet pending()}pending{/snippet}
|
||||||
|
{#snippet failed(_, reset)}
|
||||||
|
<button onclick={reset}>reset</button>
|
||||||
|
{/snippet}
|
||||||
|
</svelte:boundary>
|
@ -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,
|
||||||
|
`
|
||||||
|
<button>increment</button>
|
||||||
|
<button>pop</button>
|
||||||
|
<p>1</p>
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
increment.click();
|
||||||
|
await tick();
|
||||||
|
|
||||||
|
pop.click();
|
||||||
|
await tick();
|
||||||
|
|
||||||
|
assert.htmlEqual(
|
||||||
|
target.innerHTML,
|
||||||
|
`
|
||||||
|
<button>increment</button>
|
||||||
|
<button>pop</button>
|
||||||
|
<p>2</p>
|
||||||
|
`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
@ -0,0 +1,35 @@
|
|||||||
|
|
||||||
|
<script>
|
||||||
|
let count = $state(0);
|
||||||
|
|
||||||
|
let deferreds = [];
|
||||||
|
|
||||||
|
class X {
|
||||||
|
constructor(promise) {
|
||||||
|
this.promise = promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
get then() {
|
||||||
|
count;
|
||||||
|
|
||||||
|
return (resolve) => this.promise.then(() => count).then(resolve)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function push() {
|
||||||
|
const deferred = Promise.withResolvers();
|
||||||
|
deferreds.push(deferred);
|
||||||
|
return new X(deferred.promise);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button onclick={() => count += 1}>increment</button>
|
||||||
|
<button onclick={() => deferreds.pop()?.resolve(count)}>pop</button>
|
||||||
|
|
||||||
|
<svelte:boundary>
|
||||||
|
<p>{await push()}</p>
|
||||||
|
|
||||||
|
{#snippet pending()}
|
||||||
|
<p>loading...</p>
|
||||||
|
{/snippet}
|
||||||
|
</svelte:boundary>
|
@ -0,0 +1,7 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { resolve } from './main.svelte';
|
||||||
|
|
||||||
|
const bar = await new Promise((r) => resolve.push(() => r('bar')));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<p>bar: {bar}</p>
|
@ -0,0 +1,10 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { resolve } from './main.svelte';
|
||||||
|
import Bar from './Bar.svelte';
|
||||||
|
|
||||||
|
const foo = await new Promise((r) => resolve.push(() => r('foo')));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<p>foo: {foo}</p>
|
||||||
|
|
||||||
|
<Bar/>
|
@ -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,
|
||||||
|
`
|
||||||
|
<button>show</button>
|
||||||
|
<button>resolve</button>
|
||||||
|
<p>pending...</p>
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
resolve.click();
|
||||||
|
await tick();
|
||||||
|
assert.htmlEqual(
|
||||||
|
target.innerHTML,
|
||||||
|
`
|
||||||
|
<button>show</button>
|
||||||
|
<button>resolve</button>
|
||||||
|
<p>pending...</p>
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
resolve.click();
|
||||||
|
await tick();
|
||||||
|
assert.htmlEqual(
|
||||||
|
target.innerHTML,
|
||||||
|
`
|
||||||
|
<button>show</button>
|
||||||
|
<button>resolve</button>
|
||||||
|
<p>foo: foo</p>
|
||||||
|
<p>bar: bar</p>
|
||||||
|
`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
@ -0,0 +1,31 @@
|
|||||||
|
<script module>
|
||||||
|
export let resolve = [];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Foo from './Foo.svelte';
|
||||||
|
|
||||||
|
let show = $state(false);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button onclick={() => show = true}>
|
||||||
|
show
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button onclick={() => resolve.shift()()}>
|
||||||
|
resolve
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<svelte:boundary>
|
||||||
|
{#if show}
|
||||||
|
<Foo/>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $effect.pending()}
|
||||||
|
<p>pending...</p>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#snippet pending()}
|
||||||
|
<p>initializing...</p>
|
||||||
|
{/snippet}
|
||||||
|
</svelte:boundary>
|
@ -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');
|
||||||
|
}
|
||||||
|
});
|
@ -0,0 +1,9 @@
|
|||||||
|
<script>
|
||||||
|
const value = await 'value';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#snippet valueSnippet()}
|
||||||
|
{value}
|
||||||
|
{/snippet}
|
||||||
|
|
||||||
|
{@render valueSnippet()}
|
@ -0,0 +1,8 @@
|
|||||||
|
<script>
|
||||||
|
import App from './app.svelte';
|
||||||
|
</script>
|
||||||
|
<svelte:boundary>
|
||||||
|
{#snippet pending()}
|
||||||
|
{/snippet}
|
||||||
|
<App />
|
||||||
|
</svelte:boundary>
|
@ -0,0 +1,9 @@
|
|||||||
|
import { tick } from 'svelte';
|
||||||
|
import { ok, test } from '../../test';
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
async test({ assert, target }) {
|
||||||
|
await tick();
|
||||||
|
assert.htmlEqual(target.innerHTML, '<p>foo bar</p>');
|
||||||
|
}
|
||||||
|
});
|
@ -0,0 +1,17 @@
|
|||||||
|
<script>
|
||||||
|
function foo() {
|
||||||
|
return 'foo';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function bar() {
|
||||||
|
return Promise.resolve('bar');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:boundary>
|
||||||
|
<p>{foo()} {await bar()}</p>
|
||||||
|
|
||||||
|
{#snippet pending()}
|
||||||
|
<p>pending</p>
|
||||||
|
{/snippet}
|
||||||
|
</svelte:boundary>
|
@ -0,0 +1,8 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { resolve } from './main.svelte';
|
||||||
|
|
||||||
|
const foo = $derived(await new Promise((r) => resolve.push(() => r('foo'))));
|
||||||
|
const bar = $derived(await new Promise((r) => resolve.push(() => r('bar'))));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<p>{foo} {bar}</p>
|
@ -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,
|
||||||
|
`
|
||||||
|
<button>show</button>
|
||||||
|
<button>resolve</button>
|
||||||
|
<p>pending...</p>
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
resolve.click();
|
||||||
|
await tick();
|
||||||
|
assert.htmlEqual(
|
||||||
|
target.innerHTML,
|
||||||
|
`
|
||||||
|
<button>show</button>
|
||||||
|
<button>resolve</button>
|
||||||
|
<p>pending...</p>
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
resolve.click();
|
||||||
|
await tick();
|
||||||
|
assert.htmlEqual(
|
||||||
|
target.innerHTML,
|
||||||
|
`
|
||||||
|
<button>show</button>
|
||||||
|
<button>resolve</button>
|
||||||
|
<p>foo bar</p>
|
||||||
|
`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
@ -0,0 +1,31 @@
|
|||||||
|
<script module>
|
||||||
|
export let resolve = [];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Foo from './Foo.svelte';
|
||||||
|
|
||||||
|
let show = $state(false);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button onclick={() => show = true}>
|
||||||
|
show
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button onclick={() => resolve.shift()()}>
|
||||||
|
resolve
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<svelte:boundary>
|
||||||
|
{#if show}
|
||||||
|
<Foo/>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $effect.pending()}
|
||||||
|
<p>pending...</p>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#snippet pending()}
|
||||||
|
<p>initializing...</p>
|
||||||
|
{/snippet}
|
||||||
|
</svelte:boundary>
|
@ -0,0 +1,28 @@
|
|||||||
|
import { flushSync } from 'svelte';
|
||||||
|
import { test } from '../../test';
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
mode: ['client', 'hydrate'],
|
||||||
|
|
||||||
|
html: `<input><p>a</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, `<input><p>ab</a>`);
|
||||||
|
assert.equal(input.value, 'ab');
|
||||||
|
|
||||||
|
input.focus();
|
||||||
|
input.value = 'abc';
|
||||||
|
input.dispatchEvent(new InputEvent('input', { bubbles: true }));
|
||||||
|
flushSync();
|
||||||
|
|
||||||
|
assert.htmlEqual(target.innerHTML, `<input><p>abc</a>`);
|
||||||
|
assert.equal(input.value, 'abc');
|
||||||
|
}
|
||||||
|
});
|
@ -0,0 +1,8 @@
|
|||||||
|
<script>
|
||||||
|
let array = $state([{ value: 'a' }]);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#each array as obj}
|
||||||
|
<input bind:value={() => obj.value, (value) => array = [{ value }]} />
|
||||||
|
<p>{obj.value}</p>
|
||||||
|
{/each}
|
@ -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, `<input /><p>AB</p>`);
|
||||||
|
|
||||||
|
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, `<input /><p>ABC</p>`);
|
||||||
|
}
|
||||||
|
});
|
@ -0,0 +1,6 @@
|
|||||||
|
<script>
|
||||||
|
let text = $state('A');
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<input bind:value={() => text, (v) => text = v.toUpperCase()} />
|
||||||
|
<p>{text}</p>
|
@ -0,0 +1,18 @@
|
|||||||
|
import { flushSync } from 'svelte';
|
||||||
|
import { test } from '../../test';
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
skip_async: true,
|
||||||
|
html: '<button></button><p>2</p>',
|
||||||
|
mode: ['client'],
|
||||||
|
test({ target, assert }) {
|
||||||
|
const btn = target.querySelector('button');
|
||||||
|
const p = target.querySelector('p');
|
||||||
|
|
||||||
|
flushSync(() => {
|
||||||
|
btn?.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(p?.innerHTML, '4');
|
||||||
|
}
|
||||||
|
});
|
@ -0,0 +1,14 @@
|
|||||||
|
<script>
|
||||||
|
import FlakyComponent from "./FlakyComponent.svelte";
|
||||||
|
let test=$state(1);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button onclick={()=>test++}></button>
|
||||||
|
|
||||||
|
<svelte:boundary>
|
||||||
|
{@const double = test * 2}
|
||||||
|
{#snippet failed()}
|
||||||
|
<p>{double}</p>
|
||||||
|
{/snippet}
|
||||||
|
<FlakyComponent />
|
||||||
|
</svelte:boundary>
|
@ -1,14 +1,10 @@
|
|||||||
<script>
|
<script>
|
||||||
import FlakyComponent from "./FlakyComponent.svelte";
|
let count = $state(1);
|
||||||
let test=$state(1);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<button onclick={()=>test++}></button>
|
<button onclick={()=>count++}>increment</button>
|
||||||
|
|
||||||
<svelte:boundary>
|
<svelte:boundary>
|
||||||
{@const double = test * 2}
|
{@const double = count * 2}
|
||||||
{#snippet failed()}
|
|
||||||
<p>{double}</p>
|
<p>{double}</p>
|
||||||
{/snippet}
|
|
||||||
<FlakyComponent />
|
|
||||||
</svelte:boundary>
|
</svelte:boundary>
|
@ -0,0 +1,7 @@
|
|||||||
|
<script>
|
||||||
|
let { text } = $props();
|
||||||
|
|
||||||
|
$effect(() => console.log(text));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{text}
|
@ -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, `<button>show</button> <div>hello</div>`);
|
||||||
|
assert.deepEqual(logs, ['hello']);
|
||||||
|
},
|
||||||
|
runtime_error: async_mode ? 'flush_sync_in_effect' : undefined
|
||||||
|
});
|
@ -0,0 +1,13 @@
|
|||||||
|
<script>
|
||||||
|
import { flushSync, mount } from 'svelte'
|
||||||
|
import Child from './Child.svelte';
|
||||||
|
|
||||||
|
let show = $state(false);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button onclick={() => show = true}>show</button>
|
||||||
|
|
||||||
|
<div {@attach (target) => {
|
||||||
|
mount(Child, { target, props: { text: 'hello' } });
|
||||||
|
flushSync();
|
||||||
|
}}></div>
|
Loading…
Reference in new issue