fuzzyworld

explodorama / files / test / spatial.test.ts

spatial.test.ts

Blame: the commit that last changed each line · file · history

ebcd0a8f 2026-11-02 Pavel Safronov1import { describe, expect, it } from 'vitest'
2import { nextFocus, type FocusRect } from '../src/ui/spatial'
3
4const 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
6const grid = [0, 1, 2].flatMap((r) => [0, 1, 2].map((c) => box(c * 20, r * 20)))
7
8describe('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})