explodorama / files / test / spatial.test.ts
spatial.test.ts
ebcd0a8f 2026-11-02 Pavel Safronov | 1 | import { describe, expect, it } from 'vitest' |
| 2 | import { nextFocus, type FocusRect } from '../src/ui/spatial' | |
| 3 | ||
| 4 | const box = (x: number, y: number): FocusRect => ({ x, y, w: 10, h: 10 }) | |
| 5 | // A 3 x 3 grid, indexes row by row: 0 1 2 / 3 4 5 / 6 7 8 | |
| 6 | const grid = [0, 1, 2].flatMap((r) => [0, 1, 2].map((c) => box(c * 20, r * 20))) | |
| 7 | ||
| 8 | describe('nextFocus', () => { | |
| 9 | it('moves one cell in each direction on a grid', () => { | |
| 10 | expect(nextFocus(grid, 4, 0, -1)).toBe(1) | |
| 11 | expect(nextFocus(grid, 4, 0, 1)).toBe(7) | |
| 12 | expect(nextFocus(grid, 4, -1, 0)).toBe(3) | |
| 13 | expect(nextFocus(grid, 4, 1, 0)).toBe(5) | |
| 14 | }) | |
| 15 | it('stays put when nothing lies in that direction', () => { | |
| 16 | expect(nextFocus(grid, 0, 0, -1)).toBe(0) | |
| 17 | expect(nextFocus(grid, 0, -1, 0)).toBe(0) | |
| 18 | expect(nextFocus(grid, 8, 0, 1)).toBe(8) | |
| 19 | expect(nextFocus(grid, 8, 1, 0)).toBe(8) | |
| 20 | }) | |
| 21 | it('weights sideways distance double, so a straight neighbour beats a nearer off-row one', () => { | |
| 22 | // From 0 at (0,0) going right: A is far along the row, B is nearer but a row down. | |
| 23 | const rects = [box(0, 0), box(60, 0), box(30, 20)] | |
| 24 | expect(nextFocus(rects, 0, 1, 0)).toBe(1) // 60 vs 30 + 2*20 = 70 | |
| 25 | const near = [box(0, 0), box(60, 0), box(20, 10)] | |
| 26 | expect(nextFocus(near, 0, 1, 0)).toBe(2) // 60 vs 20 + 2*10 = 40 | |
| 27 | }) | |
| 28 | it('an equal score goes to the lower index', () => { | |
| 29 | const rects = [box(20, 20), box(40, 20), box(0, 20)] | |
| 30 | expect(nextFocus(rects, 0, 1, 0)).toBe(1) | |
| 31 | const tie = [box(20, 20), box(40, 20), box(40, 20)] | |
| 32 | expect(nextFocus(tie, 0, 1, 0)).toBe(1) | |
| 33 | }) | |
| 34 | it('does not count a centre level with the current one as in the direction', () => { | |
| 35 | const rects = [box(0, 0), box(0, 20), box(20, 0)] | |
| 36 | expect(nextFocus(rects, 0, 1, 0)).toBe(2) | |
| 37 | expect(nextFocus(rects, 0, 0, 1)).toBe(1) | |
| 38 | expect(nextFocus([box(0, 0), box(0, 20)], 0, 1, 0)).toBe(0) | |
| 39 | }) | |
| 40 | it('uses centres, not corners, so rectangles of different sizes compare fairly', () => { | |
| 41 | // Current: a wide bar, centre (50,5). A: a small box far left under it, centre (5,35). | |
| 42 | // B: a tall box at the right, x 70..80, centre (75,35). Going down, by centres A costs | |
| 43 | // 30 + 2*45 = 120 and B 30 + 2*25 = 80, so B wins. By left corners A (x 0) would be nearer | |
| 44 | // than B (x 70) to the bar's left edge, and a corner-based measure would pick A. | |
| 45 | const rects = [{ x: 0, y: 0, w: 100, h: 10 }, { x: 0, y: 30, w: 10, h: 10 }, { x: 70, y: 30, w: 10, h: 10 }] | |
| 46 | expect(nextFocus(rects, 0, 0, 1)).toBe(2) | |
| 47 | }) | |
| 48 | it('diagonals need both axes past the current centre', () => { | |
| 49 | expect(nextFocus(grid, 4, 1, 1)).toBe(8) | |
| 50 | expect(nextFocus(grid, 4, -1, -1)).toBe(0) | |
| 51 | expect(nextFocus(grid, 4, 1, -1)).toBe(2) | |
| 52 | expect(nextFocus(grid, 4, -1, 1)).toBe(6) | |
| 53 | expect(nextFocus(grid, 2, 1, 1)).toBe(2) | |
| 54 | expect(nextFocus(grid, 5, 1, 1)).toBe(5) | |
| 55 | }) | |
| 56 | it('handles an empty list and a bad current index', () => { | |
| 57 | expect(nextFocus([], 0, 1, 0)).toBe(-1) | |
| 58 | expect(nextFocus([], -1, 1, 0)).toBe(-1) | |
| 59 | expect(nextFocus(grid, -1, 1, 0)).toBe(0) | |
| 60 | expect(nextFocus(grid, 99, 0, 1)).toBe(0) | |
| 61 | }) | |
| 62 | it('a single rectangle stays focused', () => { | |
| 63 | expect(nextFocus([box(0, 0)], 0, 1, 0)).toBe(0) | |
| 64 | }) | |
| 65 | it('no direction means no move', () => { | |
| 66 | expect(nextFocus(grid, 4, 0, 0)).toBe(4) | |
| 67 | }) | |
| 68 | }) |