mirror of https://github.com/sveltejs/svelte
prevents infinite loops when mutation happens inside an effect came up in #9639pull/9685/head
parent
a31b2e1b8e
commit
82f3be2a66
@ -0,0 +1,5 @@
|
||||
---
|
||||
'svelte': patch
|
||||
---
|
||||
|
||||
fix: untrack reads of mutated object
|
||||
@ -0,0 +1,54 @@
|
||||
import { flushSync } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
// Test ensures that reading the state that's mutated is done in a manner
|
||||
// so that the read is untracked so it doesn't trigger infinite loops
|
||||
export default test({
|
||||
html: `
|
||||
<input>
|
||||
<input>
|
||||
<p>text: foo</p>
|
||||
<p>wrapped.contents: foo</p>
|
||||
`,
|
||||
skip_if_ssr: true,
|
||||
|
||||
test({ assert, target }) {
|
||||
const [input1, input2] = target.querySelectorAll('input');
|
||||
|
||||
input1.value = 'bar';
|
||||
flushSync(() => input1.dispatchEvent(new Event('input')));
|
||||
assert.htmlEqual(
|
||||
target.innerHTML,
|
||||
`
|
||||
<input>
|
||||
<input>
|
||||
<p>text: bar</p>
|
||||
<p>wrapped.contents: bar</p>
|
||||
`
|
||||
);
|
||||
|
||||
input2.value = 'baz';
|
||||
flushSync(() => input2.dispatchEvent(new Event('input')));
|
||||
assert.htmlEqual(
|
||||
target.innerHTML,
|
||||
`
|
||||
<input>
|
||||
<input>
|
||||
<p>text: bar</p>
|
||||
<p>wrapped.contents: baz</p>
|
||||
`
|
||||
);
|
||||
|
||||
input1.value = 'foo';
|
||||
flushSync(() => input1.dispatchEvent(new Event('input')));
|
||||
assert.htmlEqual(
|
||||
target.innerHTML,
|
||||
`
|
||||
<input>
|
||||
<input>
|
||||
<p>text: foo</p>
|
||||
<p>wrapped.contents: foo</p>
|
||||
`
|
||||
);
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,10 @@
|
||||
<script>
|
||||
let text = $state('foo');
|
||||
let wrapped = $state({});
|
||||
$effect(() => { wrapped.contents = text; })
|
||||
</script>
|
||||
|
||||
<input bind:value={text} />
|
||||
<input bind:value={wrapped.contents} />
|
||||
<p>text: {text}</p>
|
||||
<p>wrapped.contents: {wrapped.contents}</p>
|
||||
@ -0,0 +1,54 @@
|
||||
import { flushSync } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
// Test ensures that reading the state that's mutated is done in a manner
|
||||
// so that the read is untracked so it doesn't trigger infinite loops
|
||||
export default test({
|
||||
html: `
|
||||
<input>
|
||||
<input>
|
||||
<p>text: foo</p>
|
||||
<p>wrapped.contents: foo</p>
|
||||
`,
|
||||
skip_if_ssr: true,
|
||||
|
||||
test({ assert, target }) {
|
||||
const [input1, input2] = target.querySelectorAll('input');
|
||||
|
||||
input1.value = 'bar';
|
||||
flushSync(() => input1.dispatchEvent(new Event('input')));
|
||||
assert.htmlEqual(
|
||||
target.innerHTML,
|
||||
`
|
||||
<input>
|
||||
<input>
|
||||
<p>text: bar</p>
|
||||
<p>wrapped.contents: bar</p>
|
||||
`
|
||||
);
|
||||
|
||||
input2.value = 'baz';
|
||||
flushSync(() => input2.dispatchEvent(new Event('input')));
|
||||
assert.htmlEqual(
|
||||
target.innerHTML,
|
||||
`
|
||||
<input>
|
||||
<input>
|
||||
<p>text: bar</p>
|
||||
<p>wrapped.contents: baz</p>
|
||||
`
|
||||
);
|
||||
|
||||
input1.value = 'foo';
|
||||
flushSync(() => input1.dispatchEvent(new Event('input')));
|
||||
assert.htmlEqual(
|
||||
target.innerHTML,
|
||||
`
|
||||
<input>
|
||||
<input>
|
||||
<p>text: foo</p>
|
||||
<p>wrapped.contents: foo</p>
|
||||
`
|
||||
);
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,12 @@
|
||||
<script>
|
||||
import { writable } from "svelte/store";
|
||||
|
||||
let text = writable('foo');
|
||||
let wrapped = writable({});
|
||||
$effect(() => { $wrapped.contents = $text; })
|
||||
</script>
|
||||
|
||||
<input bind:value={$text} />
|
||||
<input bind:value={$wrapped.contents} />
|
||||
<p>text: {$text}</p>
|
||||
<p>wrapped.contents: {$wrapped.contents}</p>
|
||||
Loading…
Reference in new issue