import { expect, test } from "vitest" import { computed, derived, effect, reactiveSystem, signal } from "#Source/reactor/index.ts" test("should derived update activily or passively", () => { const a = signal(() => 1) const b = derived(() => { return a.get() + 1 }) expect(b.get()).toBe(2) a.set(2) expect(b.get()).toBe(3) b.set(2) expect(b.get()).toBe(2) a.set(3) expect(b.get()).toBe(4) }) test("should use isEqual from options", () => { const a = signal(() => [] as number[], { isEqual: (oldValue, newValue) => { return oldValue.length === newValue.length }, }) let effectRunTimes = 0 effect(() => { effectRunTimes = effectRunTimes + 1 a.get() }) expect(effectRunTimes).toBe(1) a.set([1]) expect(effectRunTimes).toBe(2) a.set([2]) expect(effectRunTimes).toBe(2) a.set([3, 4]) expect(effectRunTimes).toBe(3) }) test("should trigger callbacks", () => { const logs: string[] = [] const a = signal(() => 1, { name: "a", onDepChanged: () => logs.push("a dep changed"), onSubChanged: () => logs.push("a sub changed"), onHasDep: (dep) => logs.push(`a has first dep ${dep.getName()}`), onNoDep: (dep) => logs.push(`a no dep ${dep.getName()}`), onHasSub: (sub) => logs.push(`a has first sub ${sub.getName()}`), onNoSub: (sub) => logs.push(`a no sub ${sub.getName()}`), }) const b = signal(() => 2, { name: "b", onDepChanged: () => logs.push("b dep changed"), onSubChanged: () => logs.push("b sub changed"), onHasDep: (dep) => logs.push(`b has first dep ${dep.getName()}`), onNoDep: (dep) => logs.push(`b no dep ${dep.getName()}`), onHasSub: (sub) => logs.push(`b has first sub ${sub.getName()}`), onNoSub: (sub) => logs.push(`b no sub ${sub.getName()}`), }) const c = computed(() => a.get() + b.get(), { name: "c", onDepChanged: () => logs.push("c dep changed"), onSubChanged: () => logs.push("c sub changed"), onHasDep: (dep) => logs.push(`c has first dep ${dep.getName()}`), onNoDep: (dep) => logs.push(`c no dep ${dep.getName()}`), onHasSub: (sub) => logs.push(`c has first sub ${sub.getName()}`), onNoSub: (sub) => logs.push(`c no sub ${sub.getName()}`), }) const e = effect( () => { c.get() }, { name: "e", onDepChanged: () => logs.push("e dep changed"), onSubChanged: () => logs.push("e sub changed"), onHasDep: (dep) => logs.push(`e has first dep ${dep.getName()}`), onNoDep: (dep) => logs.push(`e no dep ${dep.getName()}`), onHasSub: (sub) => logs.push(`e has first sub ${sub.getName()}`), onNoSub: (sub) => logs.push(`e no sub ${sub.getName()}`), }, ) expect(logs).toEqual([ "c sub changed", "c has first sub e", "e dep changed", "e has first dep c", "a sub changed", "a has first sub c", "c dep changed", "c has first dep a", "b sub changed", "b has first sub c", "c dep changed", ]) logs.length = 0 a.set(3) expect(logs).toEqual(["c dep changed", "c dep changed", "e dep changed"]) logs.length = 0 e.dispose() expect(logs).toEqual([ "c sub changed", "c no sub e", "a sub changed", "a no sub c", "c dep changed", "b sub changed", "b no sub c", "c dep changed", "c no dep b", "e dep changed", "e no dep c", ]) }) test("should trigger onDispose callback", () => { const logs: string[] = [] const a = signal(() => 1, { name: "a", onDispose: () => logs.push("a disposed"), }) const b = derived(() => a.get() + 1, { name: "b", onDispose: () => logs.push("b disposed"), }) const c = computed(() => b.get() + 1, { name: "c", onDispose: () => logs.push("c disposed"), }) const e = effect( () => { c.get() }, { name: "e", onDispose: () => logs.push("e disposed"), }, ) e.dispose() expect(logs).toEqual(["a disposed", "b disposed", "c disposed", "e disposed"]) }) test("should not track when getWithoutTrack", () => { const a = signal(() => 0) const b = signal(() => 1) const c = computed(() => { return a.get() + b.get() }) let effectRunTimes = 0 effect(() => { effectRunTimes = effectRunTimes + 1 a.get() c.getWithoutTrack() }) expect(effectRunTimes).toBe(1) a.set(2) expect(effectRunTimes).toBe(2) b.set(3) expect(effectRunTimes).toBe(2) }) test("should cleanup on stop and re-track on update", () => { let cleanupRunTimes = 0 const a = signal(() => 0) const e = effect((context) => { const { setCleanup } = context a.get() setCleanup((): void => { cleanupRunTimes = cleanupRunTimes + 1 }) }) expect(cleanupRunTimes).toBe(0) a.set(1) expect(cleanupRunTimes).toBe(1) a.set(2) expect(cleanupRunTimes).toBe(2) e.dispose() expect(cleanupRunTimes).toBe(3) }) test("should not track when withoutTracking", () => { const a = signal(() => 0) const b = signal(() => 1) let effectRunTimes = 0 effect(() => { effectRunTimes = effectRunTimes + 1 a.get() reactiveSystem.withoutTracking(() => { b.get() }) }) expect(effectRunTimes).toBe(1) a.set(2) expect(effectRunTimes).toBe(2) b.set(3) expect(effectRunTimes).toBe(2) })