import { existsSync } from "node:fs"; import { join } from "node:path"; import { describe, expect, it } from "vitest"; import { CHARACTERS, createCharacterCatalog, DEFAULT_CHARACTER, getCharacterById, getCharacterBySlug, mergeRemoteCharacterCatalog, } from "@/data/constants/character"; import { CharacterListResponseSchema } from "@/data/schemas/character"; describe("local character catalog", () => { it("contains immutable and uniquely addressable character profiles", () => { expect(CHARACTERS.map((character) => character.slug)).toEqual([ "elio", "maya", "nayeli", ]); expect(CHARACTERS.map((character) => character.id)).toEqual([ "elio", "maya-tan", "nayeli-cervantes", ]); expect(new Set(CHARACTERS.map((character) => character.id)).size).toBe( CHARACTERS.length, ); expect(Object.isFrozen(CHARACTERS)).toBe(true); expect(Object.isFrozen(DEFAULT_CHARACTER)).toBe(true); expect(Object.isFrozen(DEFAULT_CHARACTER.assets)).toBe(true); expect(Object.isFrozen(DEFAULT_CHARACTER.capabilities)).toBe(true); expect(Object.isFrozen(DEFAULT_CHARACTER.copy)).toBe(true); }); it("merges backend authority with local routes and assets", () => { const snapshot = mergeRemoteCharacterCatalog( CharacterListResponseSchema.parse({ defaultCharacterId: "elio", items: [ { id: "maya-tan", displayName: "Maya Backend", isActive: true, sortOrder: 5, capabilities: { chat: true, privateContent: false }, }, { id: "elio", displayName: "Elio Backend", isActive: true, sortOrder: 10, capabilities: { chat: true, privateContent: true }, }, { id: "unknown", displayName: "Unknown", isActive: true, sortOrder: 1, capabilities: { chat: true, privateContent: true }, }, ], }), ); expect(snapshot.catalog.characters.map((item) => item.slug)).toEqual([ "maya", "elio", ]); expect(snapshot.catalog.getById("maya-tan")).toMatchObject({ displayName: "Maya Backend", assets: { avatar: "/images/avatar/maya.png" }, capabilities: { chat: true, privateZone: false, tip: true }, }); expect(snapshot.defaultCharacter.id).toBe("elio"); }); it("sorts catalog entries and rejects duplicate identities", () => { const catalog = createCharacterCatalog([...CHARACTERS].reverse()); expect(catalog.characters.map((character) => character.slug)).toEqual([ "elio", "maya", "nayeli", ]); expect(() => createCharacterCatalog([CHARACTERS[0], CHARACTERS[0]]), ).toThrow("Duplicate character identity"); }); it("resolves characters by id and normalized slug", () => { expect(getCharacterById("maya-tan")?.displayName).toBe("Maya Tan"); expect(getCharacterBySlug(" NAYELI ")?.displayName).toBe( "Nayeli Cervantes", ); expect(getCharacterById("missing")).toBeNull(); expect(getCharacterBySlug("missing")).toBeNull(); }); it("describes route capabilities and character-specific chat copy", () => { for (const character of CHARACTERS) { expect(character.capabilities).toEqual({ chat: true, privateZone: true, tip: true, }); expect(character.tagline.length).toBeGreaterThan(0); expect(character.emptyChatGreeting.length).toBeGreaterThan(0); expect(character.assets.chatBackground).toBe( "/images/chat/bg-chatpage.png", ); } }); it("references local assets that exist under public", () => { for (const character of CHARACTERS) { for (const assetPath of Object.values(character.assets)) { expect( existsSync(join(process.cwd(), "public", assetPath)), assetPath, ).toBe(true); } } }); });