e76d88e0c7
Import the existing Electron + React + TypeScript app as the version-control baseline before the scanner rework (C# input/capture sidecar, resolution-anchored layout profiles, OCR preprocessing, eval harness, rescan-merge, GOOD interop). Housekeeping in this commit: - Remove orphaned temp_inputhelper_block.ts (duplicate of the input-helper script). - Ignore .claude/scheduled_tasks.lock local session state. - Add .gitattributes to normalize line endings (LF in repo). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
112 lines
4.2 KiB
TypeScript
112 lines
4.2 KiB
TypeScript
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",
|
|
};
|
|
}
|