Files
genshin-assistant/src/lib/scoring.test.ts
T
AzuTear e76d88e0c7 chore: initialize repository baseline
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>
2026-07-05 20:31:01 +02:00

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",
};
}