diff --git a/src/tests/integration/effect-test.gts b/src/tests/integration/effect-test.gts index 3adb64a..7e15593 100644 --- a/src/tests/integration/effect-test.gts +++ b/src/tests/integration/effect-test.gts @@ -134,4 +134,25 @@ module('Integration | Internal | effect', function () { ); assert.equal(executionsCount, 3, `executions count not changed`); }); + test('check loop guard', async function (assert) { + const source = cell(1); + const derived = cell(1); + let executionsCount = 0; + const destructor = effect(() => { + derived.update(source.value); + source.value = Math.random(); + executionsCount++; + console.log('effect executed'); + }); + assert.notEqual(derived.value, source.value); + assert.equal(executionsCount, 1, `effect executed once`); + source.update(2); + await rerender(); + assert.notEqual(derived.value, source.value); + assert.equal(executionsCount, 2, `effect executed second time`); + await rerender(); + assert.equal(executionsCount, 2, `effect executed second time`); + + destructor(); + }); }); diff --git a/src/utils/vm.ts b/src/utils/vm.ts index 3e9cda8..3d02b1d 100644 --- a/src/utils/vm.ts +++ b/src/utils/vm.ts @@ -8,6 +8,7 @@ import { formula, opsFor, inNewTrackingFrame, + tagsToRevalidate, } from './reactive'; import { isFn } from './shared'; @@ -34,7 +35,22 @@ export function effect(cb: () => void): () => void { const tag = formula(() => { runEffectDestructor(destructor); destructor = undefined; - return sourceTag.value; + if (import.meta.env.DEV) { + const value = sourceTag.value; + try { + return value; + } finally { + if (import.meta.env.DEV) { + for (const tag of tagsToRevalidate) { + if (sourceTag.relatedCells?.has(tag)) { + throw new Error(`effect mutating its source: ${tag._debugName} ${JSON.stringify(tag._value)}`) + } + } + } + } + } else { + return sourceTag.value; + } }, 'effect'); const destroyOpcode = opcodeFor(tag, (value: unknown) => { if (IS_DEV_MODE) {