import { afterEach, expect, test, vi } from "vitest" import { Tube } from "#Source/tube/index.ts" const createTube = ( options: Partial>[0]> = {}, ): Tube => { return new Tube({ historyCount: 3, replayHistory: false, ...options, }) } const runAllTubeTasks = async (): Promise => { await vi.runAllTimersAsync() } afterEach(() => { vi.useRealTimers() }) test("Tube.getLatestDataOrThrow returns the latest data and throws before any data arrives", async () => { const tube = createTube() expect(() => tube.getLatestDataOrThrow()).toThrow("latestData is undefined") await tube.pushData(1) await tube.pushData(2) expect(tube.getLatestDataOrThrow()).toBe(2) }) test("Tube.getLatestErrorOrThrow returns the latest error and throws before any error arrives", async () => { const tube = createTube() expect(() => tube.getLatestErrorOrThrow()).toThrow("latestError is undefined") await tube.pushError("first") await tube.pushError("second") expect(tube.getLatestErrorOrThrow()).toBe("second") }) test("Tube.isOpen reflects whether the tube is currently open", async () => { const tube = createTube({ autoStartOnOpen: false }) expect(tube.isOpen()).toBe(false) await tube.open() expect(tube.isOpen()).toBe(true) }) test("Tube.hasOpened reflects whether the tube has ever been opened", async () => { const tube = createTube({ autoStartOnOpen: false }) expect(tube.hasOpened()).toBe(false) await tube.open() expect(tube.hasOpened()).toBe(true) }) test("Tube.open opens only once and emits the open event", async () => { vi.useFakeTimers() const tube = createTube({ autoStartOnOpen: false }) const events: string[] = [] tube.subscribeOpenEvent({ subscriber: () => { events.push("open") }, }) await tube.open() await tube.open() await runAllTubeTasks() expect(tube.isOpen()).toBe(true) expect(events).toEqual(["open"]) }) test("Tube.isClose reflects whether the tube is currently closed", async () => { const tube = createTube({ autoStartOnOpen: false }) expect(tube.isClose()).toBe(true) await tube.open() expect(tube.isClose()).toBe(false) }) test("Tube.hasClosed reflects whether the tube has ever been closed", async () => { const tube = createTube({ autoStartOnOpen: false, autoEndOnClose: false }) expect(tube.hasClosed()).toBe(false) await tube.open() await tube.close() expect(tube.hasClosed()).toBe(true) }) test("Tube.close closes only once and emits the close event", async () => { vi.useFakeTimers() const tube = createTube({ autoStartOnOpen: false, autoEndOnClose: false }) const events: string[] = [] tube.subscribeCloseEvent({ subscriber: () => { events.push("close") }, }) await tube.open() await tube.close() await tube.close() await runAllTubeTasks() expect(tube.isClose()).toBe(true) expect(events).toEqual(["close"]) }) test("Tube.isStart reflects whether the tube is currently started", async () => { const tube = createTube({ autoStartOnOpen: false }) expect(tube.isStart()).toBe(false) await tube.start() expect(tube.isStart()).toBe(true) }) test("Tube.hasStarted reflects whether the tube has ever started", async () => { const tube = createTube({ autoStartOnOpen: false }) expect(tube.hasStarted()).toBe(false) await tube.start() expect(tube.hasStarted()).toBe(true) }) test("Tube.start starts only once and auto-opens when needed", async () => { vi.useFakeTimers() const tube = createTube({ autoStartOnOpen: false }) const events: string[] = [] tube.subscribeStartEvent({ subscriber: () => { events.push("start") }, }) await tube.start() await tube.start() await runAllTubeTasks() expect(tube.isOpen()).toBe(true) expect(tube.isStart()).toBe(true) expect(events).toEqual(["start"]) }) test("Tube.isEnd reflects whether the tube is currently ended", async () => { const tube = createTube({ autoStartOnOpen: false }) expect(tube.isEnd()).toBe(true) await tube.start() expect(tube.isEnd()).toBe(false) }) test("Tube.hasEnded reflects whether the tube has ever ended", async () => { const tube = createTube({ autoStartOnOpen: false, autoCloseOnEnd: false }) expect(tube.hasEnded()).toBe(false) await tube.start() await tube.end() expect(tube.hasEnded()).toBe(true) }) test("Tube.end ends only once and closes when configured", async () => { vi.useFakeTimers() const tube = createTube({ autoStartOnOpen: false }) const events: string[] = [] tube.subscribeEndEvent({ subscriber: () => { events.push("end") }, }) await tube.start() await tube.end() await tube.end() await runAllTubeTasks() expect(tube.isEnd()).toBe(true) expect(tube.hasClosed()).toBe(true) expect(events).toEqual(["end"]) }) test("Tube.isError reflects whether the tube has seen an error", async () => { const tube = createTube() expect(tube.isError()).toBe(false) await tube.pushError("boom") expect(tube.isError()).toBe(true) }) test("Tube.isWet reflects whether the tube has received any data", async () => { const tube = createTube() expect(tube.isWet()).toBe(false) await tube.pushData(1) expect(tube.isWet()).toBe(true) }) test("Tube.subscribeOpenEvent returns a stable unsubscribe function and delivers open notifications", async () => { vi.useFakeTimers() const tube = createTube({ autoStartOnOpen: false }) const subscriber = vi.fn<() => void>(() => undefined) const firstUnsubscribe = tube.subscribeOpenEvent({ subscriber }) const secondUnsubscribe = tube.subscribeOpenEvent({ subscriber }) await tube.open() await runAllTubeTasks() expect(secondUnsubscribe).toBe(firstUnsubscribe) expect(subscriber).toHaveBeenCalledTimes(1) }) test("Tube.unsubscribeOpenEvent removes only the specified open subscriber", async () => { vi.useFakeTimers() const tube = createTube({ autoStartOnOpen: false }) const keptSubscriber = vi.fn<() => void>(() => undefined) const removedSubscriber = vi.fn<() => void>(() => undefined) tube.subscribeOpenEvent({ subscriber: keptSubscriber }) tube.subscribeOpenEvent({ subscriber: removedSubscriber }) tube.unsubscribeOpenEvent(removedSubscriber) await tube.open() await runAllTubeTasks() expect(keptSubscriber).toHaveBeenCalledTimes(1) expect(removedSubscriber).not.toHaveBeenCalled() }) test("Tube.subscribeCloseEvent returns a stable unsubscribe function and delivers close notifications", async () => { vi.useFakeTimers() const tube = createTube({ autoStartOnOpen: false, autoEndOnClose: false }) const subscriber = vi.fn<() => void>(() => undefined) const firstUnsubscribe = tube.subscribeCloseEvent({ subscriber }) const secondUnsubscribe = tube.subscribeCloseEvent({ subscriber }) await tube.open() await tube.close() await runAllTubeTasks() expect(secondUnsubscribe).toBe(firstUnsubscribe) expect(subscriber).toHaveBeenCalledTimes(1) }) test("Tube.unsubscribeCloseEvent removes only the specified close subscriber", async () => { vi.useFakeTimers() const tube = createTube({ autoStartOnOpen: false, autoEndOnClose: false }) const keptSubscriber = vi.fn<() => void>(() => undefined) const removedSubscriber = vi.fn<() => void>(() => undefined) tube.subscribeCloseEvent({ subscriber: keptSubscriber }) tube.subscribeCloseEvent({ subscriber: removedSubscriber }) tube.unsubscribeCloseEvent(removedSubscriber) await tube.open() await tube.close() await runAllTubeTasks() expect(keptSubscriber).toHaveBeenCalledTimes(1) expect(removedSubscriber).not.toHaveBeenCalled() }) test("Tube.subscribeStartEvent returns a stable unsubscribe function and delivers start notifications", async () => { vi.useFakeTimers() const tube = createTube({ autoStartOnOpen: false }) const subscriber = vi.fn<() => void>(() => undefined) const firstUnsubscribe = tube.subscribeStartEvent({ subscriber }) const secondUnsubscribe = tube.subscribeStartEvent({ subscriber }) await tube.start() await runAllTubeTasks() expect(secondUnsubscribe).toBe(firstUnsubscribe) expect(subscriber).toHaveBeenCalledTimes(1) }) test("Tube.unsubscribeStartEvent removes only the specified start subscriber", async () => { vi.useFakeTimers() const tube = createTube({ autoStartOnOpen: false }) const keptSubscriber = vi.fn<() => void>(() => undefined) const removedSubscriber = vi.fn<() => void>(() => undefined) tube.subscribeStartEvent({ subscriber: keptSubscriber }) tube.subscribeStartEvent({ subscriber: removedSubscriber }) tube.unsubscribeStartEvent(removedSubscriber) await tube.start() await runAllTubeTasks() expect(keptSubscriber).toHaveBeenCalledTimes(1) expect(removedSubscriber).not.toHaveBeenCalled() }) test("Tube.subscribeEndEvent returns a stable unsubscribe function and delivers end notifications", async () => { vi.useFakeTimers() const tube = createTube({ autoStartOnOpen: false, autoCloseOnEnd: false }) const subscriber = vi.fn<() => void>(() => undefined) const firstUnsubscribe = tube.subscribeEndEvent({ subscriber }) const secondUnsubscribe = tube.subscribeEndEvent({ subscriber }) await tube.start() await tube.end() await runAllTubeTasks() expect(secondUnsubscribe).toBe(firstUnsubscribe) expect(subscriber).toHaveBeenCalledTimes(1) }) test("Tube.unsubscribeEndEvent removes only the specified end subscriber", async () => { vi.useFakeTimers() const tube = createTube({ autoStartOnOpen: false, autoCloseOnEnd: false }) const keptSubscriber = vi.fn<() => void>(() => undefined) const removedSubscriber = vi.fn<() => void>(() => undefined) tube.subscribeEndEvent({ subscriber: keptSubscriber }) tube.subscribeEndEvent({ subscriber: removedSubscriber }) tube.unsubscribeEndEvent(removedSubscriber) await tube.start() await tube.end() await runAllTubeTasks() expect(keptSubscriber).toHaveBeenCalledTimes(1) expect(removedSubscriber).not.toHaveBeenCalled() }) test("Tube.subscribeWetEvent returns a stable unsubscribe function and delivers the first wet notification", async () => { vi.useFakeTimers() const tube = createTube() const subscriber = vi.fn<() => void>(() => undefined) const firstUnsubscribe = tube.subscribeWetEvent({ subscriber }) const secondUnsubscribe = tube.subscribeWetEvent({ subscriber }) await tube.pushData(1) await tube.pushData(2) await runAllTubeTasks() expect(secondUnsubscribe).toBe(firstUnsubscribe) expect(subscriber).toHaveBeenCalledTimes(1) }) test("Tube.unsubscribeWetEvent removes only the specified wet subscriber", async () => { vi.useFakeTimers() const tube = createTube() const keptSubscriber = vi.fn<() => void>(() => undefined) const removedSubscriber = vi.fn<() => void>(() => undefined) tube.subscribeWetEvent({ subscriber: keptSubscriber }) tube.subscribeWetEvent({ subscriber: removedSubscriber }) tube.unsubscribeWetEvent(removedSubscriber) await tube.pushData(1) await runAllTubeTasks() expect(keptSubscriber).toHaveBeenCalledTimes(1) expect(removedSubscriber).not.toHaveBeenCalled() }) test("Tube.pushError records the error, opens the tube when needed and emits error notifications", async () => { vi.useFakeTimers() const tube = createTube({ autoEndOnError: false, autoCloseOnError: false }) const errors: Array = [] tube.subscribeErrorEvent({ subscriber: (error) => { errors.push(error) }, }) await tube.pushError("boom") await runAllTubeTasks() expect(tube.isOpen()).toBe(true) expect(tube.isError()).toBe(true) expect(errors).toEqual(["boom"]) }) test("Tube.subscribeErrorEvent returns a stable unsubscribe function and delivers error notifications", async () => { vi.useFakeTimers() const tube = createTube({ autoEndOnError: false, autoCloseOnError: false }) const subscriber = vi.fn<(error: string | Error) => void>(() => undefined) const firstUnsubscribe = tube.subscribeErrorEvent({ subscriber }) const secondUnsubscribe = tube.subscribeErrorEvent({ subscriber }) await tube.pushError("boom") await runAllTubeTasks() expect(secondUnsubscribe).toBe(firstUnsubscribe) expect(subscriber).toHaveBeenCalledTimes(1) expect(subscriber).toHaveBeenCalledWith("boom") }) test("Tube.unsubscribeErrorEvent removes only the specified error subscriber", async () => { vi.useFakeTimers() const tube = createTube({ autoEndOnError: false, autoCloseOnError: false }) const keptSubscriber = vi.fn<(error: string | Error) => void>(() => undefined) const removedSubscriber = vi.fn<(error: string | Error) => void>(() => undefined) tube.subscribeErrorEvent({ subscriber: keptSubscriber }) tube.subscribeErrorEvent({ subscriber: removedSubscriber }) tube.unsubscribeErrorEvent(removedSubscriber) await tube.pushError("boom") await runAllTubeTasks() expect(keptSubscriber).toHaveBeenCalledTimes(1) expect(removedSubscriber).not.toHaveBeenCalled() }) test("Tube.pushData records data, auto-starts the tube and trims history by historyCount", async () => { vi.useFakeTimers() const tube = createTube({ historyCount: 1 }) const values: number[] = [] tube.subscribeData({ subscriber: (value) => { values.push(value) }, }) await tube.pushData(1) await tube.pushData(2) await runAllTubeTasks() expect(tube.isOpen()).toBe(true) expect(tube.isStart()).toBe(true) expect(tube.isWet()).toBe(true) expect(tube.getLatestDataOrThrow()).toBe(2) expect(values).toEqual([1, 2]) }) test("Tube.subscribeData returns a stable unsubscribe function and can replay history", async () => { vi.useFakeTimers() const tube = createTube() const values: number[] = [] const subscriber = vi.fn<(value: number) => void>((value) => { values.push(value) }) await tube.pushData(1) await tube.pushData(2) const firstUnsubscribe = tube.subscribeData({ subscriber, replayHistory: true }) const secondUnsubscribe = tube.subscribeData({ subscriber, replayHistory: true }) await tube.pushData(3) await runAllTubeTasks() expect(secondUnsubscribe).toBe(firstUnsubscribe) expect(values).toEqual([1, 2, 3]) expect(subscriber).toHaveBeenCalledTimes(3) }) test("Tube.unsubscribeData removes only the specified data subscriber", async () => { vi.useFakeTimers() const tube = createTube() const keptSubscriber = vi.fn<(value: number) => void>(() => undefined) const removedSubscriber = vi.fn<(value: number) => void>(() => undefined) tube.subscribeData({ subscriber: keptSubscriber }) tube.subscribeData({ subscriber: removedSubscriber }) tube.unsubscribeData(removedSubscriber) await tube.pushData(1) await runAllTubeTasks() expect(keptSubscriber).toHaveBeenCalledTimes(1) expect(removedSubscriber).not.toHaveBeenCalled() })