import { expect, test, vi } from "vitest" import { debounce, functionCompose, functionDebounceSimple, functionIife, functionMemorize, functionOnce, functionPipe, functionThrottle, functionThrottleSimple, functionThrottleTimeSimple, throttleTime, } from "#Source/basic/index.ts" test("functionIife invokes immediately", () => { expect(functionIife((a: number, b: number) => a + b, 1, 2)).toBe(3) expect(functionIife(() => "ok")).toBe("ok") }) test("functionOnce only executes once", () => { let calls = 0 const onceFn = functionOnce((value: number) => { calls = calls + 1 return value * 2 }) expect(onceFn(5)).toBe(10) expect(onceFn(10)).toBe(10) expect(calls).toBe(1) let times = 0 const onceWithSubscriber = functionOnce( (value: number) => value * 2, (count) => { times = count }, ) onceWithSubscriber(5) onceWithSubscriber(10) expect(times).toBe(2) }) test("functionDebounceSimple delays execution", () => { vi.useFakeTimers() try { const spy = vi.fn() const debounced = functionDebounceSimple(spy, 100) debounced(5) debounced(10) expect(spy).not.toHaveBeenCalled() vi.advanceTimersByTime(100) expect(spy).toHaveBeenCalledTimes(1) expect(spy).toHaveBeenCalledWith(10) } finally { vi.useRealTimers() } }) test("debounce resolves all calls with the last result", async () => { vi.useFakeTimers() try { const debounced = debounce((value: number) => value * 2, 100) const result1 = debounced(5) const result2 = debounced(10) vi.advanceTimersByTime(100) await expect(Promise.all([result1, result2])).resolves.toEqual([20, 20]) } finally { vi.useRealTimers() } }) test("functionThrottleTimeSimple limits calls by time", () => { vi.useFakeTimers() try { const spy = vi.fn() const throttled = functionThrottleTimeSimple(spy, 100) throttled(1) throttled(2) expect(spy).toHaveBeenCalledTimes(1) vi.advanceTimersByTime(100) throttled(3) expect(spy).toHaveBeenCalledTimes(2) } finally { vi.useRealTimers() } }) test("functionThrottleSimple coalesces async calls", async () => { let calls = 0 // oxlint-disable-next-line require-await const throttled = functionThrottleSimple(async () => { calls = calls + 1 }) throttled() throttled() await Promise.resolve() expect(calls).toBe(1) throttled() await Promise.resolve() expect(calls).toBe(2) }) test("throttleTime resolves concurrent calls with one execution", async () => { vi.useFakeTimers() try { const spy = vi.fn((value: number) => value * 2) const throttled = throttleTime(spy, 100) const result1 = throttled(2) const result2 = throttled(3) await expect(Promise.all([result1, result2])).resolves.toEqual([4, 4]) expect(spy).toHaveBeenCalledTimes(1) vi.advanceTimersByTime(100) const result3 = throttled(4) await expect(result3).resolves.toBe(8) expect(spy).toHaveBeenCalledTimes(2) } finally { vi.useRealTimers() } }) test("functionThrottle coalesces concurrent calls", async () => { let calls = 0 const throttled = functionThrottle(() => { calls = calls + 1 return "ok" }) const result1 = throttled() const result2 = throttled() await expect(Promise.all([result1, result2])).resolves.toEqual(["ok", "ok"]) expect(calls).toBe(1) }) test("functionCompose applies right-to-left", () => { const addOne = (value: number): number => value + 1 const double = (value: number): number => value * 2 const composed = functionCompose(addOne, double) expect(composed(2)).toBe(5) expect(functionCompose()(3)).toBe(3) }) test("functionPipe applies left-to-right", () => { const addOne = (value: number): number => value + 1 const double = (value: number): number => value * 2 const piped = functionPipe(addOne, double) expect(piped(2)).toBe(6) expect(functionPipe()(3)).toBe(3) }) test("functionMemorize caches results", () => { let calls = 0 const sum = (a: number, b: number): number => { calls = calls + 1 return a + b } const memoized = functionMemorize(sum) expect(memoized(1, 2)).toBe(3) expect(memoized(1, 2)).toBe(3) expect(calls).toBe(1) })