import { expect, test } from "vitest" import type { Signal } from "#Source/reactor/index.ts" import { bufferByCount, bufferByDynamic, bufferByTime, bufferByToggle, bufferByTrigger, contextualMap, groupToArrayBy, groupToObjectBy, groupToSignalArrayBy, groupToSignalObjectBy, interval, map, mergeMap, pairwise, partition, scan, signal, switchMap, tap, withHistory, } from "#Source/reactor/index.ts" test("tap operator should work correctly", () => { const logs: string[] = [] const source = signal(() => "start") const tapped = tap({ target: source, tapper: (value) => { logs.push(`tapped: ${value}`) }, }) expect(tapped.get()).toBe("start") expect(logs).toEqual(["tapped: start"]) source.set("next") expect(tapped.get()).toBe("next") expect(logs).toEqual(["tapped: start", "tapped: next"]) }) test("withHistory operator should work correctly", () => { const a = signal(() => 1) const aWithHistory = withHistory({ target: a }) expect(aWithHistory.get()).toEqual([1]) a.set(2) expect(aWithHistory.get()).toEqual([1, 2]) a.set(3) expect(aWithHistory.get()).toEqual([1, 2, 3]) }) test("bufferByCount operator should work correctly", () => { const source = signal(() => 1) const buffered = bufferByCount({ target: source, count: 3 }) expect(buffered.get()).toEqual([]) source.set(2) expect(buffered.get()).toEqual([]) source.set(3) expect(buffered.get()).toEqual([1, 2, 3]) source.set(4) source.set(5) source.set(6) expect(buffered.get()).toEqual([4, 5, 6]) }) test("bufferByTime operator should work correctly", async () => { const source = signal(() => 1) const buffered = bufferByTime({ target: source, time: 100 }) expect(buffered.get()).toEqual([]) source.set(2) source.set(3) expect(buffered.get()).toEqual([]) await new Promise((resolve) => { setTimeout(resolve, 120) }) expect(buffered.get()).toEqual([1, 2, 3]) source.set(4) source.set(5) source.set(6) expect(buffered.get()).toEqual([1, 2, 3]) await new Promise((resolve) => { setTimeout(resolve, 110) }) expect(buffered.get()).toEqual([4, 5, 6]) }) test("bufferByTrigger operator should work correctly", () => { const source = signal(() => 1) const trigger = signal(() => 0) const buffered = bufferByTrigger({ target: source, trigger }) expect(buffered.get()).toEqual([1]) source.set(2) source.set(3) expect(buffered.get()).toEqual([1]) trigger.set(1) expect(buffered.get()).toEqual([2, 3]) source.set(4) source.set(5) source.set(6) expect(buffered.get()).toEqual([2, 3]) trigger.set(2) expect(buffered.get()).toEqual([4, 5, 6]) }) test("bufferByToggle operator should work correctly", () => { const source = signal(() => 1) const open = signal(() => []) const close = signal(() => []) const buffered = bufferByToggle({ target: source, open, close }) expect(buffered.get()).toEqual([1]) source.set(2) expect(buffered.get()).toEqual([1]) open.set([]) source.set(3) source.set(4) expect(buffered.get()).toEqual([1]) close.set([]) expect(buffered.get()).toEqual([3, 4]) source.set(5) expect(buffered.get()).toEqual([3, 4]) open.set([]) source.set(6) source.set(7) expect(buffered.get()).toEqual([3, 4]) close.set([]) expect(buffered.get()).toEqual([6, 7]) }) test("bufferByDynamic operator should work correctly", async () => { const source = signal(() => 1) const dynamic = (): Signal => interval({ interval: 100 }) const buffered = bufferByDynamic({ target: source, dynamic }) expect(buffered.get()).toEqual([1]) source.set(2) source.set(3) expect(buffered.get()).toEqual([1]) await new Promise((resolve) => { setTimeout(resolve, 120) }) expect(buffered.get()).toEqual([2, 3]) source.set(4) source.set(5) source.set(6) expect(buffered.get()).toEqual([2, 3]) await new Promise((resolve) => { setTimeout(resolve, 110) }) expect(buffered.get()).toEqual([4, 5, 6]) }) test("groupToArrayBy operator should work correctly", () => { const source = signal(() => ({ id: 1, value: "a" })) const grouped = groupToArrayBy({ target: source, keyGetter: (item) => item.id }) expect(grouped.get().at(0)).toEqual([{ id: 1, value: "a" }]) source.set({ id: 2, value: "b" }) expect(grouped.get().at(0)).toEqual([{ id: 1, value: "a" }]) expect(grouped.get().at(1)).toEqual([{ id: 2, value: "b" }]) source.set({ id: 1, value: "c" }) expect(grouped.get().at(0)).toEqual([ { id: 1, value: "a" }, { id: 1, value: "c" }, ]) expect(grouped.get().at(1)).toEqual([{ id: 2, value: "b" }]) }) test("groupToObjectBy operator should work correctly", () => { const source = signal(() => ({ id: "x", value: 10 })) const grouped = groupToObjectBy({ target: source, keyGetter: (item) => item.id }) expect(grouped.get()["x"]).toEqual([{ id: "x", value: 10 }]) source.set({ id: "y", value: 20 }) expect(grouped.get()["x"]).toEqual([{ id: "x", value: 10 }]) expect(grouped.get()["y"]).toEqual([{ id: "y", value: 20 }]) source.set({ id: "x", value: 30 }) expect(grouped.get()["x"]).toEqual([ { id: "x", value: 10 }, { id: "x", value: 30 }, ]) expect(grouped.get()["y"]).toEqual([{ id: "y", value: 20 }]) }) test("groupToSignalArrayBy operator should work correctly", () => { const source = signal(() => ({ id: 1, value: "a" })) const grouped = groupToSignalArrayBy(source, (item) => item.id) expect(grouped.get().at(0)?.get()).toEqual([{ id: 1, value: "a" }]) source.set({ id: 2, value: "b" }) expect(grouped.get().at(0)?.get()).toEqual([{ id: 1, value: "a" }]) expect(grouped.get().at(1)?.get()).toEqual([{ id: 2, value: "b" }]) source.set({ id: 1, value: "c" }) expect(grouped.get().at(0)?.get()).toEqual([ { id: 1, value: "a" }, { id: 1, value: "c" }, ]) expect(grouped.get().at(1)?.get()).toEqual([{ id: 2, value: "b" }]) }) test("groupToSignalObjectBy operator should work correctly", () => { const source = signal(() => ({ id: "x", value: 10 })) const grouped = groupToSignalObjectBy({ target: source, keyGetter: (item) => item.id }) expect(grouped.get()["x"]?.get()).toEqual([{ id: "x", value: 10 }]) source.set({ id: "y", value: 20 }) expect(grouped.get()["x"]?.get()).toEqual([{ id: "x", value: 10 }]) expect(grouped.get()["y"]?.get()).toEqual([{ id: "y", value: 20 }]) source.set({ id: "x", value: 30 }) expect(grouped.get()["x"]?.get()).toEqual([ { id: "x", value: 10 }, { id: "x", value: 30 }, ]) expect(grouped.get()["y"]?.get()).toEqual([{ id: "y", value: 20 }]) }) test("map operator should work correctly", () => { const source = signal(() => 2) const mapped = map({ target: source, mapper: (value) => value * 3 }) expect(mapped.get()).toBe(6) source.set(4) expect(mapped.get()).toBe(12) source.set(0) expect(mapped.get()).toBe(0) }) test("contextualMap operator should work correctly", () => { const source = signal(() => 5) const mapped = contextualMap({ target: source, mapper: (context): number => { if (context.isInitializingRun) { return context.targetValue + 1 } else { return context.targetValue + context.previousMappedValue } }, }) expect(mapped.get()).toBe(6) source.set(10) expect(mapped.get()).toBe(16) source.set(3) expect(mapped.get()).toBe(19) }) test("mergeMap operator should work correctly", () => { const source = signal(() => 1) const merged = mergeMap({ target: source, mapper: (value) => signal(() => value * 10) }) expect(merged.get()).toBe(10) source.set(3) expect(merged.get()).toBe(30) source.set(5) expect(merged.get()).toBe(50) }) test("switchMap operator should work correctly", () => { const source = signal(() => 1) const switched = switchMap({ target: source, mapper: (value) => signal(() => value + 5) }) expect(switched.get()).toBe(6) source.set(4) expect(switched.get()).toBe(9) source.set(10) expect(switched.get()).toBe(15) }) test("scan operator should work correctly", () => { const source = signal(() => 1) const scanned = scan({ target: source, accumulator: (acc, value) => acc + value, seed: 0 }) expect(scanned.get()).toBe(1) source.set(2) expect(scanned.get()).toBe(3) source.set(3) expect(scanned.get()).toBe(6) }) test("pairwise operator should work correctly", () => { const source = signal(() => 1) const paired = pairwise({ target: source }) expect(paired.get()).toEqual([undefined, 1]) source.set(2) expect(paired.get()).toEqual([1, 2]) source.set(3) expect(paired.get()).toEqual([2, 3]) }) test("partition operator should work correctly", () => { const source = signal(() => 1) const [even, odd] = partition({ target: source, predicate: (value) => value % 2 === 0 }) expect(even.get()).toBe(undefined) expect(odd.get()).toBe(1) source.set(2) expect(even.get()).toBe(2) expect(odd.get()).toBe(1) source.set(3) expect(even.get()).toBe(2) expect(odd.get()).toBe(3) })