import { expect, test } from "vitest" import type { SingletonItem } from "#Source/singleton/index.ts" import { getSingletonFactory, singletonCollection, singletonItem } from "#Source/singleton/index.ts" test("getSingletonFactory caches first produced value", () => { let makeCalls = 0 const createSingleton = getSingletonFactory(() => { makeCalls = makeCalls + 1 return { value: "singleton" } }) const first = createSingleton() const second = createSingleton() expect(first).toBe(second) expect(first.value).toBe("singleton") expect(makeCalls).toBe(1) }) test("singletonItem creates named lazy singleton item", () => { let valueCalls = 0 const item = singletonItem("config", () => { valueCalls = valueCalls + 1 return { enabled: true } }) const first = item.getValue() const second = item.getValue() expect(item.getName()).toBe("config") expect(first).toBe(second) expect(first.enabled).toBe(true) expect(valueCalls).toBe(1) }) test("singletonItem caches null values and supports getValueAsync", async () => { let nullableCalls = 0 const nullableItem = singletonItem("nullable", () => { nullableCalls = nullableCalls + 1 return null }) expect(nullableItem.getValue()).toBeNull() expect(nullableItem.getValue()).toBeNull() await expect(nullableItem.getValueAsync()).resolves.toBeNull() expect(nullableCalls).toBe(1) const syncItem = singletonItem("sync-config", () => ({ enabled: true })) await expect(syncItem.getValueAsync()).resolves.toEqual({ enabled: true }) }) test("singletonItem caches promise values and supports async access", async () => { let asyncCalls = 0 const asyncItem = singletonItem("async-config", async () => { asyncCalls = asyncCalls + 1 await Promise.resolve() return { enabled: true } }) const firstPromise = asyncItem.getValue() const secondPromise = asyncItem.getValue() const thirdPromise = asyncItem.getValueAsync() const fourthPromise = asyncItem.getValueAsync() expect(asyncItem.getName()).toBe("async-config") expect(firstPromise).toBe(secondPromise) await expect(firstPromise).resolves.toEqual({ enabled: true }) await expect(thirdPromise).resolves.toEqual({ enabled: true }) await expect(fourthPromise).resolves.toEqual({ enabled: true }) expect(asyncCalls).toBe(1) }) test("singletonItem reset clears cached sync and promise values", async () => { let syncCalls = 0 const syncItem = singletonItem("sync-reset", () => { syncCalls = syncCalls + 1 return { version: syncCalls } }) const firstSync = syncItem.getValue() syncItem.reset() const secondSync = syncItem.getValue() expect(firstSync).toEqual({ version: 1 }) expect(secondSync).toEqual({ version: 2 }) expect(firstSync).not.toBe(secondSync) expect(syncCalls).toBe(2) let asyncCalls = 0 const asyncItem = singletonItem("async-reset", async () => { asyncCalls = asyncCalls + 1 await Promise.resolve() return { version: asyncCalls } }) const firstPromise = asyncItem.getValueAsync() await expect(firstPromise).resolves.toEqual({ version: 1 }) asyncItem.reset() const secondPromise = asyncItem.getValue() expect(firstPromise).not.toBe(secondPromise) await expect(secondPromise).resolves.toEqual({ version: 2 }) expect(asyncCalls).toBe(2) }) test("singletonCollection resolves sync and promise items and supports getItemAsync", async () => { const portItem: SingletonItem<"port", number> = singletonItem("port", () => 3_000) const modeItem: SingletonItem<"mode", string> = singletonItem("mode", () => "test") let readyCalls = 0 const readyItem: SingletonItem<"ready", Promise> = singletonItem("ready", async () => { readyCalls = readyCalls + 1 await Promise.resolve() return "ok" }) const collection = singletonCollection([portItem, modeItem, readyItem]) expect(collection.getItem("port")).toBe(3_000) expect(collection.getItem("mode")).toBe("test") await expect(collection.getItemAsync("port")).resolves.toBe(3_000) await expect(collection.getItemAsync("mode")).resolves.toBe("test") const firstReady = collection.getItem("ready") const secondReady = collection.getItem("ready") const thirdReady = collection.getItemAsync("ready") const fourthReady = collection.getItemAsync("ready") expect(firstReady).toBe(secondReady) await expect(firstReady).resolves.toBe("ok") await expect(thirdReady).resolves.toBe("ok") await expect(fourthReady).resolves.toBe("ok") expect(readyCalls).toBe(1) const runtimeCollection = singletonCollection< [SingletonItem, SingletonItem, SingletonItem] >([portItem, modeItem, readyItem]) expect(() => runtimeCollection.getItem("missing")).toThrow( "GlobalService: item missing not found", ) await expect(runtimeCollection.getItemAsync("missing")).rejects.toThrow( "GlobalService: item missing not found", ) }) test("singletonCollection reset clears cached values for all items", async () => { let portCalls = 0 const portItem: SingletonItem<"port", number> = singletonItem("port", () => { portCalls = portCalls + 1 return 3_000 + portCalls }) let readyCalls = 0 const readyItem: SingletonItem<"ready", Promise> = singletonItem("ready", async () => { readyCalls = readyCalls + 1 await Promise.resolve() return `ok-${readyCalls}` }) const collection = singletonCollection([portItem, readyItem]) expect(collection.getItem("port")).toBe(3_001) const firstReady = collection.getItem("ready") await expect(firstReady).resolves.toBe("ok-1") collection.reset() expect(collection.getItem("port")).toBe(3_002) const secondReady = collection.getItem("ready") expect(firstReady).not.toBe(secondReady) await expect(secondReady).resolves.toBe("ok-2") expect(portCalls).toBe(2) expect(readyCalls).toBe(2) })