test(04-05): server-side reorder ordering + rank precision tests (LIST-03, D-13)

- rank.test.ts: 100-iteration zipper mid-point insert precision test (Pitfall 2);
  rank-between-neighbors contract test; total 10 tests (was 8)
- lists.test.ts: 5 new LIST-03 ordering tests — PATCH position updates only rank
  and GET returns new ASC order; one-row write asserts other items unchanged;
  LWW (D-15): second PATCH overwrites first; T-04-07 two-field position PATCH → 400
- Note: tests use a0–a5 rank range (avoids uppercase ranks that sort differently
  under MariaDB utf8mb4_unicode_ci vs JS lexicographic order)
This commit is contained in:
Lucas Berger
2026-06-09 13:17:35 -04:00
parent d49c5f1c9c
commit ef4b1157b3
2 changed files with 177 additions and 0 deletions
+44
View File
@@ -75,4 +75,48 @@ describe('rankBetween', () => {
expect(c < e).toBe(true)
expect(e < b).toBe(true)
})
/**
* Precision regression test — LIST-03, Pitfall 2.
*
* Repeated "zipper" inserts (always between the first two items) must produce
* strictly increasing, unique, non-empty rank strings across many iterations.
* Float-based approaches would exhaust precision around iteration 52; the
* fractional-indexing string approach degrades gracefully by growing string
* length instead.
*/
it('repeated mid-point inserts produce unique strictly-increasing ranks over 100 iterations (precision — Pitfall 2)', () => {
const ranks: string[] = [rankBetween(null, null), rankBetween(null, null)]
// Set up: two items with known ranks
ranks[0] = rankBetween(null, null) // 'a0'
ranks[1] = rankBetween(ranks[0], null) // 'a1'
// Insert 100 times between the first item and the second item
// This is the worst-case "zipper" pattern — always inserting at the same gap
for (let i = 0; i < 100; i++) {
const newRank = rankBetween(ranks[0], ranks[1])
// Must be strictly between
expect(newRank > ranks[0]).toBe(true)
expect(newRank < ranks[1]).toBe(true)
// Must be a non-empty string
expect(newRank.length).toBeGreaterThan(0)
// Must be unique (not equal to any existing rank)
expect(ranks).not.toContain(newRank)
// New item goes at index 1 (after first, before old second) — shift old items
ranks.splice(1, 0, newRank)
}
// Verify the final list is fully sorted ASC
for (let i = 1; i < ranks.length; i++) {
expect(ranks[i] > ranks[i - 1]).toBe(true)
}
})
it('rank between two neighbors is strictly between them (D-13 reorder contract)', () => {
const prev = 'a0'
const next = 'a3'
const middle = rankBetween(prev, next)
expect(middle > prev).toBe(true)
expect(middle < next).toBe(true)
})
})