Files
genshin-assistant/src/lib/artifactMerge.test.ts
T
AzuTear dcac155887 feat(store): GOOD interop, rescan-merge, data staleness, lock detection
Task #5 building blocks, each a pure + unit-tested module:

- goodInterop.ts: GOOD (Genshin Optimizer / Inventory Kamera / Akasha) import and
  export for scanned StoredArtifactRecords - slot/stat/set key maps both ways,
  substat string <-> { key, value }, main-value reconstruction on import
  (ADR-003). Export is lossless; import is best-effort (GOOD lacks piece names).
- artifactMerge.ts: rescan-merge (ADR-006 follow-up). Level-independent identity
  (set + slot + main + substat NAME set) collapses leveled re-scan duplicates,
  keeping the higher-level/stronger record and summing timesSeen. Conservative:
  differing substat lineups never merge.
- dataPackageStatus.ts: warns when the genshin-db package is older than ~45 days
  (a patch cycle) so new sets/characters aren't silently missed; surfaced in the
  Scanner Diagnose data-package line. Adds dataGeneratedAt to genshinData.
- lockDetection.ts: EXPERIMENTAL read-only lock-status heuristic (gold-pixel
  ratio in a top-right icon crop). Pure + tested but not wired into capture; crop
  position and threshold need calibration against a reference 16:9 screenshot.

120 tests + build green. Remaining wiring (needs UI / live calibration): GOOD
import/export buttons and live lock detection.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 21:55:20 +02:00

75 lines
2.9 KiB
TypeScript

import { describe, expect, it } from "vitest";
import type { StoredArtifactRecord } from "../types/storage";
import { mergeIdentity, mergeRescannedArtifacts, substatName } from "./artifactMerge";
function record(overrides: Partial<StoredArtifactRecord> = {}): StoredArtifactRecord {
return {
id: "x",
name: "Gladiator's Nostalgia",
slot: "Flower of Life",
level: 0,
setName: "Gladiator's Finale",
mainStat: "HP",
mainValue: "717",
substats: ["CRIT DMG+5.4%", "ATK+19", "HP%+4.1%", "Energy Recharge+5.2%"],
equipped: "Not detected",
confidence: 80,
needsReview: false,
source: "auto-scan",
...overrides,
};
}
describe("artifactMerge", () => {
it("strips values to get the substat name", () => {
expect(substatName("CRIT DMG+13.2%")).toBe("CRIT DMG");
expect(substatName("ATK+19")).toBe("ATK");
});
it("identity ignores level and substat values", () => {
const low = record({ level: 0, substats: ["CRIT DMG+5.4%", "ATK+19"] });
const high = record({ level: 20, substats: ["CRIT DMG+13.2%", "ATK+37"] });
expect(mergeIdentity(low)).toBe(mergeIdentity(high));
});
it("collapses a leveled re-scan into one record, keeping the higher level", () => {
const low = record({ id: "a", level: 0, timesSeen: 1 });
const high = record({
id: "b",
level: 20,
timesSeen: 1,
substats: ["CRIT DMG+13.2%", "ATK+37", "HP%+15.7%", "Energy Recharge+11.7%"],
equipped: "Bennett",
});
const { merged, collapsed } = mergeRescannedArtifacts([low, high]);
expect(collapsed).toBe(1);
expect(merged).toHaveLength(1);
expect(merged[0].level).toBe(20);
expect(merged[0].equipped).toBe("Bennett");
expect(merged[0].timesSeen).toBe(2);
});
it("does not merge pieces with different substat lineups", () => {
const threeLine = record({ id: "a", substats: ["CRIT DMG+5.4%", "ATK+19", "HP%+4.1%"] });
const fourLine = record({ id: "b", substats: ["CRIT DMG+5.4%", "ATK+19", "HP%+4.1%", "DEF+16"] });
const { merged, collapsed } = mergeRescannedArtifacts([threeLine, fourLine]);
expect(collapsed).toBe(0);
expect(merged).toHaveLength(2);
});
it("keeps distinct sets/slots/mains apart", () => {
const flower = record({ slot: "Flower of Life", mainStat: "HP" });
const plume = record({ slot: "Plume of Death", mainStat: "ATK" });
const { merged } = mergeRescannedArtifacts([flower, plume]);
expect(merged).toHaveLength(2);
});
it("preserves the earliest firstSeenAt and latest lastSeenAt", () => {
const older = record({ id: "a", level: 0, firstSeenAt: "2026-01-01", lastSeenAt: "2026-01-02" });
const newer = record({ id: "b", level: 20, firstSeenAt: "2026-06-01", lastSeenAt: "2026-06-10" });
const { merged } = mergeRescannedArtifacts([older, newer]);
expect(merged[0].firstSeenAt).toBe("2026-01-01");
expect(merged[0].lastSeenAt).toBe("2026-06-10");
});
});