|
| 1 | +import { test } from "node:test"; |
| 2 | +import assert from "node:assert/strict"; |
| 3 | +import { TOKEN_POSTS_DEFAULT_LIMIT, TOKEN_POSTS_MAX_LIMIT, nextPostsCursor, parseTokenPostsPaging, postsCursorKey } from "./posts-paging.ts"; |
| 4 | + |
| 5 | +test("parseTokenPostsPaging defaults, clamps and reads the cursor", () => { |
| 6 | + assert.deepEqual(parseTokenPostsPaging({}), { limit: TOKEN_POSTS_DEFAULT_LIMIT, beforeId: null }); |
| 7 | + assert.deepEqual(parseTokenPostsPaging({ limit: null }), { limit: TOKEN_POSTS_DEFAULT_LIMIT, beforeId: null }, "absent query param (URLSearchParams.get → null) uses the default, not 1"); |
| 8 | + assert.deepEqual(parseTokenPostsPaging({ limit: "" }), { limit: TOKEN_POSTS_DEFAULT_LIMIT, beforeId: null }); |
| 9 | + assert.deepEqual(parseTokenPostsPaging({ limit: undefined }), { limit: TOKEN_POSTS_DEFAULT_LIMIT, beforeId: null }); |
| 10 | + assert.deepEqual(parseTokenPostsPaging({ limit: "20", before: "42" }), { limit: 20, beforeId: 42 }); |
| 11 | + assert.deepEqual(parseTokenPostsPaging({ limit: 0, before: 0 }), { limit: 1, beforeId: null }); |
| 12 | + assert.deepEqual(parseTokenPostsPaging({ limit: 9999 }), { limit: TOKEN_POSTS_MAX_LIMIT, beforeId: null }); |
| 13 | + assert.deepEqual(parseTokenPostsPaging({ limit: "abc", before: "xyz" }), { limit: TOKEN_POSTS_DEFAULT_LIMIT, beforeId: null }); |
| 14 | + assert.deepEqual(parseTokenPostsPaging({ limit: 25.9 }), { limit: 25, beforeId: null }, "truncates"); |
| 15 | + assert.deepEqual(parseTokenPostsPaging({ before: "" }), { limit: TOKEN_POSTS_DEFAULT_LIMIT, beforeId: null }); |
| 16 | + assert.equal(TOKEN_POSTS_DEFAULT_LIMIT, 100, "default preserves the previous response (100 posts, not 50) so existing clients lose nothing"); |
| 17 | + assert.equal(TOKEN_POSTS_MAX_LIMIT, 100); |
| 18 | +}); |
| 19 | + |
| 20 | +test("cursor key namespaces chain/token/limit/cursor", () => { |
| 21 | + assert.equal(postsCursorKey("base", "0xABC", 50, null), "posts:base:0xabc:50:head"); |
| 22 | + assert.equal(postsCursorKey("base", "0xabc", 20, 42), "posts:base:0xabc:20:42"); |
| 23 | + assert.notEqual(postsCursorKey("base", "0xabc", 20, 42), postsCursorKey("base", "0xabc", 20, 43)); |
| 24 | +}); |
| 25 | + |
| 26 | +test("nextPostsCursor ends pagination on a short page", () => { |
| 27 | + assert.equal(nextPostsCursor([9, 8, 7], 3), 7, "full page → oldest id is the next cursor"); |
| 28 | + assert.equal(nextPostsCursor([9, 8], 3), null, "short page → done"); |
| 29 | + assert.equal(nextPostsCursor([], 3), null); |
| 30 | +}); |
| 31 | + |
| 32 | +test("default page preserves existing discussions until the client follows cursors (PR #32)", () => { |
| 33 | + // Maintainer repro: 75 top-level comments must not truncate, and 1 parent + |
| 34 | + // 50 replies (51 rows) must arrive together so the UI never sees a |
| 35 | + // replies-only page with "No comments yet." |
| 36 | + assert.ok(TOKEN_POSTS_DEFAULT_LIMIT >= 75, "75 previously visible comments stay visible"); |
| 37 | + assert.ok(TOKEN_POSTS_DEFAULT_LIMIT >= 51, "1 parent + 50 replies arrive on the first page"); |
| 38 | + assert.equal(nextPostsCursor(Array.from({ length: 75 }, (_, i) => 75 - i), TOKEN_POSTS_DEFAULT_LIMIT), null); |
| 39 | +}); |
0 commit comments