Skip to content

Commit 89780e3

Browse files
committed
add tests
1 parent 3221d65 commit 89780e3

File tree

3 files changed

+122
-8
lines changed

3 files changed

+122
-8
lines changed

packages/svelte/src/internal/client/reactivity/sources.js

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -239,9 +239,10 @@ export function internal_set(source, value) {
239239
/**
240240
* @param {Value} signal
241241
* @param {number} status should be DIRTY or MAYBE_DIRTY
242+
* @param {boolean} [unsafe] mark all reactions for unsafe mutations
242243
* @returns {void}
243244
*/
244-
export function mark_reactions(signal, status) {
245+
export function mark_reactions(signal, status, unsafe = false) {
245246
var reactions = signal.reactions;
246247
if (reactions === null) return;
247248

@@ -253,23 +254,25 @@ export function mark_reactions(signal, status) {
253254
var flags = reaction.f;
254255

255256
// Skip any effects that are already dirty
256-
if ((flags & DIRTY) !== 0) continue;
257+
if ((flags & DIRTY) !== 0 && !unsafe) continue;
257258

258259
// In legacy mode, skip the current effect to prevent infinite loops
259260
if (!runes && reaction === active_effect) continue;
260261

261-
// Inspect effects need to run immediately, so that the stack trace makes sense
262-
if (DEV && (flags & INSPECT_EFFECT) !== 0) {
262+
// Inspect effects need to run immediately, so that the stack trace makes sense.
263+
// Skip doing this for the unsafe mutations as they will have already been added
264+
// in the unsafe() wrapper
265+
if (DEV && !unsafe && (flags & INSPECT_EFFECT) !== 0) {
263266
inspect_effects.add(reaction);
264267
continue;
265268
}
266269

267270
set_signal_status(reaction, status);
268271

269-
// If the signal a) was previously clean or b) is an unowned derived, then mark it
270-
if ((flags & (CLEAN | UNOWNED)) !== 0) {
272+
// If the signal a) was previously clean or b) is an unowned derived then mark it
273+
if ((flags & (CLEAN | UNOWNED)) !== 0 || unsafe) {
271274
if ((flags & DERIVED) !== 0) {
272-
mark_reactions(/** @type {Derived} */ (reaction), MAYBE_DIRTY);
275+
mark_reactions(/** @type {Derived} */ (reaction), MAYBE_DIRTY, unsafe);
273276
} else {
274277
schedule_effect(/** @type {Effect} */ (reaction));
275278
}

packages/svelte/src/internal/client/runtime.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -552,7 +552,7 @@ export function update_effect(effect) {
552552
// to ensure consistency of the graph
553553
if (unsafe_sources !== null && (effect.f & CLEAN) !== 0) {
554554
for (let i = 0; i < /** @type {Source[]} */ (unsafe_sources).length; i++) {
555-
mark_reactions(unsafe_sources[i], DIRTY);
555+
mark_reactions(unsafe_sources[i], DIRTY, true);
556556
}
557557
}
558558

packages/svelte/tests/signals/test.ts

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -781,4 +781,115 @@ describe('signals', () => {
781781
assert.equal($.get(count), 0n);
782782
};
783783
});
784+
785+
test('unsafe() correctly ensures graph consistency', () => {
786+
return () => {
787+
const output: any[] = [];
788+
789+
const destroy = effect_root(() => {
790+
const a = state(0);
791+
const b = state(0);
792+
const c = derived(() => {
793+
$.unsafe(() => {
794+
set(b, $.get(a) + 1);
795+
});
796+
return $.get(a);
797+
});
798+
799+
render_effect(() => {
800+
output.push('b' + $.get(b));
801+
});
802+
803+
render_effect(() => {
804+
output.push('b' + $.get(b), 'c' + $.get(c));
805+
});
806+
807+
flushSync();
808+
809+
set(a, 1);
810+
811+
flushSync();
812+
});
813+
814+
destroy();
815+
816+
assert.deepEqual(output, ['b0', 'b0', 'c0', 'b1', 'b1', 'c0', 'b2', 'c1', 'b2']);
817+
};
818+
});
819+
820+
test('unsafe() correctly ensures graph consistency #2', () => {
821+
return () => {
822+
const output: any[] = [];
823+
824+
const destroy = effect_root(() => {
825+
const a = state(0);
826+
const b = state(0);
827+
const c = derived(() => {
828+
$.unsafe(() => {
829+
set(b, $.get(a) + 1);
830+
});
831+
return $.get(a);
832+
});
833+
let d = derived(() => $.get(b));
834+
835+
render_effect(() => {
836+
output.push('d' + $.get(d));
837+
});
838+
839+
render_effect(() => {
840+
output.push('d' + $.get(d), 'c' + $.get(c));
841+
});
842+
843+
flushSync();
844+
845+
set(a, 1);
846+
847+
flushSync();
848+
});
849+
850+
destroy();
851+
852+
assert.deepEqual(output, ['d0', 'd0', 'c0', 'd1', 'd1', 'c0', 'd2', 'c1', 'd2']);
853+
};
854+
});
855+
856+
test('unsafe() correctly ensures graph consistency #3', () => {
857+
return () => {
858+
const output: any[] = [];
859+
860+
const destroy = effect_root(() => {
861+
const a = state(0);
862+
const b = state(0);
863+
const c = derived(() => {
864+
$.unsafe(() => {
865+
set(b, $.get(a) + 1);
866+
});
867+
return $.get(a);
868+
});
869+
let d = state(true);
870+
let e = derived(() => $.get(b));
871+
872+
render_effect(() => {
873+
if ($.get(d)) {
874+
return;
875+
}
876+
output.push('e' + $.get(e), 'c' + $.get(c));
877+
});
878+
879+
flushSync();
880+
881+
set(d, false);
882+
883+
flushSync();
884+
885+
set(a, 1);
886+
887+
flushSync();
888+
});
889+
890+
destroy();
891+
892+
assert.deepEqual(output, ['e0', 'c0', 'e1', 'c0', 'e2', 'c1']);
893+
};
894+
});
784895
});

0 commit comments

Comments
 (0)