Files
cozsweet-frontend-nextjs/src/data/constants/__tests__/character.test.ts
T

126 lines
3.9 KiB
TypeScript

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);
}
}
});
});