feat: add market result store

This commit is contained in:
admin123 2026-04-20 20:06:21 +08:00
parent 649de1608c
commit 98dc078a15
3 changed files with 179 additions and 0 deletions

View File

@ -0,0 +1,63 @@
import type {
MarketApiFailureReason,
MarketRecord,
MarketRowSnapshot
} from "./types";
import type { AfterSearchRates } from "./types";
export function createMarketResultStore() {
const records = new Map<string, MarketRecord>();
return {
getRecord(authorId: string) {
return records.get(authorId) ?? null;
},
listRecords() {
return Array.from(records.values());
},
setAuthorFailed(authorId: string, reason: MarketApiFailureReason) {
const existingRecord = ensureRecord(authorId);
existingRecord.status = "failed";
existingRecord.failureReason = reason;
},
setAuthorLoading(authorId: string) {
const existingRecord = ensureRecord(authorId);
existingRecord.status = "loading";
delete existingRecord.failureReason;
},
setAuthorSuccess(authorId: string, rates: Required<AfterSearchRates>) {
const existingRecord = ensureRecord(authorId);
existingRecord.status = "success";
existingRecord.rates = rates;
delete existingRecord.failureReason;
},
upsertMarketRow(row: MarketRowSnapshot) {
const existingRecord = records.get(row.authorId);
if (existingRecord) {
return existingRecord;
}
const nextRecord: MarketRecord = {
...row,
status: "idle"
};
records.set(row.authorId, nextRecord);
return nextRecord;
}
};
function ensureRecord(authorId: string): MarketRecord {
const existingRecord = records.get(authorId);
if (existingRecord) {
return existingRecord;
}
const nextRecord: MarketRecord = {
authorId,
authorName: authorId,
status: "idle"
};
records.set(authorId, nextRecord);
return nextRecord;
}
}

View File

@ -3,6 +3,21 @@ export interface AfterSearchRates {
singleVideoAfterSearchRate?: string; singleVideoAfterSearchRate?: string;
} }
export type MarketRecordStatus = "idle" | "loading" | "success" | "failed" | "missing";
export interface MarketRowSnapshot {
authorId: string;
authorName: string;
location?: string;
price21To60s?: string;
}
export interface MarketRecord extends MarketRowSnapshot {
status: MarketRecordStatus;
failureReason?: MarketApiFailureReason;
rates?: Required<AfterSearchRates>;
}
export type MarketApiFailureReason = export type MarketApiFailureReason =
| "bad-response" | "bad-response"
| "missing-rate" | "missing-rate"

101
tests/result-store.test.ts Normal file
View File

@ -0,0 +1,101 @@
import { describe, expect, test } from "vitest";
import { createMarketResultStore } from "../src/content/market/result-store";
describe("result-store", () => {
test("creates loading records from current-page rows", () => {
const store = createMarketResultStore();
store.upsertMarketRow({
authorId: "123",
authorName: "Alice",
price21To60s: "450000"
});
store.setAuthorLoading("123");
expect(store.getRecord("123")).toMatchObject({
authorId: "123",
authorName: "Alice",
price21To60s: "450000",
status: "loading"
});
});
test("updates one author to success", () => {
const store = createMarketResultStore();
store.upsertMarketRow({
authorId: "123",
authorName: "Alice"
});
store.setAuthorSuccess("123", {
singleVideoAfterSearchRate: "<0.02%",
personalVideoAfterSearchRate: "0.02% - 0.1%"
});
expect(store.getRecord("123")).toMatchObject({
status: "success",
rates: {
singleVideoAfterSearchRate: "<0.02%",
personalVideoAfterSearchRate: "0.02% - 0.1%"
}
});
});
test("preserves failed authors instead of dropping them", () => {
const store = createMarketResultStore();
store.upsertMarketRow({
authorId: "123",
authorName: "Alice"
});
store.setAuthorFailed("123", "request-failed");
expect(store.listRecords()).toHaveLength(1);
expect(store.getRecord("123")).toMatchObject({
status: "failed",
failureReason: "request-failed"
});
});
test("dedupes the same author across repeated page writes", () => {
const store = createMarketResultStore();
store.upsertMarketRow({
authorId: "123",
authorName: "Alice",
price21To60s: "450000"
});
store.upsertMarketRow({
authorId: "123",
authorName: "Alice v2",
price21To60s: "470000"
});
expect(store.listRecords()).toHaveLength(1);
});
test("keeps the original major fields stable after repeated writes", () => {
const store = createMarketResultStore();
store.upsertMarketRow({
authorId: "123",
authorName: "Alice",
location: "Hangzhou",
price21To60s: "450000"
});
store.upsertMarketRow({
authorId: "123",
authorName: "Alice v2",
location: "Shanghai",
price21To60s: "470000"
});
expect(store.getRecord("123")).toMatchObject({
authorId: "123",
authorName: "Alice",
location: "Hangzhou",
price21To60s: "450000"
});
});
});