dcac155887
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>
75 lines
2.9 KiB
TypeScript
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");
|
|
});
|
|
});
|