import { describe, expect, it } from "vitest"; import { createDemoArtifacts, createDemoCharacters, getPresets } from "./demoData"; import { recommendArtifacts, suggestBuilds } from "./scoring"; import type { Artifact, ArtifactSlot, Character, CharacterPreset } from "../types/domain"; describe("recommendation engine", () => { it("flags low-confidence artifacts for review", () => { const artifacts = createDemoArtifacts(); const recommendations = recommendArtifacts(artifacts, createDemoCharacters(), getPresets()); const reviewed = recommendations.find((entry) => entry.artifactId === "art-007"); expect(reviewed?.verdict).toBe("needs_review"); }); it("suggests build candidates for owned characters", () => { const builds = suggestBuilds(createDemoArtifacts(), createDemoCharacters(), getPresets()); expect(builds.length).toBeGreaterThan(0); expect(builds[0].artifactIds.length).toBe(5); }); it("suggests partial builds when the scanned inventory has missing slots", () => { const partialArtifacts = createDemoArtifacts().filter((artifact) => artifact.slot === "sands" || artifact.slot === "goblet"); const builds = suggestBuilds(partialArtifacts, createDemoCharacters(), getPresets()); expect(builds.length).toBeGreaterThan(0); expect(builds[0].artifactIds.length).toBeLessThan(5); expect(builds[0].warnings.join(" ")).toContain("Missing"); }); it("warns when a suggested build wants an artifact equipped by another character", () => { const artifacts = createDemoArtifacts().map((artifact) => artifact.slot === "flower" ? { ...artifact, equipped: "Furina" } : artifact, ); const characters = createDemoCharacters().map((character) => character.id === "furina" ? { ...character, owned: false } : character, ); const builds = suggestBuilds(artifacts, characters, getPresets()); const conflictBuild = builds.find((build) => build.warnings.some((warning) => warning.includes("Conflicts"))); expect(conflictBuild?.warnings.join(" ")).toContain("Furina"); }); it("allows a preferred 4-piece build with one off-piece", () => { const preset = testPreset(["preferred"], ["fallback"]); const builds = suggestBuilds([ artifact("a", "flower", "preferred", "HP"), artifact("b", "plume", "preferred", "ATK"), artifact("c", "sands", "preferred", "ATK%"), artifact("d", "goblet", "preferred", "ATK%"), artifact("e", "circlet", "off_piece", "CRIT Rate"), ], [testCharacter()], [preset]); const recommended = builds.find((build) => build.quality === "recommended_set"); expect(recommended?.artifactIds).toEqual(expect.arrayContaining(["a", "b", "c", "d", "e"])); }); it("creates a practical 2pc+2pc fallback build", () => { const preset = testPreset(["preferred"], ["fallback"]); const builds = suggestBuilds([ artifact("a", "flower", "preferred", "HP"), artifact("b", "plume", "preferred", "ATK"), artifact("c", "sands", "fallback", "ATK%"), artifact("d", "goblet", "fallback", "ATK%"), artifact("e", "circlet", "off_piece", "CRIT Rate"), ], [testCharacter()], [preset]); const fallback = builds.find((build) => build.quality === "alternative_set"); expect(fallback?.explanation).toContain("2pc+2pc"); }); }); function testCharacter(): Character { return { id: "tester", name: "Tester", owned: true, level: 90, constellation: 0, rolePreference: "main_dps", confidence: 1 }; } function testPreset(recommendedSets: string[], alternativeSets: string[]): CharacterPreset { return { characterId: "tester", role: "main_dps", recommendedSets, alternativeSets, mainStats: { sands: ["ATK%"], goblet: ["ATK%"], circlet: ["CRIT Rate"], }, substatWeights: { "CRIT Rate": 1, "CRIT DMG": 1, "ATK%": 0.8 }, explanation: "Test preset", }; } function artifact(id: string, slot: ArtifactSlot, setKey: string, mainStat: string): Artifact { return { id, setKey, setName: setKey, slot, rarity: 5, level: 20, mainStat, substats: [ { key: "CRIT Rate", value: 7, unit: "%" }, { key: "CRIT DMG", value: 14, unit: "%" }, ], locked: false, source: "mock", confidence: 0.99, lastSeenAt: "2026-07-04T00:00:00.000Z", }; }