diff --git a/.changeset/gold-times-see.md b/.changeset/gold-times-see.md new file mode 100644 index 0000000000..f8d5da5042 --- /dev/null +++ b/.changeset/gold-times-see.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: ensure deriveds values are correct across batches diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index a09654bfc0..ebaed93e9c 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -366,11 +366,11 @@ export class Batch { * Associate a change to a given source with the current * batch, noting its previous and current values * @param {Source} source - * @param {any} value + * @param {any} old_value */ - capture(source, value) { - if (value !== UNINITIALIZED && !this.previous.has(source)) { - this.previous.set(source, value); + capture(source, old_value) { + if (old_value !== UNINITIALIZED && !this.previous.has(source)) { + this.previous.set(source, old_value); } // Don't save errors in `batch_values`, or they won't be thrown in `runtime.js#get` @@ -572,7 +572,7 @@ export class Batch { // ...and undo changes belonging to other batches for (const batch of batches) { - if (batch === this) continue; + if (batch === this || batch.is_fork) continue; for (const [source, previous] of batch.previous) { if (!batch_values.has(source)) { @@ -1020,13 +1020,6 @@ export function fork(fn) { source.v = value; } - // make writable deriveds dirty, so they recalculate correctly - for (source of batch.current.keys()) { - if ((source.f & DERIVED) !== 0) { - set_signal_status(source, DIRTY); - } - } - return { commit: async () => { if (committed) { diff --git a/packages/svelte/src/internal/client/reactivity/deriveds.js b/packages/svelte/src/internal/client/reactivity/deriveds.js index 3478784309..aed55f7fba 100644 --- a/packages/svelte/src/internal/client/reactivity/deriveds.js +++ b/packages/svelte/src/internal/client/reactivity/deriveds.js @@ -371,6 +371,7 @@ export function execute_derived(derived) { * @returns {void} */ export function update_derived(derived) { + var old_value = derived.v; var value = execute_derived(derived); if (!derived.equals(value)) { @@ -382,6 +383,7 @@ export function update_derived(derived) { // change, `derived.equals` may incorrectly return `true` if (!current_batch?.is_fork || derived.deps === null) { derived.v = value; + current_batch?.capture(derived, old_value); // deriveds without dependencies should never be recomputed if (derived.deps === null) { diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js index f4ae92659c..3ccde0f211 100644 --- a/packages/svelte/src/internal/client/reactivity/sources.js +++ b/packages/svelte/src/internal/client/reactivity/sources.js @@ -231,7 +231,11 @@ export function internal_set(source, value, updated_during_traversal = null) { execute_derived(derived); } - update_derived_status(derived); + // During time traveling we don't want to reset the status so that + // traversal of the graph in the other batches still happens + if (batch_values === null) { + update_derived_status(derived); + } } source.wv = increment_write_version(); diff --git a/packages/svelte/tests/runtime-runes/samples/async-eager-derived/_config.js b/packages/svelte/tests/runtime-runes/samples/async-eager-derived/_config.js new file mode 100644 index 0000000000..043f1610fb --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-eager-derived/_config.js @@ -0,0 +1,23 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target }) { + await tick(); + const [increment, shift] = target.querySelectorAll('button'); + + increment.click(); + await tick(); + assert.htmlEqual( + target.innerHTML, + `
true - true
` + ); + + shift.click(); + await tick(); + assert.htmlEqual( + target.innerHTML, + `false - false
` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-eager-derived/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-eager-derived/main.svelte new file mode 100644 index 0000000000..d1d979126d --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-eager-derived/main.svelte @@ -0,0 +1,22 @@ + + + + + +{$state.eager(count) !== count} - {$state.eager(derivedCount) !== derivedCount}
diff --git a/packages/svelte/tests/runtime-runes/samples/async-fork-discard-derived-writable-uninitialized/_config.js b/packages/svelte/tests/runtime-runes/samples/async-fork-discard-derived-writable-uninitialized/_config.js new file mode 100644 index 0000000000..98440b6922 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-fork-discard-derived-writable-uninitialized/_config.js @@ -0,0 +1,16 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target, logs }) { + const [btn] = target.querySelectorAll('button'); + + btn.click(); + await tick(); + assert.deepEqual(logs, [10]); + + btn.click(); + await tick(); + assert.deepEqual(logs, [10, 10]); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/async-fork-discard-derived-writable-uninitialized/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-fork-discard-derived-writable-uninitialized/main.svelte new file mode 100644 index 0000000000..16c1668480 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/async-fork-discard-derived-writable-uninitialized/main.svelte @@ -0,0 +1,21 @@ + + + diff --git a/packages/svelte/tests/runtime-runes/samples/async-unresolved-promise/_config.js b/packages/svelte/tests/runtime-runes/samples/async-unresolved-promise/_config.js index e9ccbba2b6..c6f65c33be 100644 --- a/packages/svelte/tests/runtime-runes/samples/async-unresolved-promise/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/async-unresolved-promise/_config.js @@ -18,6 +18,14 @@ export default test({ increment.click(); await tick(); + assert.htmlEqual( + target.innerHTML, + ` + +0
+ ` + ); + increment.click(); await tick(); @@ -28,5 +36,27 @@ export default test({2
` ); + + increment.click(); + await tick(); + + assert.htmlEqual( + target.innerHTML, + ` + +2
+ ` + ); + + increment.click(); + await tick(); + + assert.htmlEqual( + target.innerHTML, + ` + +4
+ ` + ); } });