@flighthq/gui 0.4.1-edge.28.7f8e221
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +9 -0
- package/README.md +17 -0
- package/dist/buttonController.d.ts +7 -0
- package/dist/buttonController.d.ts.map +1 -0
- package/dist/buttonController.js +83 -0
- package/dist/buttonController.js.map +1 -0
- package/dist/comboBoxController.d.ts +7 -0
- package/dist/comboBoxController.d.ts.map +1 -0
- package/dist/comboBoxController.js +46 -0
- package/dist/comboBoxController.js.map +1 -0
- package/dist/contract.d.ts +16 -0
- package/dist/contract.d.ts.map +1 -0
- package/dist/contract.js +16 -0
- package/dist/contract.js.map +1 -0
- package/dist/guiController.d.ts +24 -0
- package/dist/guiController.d.ts.map +1 -0
- package/dist/guiController.js +101 -0
- package/dist/guiController.js.map +1 -0
- package/dist/guiTestHelper.d.ts +5 -0
- package/dist/guiTestHelper.d.ts.map +1 -0
- package/dist/guiTestHelper.js +57 -0
- package/dist/guiTestHelper.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/listController.d.ts +8 -0
- package/dist/listController.d.ts.map +1 -0
- package/dist/listController.js +82 -0
- package/dist/listController.js.map +1 -0
- package/dist/progressBarController.d.ts +6 -0
- package/dist/progressBarController.d.ts.map +1 -0
- package/dist/progressBarController.js +42 -0
- package/dist/progressBarController.js.map +1 -0
- package/dist/radioGroupController.d.ts +7 -0
- package/dist/radioGroupController.d.ts.map +1 -0
- package/dist/radioGroupController.js +50 -0
- package/dist/radioGroupController.js.map +1 -0
- package/dist/scrollBarController.d.ts +7 -0
- package/dist/scrollBarController.d.ts.map +1 -0
- package/dist/scrollBarController.js +124 -0
- package/dist/scrollBarController.js.map +1 -0
- package/dist/scrollViewController.d.ts +8 -0
- package/dist/scrollViewController.d.ts.map +1 -0
- package/dist/scrollViewController.js +99 -0
- package/dist/scrollViewController.js.map +1 -0
- package/dist/sliderController.d.ts +7 -0
- package/dist/sliderController.d.ts.map +1 -0
- package/dist/sliderController.js +105 -0
- package/dist/sliderController.js.map +1 -0
- package/dist/splitPaneController.d.ts +7 -0
- package/dist/splitPaneController.d.ts.map +1 -0
- package/dist/splitPaneController.js +87 -0
- package/dist/splitPaneController.js.map +1 -0
- package/dist/tabBarController.d.ts +7 -0
- package/dist/tabBarController.d.ts.map +1 -0
- package/dist/tabBarController.js +43 -0
- package/dist/tabBarController.js.map +1 -0
- package/dist/textInputController.d.ts +9 -0
- package/dist/textInputController.d.ts.map +1 -0
- package/dist/textInputController.js +132 -0
- package/dist/textInputController.js.map +1 -0
- package/dist/toggleController.d.ts +7 -0
- package/dist/toggleController.d.ts.map +1 -0
- package/dist/toggleController.js +57 -0
- package/dist/toggleController.js.map +1 -0
- package/dist/tooltipController.d.ts +6 -0
- package/dist/tooltipController.d.ts.map +1 -0
- package/dist/tooltipController.js +62 -0
- package/dist/tooltipController.js.map +1 -0
- package/dist/treeViewController.d.ts +10 -0
- package/dist/treeViewController.d.ts.map +1 -0
- package/dist/treeViewController.js +128 -0
- package/dist/treeViewController.js.map +1 -0
- package/dist/windowController.d.ts +7 -0
- package/dist/windowController.d.ts.map +1 -0
- package/dist/windowController.js +119 -0
- package/dist/windowController.js.map +1 -0
- package/package.json +53 -0
- package/src/buttonController.test.ts +90 -0
- package/src/comboBoxController.test.ts +71 -0
- package/src/guiController.test.ts +162 -0
- package/src/guiTreeShaking.test.ts +35 -0
- package/src/listController.test.ts +96 -0
- package/src/progressBarController.test.ts +49 -0
- package/src/radioGroupController.test.ts +61 -0
- package/src/scrollBarController.test.ts +86 -0
- package/src/scrollViewController.test.ts +80 -0
- package/src/sliderController.test.ts +74 -0
- package/src/splitPaneController.test.ts +85 -0
- package/src/tabBarController.test.ts +50 -0
- package/src/textInputController.test.ts +93 -0
- package/src/toggleController.test.ts +72 -0
- package/src/tooltipController.test.ts +56 -0
- package/src/treeViewController.test.ts +105 -0
- package/src/windowController.test.ts +75 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { createGuiTestNode } from './guiTestHelper';
|
|
2
|
+
import {
|
|
3
|
+
createProgressBarController,
|
|
4
|
+
disposeProgressBarController,
|
|
5
|
+
getProgressBarControllerValue,
|
|
6
|
+
setProgressBarControllerValue,
|
|
7
|
+
} from './progressBarController';
|
|
8
|
+
|
|
9
|
+
describe('createProgressBarController', () => {
|
|
10
|
+
it('scales the caller fill without changing the track', () => {
|
|
11
|
+
const fill = createGuiTestNode();
|
|
12
|
+
const track = createGuiTestNode();
|
|
13
|
+
createProgressBarController({ fill, maximum: 100, track, value: 25 });
|
|
14
|
+
expect(fill.scaleX).toBe(0.25);
|
|
15
|
+
expect(track.scaleX).toBe(1);
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe('disposeProgressBarController', () => {
|
|
20
|
+
it('is idempotent', () => {
|
|
21
|
+
const controller = createProgressBarController({ fill: createGuiTestNode(), track: createGuiTestNode() });
|
|
22
|
+
disposeProgressBarController(controller);
|
|
23
|
+
expect(() => disposeProgressBarController(controller)).not.toThrow();
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe('getProgressBarControllerValue', () => {
|
|
28
|
+
it('returns a clamped initial value', () => {
|
|
29
|
+
expect(
|
|
30
|
+
getProgressBarControllerValue(
|
|
31
|
+
createProgressBarController({ fill: createGuiTestNode(), maximum: 10, track: createGuiTestNode(), value: 20 }),
|
|
32
|
+
),
|
|
33
|
+
).toBe(10);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('setProgressBarControllerValue', () => {
|
|
38
|
+
it('updates vertical fill scale', () => {
|
|
39
|
+
const fill = createGuiTestNode();
|
|
40
|
+
const controller = createProgressBarController({
|
|
41
|
+
fill,
|
|
42
|
+
maximum: 10,
|
|
43
|
+
orientation: 'vertical',
|
|
44
|
+
track: createGuiTestNode(),
|
|
45
|
+
});
|
|
46
|
+
setProgressBarControllerValue(controller, 5);
|
|
47
|
+
expect(fill.scaleY).toBe(0.5);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { connectSignal } from '@flighthq/signals/contract';
|
|
2
|
+
|
|
3
|
+
import { createGuiTestNode } from './guiTestHelper';
|
|
4
|
+
import {
|
|
5
|
+
createRadioGroupController,
|
|
6
|
+
disposeRadioGroupController,
|
|
7
|
+
getRadioGroupControllerSelectedIndex,
|
|
8
|
+
getRadioGroupControllerSignals,
|
|
9
|
+
setRadioGroupControllerSelectedIndex,
|
|
10
|
+
} from './radioGroupController';
|
|
11
|
+
import { createToggleController, isToggleControllerChecked, setToggleControllerChecked } from './toggleController';
|
|
12
|
+
|
|
13
|
+
function toggle() {
|
|
14
|
+
return createToggleController({ checkedState: createGuiTestNode(), uncheckedState: createGuiTestNode() });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe('createRadioGroupController', () => {
|
|
18
|
+
it('enforces mutual exclusion from toggle changes', () => {
|
|
19
|
+
const toggles = [toggle(), toggle(), toggle()];
|
|
20
|
+
const controller = createRadioGroupController({ selectedIndex: 0, toggles });
|
|
21
|
+
setToggleControllerChecked(toggles[2], true);
|
|
22
|
+
expect(getRadioGroupControllerSelectedIndex(controller)).toBe(2);
|
|
23
|
+
expect(toggles.map(isToggleControllerChecked)).toEqual([false, false, true]);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe('disposeRadioGroupController', () => {
|
|
28
|
+
it('stops observing member toggles', () => {
|
|
29
|
+
const member = toggle();
|
|
30
|
+
const controller = createRadioGroupController({ selectedIndex: -1, toggles: [member] });
|
|
31
|
+
disposeRadioGroupController(controller);
|
|
32
|
+
setToggleControllerChecked(member, true);
|
|
33
|
+
expect(getRadioGroupControllerSelectedIndex(controller)).toBe(-1);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('getRadioGroupControllerSelectedIndex', () => {
|
|
38
|
+
it('normalizes out-of-range values to -1', () => {
|
|
39
|
+
expect(
|
|
40
|
+
getRadioGroupControllerSelectedIndex(createRadioGroupController({ selectedIndex: 4, toggles: [toggle()] })),
|
|
41
|
+
).toBe(-1);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('getRadioGroupControllerSignals', () => {
|
|
46
|
+
it('emits selected indices', () => {
|
|
47
|
+
const controller = createRadioGroupController({ toggles: [toggle(), toggle()] });
|
|
48
|
+
const values: number[] = [];
|
|
49
|
+
connectSignal(getRadioGroupControllerSignals(controller).onChange, (value) => values.push(value));
|
|
50
|
+
setRadioGroupControllerSelectedIndex(controller, 1);
|
|
51
|
+
expect(values).toEqual([1]);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe('setRadioGroupControllerSelectedIndex', () => {
|
|
56
|
+
it('can clear the selection', () => {
|
|
57
|
+
const controller = createRadioGroupController({ toggles: [toggle()] });
|
|
58
|
+
setRadioGroupControllerSelectedIndex(controller, -1);
|
|
59
|
+
expect(getRadioGroupControllerSelectedIndex(controller)).toBe(-1);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { connectSignal } from '@flighthq/signals/contract';
|
|
2
|
+
|
|
3
|
+
import { createGuiTestNode, emitGuiPointer } from './guiTestHelper';
|
|
4
|
+
import {
|
|
5
|
+
createScrollBarController,
|
|
6
|
+
disposeScrollBarController,
|
|
7
|
+
getScrollBarControllerSignals,
|
|
8
|
+
getScrollBarControllerValue,
|
|
9
|
+
setScrollBarControllerValue,
|
|
10
|
+
} from './scrollBarController';
|
|
11
|
+
|
|
12
|
+
describe('createScrollBarController', () => {
|
|
13
|
+
it('pages on track clicks and positions the thumb', () => {
|
|
14
|
+
const track = createGuiTestNode(20, 100);
|
|
15
|
+
const thumb = createGuiTestNode(20, 10);
|
|
16
|
+
const controller = createScrollBarController({ maximum: 100, pageSize: 20, thumb, track });
|
|
17
|
+
emitGuiPointer(track, 'onPointerDown', { localY: 80 });
|
|
18
|
+
expect(getScrollBarControllerValue(controller)).toBe(20);
|
|
19
|
+
expect(thumb.y).toBe(18);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('repeat-clicks optional line buttons and cancels the timer', () => {
|
|
23
|
+
vi.useFakeTimers();
|
|
24
|
+
const down = createGuiTestNode();
|
|
25
|
+
const controller = createScrollBarController({
|
|
26
|
+
downButton: down,
|
|
27
|
+
maximum: 10,
|
|
28
|
+
thumb: createGuiTestNode(),
|
|
29
|
+
track: createGuiTestNode(),
|
|
30
|
+
});
|
|
31
|
+
emitGuiPointer(down, 'onPointerDown');
|
|
32
|
+
vi.advanceTimersByTime(250);
|
|
33
|
+
emitGuiPointer(down, 'onPointerUp');
|
|
34
|
+
expect(getScrollBarControllerValue(controller)).toBe(3);
|
|
35
|
+
vi.useRealTimers();
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe('disposeScrollBarController', () => {
|
|
40
|
+
it('cancels repeat timers', () => {
|
|
41
|
+
vi.useFakeTimers();
|
|
42
|
+
const down = createGuiTestNode();
|
|
43
|
+
const controller = createScrollBarController({
|
|
44
|
+
downButton: down,
|
|
45
|
+
thumb: createGuiTestNode(),
|
|
46
|
+
track: createGuiTestNode(),
|
|
47
|
+
});
|
|
48
|
+
emitGuiPointer(down, 'onPointerDown');
|
|
49
|
+
disposeScrollBarController(controller);
|
|
50
|
+
vi.advanceTimersByTime(500);
|
|
51
|
+
expect(getScrollBarControllerValue(controller)).toBe(1);
|
|
52
|
+
vi.useRealTimers();
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe('getScrollBarControllerSignals', () => {
|
|
57
|
+
it('emits changes', () => {
|
|
58
|
+
const controller = createScrollBarController({ thumb: createGuiTestNode(), track: createGuiTestNode() });
|
|
59
|
+
const values: number[] = [];
|
|
60
|
+
connectSignal(getScrollBarControllerSignals(controller).onChange, (value) => values.push(value));
|
|
61
|
+
setScrollBarControllerValue(controller, 5);
|
|
62
|
+
expect(values).toEqual([5]);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('getScrollBarControllerValue', () => {
|
|
67
|
+
it('returns the initial value', () => {
|
|
68
|
+
expect(
|
|
69
|
+
getScrollBarControllerValue(
|
|
70
|
+
createScrollBarController({ thumb: createGuiTestNode(), track: createGuiTestNode(), value: 5 }),
|
|
71
|
+
),
|
|
72
|
+
).toBe(5);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe('setScrollBarControllerValue', () => {
|
|
77
|
+
it('clamps values', () => {
|
|
78
|
+
const controller = createScrollBarController({
|
|
79
|
+
maximum: 10,
|
|
80
|
+
thumb: createGuiTestNode(),
|
|
81
|
+
track: createGuiTestNode(),
|
|
82
|
+
});
|
|
83
|
+
setScrollBarControllerValue(controller, 20);
|
|
84
|
+
expect(getScrollBarControllerValue(controller)).toBe(10);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { connectSignal } from '@flighthq/signals/contract';
|
|
2
|
+
|
|
3
|
+
import { createGuiTestNode, emitGuiPointer } from './guiTestHelper';
|
|
4
|
+
import {
|
|
5
|
+
createScrollViewController,
|
|
6
|
+
disposeScrollViewController,
|
|
7
|
+
getScrollViewControllerSignals,
|
|
8
|
+
getScrollViewControllerX,
|
|
9
|
+
getScrollViewControllerY,
|
|
10
|
+
setScrollViewControllerPosition,
|
|
11
|
+
} from './scrollViewController';
|
|
12
|
+
|
|
13
|
+
describe('createScrollViewController', () => {
|
|
14
|
+
it('pans content by drag and wheel within viewport bounds', () => {
|
|
15
|
+
const viewport = createGuiTestNode(100, 100);
|
|
16
|
+
const content = createGuiTestNode(300, 300);
|
|
17
|
+
const controller = createScrollViewController({ content, viewport });
|
|
18
|
+
emitGuiPointer(viewport, 'onPointerDown', { worldX: 50, worldY: 50 });
|
|
19
|
+
emitGuiPointer(viewport, 'onPointerMove', { worldX: 20, worldY: 10 });
|
|
20
|
+
expect([getScrollViewControllerX(controller), getScrollViewControllerY(controller)]).toEqual([30, 40]);
|
|
21
|
+
emitGuiPointer(viewport, 'onWheel', { deltaY: 20 });
|
|
22
|
+
expect(content.y).toBe(-60);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe('disposeScrollViewController', () => {
|
|
27
|
+
it('detaches viewport input', () => {
|
|
28
|
+
const viewport = createGuiTestNode(100, 100);
|
|
29
|
+
const controller = createScrollViewController({ content: createGuiTestNode(200, 200), viewport });
|
|
30
|
+
disposeScrollViewController(controller);
|
|
31
|
+
emitGuiPointer(viewport, 'onWheel', { deltaY: 50 });
|
|
32
|
+
expect(getScrollViewControllerY(controller)).toBe(0);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe('getScrollViewControllerSignals', () => {
|
|
37
|
+
it('emits both axes', () => {
|
|
38
|
+
const controller = createScrollViewController({
|
|
39
|
+
content: createGuiTestNode(200, 200),
|
|
40
|
+
viewport: createGuiTestNode(),
|
|
41
|
+
});
|
|
42
|
+
const values: number[][] = [];
|
|
43
|
+
connectSignal(getScrollViewControllerSignals(controller).onScroll, (x, y) => values.push([x, y]));
|
|
44
|
+
setScrollViewControllerPosition(controller, 10, 20);
|
|
45
|
+
expect(values).toEqual([[10, 20]]);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe('getScrollViewControllerX', () => {
|
|
50
|
+
it('returns horizontal offset', () => {
|
|
51
|
+
const controller = createScrollViewController({
|
|
52
|
+
content: createGuiTestNode(200, 200),
|
|
53
|
+
viewport: createGuiTestNode(),
|
|
54
|
+
});
|
|
55
|
+
setScrollViewControllerPosition(controller, 5, 0);
|
|
56
|
+
expect(getScrollViewControllerX(controller)).toBe(5);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe('getScrollViewControllerY', () => {
|
|
61
|
+
it('returns vertical offset', () => {
|
|
62
|
+
const controller = createScrollViewController({
|
|
63
|
+
content: createGuiTestNode(200, 200),
|
|
64
|
+
viewport: createGuiTestNode(),
|
|
65
|
+
});
|
|
66
|
+
setScrollViewControllerPosition(controller, 0, 5);
|
|
67
|
+
expect(getScrollViewControllerY(controller)).toBe(5);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe('setScrollViewControllerPosition', () => {
|
|
72
|
+
it('clamps to content extent', () => {
|
|
73
|
+
const controller = createScrollViewController({
|
|
74
|
+
content: createGuiTestNode(150, 150),
|
|
75
|
+
viewport: createGuiTestNode(100, 100),
|
|
76
|
+
});
|
|
77
|
+
setScrollViewControllerPosition(controller, 500, 500);
|
|
78
|
+
expect([getScrollViewControllerX(controller), getScrollViewControllerY(controller)]).toEqual([50, 50]);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { connectSignal } from '@flighthq/signals/contract';
|
|
2
|
+
|
|
3
|
+
import { createGuiTestNode, emitGuiKeyboard, emitGuiPointer } from './guiTestHelper';
|
|
4
|
+
import {
|
|
5
|
+
createSliderController,
|
|
6
|
+
disposeSliderController,
|
|
7
|
+
getSliderControllerSignals,
|
|
8
|
+
getSliderControllerValue,
|
|
9
|
+
setSliderControllerValue,
|
|
10
|
+
} from './sliderController';
|
|
11
|
+
|
|
12
|
+
describe('createSliderController', () => {
|
|
13
|
+
it('maps track positions and thumb drags to values', () => {
|
|
14
|
+
const track = createGuiTestNode(100, 10);
|
|
15
|
+
const thumb = createGuiTestNode(10, 10);
|
|
16
|
+
const controller = createSliderController({ maximum: 10, thumb, track });
|
|
17
|
+
emitGuiPointer(track, 'onPointerDown', { localX: 50 });
|
|
18
|
+
expect(getSliderControllerValue(controller)).toBe(5);
|
|
19
|
+
expect(thumb.x).toBe(45);
|
|
20
|
+
emitGuiPointer(thumb, 'onPointerDown', { worldX: 45 });
|
|
21
|
+
emitGuiPointer(thumb, 'onPointerMove', { worldX: 90 });
|
|
22
|
+
expect(getSliderControllerValue(controller)).toBe(10);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('supports keyboard selection', () => {
|
|
26
|
+
const track = createGuiTestNode();
|
|
27
|
+
const controller = createSliderController({ maximum: 10, step: 2, thumb: createGuiTestNode(), track });
|
|
28
|
+
emitGuiKeyboard(track, 'onKeyDown', 'ArrowRight');
|
|
29
|
+
expect(getSliderControllerValue(controller)).toBe(2);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe('disposeSliderController', () => {
|
|
34
|
+
it('detaches track input', () => {
|
|
35
|
+
const track = createGuiTestNode();
|
|
36
|
+
const controller = createSliderController({ thumb: createGuiTestNode(), track });
|
|
37
|
+
disposeSliderController(controller);
|
|
38
|
+
emitGuiPointer(track, 'onPointerDown', { localX: 100 });
|
|
39
|
+
expect(getSliderControllerValue(controller)).toBe(0);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe('getSliderControllerSignals', () => {
|
|
44
|
+
it('emits snapped values', () => {
|
|
45
|
+
const controller = createSliderController({
|
|
46
|
+
maximum: 10,
|
|
47
|
+
step: 2,
|
|
48
|
+
thumb: createGuiTestNode(),
|
|
49
|
+
track: createGuiTestNode(),
|
|
50
|
+
});
|
|
51
|
+
const values: number[] = [];
|
|
52
|
+
connectSignal(getSliderControllerSignals(controller).onChange, (value) => values.push(value));
|
|
53
|
+
setSliderControllerValue(controller, 3);
|
|
54
|
+
expect(values).toEqual([4]);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe('getSliderControllerValue', () => {
|
|
59
|
+
it('returns the configured value', () => {
|
|
60
|
+
expect(
|
|
61
|
+
getSliderControllerValue(
|
|
62
|
+
createSliderController({ thumb: createGuiTestNode(), track: createGuiTestNode(), value: 0.5 }),
|
|
63
|
+
),
|
|
64
|
+
).toBe(0.5);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe('setSliderControllerValue', () => {
|
|
69
|
+
it('clamps to the range', () => {
|
|
70
|
+
const controller = createSliderController({ thumb: createGuiTestNode(), track: createGuiTestNode() });
|
|
71
|
+
setSliderControllerValue(controller, 5);
|
|
72
|
+
expect(getSliderControllerValue(controller)).toBe(1);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { connectSignal } from '@flighthq/signals/contract';
|
|
2
|
+
|
|
3
|
+
import { createGuiTestNode, emitGuiPointer } from './guiTestHelper';
|
|
4
|
+
import {
|
|
5
|
+
createSplitPaneController,
|
|
6
|
+
disposeSplitPaneController,
|
|
7
|
+
getSplitPaneControllerPosition,
|
|
8
|
+
getSplitPaneControllerSignals,
|
|
9
|
+
setSplitPaneControllerPosition,
|
|
10
|
+
} from './splitPaneController';
|
|
11
|
+
|
|
12
|
+
describe('createSplitPaneController', () => {
|
|
13
|
+
it('drags a distinct constrained divider behavior', () => {
|
|
14
|
+
const first = createGuiTestNode(100, 20);
|
|
15
|
+
const divider = createGuiTestNode(10, 20);
|
|
16
|
+
const second = createGuiTestNode(100, 20);
|
|
17
|
+
const controller = createSplitPaneController({
|
|
18
|
+
divider,
|
|
19
|
+
firstRegion: first,
|
|
20
|
+
minimumFirst: 50,
|
|
21
|
+
minimumSecond: 50,
|
|
22
|
+
secondRegion: second,
|
|
23
|
+
totalSize: 210,
|
|
24
|
+
});
|
|
25
|
+
emitGuiPointer(divider, 'onPointerDown', { worldX: 100 });
|
|
26
|
+
emitGuiPointer(divider, 'onPointerMove', { worldX: 170 });
|
|
27
|
+
expect(getSplitPaneControllerPosition(controller)).toBe(150);
|
|
28
|
+
expect(divider.x).toBe(150);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('disposeSplitPaneController', () => {
|
|
33
|
+
it('detaches divider dragging', () => {
|
|
34
|
+
const divider = createGuiTestNode();
|
|
35
|
+
const controller = createSplitPaneController({
|
|
36
|
+
divider,
|
|
37
|
+
firstRegion: createGuiTestNode(),
|
|
38
|
+
secondRegion: createGuiTestNode(),
|
|
39
|
+
});
|
|
40
|
+
disposeSplitPaneController(controller);
|
|
41
|
+
emitGuiPointer(divider, 'onPointerDown', { worldX: 0 });
|
|
42
|
+
emitGuiPointer(divider, 'onPointerMove', { worldX: 50 });
|
|
43
|
+
expect(getSplitPaneControllerPosition(controller)).toBe(100);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe('getSplitPaneControllerPosition', () => {
|
|
48
|
+
it('returns the clamped initial position', () => {
|
|
49
|
+
const controller = createSplitPaneController({
|
|
50
|
+
divider: createGuiTestNode(),
|
|
51
|
+
firstRegion: createGuiTestNode(),
|
|
52
|
+
minimumFirst: 20,
|
|
53
|
+
position: 0,
|
|
54
|
+
secondRegion: createGuiTestNode(),
|
|
55
|
+
});
|
|
56
|
+
expect(getSplitPaneControllerPosition(controller)).toBe(20);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe('getSplitPaneControllerSignals', () => {
|
|
61
|
+
it('emits changed positions', () => {
|
|
62
|
+
const controller = createSplitPaneController({
|
|
63
|
+
divider: createGuiTestNode(),
|
|
64
|
+
firstRegion: createGuiTestNode(),
|
|
65
|
+
secondRegion: createGuiTestNode(),
|
|
66
|
+
});
|
|
67
|
+
const values: number[] = [];
|
|
68
|
+
connectSignal(getSplitPaneControllerSignals(controller).onChange, (value) => values.push(value));
|
|
69
|
+
setSplitPaneControllerPosition(controller, 50);
|
|
70
|
+
expect(values).toEqual([50]);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe('setSplitPaneControllerPosition', () => {
|
|
75
|
+
it('honors the maximum', () => {
|
|
76
|
+
const controller = createSplitPaneController({
|
|
77
|
+
divider: createGuiTestNode(),
|
|
78
|
+
firstRegion: createGuiTestNode(),
|
|
79
|
+
maximumFirst: 60,
|
|
80
|
+
secondRegion: createGuiTestNode(),
|
|
81
|
+
});
|
|
82
|
+
setSplitPaneControllerPosition(controller, 90);
|
|
83
|
+
expect(getSplitPaneControllerPosition(controller)).toBe(60);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { createGuiTestNode, emitGuiPointer } from './guiTestHelper';
|
|
2
|
+
import {
|
|
3
|
+
createTabBarController,
|
|
4
|
+
disposeTabBarController,
|
|
5
|
+
getTabBarControllerSelectedIndex,
|
|
6
|
+
getTabBarControllerSignals,
|
|
7
|
+
setTabBarControllerSelectedIndex,
|
|
8
|
+
} from './tabBarController';
|
|
9
|
+
|
|
10
|
+
describe('createTabBarController', () => {
|
|
11
|
+
it('swaps selected and unselected visuals from clicks', () => {
|
|
12
|
+
const a = { selectedState: createGuiTestNode(), unselectedState: createGuiTestNode() };
|
|
13
|
+
const b = { selectedState: createGuiTestNode(), unselectedState: createGuiTestNode() };
|
|
14
|
+
const controller = createTabBarController({ tabs: [a, b] });
|
|
15
|
+
emitGuiPointer(b.unselectedState, 'onClick');
|
|
16
|
+
expect(getTabBarControllerSelectedIndex(controller)).toBe(1);
|
|
17
|
+
expect([b.selectedState.visible, b.unselectedState.visible]).toEqual([true, false]);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe('disposeTabBarController', () => {
|
|
22
|
+
it('detaches tab listeners', () => {
|
|
23
|
+
const tab = { selectedState: createGuiTestNode(), unselectedState: createGuiTestNode() };
|
|
24
|
+
const controller = createTabBarController({ selectedIndex: -1, tabs: [tab] });
|
|
25
|
+
disposeTabBarController(controller);
|
|
26
|
+
emitGuiPointer(tab.unselectedState, 'onClick');
|
|
27
|
+
expect(getTabBarControllerSelectedIndex(controller)).toBe(-1);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe('getTabBarControllerSelectedIndex', () => {
|
|
32
|
+
it('reports the initial index', () => {
|
|
33
|
+
expect(getTabBarControllerSelectedIndex(createTabBarController({ selectedIndex: -1, tabs: [] }))).toBe(-1);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('getTabBarControllerSignals', () => {
|
|
38
|
+
it('returns stable signals', () => {
|
|
39
|
+
const controller = createTabBarController({ tabs: [] });
|
|
40
|
+
expect(getTabBarControllerSignals(controller)).toBe(getTabBarControllerSignals(controller));
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe('setTabBarControllerSelectedIndex', () => {
|
|
45
|
+
it('normalizes invalid indices', () => {
|
|
46
|
+
const controller = createTabBarController({ tabs: [] });
|
|
47
|
+
setTabBarControllerSelectedIndex(controller, 2);
|
|
48
|
+
expect(getTabBarControllerSelectedIndex(controller)).toBe(-1);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { connectSignal } from '@flighthq/signals/contract';
|
|
2
|
+
import { createRichText } from '@flighthq/text/contract';
|
|
3
|
+
|
|
4
|
+
import { createGuiTestNode } from './guiTestHelper';
|
|
5
|
+
import {
|
|
6
|
+
blurTextInputController,
|
|
7
|
+
createTextInputController,
|
|
8
|
+
dispatchTextInputControllerKeyDown,
|
|
9
|
+
dispatchTextInputControllerText,
|
|
10
|
+
disposeTextInputController,
|
|
11
|
+
focusTextInputController,
|
|
12
|
+
getTextInputControllerSignals,
|
|
13
|
+
} from './textInputController';
|
|
14
|
+
|
|
15
|
+
describe('blurTextInputController', () => {
|
|
16
|
+
it('hides a caller caret', () => {
|
|
17
|
+
const caret = createGuiTestNode();
|
|
18
|
+
const controller = createTextInputController({ caret, textField: createRichText() });
|
|
19
|
+
focusTextInputController(controller);
|
|
20
|
+
blurTextInputController(controller);
|
|
21
|
+
expect(caret.visible).toBe(false);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe('createTextInputController', () => {
|
|
26
|
+
it('wraps textinput and relays text changes', () => {
|
|
27
|
+
const field = createRichText();
|
|
28
|
+
const controller = createTextInputController({ textField: field });
|
|
29
|
+
const values: string[] = [];
|
|
30
|
+
connectSignal(getTextInputControllerSignals(controller).onChange, (text) => values.push(text));
|
|
31
|
+
focusTextInputController(controller);
|
|
32
|
+
dispatchTextInputControllerText(controller, 'abc');
|
|
33
|
+
expect(field.data.text).toBe('abc');
|
|
34
|
+
expect(values).toEqual(['abc']);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe('dispatchTextInputControllerKeyDown', () => {
|
|
39
|
+
it('emits submit on Enter', () => {
|
|
40
|
+
const controller = createTextInputController({ textField: createRichText() });
|
|
41
|
+
let submitted = '';
|
|
42
|
+
connectSignal(getTextInputControllerSignals(controller).onSubmit, (text) => (submitted = text));
|
|
43
|
+
focusTextInputController(controller);
|
|
44
|
+
dispatchTextInputControllerText(controller, 'abc');
|
|
45
|
+
dispatchTextInputControllerKeyDown(controller, {
|
|
46
|
+
altKey: false,
|
|
47
|
+
capsLock: false,
|
|
48
|
+
code: 'Enter',
|
|
49
|
+
ctrlKey: false,
|
|
50
|
+
key: 'Enter',
|
|
51
|
+
keyCode: 13,
|
|
52
|
+
location: 0,
|
|
53
|
+
metaKey: false,
|
|
54
|
+
modifier: 0,
|
|
55
|
+
numLock: false,
|
|
56
|
+
repeat: false,
|
|
57
|
+
shiftKey: false,
|
|
58
|
+
timeStamp: 0,
|
|
59
|
+
});
|
|
60
|
+
expect(submitted).toBe('abc');
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe('dispatchTextInputControllerText', () => {
|
|
65
|
+
it('returns false while unfocused', () => {
|
|
66
|
+
const controller = createTextInputController({ textField: createRichText() });
|
|
67
|
+
expect(dispatchTextInputControllerText(controller, 'x')).toBe(false);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe('disposeTextInputController', () => {
|
|
72
|
+
it('releases owned text-input capability', () => {
|
|
73
|
+
const controller = createTextInputController({ textField: createRichText() });
|
|
74
|
+
disposeTextInputController(controller);
|
|
75
|
+
expect(dispatchTextInputControllerText(controller, 'x')).toBe(false);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe('focusTextInputController', () => {
|
|
80
|
+
it('shows a caller caret', () => {
|
|
81
|
+
const caret = createGuiTestNode();
|
|
82
|
+
const controller = createTextInputController({ caret, textField: createRichText() });
|
|
83
|
+
focusTextInputController(controller);
|
|
84
|
+
expect(caret.visible).toBe(true);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe('getTextInputControllerSignals', () => {
|
|
89
|
+
it('returns a stable group', () => {
|
|
90
|
+
const controller = createTextInputController({ textField: createRichText() });
|
|
91
|
+
expect(getTextInputControllerSignals(controller)).toBe(getTextInputControllerSignals(controller));
|
|
92
|
+
});
|
|
93
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { connectSignal } from '@flighthq/signals/contract';
|
|
2
|
+
|
|
3
|
+
import { createGuiTestNode, emitGuiPointer } from './guiTestHelper';
|
|
4
|
+
import {
|
|
5
|
+
createToggleController,
|
|
6
|
+
disposeToggleController,
|
|
7
|
+
getToggleControllerSignals,
|
|
8
|
+
isToggleControllerChecked,
|
|
9
|
+
setToggleControllerChecked,
|
|
10
|
+
} from './toggleController';
|
|
11
|
+
|
|
12
|
+
describe('createToggleController', () => {
|
|
13
|
+
it('toggles through either state visual and an optional label', () => {
|
|
14
|
+
const unchecked = createGuiTestNode();
|
|
15
|
+
const checked = createGuiTestNode();
|
|
16
|
+
const label = createGuiTestNode();
|
|
17
|
+
const controller = createToggleController({ checkedState: checked, label, uncheckedState: unchecked });
|
|
18
|
+
emitGuiPointer(label, 'onClick');
|
|
19
|
+
expect(isToggleControllerChecked(controller)).toBe(true);
|
|
20
|
+
expect([unchecked.visible, checked.visible]).toEqual([false, true]);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe('disposeToggleController', () => {
|
|
25
|
+
it('detaches click listeners', () => {
|
|
26
|
+
const unchecked = createGuiTestNode();
|
|
27
|
+
const controller = createToggleController({ checkedState: createGuiTestNode(), uncheckedState: unchecked });
|
|
28
|
+
disposeToggleController(controller);
|
|
29
|
+
emitGuiPointer(unchecked, 'onClick');
|
|
30
|
+
expect(isToggleControllerChecked(controller)).toBe(false);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe('getToggleControllerSignals', () => {
|
|
35
|
+
it('emits only real state changes', () => {
|
|
36
|
+
const controller = createToggleController({
|
|
37
|
+
checkedState: createGuiTestNode(),
|
|
38
|
+
uncheckedState: createGuiTestNode(),
|
|
39
|
+
});
|
|
40
|
+
const values: boolean[] = [];
|
|
41
|
+
connectSignal(getToggleControllerSignals(controller).onChange, (value) => values.push(value));
|
|
42
|
+
setToggleControllerChecked(controller, true);
|
|
43
|
+
setToggleControllerChecked(controller, true);
|
|
44
|
+
expect(values).toEqual([true]);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe('isToggleControllerChecked', () => {
|
|
49
|
+
it('reads the current value', () => {
|
|
50
|
+
expect(
|
|
51
|
+
isToggleControllerChecked(
|
|
52
|
+
createToggleController({
|
|
53
|
+
checked: true,
|
|
54
|
+
checkedState: createGuiTestNode(),
|
|
55
|
+
uncheckedState: createGuiTestNode(),
|
|
56
|
+
}),
|
|
57
|
+
),
|
|
58
|
+
).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('setToggleControllerChecked', () => {
|
|
63
|
+
it('sets both directions', () => {
|
|
64
|
+
const controller = createToggleController({
|
|
65
|
+
checked: true,
|
|
66
|
+
checkedState: createGuiTestNode(),
|
|
67
|
+
uncheckedState: createGuiTestNode(),
|
|
68
|
+
});
|
|
69
|
+
setToggleControllerChecked(controller, false);
|
|
70
|
+
expect(isToggleControllerChecked(controller)).toBe(false);
|
|
71
|
+
});
|
|
72
|
+
});
|