Spaces:
Sleeping
Sleeping
| /** | |
| * 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::"); | |
| }); | |