import { describe, expect, it } from 'vitest'; import { MAX_SORT_KEY_LENGTH, evenlySpacedKeys, nextKeyOrRebalance } from './sort-key'; describe('sort-key helpers (issue #45)', () => { it('produces a key strictly between two neighbours', () => { const key = nextKeyOrRebalance('a0', 'a1'); expect(key).not.toBeNull(); expect(key! > 'a0' && key! < 'a1').toBe(true); }); it('signals rebalance (null) when neighbours are out of order', () => { expect(nextKeyOrRebalance('a1', 'a0')).toBeNull(); expect(nextKeyOrRebalance('a0', 'a0')).toBeNull(); }); it('evenlySpacedKeys returns n sorted, unique keys', () => { const keys = evenlySpacedKeys(100); expect(keys).toHaveLength(100); expect(new Set(keys).size).toBe(100); expect([...keys].sort()).toEqual(keys); }); /** * Property test (acceptance criterion): 10.000 reorders in the adversarial * pattern — repeatedly drop the last page between the first two — must never * collide and never overflow the key length, because the caller rebalances * when {@link nextKeyOrRebalance} returns null. */ it('10.000 adversarial reorders never collide or overflow (rebalance verified)', () => { // Start with five pages in a fixed order. let order = evenlySpacedKeys(5).map((key, i) => ({ id: `p${i}`, key })); let rebalances = 0; const rebalance = (): void => { const keys = evenlySpacedKeys(order.length); order = order.map((page, i) => ({ ...page, key: keys[i]! })); rebalances += 1; }; for (let i = 0; i < 10_000; i += 1) { // Move the last page to sit between the first and second — the tightest // possible gap, which is what grows key length fastest. const moved = order[order.length - 1]!; const rest = order.slice(0, -1); const afterKey = rest[0]!.key; const beforeKey = rest[1]!.key; const key = nextKeyOrRebalance(afterKey, beforeKey); if (key === null) { // Rebalance keeps the CURRENT order, then retry the move once. rebalance(); const k2 = nextKeyOrRebalance(order[0]!.key, order[1]!.key); expect(k2).not.toBeNull(); order = [order[0]!, { ...moved, key: k2! }, ...order.slice(1)]; } else { order = [rest[0]!, { ...moved, key }, ...rest.slice(1)]; } // Invariants after every move: keys unique, bounded, and consistent with // the intended array order. const keys = order.map((p) => p.key); expect(new Set(keys).size).toBe(keys.length); expect(Math.max(...keys.map((k) => k.length))).toBeLessThanOrEqual(MAX_SORT_KEY_LENGTH); for (let j = 1; j < keys.length; j += 1) { expect(keys[j - 1]! < keys[j]!).toBe(true); } } // The adversarial pattern must have forced at least one rebalance. expect(rebalances).toBeGreaterThan(0); }); });