126 lines
3.9 KiB
TypeScript
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);
|
|
}
|
|
}
|
|
});
|
|
});
|