feat(sp28): claimAcks slice + useTailStream/useMergedTail dispatch
This commit is contained in:
@@ -5,7 +5,14 @@
|
||||
// state shape.
|
||||
|
||||
import { beforeEach, describe, expect, it } from "vitest";
|
||||
import type { Ack, Activity, Claim, Remittance, Ta1Ack } from "@/types";
|
||||
import type {
|
||||
Ack,
|
||||
Activity,
|
||||
Claim,
|
||||
ClaimAck,
|
||||
Remittance,
|
||||
Ta1Ack,
|
||||
} from "@/types";
|
||||
import { TAIL_CAP, useTailStore } from "./tail-store";
|
||||
|
||||
function makeClaim(id: string, overrides: Partial<Claim> = {}): Claim {
|
||||
@@ -77,6 +84,23 @@ function makeTa1Ack(id: number, overrides: Partial<Ta1Ack> = {}): Ta1Ack {
|
||||
};
|
||||
}
|
||||
|
||||
function makeClaimAck(id: number, overrides: Partial<ClaimAck> = {}): ClaimAck {
|
||||
return {
|
||||
id,
|
||||
claimId: "CLM-1",
|
||||
batchId: null,
|
||||
ackId: 99,
|
||||
ackKind: "999",
|
||||
ak2Index: 0,
|
||||
setControlNumber: "991102989",
|
||||
setAcceptRejectCode: "A",
|
||||
linkedAt: "2026-07-02T12:00:00Z",
|
||||
linkedBy: "auto",
|
||||
claimState: "received",
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe("useTailStore", () => {
|
||||
// Each test starts from a known-empty state. The store is module-level
|
||||
// (singleton), so we use `reset()` to clear between cases — that's also
|
||||
@@ -87,6 +111,7 @@ describe("useTailStore", () => {
|
||||
useTailStore.getState().reset("activity");
|
||||
useTailStore.getState().reset("acks");
|
||||
useTailStore.getState().reset("ta1_acks");
|
||||
useTailStore.getState().reset("claim-acks");
|
||||
});
|
||||
|
||||
it("test_add_claim_adds_new_claim_keyed_by_id", () => {
|
||||
@@ -227,4 +252,62 @@ describe("useTailStore", () => {
|
||||
expect(Object.keys(s.ta1Acks)).toHaveLength(0);
|
||||
expect(Object.keys(s.acks)).toHaveLength(1);
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// SP28: the claim↔ack link slice mirrors the SP25 acks/ta1_acks slice
|
||||
// (numeric id from the `claim_acks` table, first write wins, FIFO-capped
|
||||
// at TAIL_CAP). The slice drives both the ClaimDrawer's
|
||||
// <Acknowledgments /> panel and the AckDrawer's <MatchedClaim /> panel
|
||||
// via `useTailStream("claim-acks")` + `useMergedTail("claim-acks", …)`.
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
it("test_add_claim_ack_adds_new_claim_ack_keyed_by_id", () => {
|
||||
const { addClaimAck } = useTailStore.getState();
|
||||
addClaimAck(makeClaimAck(1));
|
||||
addClaimAck(makeClaimAck(2));
|
||||
const { claimAcks } = useTailStore.getState();
|
||||
expect(Object.keys(claimAcks)).toHaveLength(2);
|
||||
expect(claimAcks["1"]?.claimId).toBe("CLM-1");
|
||||
expect(claimAcks["2"]?.claimId).toBe("CLM-1");
|
||||
});
|
||||
|
||||
it("test_add_claim_ack_with_duplicate_id_is_noop", () => {
|
||||
const { addClaimAck } = useTailStore.getState();
|
||||
addClaimAck(makeClaimAck(1, { setAcceptRejectCode: "A" }));
|
||||
addClaimAck(makeClaimAck(1, { setAcceptRejectCode: "R" }));
|
||||
const { claimAcks } = useTailStore.getState();
|
||||
expect(Object.keys(claimAcks)).toHaveLength(1);
|
||||
// First write wins; snapshot replay must not trample the canonical row.
|
||||
expect(claimAcks["1"]?.setAcceptRejectCode).toBe("A");
|
||||
});
|
||||
|
||||
it("test_reset_claim_acks_clears_only_claim_acks_slice", () => {
|
||||
const { addClaimAck, addAck, reset } = useTailStore.getState();
|
||||
addClaimAck(makeClaimAck(1));
|
||||
addAck(makeAck(1));
|
||||
|
||||
reset("claim-acks");
|
||||
const s = useTailStore.getState();
|
||||
expect(Object.keys(s.claimAcks)).toHaveLength(0);
|
||||
// The other slices are untouched.
|
||||
expect(Object.keys(s.acks)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("test_claim_acks_slice_evicts_oldest_when_over_10000", () => {
|
||||
// Cap-eviction smoke test — same shape as the claims FIFO test
|
||||
// above. 5 over the cap to exercise the eviction path without
|
||||
// pinning down the wall-clock cost of a full 10 005-insert run.
|
||||
const N = TAIL_CAP + 5;
|
||||
const { addClaimAck } = useTailStore.getState();
|
||||
for (let i = 0; i < N; i++) {
|
||||
addClaimAck(makeClaimAck(i));
|
||||
}
|
||||
const { claimAcks } = useTailStore.getState();
|
||||
expect(Object.keys(claimAcks)).toHaveLength(TAIL_CAP);
|
||||
// The five oldest ids must be gone.
|
||||
expect(claimAcks["0"]).toBeUndefined();
|
||||
expect(claimAcks["4"]).toBeUndefined();
|
||||
// The most recent id must be present.
|
||||
expect(claimAcks[String(N - 1)]).toBeDefined();
|
||||
}, 60_000);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user