fuzzyworld

explodorama / files / test / padDialog.test.ts

padDialog.test.ts

7.3 KB · history · blame

1import { describe, expect, it } from 'vitest'
2import { assignFaces, dismissIndex, faceAction, padMode, restoreIndex, scrollDelta, showsTapHint, startIndex, tabStep, type FooterButton } from '../src/ui/padDialogMap'
3
4const b = (label: string, kind: FooterButton['kind'] = 'plain'): FooterButton => ({ label, kind })
5
6describe('footer buttons on face buttons', () => {
7 it('primary on Cross, others left to right on Triangle then Square', () => {
8 expect(assignFaces([b('Keep', 'plain'), b('Take', 'primary'), b('Use', 'primary')], false)).toEqual([3, 0, 2])
9 })
10 it('the dismissing button takes Circle and leaves the others their faces', () => {
11 expect(assignFaces([b('Kick it', 'primary'), b('Walk', 'primary'), b('Cancel')], true)).toEqual([0, 3, 1])
12 expect(assignFaces([b('Walk there', 'primary'), b('Got it')], true)).toEqual([0, 1])
13 })
14 it('a required dialog has no Circle, even for a button called Cancel', () => {
15 expect(assignFaces([b('Cancel')], false)).toEqual([3])
16 })
17 it('a dialog with no primary leaves Cross unused', () => {
18 expect(assignFaces([b('Back')], true)).toEqual([1])
19 expect(assignFaces([b('A'), b('B')], false)).toEqual([3, 2])
20 })
21 it('crossTaken keeps Cross free for the body (the shop)', () => {
22 expect(assignFaces([b('Restock'), b('Next level', 'primary')], false, true)).toEqual([3, 2])
23 })
24 it('too many buttons: null, so the selector is used', () => {
25 expect(assignFaces([b('Resume', 'primary'), b('New', 'danger'), b('Almanac'), b('Scores'), b('Demo'), b('Bug')], true)).toBeNull()
26 expect(assignFaces([b('a', 'primary'), b('b'), b('c'), b('d')], false)).toBeNull()
27 })
28 it('dismissIndex: only when dismissable, by label', () => {
29 expect(dismissIndex([b('Start'), b('Keep playing')], true)).toBe(1)
30 expect(dismissIndex([b('Start'), b('Keep playing')], false)).toBe(-1)
31 expect(dismissIndex([b('Start')], true)).toBe(-1)
32 })
33})
34
35describe('mode and actions', () => {
36 it('controls in the body force the selector', () => {
37 expect(padMode([0, 1], 0)).toBe('faces')
38 expect(padMode([0, 1], 2)).toBe('selector')
39 expect(padMode(null, 0)).toBe('selector')
40 })
41 it('faces mode: a face presses its footer button; Circle closes when nothing is on it', () => {
42 const faces = assignFaces([b('Start', 'primary'), b('Other')], false)
43 expect(faceAction(0, faces, 'faces', false, false)).toEqual({ kind: 'footer', index: 0 })
44 expect(faceAction(3, faces, 'faces', false, false)).toEqual({ kind: 'footer', index: 1 })
45 expect(faceAction(2, faces, 'faces', false, false)).toBeNull()
46 expect(faceAction(1, faces, 'faces', false, false)).toBeNull()
47 expect(faceAction(1, faces, 'faces', true, false)).toEqual({ kind: 'close' })
48 })
49 it('Nintendo: primary on A (right, 1), the dismissing button on B (bottom, 0), the rest as before', () => {
50 expect(assignFaces([b('Walk there', 'primary'), b('Got it')], true, false, 'nintendo')).toEqual([1, 0])
51 expect(assignFaces([b('Kick it', 'primary'), b('Walk', 'primary'), b('Cancel')], true, false, 'nintendo')).toEqual([1, 3, 0])
52 expect(assignFaces([b('Restock'), b('Next level', 'primary')], false, true, 'nintendo')).toEqual([3, 2])
53 expect(assignFaces([b('Start', 'primary'), b('Other')], false, false, 'nintendo')).toEqual([1, 3])
54 })
55 it('Nintendo: A presses the primary / buys / focused, B closes', () => {
56 const faces = assignFaces([b('Start', 'primary'), b('Other')], false, false, 'nintendo')
57 expect(faceAction(1, faces, 'faces', false, false, -1, 'nintendo')).toEqual({ kind: 'footer', index: 0 })
58 expect(faceAction(0, faces, 'faces', false, false, -1, 'nintendo')).toBeNull()
59 expect(faceAction(0, faces, 'faces', true, false, -1, 'nintendo')).toEqual({ kind: 'close' })
60 expect(faceAction(1, [3, 2], 'faces', false, true, -1, 'nintendo')).toEqual({ kind: 'buy' })
61 expect(faceAction(1, null, 'selector', false, false, -1, 'nintendo')).toEqual({ kind: 'focused' })
62 expect(faceAction(0, null, 'selector', true, false, -1, 'nintendo')).toEqual({ kind: 'close' })
63 expect(faceAction(0, [0], 'selector', true, false, 0, 'nintendo')).toEqual({ kind: 'footer', index: 0 })
64 })
65 it('Cross buys on a card with a Buy button', () => {
66 expect(faceAction(0, [3, 2], 'faces', false, true)).toEqual({ kind: 'buy' })
67 })
68 it('selector mode: Cross presses the focused element, Circle closes, the rest nothing', () => {
69 expect(faceAction(0, null, 'selector', false, false)).toEqual({ kind: 'focused' })
70 expect(faceAction(1, null, 'selector', true, false)).toEqual({ kind: 'close' })
71 expect(faceAction(1, null, 'selector', false, false)).toBeNull()
72 // With a dismissing footer button (Back with an action), Circle presses that button.
73 expect(faceAction(1, [1], 'selector', true, false, 0)).toEqual({ kind: 'footer', index: 0 })
74 expect(faceAction(1, null, 'selector', false, false, 0)).toBeNull()
75 expect(faceAction(3, null, 'selector', true, false)).toBeNull()
76 })
77})
78
79describe('where the selector starts', () => {
80 it('the primary footer button, else the pressed item, else the first', () => {
81 expect(startIndex([{ primary: false, pressed: false }, { primary: false, pressed: true }, { primary: true, pressed: false }])).toBe(2)
82 expect(startIndex([{ primary: false, pressed: false }, { primary: false, pressed: true }])).toBe(1)
83 expect(startIndex([{ primary: false, pressed: false }])).toBe(0)
84 expect(startIndex([])).toBe(0)
85 })
86})
87
88describe('helpers', () => {
89 it('restoreIndex keeps the place, clamps, and handles empty', () => {
90 expect(restoreIndex(5, 2)).toBe(2)
91 expect(restoreIndex(2, 4)).toBe(1)
92 expect(restoreIndex(3, -1)).toBe(0)
93 expect(restoreIndex(0, 1)).toBe(-1)
94 })
95 it('tabStep stops at the ends', () => {
96 expect(tabStep(4, 0, -1)).toBe(0)
97 expect(tabStep(4, 3, 1)).toBe(3)
98 expect(tabStep(4, 1, 1)).toBe(2)
99 })
100 it('scrollDelta is proportional to push and time', () => {
101 expect(scrollDelta(0.5, 0.1)).toBeCloseTo(scrollDelta(1, 0.05))
102 expect(scrollDelta(-1, 0.1)).toBeLessThan(0)
103 })
104})
105
106describe('the hint for a confirm button let go too early', () => {
107 it('shows on a selected item in selector mode, once per dialog, for the confirm button only', () => {
108 expect(showsTapHint(0, 'selector', true, false, 'xbox')).toBe(true)
109 expect(showsTapHint(0, 'selector', true, true, 'xbox')).toBe(false)
110 expect(showsTapHint(0, 'selector', false, false, 'xbox')).toBe(false)
111 expect(showsTapHint(0, 'faces', true, false, 'xbox')).toBe(false)
112 expect(showsTapHint(1, 'selector', true, false, 'xbox')).toBe(false)
113 // A Nintendo pad confirms with its right-hand A (raw 1).
114 expect(showsTapHint(1, 'selector', true, false, 'nintendo')).toBe(true)
115 expect(showsTapHint(0, 'selector', true, false, 'nintendo')).toBe(false)
116 })
117})
118
119describe('a Nintendo pad in selector mode', () => {
120 it('closes with the bottom button (B) and presses the selection with the right one (A)', () => {
121 // Raw face indices: 0 bottom, 1 right.
122 expect(faceAction(0, null, 'selector', true, false, -1, 'nintendo')).toEqual({ kind: 'close' })
123 expect(faceAction(1, null, 'selector', true, false, -1, 'nintendo')).toEqual({ kind: 'focused' })
124 // Elsewhere it is the other way round.
125 expect(faceAction(1, null, 'selector', true, false, -1, 'xbox')).toEqual({ kind: 'close' })
126 expect(faceAction(0, null, 'selector', true, false, -1, 'xbox')).toEqual({ kind: 'focused' })
127 })
128})