/** * Contract: optimistic board updates must never drop a rated kink when play-board cache is still empty. * (Regression: early return when sourceItem was null left all buckets empty.) */ import assert from "node:assert/strict"; import test from "node:test"; import { findBoardItemInData, optimisticBoard, playCommitKey, removeKinkFromItemsPayload, sortedBoardItems, } from "./board-utils.js"; test("sortedBoardItems orders by popularity descending then name ascending without mutating input", () => { const input = [ { id: "low", name: "Zed", popularity: 5 }, { id: "b", name: "Beta", popularity: 10 }, { id: "a", name: "Alpha", popularity: 10 }, ]; assert.deepEqual(sortedBoardItems(input).map((item) => item.id), ["a", "b", "low"]); assert.deepEqual(input.map((item) => item.id), ["low", "b", "a"]); }); test("optimisticBoard places stub when no server board and no query cache", () => { const b = optimisticBoard(null, "fetlife_999", "like", ["by_me", "to_me"], null, null); const inTo = b.to_me.some((x) => x.id === "fetlife_999"); const inBy = b.by_me.some((x) => x.id === "fetlife_999"); assert.ok(inTo && inBy, "stub row must appear in every direction bucket requested"); }); test("optimisticBoard removes stale copies and routes hard_no / not_interested to private buckets", () => { const previous = { to_me: [{ id: "k1", name: "Alpha", popularity: 1 }], by_me: [{ id: "k1", name: "Alpha", popularity: 1 }], together: [{ id: "k2", name: "Beta", popularity: 2 }], hidden: [], no_go: [], }; const hidden = optimisticBoard(previous, "k1", "not_interested", ["to_me"], null, null); assert.deepEqual(hidden.to_me, []); assert.deepEqual(hidden.by_me, []); assert.deepEqual(hidden.hidden.map((item) => item.id), ["k1"]); const noGo = optimisticBoard(previous, "k1", "hard_no", ["to_me"], null, null); assert.deepEqual(noGo.to_me, []); assert.deepEqual(noGo.by_me, []); assert.deepEqual(noGo.no_go.map((item) => item.id), ["k1"]); }); test("optimisticBoard uses together when positive directions are empty", () => { const b = optimisticBoard(null, "k1", "love", [], null, null); assert.deepEqual(b.together.map((item) => item.id), ["k1"]); assert.deepEqual(b.to_me, []); assert.deepEqual(b.by_me, []); }); test("optimisticBoard can hydrate item details from query caches", () => { const queries = [ { queryKey: ["search", "Bond"], state: { data: { items: [{ kink: { id: "k1", name: "Bondage", definition: "Fixture", popularity: 42 } }] } } }, ]; const qc = { getQueryData(key) { if (JSON.stringify(key) === JSON.stringify(["kink", "k2"])) { return { id: "k2", name: "Spanking", detail_summary: "Fixture", popularity: 11 }; } return null; }, getQueryCache() { return { findAll: ({ predicate }) => queries.filter(predicate) }; }, }; const fromSearch = optimisticBoard(null, "k1", "like", ["to_me"], qc, { userId: "u1" }); assert.deepEqual(fromSearch.to_me[0], { id: "k1", name: "Bondage", summary: "Fixture", popularity: 42, interest_state: "like", directions: ["to_me"], }); const fromDetail = optimisticBoard(null, "k2", "like", ["by_me"], qc, { userId: "u1" }); assert.equal(fromDetail.by_me[0].name, "Spanking"); assert.equal(fromDetail.by_me[0].summary, "Fixture"); }); test("optimisticBoard hydrates from scoped rec and prompt caches before falling back to global scans", () => { const scoped = new Map([ [ JSON.stringify(["recs", "u1", ""]), { items: [{ kink: { id: "rec-hit", name: "Rec Hit", summary: "rec", popularity: 9 } }] }, ], [ JSON.stringify(["prompts", "u1", ""]), { items: [{ kink: { id: "prompt-hit", name: "Prompt Hit", definition: "prompt", popularity: 8 } }] }, ], ]); const qc = { getQueryData(key) { return scoped.get(JSON.stringify(key)) || null; }, getQueryCache() { return { findAll: () => [] }; }, }; const fromRec = optimisticBoard(null, "rec-hit", "like", ["to_me"], qc, { userId: "u1" }); const fromPrompt = optimisticBoard(null, "prompt-hit", "like", ["by_me"], qc, { userId: "u1" }); assert.equal(fromRec.to_me[0].name, "Rec Hit"); assert.equal(fromRec.to_me[0].summary, "rec"); assert.equal(fromPrompt.by_me[0].name, "Prompt Hit"); assert.equal(fromPrompt.by_me[0].summary, "prompt"); }); test("optimisticBoard hydrates from group-overlap direct and similar caches only", () => { const queries = [ { queryKey: ["group-overlap", "u1", "g1"], state: { data: { direct_matches: [{ id: "direct-hit", name: "Direct Hit", summary: "direct", popularity: 7 }], similar_matches: [{ kink: { id: "similar-hit", name: "Similar Hit", detail_summary: "similar", popularity: 6 } }], }, }, }, { queryKey: ["other", "u1"], state: { data: { direct_matches: [{ id: "wrong-hit", name: "Wrong Hit", popularity: 99 }], }, }, }, ]; const qc = { getQueryData() { return null; }, getQueryCache() { return { findAll: ({ predicate }) => queries.filter(predicate) }; }, }; const direct = optimisticBoard(null, "direct-hit", "love", ["together"], qc, { userId: "u1" }); const similar = optimisticBoard(null, "similar-hit", "like", ["to_me"], qc, { userId: "u1" }); const wrong = optimisticBoard(null, "wrong-hit", "like", ["by_me"], qc, { userId: "u1" }); assert.equal(direct.together[0].name, "Direct Hit"); assert.equal(direct.together[0].summary, "direct"); assert.equal(similar.to_me[0].name, "Similar Hit"); assert.equal(similar.to_me[0].summary, "similar"); assert.equal(wrong.by_me[0].name, "wrong-hit"); }); test("findBoardItemInData scans every bucket and tolerates missing boards", () => { const board = { to_me: [], by_me: [{ id: 7, name: "Seven" }], together: [], hidden: [], no_go: [], }; assert.equal(findBoardItemInData(null, 7), null); assert.deepEqual(findBoardItemInData(board, "7"), { id: 7, name: "Seven" }); }); test("removeKinkFromItemsPayload filters by kink id while preserving payload metadata", () => { const payload = { cursor: "next", items: [{ kink: { id: "keep" } }, { kink: { id: 12 } }, { kink: { id: "12" } }], }; assert.equal(removeKinkFromItemsPayload(null, "x"), null); assert.deepEqual(removeKinkFromItemsPayload(payload, 12), { cursor: "next", items: [{ kink: { id: "keep" } }], }); }); test("removeKinkFromItemsPayload preserves malformed rows that are not the removed kink", () => { const payload = { items: [ {}, { kink: null }, { kink: { id: "remove" } }, { kink: { id: "keep" } }, ], }; assert.deepEqual(removeKinkFromItemsPayload(payload, "remove"), { items: [{}, { kink: null }, { kink: { id: "keep" } }], }); }); test("playCommitKey is stable for direction ordering and empty directions", () => { assert.equal(playCommitKey("k1", "like", ["by_me", "to_me"]), "k1::like::by_me|to_me"); assert.equal(playCommitKey("k1", "like", ["to_me", "by_me"]), "k1::like::by_me|to_me"); assert.equal(playCommitKey("k1", "hard_no"), "k1::hard_no::"); });