All checks were successful
CD / Build and push images (push) Successful in 3m1s
CI / Lint, typecheck, test (push) Successful in 2m13s
CI / Auth e2e pack (push) Successful in 2m36s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 9s
CD / Smoke tests against Test (push) Successful in 1m13s
CD / Promote to Int (push) Successful in 11s
Enable the third sidebar sort mode — a freely defined order. - api: `PATCH /pages/:id/position` (before/after neighbour) recomputes only the moved page's fractional `sort_key`. Pure `sort-key.ts` helpers (`nextKeyOrRebalance`, `evenlySpacedKeys`) decide between the cheap single-key path and a full pond rebalance to evenly-spaced keys when a key would exceed MAX_SORT_KEY_LENGTH or the client's neighbours are stale; rebalance runs in one transaction. Order is server-authoritative. - web: enable 'manual' in the sort-mode switch; in manual mode the owner can reorder via native drag-and-drop (drop above/below by pointer half) or the keyboard (per-row up/down buttons), each announced through an aria-live region. Reordering is hidden while a label filter narrows the list. New pages already append at the end (create uses generateKeyBetween(last, null)). Pure `reorder.ts` neighbour helpers, unit-tested. - i18n: manual sort mode + reorder strings (de + en). - tests: sort-key property test (10.000 adversarial reorders never collide or overflow — rebalance verified); reposition db test (persist, server-order, sort-mode switch keeps manual order); reorder e2e pack (keyboard reorder persists across reload + identical on a fresh read; aria-live announced). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PGdhRiwU1WRL4XxJfZYipY
38 lines
1.3 KiB
TypeScript
38 lines
1.3 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
|
|
import { dropIndex, neighborsForMove } from './reorder';
|
|
|
|
const ids = ['A', 'B', 'C', 'D'];
|
|
|
|
describe('neighborsForMove (issue #45)', () => {
|
|
it('moves up one: D between B and C', () => {
|
|
// D at index 3 → newIndex 2 in the without-array [A, B, C].
|
|
expect(neighborsForMove(ids, 'D', 2)).toEqual({ afterId: 'B', beforeId: 'C' });
|
|
});
|
|
|
|
it('moves to the top: afterId null', () => {
|
|
expect(neighborsForMove(ids, 'C', 0)).toEqual({ afterId: null, beforeId: 'A' });
|
|
});
|
|
|
|
it('moves to the bottom: beforeId null', () => {
|
|
expect(neighborsForMove(ids, 'A', 3)).toEqual({ afterId: 'D', beforeId: null });
|
|
});
|
|
|
|
it('clamps an out-of-range index', () => {
|
|
expect(neighborsForMove(ids, 'A', 99)).toEqual({ afterId: 'D', beforeId: null });
|
|
expect(neighborsForMove(ids, 'A', -5)).toEqual({ afterId: null, beforeId: 'B' });
|
|
});
|
|
});
|
|
|
|
describe('dropIndex (issue #45)', () => {
|
|
it('drops before the target by default', () => {
|
|
// Drop A onto C (upper half) → before C. without = [B, C, D], C at 1.
|
|
expect(dropIndex(ids, 'A', 'C', false)).toBe(1);
|
|
});
|
|
|
|
it('drops after the target on the lower half — reaches the bottom', () => {
|
|
// Drop A onto D lower half → after D. without = [B, C, D], D at 2 → 3 (end).
|
|
expect(dropIndex(ids, 'A', 'D', true)).toBe(3);
|
|
});
|
|
});
|