test(04-04): add failing tests for item CRUD endpoints + rank helpers
- Add rank.test.ts: unit tests for rankForAppend/rankBetween (RED - no impl yet) - Extend lists.test.ts with item route tests: POST /:id/items, GET /:id/items, PATCH /list-items/:id (per-field LWW D-08), DELETE /list-items/:id (D-09) - Import listItems from schema; add seedItem helper - Tests cover: fractional rank assignment (D-13), exact-one-field refine (T-04-07), uncheck rank recompute, access gating T-04-05, delete-wins no resurrection D-09
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* Unit tests for rank.ts — fractional-indexing helpers (D-13).
|
||||
*
|
||||
* Pure function tests, no DB needed.
|
||||
* Run: pnpm --filter @familysync/api exec vitest run src/lib/rank.test.ts
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { rankForAppend, rankBetween } from './rank.js'
|
||||
|
||||
describe('rankForAppend', () => {
|
||||
it('returns "a0" when list is empty (no existing rank)', () => {
|
||||
const rank = rankForAppend(null)
|
||||
expect(rank).toBe('a0')
|
||||
})
|
||||
|
||||
it('returns a rank string that sorts AFTER the given last rank', () => {
|
||||
const lastRank = 'a0'
|
||||
const newRank = rankForAppend(lastRank)
|
||||
expect(newRank > lastRank).toBe(true)
|
||||
})
|
||||
|
||||
it('multiple appends produce strictly increasing ranks', () => {
|
||||
let last: string | null = null
|
||||
const ranks: string[] = []
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const r = rankForAppend(last)
|
||||
ranks.push(r)
|
||||
last = r
|
||||
}
|
||||
// Each successive rank must be greater than the previous
|
||||
for (let i = 1; i < ranks.length; i++) {
|
||||
expect(ranks[i] > ranks[i - 1]).toBe(true)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('rankBetween', () => {
|
||||
it('returns "a0" when both prev and next are null (empty list)', () => {
|
||||
const rank = rankBetween(null, null)
|
||||
expect(rank).toBe('a0')
|
||||
})
|
||||
|
||||
it('returns a rank that sorts between two existing ranks', () => {
|
||||
const first = rankForAppend(null) // 'a0'
|
||||
const second = rankForAppend(first) // 'a1'
|
||||
const between = rankBetween(first, second)
|
||||
expect(between > first).toBe(true)
|
||||
expect(between < second).toBe(true)
|
||||
})
|
||||
|
||||
it('returns a rank that sorts AFTER prev when next is null', () => {
|
||||
const prev = 'a0'
|
||||
const rank = rankBetween(prev, null)
|
||||
expect(rank > prev).toBe(true)
|
||||
})
|
||||
|
||||
it('returns a rank that sorts BEFORE next when prev is null', () => {
|
||||
const next = 'a1'
|
||||
const rank = rankBetween(null, next)
|
||||
expect(rank < next).toBe(true)
|
||||
})
|
||||
|
||||
it('produces lexicographically stable ordering across multiple insertions', () => {
|
||||
// Simulate inserting between 'a0' and 'a1' repeatedly
|
||||
const a = 'a0'
|
||||
const b = 'a1'
|
||||
const c = rankBetween(a, b)
|
||||
const d = rankBetween(a, c)
|
||||
const e = rankBetween(c, b)
|
||||
|
||||
// All four ranks should be orderable
|
||||
expect(a < d).toBe(true)
|
||||
expect(d < c).toBe(true)
|
||||
expect(c < e).toBe(true)
|
||||
expect(e < b).toBe(true)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user