From 32abe077fcfa20dd7c4130a8e82cd1faa2926f54 Mon Sep 17 00:00:00 2001 From: Marvin Hagemeister Date: Fri, 9 Sep 2022 17:37:46 +0200 Subject: [PATCH] Fix internal API able to unmark non-marked signals --- .changeset/neat-dingos-shake.md | 5 +++++ packages/core/src/index.ts | 6 +++++- packages/core/test/signal.test.tsx | 15 +++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 .changeset/neat-dingos-shake.md diff --git a/.changeset/neat-dingos-shake.md b/.changeset/neat-dingos-shake.md new file mode 100644 index 000000000..02007fd44 --- /dev/null +++ b/.changeset/neat-dingos-shake.md @@ -0,0 +1,5 @@ +--- +"@preact/signals-core": patch +--- + +Fix internal API functions being able to unmark non-invalidated signals diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 9b5dbe2d9..c01da22d6 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -162,7 +162,11 @@ function unmark(signal: Signal) { // wasn't flagged as needing an update by someone else. This is // done to make the sweeping logic independent of the order // in which a dependency tries to unmark a subtree. - if (!signal._requiresUpdate && --signal._pending === 0) { + if ( + !signal._requiresUpdate && + signal._pending > 0 && + --signal._pending === 0 + ) { signal._subs.forEach(unmark); } } diff --git a/packages/core/test/signal.test.tsx b/packages/core/test/signal.test.tsx index 68e633f2a..e76a9d076 100644 --- a/packages/core/test/signal.test.tsx +++ b/packages/core/test/signal.test.tsx @@ -470,6 +470,21 @@ describe("computed()", () => { a.value = "aa"; expect(spy).to.returned("aa c d"); }); + + it("should prevent invalid unmark state when called on a source signal", () => { + // Don't allow our internal logic to get in an invalid state, even through + // our own internal API. The bug this tests for is that a source signal + // will be unmarked, leading to all its subscribers `_pending` value to become + // negative. This is invalid and breaks further updates. + const a = signal("a"); + const b = computed(() => a.value); + effect(() => b.value); + + a._setCurrent()(true, true); + + a.value = "aa"; + expect(b.value).to.equal("aa"); + }); }); describe("error handling", () => {