@tokis/core 1.0.1 → 1.2.0
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/README.md +5 -5
- package/dist/__tests__/accordion.test.d.ts +2 -0
- package/dist/__tests__/accordion.test.d.ts.map +1 -0
- package/dist/__tests__/accordion.test.js +127 -0
- package/dist/__tests__/accordion.test.js.map +1 -0
- package/dist/__tests__/create-machine.test.d.ts +2 -0
- package/dist/__tests__/create-machine.test.d.ts.map +1 -0
- package/dist/__tests__/create-machine.test.js +79 -0
- package/dist/__tests__/create-machine.test.js.map +1 -0
- package/dist/__tests__/dialog.test.d.ts +2 -0
- package/dist/__tests__/dialog.test.d.ts.map +1 -0
- package/dist/__tests__/dialog.test.js +74 -0
- package/dist/__tests__/dialog.test.js.map +1 -0
- package/dist/__tests__/menu.test.d.ts +2 -0
- package/dist/__tests__/menu.test.d.ts.map +1 -0
- package/dist/__tests__/menu.test.js +177 -0
- package/dist/__tests__/menu.test.js.map +1 -0
- package/dist/__tests__/tabs.test.d.ts +2 -0
- package/dist/__tests__/tabs.test.d.ts.map +1 -0
- package/dist/__tests__/tabs.test.js +147 -0
- package/dist/__tests__/tabs.test.js.map +1 -0
- package/dist/cjs/__tests__/accordion.test.js +128 -0
- package/dist/cjs/__tests__/create-machine.test.js +80 -0
- package/dist/cjs/__tests__/dialog.test.js +75 -0
- package/dist/cjs/__tests__/menu.test.js +178 -0
- package/dist/cjs/__tests__/tabs.test.js +148 -0
- package/dist/cjs/index.js +9 -0
- package/dist/cjs/primitives/accordion/accordion.machine.js +109 -0
- package/dist/cjs/primitives/accordion/accordion.types.js +6 -0
- package/dist/cjs/primitives/accordion/index.js +18 -0
- package/dist/cjs/primitives/dialog/dialog.machine.js +62 -0
- package/dist/cjs/primitives/dialog/dialog.types.js +6 -0
- package/dist/cjs/primitives/dialog/index.js +18 -0
- package/dist/cjs/primitives/menu/index.js +18 -0
- package/dist/cjs/primitives/menu/menu.machine.js +135 -0
- package/dist/cjs/primitives/menu/menu.types.js +6 -0
- package/dist/cjs/primitives/popover/index.js +18 -0
- package/dist/cjs/primitives/popover/popover.machine.js +87 -0
- package/dist/cjs/primitives/popover/popover.types.js +7 -0
- package/dist/cjs/primitives/tabs/index.js +18 -0
- package/dist/cjs/primitives/tabs/tabs.machine.js +124 -0
- package/dist/cjs/primitives/tabs/tabs.types.js +6 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/dist/primitives/accordion/accordion.machine.d.ts +22 -0
- package/dist/primitives/accordion/accordion.machine.d.ts.map +1 -0
- package/dist/primitives/accordion/accordion.machine.js +103 -0
- package/dist/primitives/accordion/accordion.machine.js.map +1 -0
- package/dist/primitives/accordion/accordion.types.d.ts +28 -0
- package/dist/primitives/accordion/accordion.types.d.ts.map +1 -0
- package/dist/primitives/accordion/accordion.types.js +6 -0
- package/dist/primitives/accordion/accordion.types.js.map +1 -0
- package/dist/primitives/accordion/index.d.ts +3 -0
- package/dist/primitives/accordion/index.d.ts.map +1 -0
- package/dist/primitives/accordion/index.js +3 -0
- package/dist/primitives/accordion/index.js.map +1 -0
- package/dist/primitives/dialog/dialog.machine.d.ts +23 -0
- package/dist/primitives/dialog/dialog.machine.d.ts.map +1 -0
- package/dist/primitives/dialog/dialog.machine.js +58 -0
- package/dist/primitives/dialog/dialog.machine.js.map +1 -0
- package/dist/primitives/dialog/dialog.types.d.ts +25 -0
- package/dist/primitives/dialog/dialog.types.d.ts.map +1 -0
- package/dist/primitives/dialog/dialog.types.js +6 -0
- package/dist/primitives/dialog/dialog.types.js.map +1 -0
- package/dist/primitives/dialog/index.d.ts +3 -0
- package/dist/primitives/dialog/index.d.ts.map +1 -0
- package/dist/primitives/dialog/index.js +3 -0
- package/dist/primitives/dialog/index.js.map +1 -0
- package/dist/primitives/menu/index.d.ts +3 -0
- package/dist/primitives/menu/index.d.ts.map +1 -0
- package/dist/primitives/menu/index.js +3 -0
- package/dist/primitives/menu/index.js.map +1 -0
- package/dist/primitives/menu/menu.machine.d.ts +25 -0
- package/dist/primitives/menu/menu.machine.d.ts.map +1 -0
- package/dist/primitives/menu/menu.machine.js +129 -0
- package/dist/primitives/menu/menu.machine.js.map +1 -0
- package/dist/primitives/menu/menu.types.d.ts +30 -0
- package/dist/primitives/menu/menu.types.d.ts.map +1 -0
- package/dist/primitives/menu/menu.types.js +6 -0
- package/dist/primitives/menu/menu.types.js.map +1 -0
- package/dist/primitives/popover/index.d.ts +3 -0
- package/dist/primitives/popover/index.d.ts.map +1 -0
- package/dist/primitives/popover/index.js +3 -0
- package/dist/primitives/popover/index.js.map +1 -0
- package/dist/primitives/popover/popover.machine.d.ts +30 -0
- package/dist/primitives/popover/popover.machine.d.ts.map +1 -0
- package/dist/primitives/popover/popover.machine.js +80 -0
- package/dist/primitives/popover/popover.machine.js.map +1 -0
- package/dist/primitives/popover/popover.types.d.ts +34 -0
- package/dist/primitives/popover/popover.types.d.ts.map +1 -0
- package/dist/primitives/popover/popover.types.js +7 -0
- package/dist/primitives/popover/popover.types.js.map +1 -0
- package/dist/primitives/tabs/index.d.ts +3 -0
- package/dist/primitives/tabs/index.d.ts.map +1 -0
- package/dist/primitives/tabs/index.js +3 -0
- package/dist/primitives/tabs/index.js.map +1 -0
- package/dist/primitives/tabs/tabs.machine.d.ts +25 -0
- package/dist/primitives/tabs/tabs.machine.d.ts.map +1 -0
- package/dist/primitives/tabs/tabs.machine.js +117 -0
- package/dist/primitives/tabs/tabs.machine.js.map +1 -0
- package/dist/primitives/tabs/tabs.types.d.ts +34 -0
- package/dist/primitives/tabs/tabs.types.d.ts.map +1 -0
- package/dist/primitives/tabs/tabs.types.js +6 -0
- package/dist/primitives/tabs/tabs.types.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { tabsMachine, reduceTabsContext, getNextTabId, getPrevTabId, getTabAriaProps, getTabPanelAriaProps, } from '../primitives/tabs/tabs.machine.js';
|
|
3
|
+
// ─── Fixtures ──────────────────────────────────────────────────────────────
|
|
4
|
+
function makeCtx(overrides = {}) {
|
|
5
|
+
return {
|
|
6
|
+
tabIds: ['tab-1', 'tab-2', 'tab-3'],
|
|
7
|
+
activeTabId: 'tab-1',
|
|
8
|
+
focusedTabId: 'tab-1',
|
|
9
|
+
activationMode: 'automatic',
|
|
10
|
+
orientation: 'horizontal',
|
|
11
|
+
loop: true,
|
|
12
|
+
...overrides,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
// ─── Machine tests ─────────────────────────────────────────────────────────
|
|
16
|
+
describe('tabsMachine', () => {
|
|
17
|
+
it('starts in idle state', () => {
|
|
18
|
+
expect(tabsMachine.initialState).toBe('idle');
|
|
19
|
+
});
|
|
20
|
+
it('stays in idle on any event (all context work is done by helpers)', () => {
|
|
21
|
+
expect(tabsMachine.transition('idle', 'SELECT_TAB')).toBe('idle');
|
|
22
|
+
expect(tabsMachine.transition('idle', 'NEXT_TAB')).toBe('idle');
|
|
23
|
+
expect(tabsMachine.transition('idle', 'PREV_TAB')).toBe('idle');
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
// ─── Navigation helpers ────────────────────────────────────────────────────
|
|
27
|
+
describe('getNextTabId', () => {
|
|
28
|
+
it('moves from first → second tab', () => {
|
|
29
|
+
const ctx = makeCtx({ focusedTabId: 'tab-1' });
|
|
30
|
+
expect(getNextTabId(ctx)).toBe('tab-2');
|
|
31
|
+
});
|
|
32
|
+
it('moves from second → third tab', () => {
|
|
33
|
+
const ctx = makeCtx({ focusedTabId: 'tab-2' });
|
|
34
|
+
expect(getNextTabId(ctx)).toBe('tab-3');
|
|
35
|
+
});
|
|
36
|
+
it('wraps from last → first when loop=true', () => {
|
|
37
|
+
const ctx = makeCtx({ focusedTabId: 'tab-3', loop: true });
|
|
38
|
+
expect(getNextTabId(ctx)).toBe('tab-1');
|
|
39
|
+
});
|
|
40
|
+
it('stays at last tab when loop=false', () => {
|
|
41
|
+
const ctx = makeCtx({ focusedTabId: 'tab-3', loop: false });
|
|
42
|
+
expect(getNextTabId(ctx)).toBe('tab-3');
|
|
43
|
+
});
|
|
44
|
+
it('returns first tab when current focus is not in tabIds', () => {
|
|
45
|
+
const ctx = makeCtx({ focusedTabId: 'unknown' });
|
|
46
|
+
expect(getNextTabId(ctx)).toBe('tab-1');
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
describe('getPrevTabId', () => {
|
|
50
|
+
it('moves from third → second tab', () => {
|
|
51
|
+
const ctx = makeCtx({ focusedTabId: 'tab-3' });
|
|
52
|
+
expect(getPrevTabId(ctx)).toBe('tab-2');
|
|
53
|
+
});
|
|
54
|
+
it('wraps from first → last when loop=true', () => {
|
|
55
|
+
const ctx = makeCtx({ focusedTabId: 'tab-1', loop: true });
|
|
56
|
+
expect(getPrevTabId(ctx)).toBe('tab-3');
|
|
57
|
+
});
|
|
58
|
+
it('stays at first tab when loop=false', () => {
|
|
59
|
+
const ctx = makeCtx({ focusedTabId: 'tab-1', loop: false });
|
|
60
|
+
expect(getPrevTabId(ctx)).toBe('tab-1');
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
// ─── reduceTabsContext ─────────────────────────────────────────────────────
|
|
64
|
+
describe('reduceTabsContext', () => {
|
|
65
|
+
it('SELECT_TAB with payload sets activeTabId and focusedTabId', () => {
|
|
66
|
+
const ctx = makeCtx();
|
|
67
|
+
const patch = reduceTabsContext(ctx, 'SELECT_TAB', { tabId: 'tab-2' });
|
|
68
|
+
expect(patch).toEqual({ activeTabId: 'tab-2', focusedTabId: 'tab-2' });
|
|
69
|
+
});
|
|
70
|
+
it('SELECT_TAB without payload uses current focusedTabId', () => {
|
|
71
|
+
const ctx = makeCtx({ focusedTabId: 'tab-3' });
|
|
72
|
+
const patch = reduceTabsContext(ctx, 'SELECT_TAB');
|
|
73
|
+
expect(patch).toEqual({ activeTabId: 'tab-3', focusedTabId: 'tab-3' });
|
|
74
|
+
});
|
|
75
|
+
it('FOCUS_TAB in automatic mode also updates activeTabId', () => {
|
|
76
|
+
const ctx = makeCtx({ activationMode: 'automatic' });
|
|
77
|
+
const patch = reduceTabsContext(ctx, 'FOCUS_TAB', { tabId: 'tab-2' });
|
|
78
|
+
expect(patch.focusedTabId).toBe('tab-2');
|
|
79
|
+
expect(patch.activeTabId).toBe('tab-2');
|
|
80
|
+
});
|
|
81
|
+
it('FOCUS_TAB in manual mode only updates focusedTabId', () => {
|
|
82
|
+
const ctx = makeCtx({ activationMode: 'manual' });
|
|
83
|
+
const patch = reduceTabsContext(ctx, 'FOCUS_TAB', { tabId: 'tab-2' });
|
|
84
|
+
expect(patch.focusedTabId).toBe('tab-2');
|
|
85
|
+
expect(patch.activeTabId).toBeUndefined();
|
|
86
|
+
});
|
|
87
|
+
it('NEXT_TAB advances focus in automatic mode', () => {
|
|
88
|
+
const ctx = makeCtx({ focusedTabId: 'tab-1', activationMode: 'automatic' });
|
|
89
|
+
const patch = reduceTabsContext(ctx, 'NEXT_TAB');
|
|
90
|
+
expect(patch.focusedTabId).toBe('tab-2');
|
|
91
|
+
expect(patch.activeTabId).toBe('tab-2');
|
|
92
|
+
});
|
|
93
|
+
it('PREV_TAB moves focus backward', () => {
|
|
94
|
+
const ctx = makeCtx({ focusedTabId: 'tab-2' });
|
|
95
|
+
const patch = reduceTabsContext(ctx, 'PREV_TAB');
|
|
96
|
+
expect(patch.focusedTabId).toBe('tab-1');
|
|
97
|
+
});
|
|
98
|
+
it('FIRST_TAB jumps to first tab', () => {
|
|
99
|
+
const ctx = makeCtx({ focusedTabId: 'tab-3' });
|
|
100
|
+
const patch = reduceTabsContext(ctx, 'FIRST_TAB');
|
|
101
|
+
expect(patch.focusedTabId).toBe('tab-1');
|
|
102
|
+
});
|
|
103
|
+
it('LAST_TAB jumps to last tab', () => {
|
|
104
|
+
const ctx = makeCtx({ focusedTabId: 'tab-1' });
|
|
105
|
+
const patch = reduceTabsContext(ctx, 'LAST_TAB');
|
|
106
|
+
expect(patch.focusedTabId).toBe('tab-3');
|
|
107
|
+
});
|
|
108
|
+
it('unknown event returns empty patch (no-op)', () => {
|
|
109
|
+
const ctx = makeCtx();
|
|
110
|
+
// @ts-expect-error — intentional unknown event
|
|
111
|
+
const patch = reduceTabsContext(ctx, 'UNKNOWN_EVENT');
|
|
112
|
+
expect(patch).toEqual({});
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
// ─── ARIA helpers ──────────────────────────────────────────────────────────
|
|
116
|
+
describe('getTabAriaProps', () => {
|
|
117
|
+
it('returns correct ARIA attributes for the active tab', () => {
|
|
118
|
+
const ctx = makeCtx({ activeTabId: 'tab-1' });
|
|
119
|
+
const props = getTabAriaProps(ctx, 'tab-1', 'panel-1');
|
|
120
|
+
expect(props.role).toBe('tab');
|
|
121
|
+
expect(props['aria-selected']).toBe(true);
|
|
122
|
+
expect(props['aria-controls']).toBe('panel-1');
|
|
123
|
+
expect(props.tabIndex).toBe(0);
|
|
124
|
+
});
|
|
125
|
+
it('marks non-active tabs as not selected with tabIndex -1', () => {
|
|
126
|
+
const ctx = makeCtx({ activeTabId: 'tab-1' });
|
|
127
|
+
const props = getTabAriaProps(ctx, 'tab-2', 'panel-2');
|
|
128
|
+
expect(props['aria-selected']).toBe(false);
|
|
129
|
+
expect(props.tabIndex).toBe(-1);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
describe('getTabPanelAriaProps', () => {
|
|
133
|
+
it('returns correct ARIA attributes for the visible panel', () => {
|
|
134
|
+
const ctx = makeCtx({ activeTabId: 'tab-1' });
|
|
135
|
+
const props = getTabPanelAriaProps(ctx, 'panel-1', 'tab-1');
|
|
136
|
+
expect(props.role).toBe('tabpanel');
|
|
137
|
+
expect(props.hidden).toBe(false);
|
|
138
|
+
expect(props['aria-labelledby']).toBe('tab-1');
|
|
139
|
+
expect(props.tabIndex).toBe(0);
|
|
140
|
+
});
|
|
141
|
+
it('marks non-visible panels as hidden', () => {
|
|
142
|
+
const ctx = makeCtx({ activeTabId: 'tab-1' });
|
|
143
|
+
const props = getTabPanelAriaProps(ctx, 'panel-2', 'tab-2');
|
|
144
|
+
expect(props.hidden).toBe(true);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
//# sourceMappingURL=tabs.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tabs.test.js","sourceRoot":"","sources":["../../src/__tests__/tabs.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const accordion_machine_js_1 = require("../primitives/accordion/accordion.machine");
|
|
5
|
+
// ─── Fixtures ──────────────────────────────────────────────────────────────
|
|
6
|
+
function makeCtx(overrides = {}) {
|
|
7
|
+
return {
|
|
8
|
+
itemIds: ['item-1', 'item-2', 'item-3'],
|
|
9
|
+
openItemIds: new Set(),
|
|
10
|
+
multiple: false,
|
|
11
|
+
collapsible: true,
|
|
12
|
+
...overrides,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
// ─── TOGGLE_ITEM ───────────────────────────────────────────────────────────
|
|
16
|
+
(0, vitest_1.describe)('reduceAccordionContext — TOGGLE_ITEM', () => {
|
|
17
|
+
(0, vitest_1.it)('opens a closed item', () => {
|
|
18
|
+
const ctx = makeCtx();
|
|
19
|
+
const patch = (0, accordion_machine_js_1.reduceAccordionContext)(ctx, 'TOGGLE_ITEM', { itemId: 'item-1' });
|
|
20
|
+
(0, vitest_1.expect)(patch.openItemIds?.has('item-1')).toBe(true);
|
|
21
|
+
});
|
|
22
|
+
(0, vitest_1.it)('closes an open item when collapsible=true', () => {
|
|
23
|
+
const ctx = makeCtx({ openItemIds: new Set(['item-1']) });
|
|
24
|
+
const patch = (0, accordion_machine_js_1.reduceAccordionContext)(ctx, 'TOGGLE_ITEM', { itemId: 'item-1' });
|
|
25
|
+
(0, vitest_1.expect)(patch.openItemIds?.has('item-1')).toBe(false);
|
|
26
|
+
});
|
|
27
|
+
(0, vitest_1.it)('prevents closing the last item when collapsible=false', () => {
|
|
28
|
+
const ctx = makeCtx({ openItemIds: new Set(['item-1']), collapsible: false });
|
|
29
|
+
const patch = (0, accordion_machine_js_1.reduceAccordionContext)(ctx, 'TOGGLE_ITEM', { itemId: 'item-1' });
|
|
30
|
+
// Should return empty patch — item stays open
|
|
31
|
+
(0, vitest_1.expect)(patch).toEqual({});
|
|
32
|
+
});
|
|
33
|
+
(0, vitest_1.it)('exclusive mode: opening one item closes all others', () => {
|
|
34
|
+
const ctx = makeCtx({ openItemIds: new Set(['item-1']), multiple: false });
|
|
35
|
+
const patch = (0, accordion_machine_js_1.reduceAccordionContext)(ctx, 'TOGGLE_ITEM', { itemId: 'item-2' });
|
|
36
|
+
(0, vitest_1.expect)(patch.openItemIds?.has('item-2')).toBe(true);
|
|
37
|
+
(0, vitest_1.expect)(patch.openItemIds?.has('item-1')).toBe(false);
|
|
38
|
+
});
|
|
39
|
+
(0, vitest_1.it)('multiple mode: opening one item leaves others open', () => {
|
|
40
|
+
const ctx = makeCtx({ openItemIds: new Set(['item-1']), multiple: true });
|
|
41
|
+
const patch = (0, accordion_machine_js_1.reduceAccordionContext)(ctx, 'TOGGLE_ITEM', { itemId: 'item-2' });
|
|
42
|
+
(0, vitest_1.expect)(patch.openItemIds?.has('item-1')).toBe(true);
|
|
43
|
+
(0, vitest_1.expect)(patch.openItemIds?.has('item-2')).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
(0, vitest_1.it)('returns empty patch when no itemId is provided', () => {
|
|
46
|
+
const ctx = makeCtx();
|
|
47
|
+
const patch = (0, accordion_machine_js_1.reduceAccordionContext)(ctx, 'TOGGLE_ITEM');
|
|
48
|
+
(0, vitest_1.expect)(patch).toEqual({});
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
// ─── OPEN_ITEM / CLOSE_ITEM ────────────────────────────────────────────────
|
|
52
|
+
(0, vitest_1.describe)('reduceAccordionContext — OPEN_ITEM / CLOSE_ITEM', () => {
|
|
53
|
+
(0, vitest_1.it)('OPEN_ITEM adds the item to open set (exclusive mode clears others)', () => {
|
|
54
|
+
const ctx = makeCtx({ openItemIds: new Set(['item-1']), multiple: false });
|
|
55
|
+
const patch = (0, accordion_machine_js_1.reduceAccordionContext)(ctx, 'OPEN_ITEM', { itemId: 'item-2' });
|
|
56
|
+
(0, vitest_1.expect)(patch.openItemIds?.has('item-2')).toBe(true);
|
|
57
|
+
(0, vitest_1.expect)(patch.openItemIds?.has('item-1')).toBe(false);
|
|
58
|
+
});
|
|
59
|
+
(0, vitest_1.it)('OPEN_ITEM in multiple mode preserves existing open items', () => {
|
|
60
|
+
const ctx = makeCtx({ openItemIds: new Set(['item-1']), multiple: true });
|
|
61
|
+
const patch = (0, accordion_machine_js_1.reduceAccordionContext)(ctx, 'OPEN_ITEM', { itemId: 'item-2' });
|
|
62
|
+
(0, vitest_1.expect)(patch.openItemIds?.has('item-1')).toBe(true);
|
|
63
|
+
(0, vitest_1.expect)(patch.openItemIds?.has('item-2')).toBe(true);
|
|
64
|
+
});
|
|
65
|
+
(0, vitest_1.it)('CLOSE_ITEM removes an open item', () => {
|
|
66
|
+
const ctx = makeCtx({ openItemIds: new Set(['item-1', 'item-2']), multiple: true });
|
|
67
|
+
const patch = (0, accordion_machine_js_1.reduceAccordionContext)(ctx, 'CLOSE_ITEM', { itemId: 'item-1' });
|
|
68
|
+
(0, vitest_1.expect)(patch.openItemIds?.has('item-1')).toBe(false);
|
|
69
|
+
(0, vitest_1.expect)(patch.openItemIds?.has('item-2')).toBe(true);
|
|
70
|
+
});
|
|
71
|
+
(0, vitest_1.it)('CLOSE_ITEM is a no-op when collapsible=false and only one item is open', () => {
|
|
72
|
+
const ctx = makeCtx({ openItemIds: new Set(['item-1']), collapsible: false });
|
|
73
|
+
const patch = (0, accordion_machine_js_1.reduceAccordionContext)(ctx, 'CLOSE_ITEM', { itemId: 'item-1' });
|
|
74
|
+
(0, vitest_1.expect)(patch).toEqual({});
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
// ─── OPEN_ALL / CLOSE_ALL ──────────────────────────────────────────────────
|
|
78
|
+
(0, vitest_1.describe)('reduceAccordionContext — OPEN_ALL / CLOSE_ALL', () => {
|
|
79
|
+
(0, vitest_1.it)('OPEN_ALL opens every item when multiple=true', () => {
|
|
80
|
+
const ctx = makeCtx({ multiple: true });
|
|
81
|
+
const patch = (0, accordion_machine_js_1.reduceAccordionContext)(ctx, 'OPEN_ALL');
|
|
82
|
+
(0, vitest_1.expect)(patch.openItemIds?.size).toBe(3);
|
|
83
|
+
ctx.itemIds.forEach((id) => {
|
|
84
|
+
(0, vitest_1.expect)(patch.openItemIds?.has(id)).toBe(true);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
(0, vitest_1.it)('OPEN_ALL is a no-op in exclusive mode', () => {
|
|
88
|
+
const ctx = makeCtx({ multiple: false });
|
|
89
|
+
const patch = (0, accordion_machine_js_1.reduceAccordionContext)(ctx, 'OPEN_ALL');
|
|
90
|
+
(0, vitest_1.expect)(patch).toEqual({});
|
|
91
|
+
});
|
|
92
|
+
(0, vitest_1.it)('CLOSE_ALL clears all open items when collapsible=true', () => {
|
|
93
|
+
const ctx = makeCtx({ openItemIds: new Set(['item-1', 'item-2']), multiple: true, collapsible: true });
|
|
94
|
+
const patch = (0, accordion_machine_js_1.reduceAccordionContext)(ctx, 'CLOSE_ALL');
|
|
95
|
+
(0, vitest_1.expect)(patch.openItemIds?.size).toBe(0);
|
|
96
|
+
});
|
|
97
|
+
(0, vitest_1.it)('CLOSE_ALL is a no-op when collapsible=false', () => {
|
|
98
|
+
const ctx = makeCtx({ openItemIds: new Set(['item-1']), collapsible: false });
|
|
99
|
+
const patch = (0, accordion_machine_js_1.reduceAccordionContext)(ctx, 'CLOSE_ALL');
|
|
100
|
+
(0, vitest_1.expect)(patch).toEqual({});
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
// ─── isAccordionItemOpen ───────────────────────────────────────────────────
|
|
104
|
+
(0, vitest_1.describe)('isAccordionItemOpen', () => {
|
|
105
|
+
(0, vitest_1.it)('returns true for an item in openItemIds', () => {
|
|
106
|
+
const ctx = makeCtx({ openItemIds: new Set(['item-2']) });
|
|
107
|
+
(0, vitest_1.expect)((0, accordion_machine_js_1.isAccordionItemOpen)(ctx, 'item-2')).toBe(true);
|
|
108
|
+
});
|
|
109
|
+
(0, vitest_1.it)('returns false for an item not in openItemIds', () => {
|
|
110
|
+
const ctx = makeCtx({ openItemIds: new Set(['item-2']) });
|
|
111
|
+
(0, vitest_1.expect)((0, accordion_machine_js_1.isAccordionItemOpen)(ctx, 'item-1')).toBe(false);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
// ─── ARIA helpers ──────────────────────────────────────────────────────────
|
|
115
|
+
(0, vitest_1.describe)('getAccordionTriggerAriaProps', () => {
|
|
116
|
+
(0, vitest_1.it)('returns aria-expanded=true for an open item', () => {
|
|
117
|
+
const ctx = makeCtx({ openItemIds: new Set(['item-1']) });
|
|
118
|
+
const props = (0, accordion_machine_js_1.getAccordionTriggerAriaProps)(ctx, 'item-1', 'panel-1');
|
|
119
|
+
(0, vitest_1.expect)(props['aria-expanded']).toBe(true);
|
|
120
|
+
(0, vitest_1.expect)(props['aria-controls']).toBe('panel-1');
|
|
121
|
+
(0, vitest_1.expect)(props.role).toBe('button');
|
|
122
|
+
});
|
|
123
|
+
(0, vitest_1.it)('returns aria-expanded=false for a closed item', () => {
|
|
124
|
+
const ctx = makeCtx();
|
|
125
|
+
const props = (0, accordion_machine_js_1.getAccordionTriggerAriaProps)(ctx, 'item-1', 'panel-1');
|
|
126
|
+
(0, vitest_1.expect)(props['aria-expanded']).toBe(false);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const create_machine_js_1 = require("../state/create-machine");
|
|
5
|
+
const trafficMachine = (0, create_machine_js_1.createMachine)({
|
|
6
|
+
id: 'traffic',
|
|
7
|
+
initial: 'red',
|
|
8
|
+
context: { cycles: 0 },
|
|
9
|
+
states: {
|
|
10
|
+
red: { NEXT: 'green' },
|
|
11
|
+
green: { NEXT: 'yellow' },
|
|
12
|
+
yellow: { NEXT: 'red' },
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
// ─── Tests ─────────────────────────────────────────────────────────────────
|
|
16
|
+
(0, vitest_1.describe)('createMachine', () => {
|
|
17
|
+
(0, vitest_1.it)('exposes the machine id and initial state from config', () => {
|
|
18
|
+
(0, vitest_1.expect)(trafficMachine.id).toBe('traffic');
|
|
19
|
+
(0, vitest_1.expect)(trafficMachine.initialState).toBe('red');
|
|
20
|
+
});
|
|
21
|
+
(0, vitest_1.it)('exposes the initial context', () => {
|
|
22
|
+
(0, vitest_1.expect)(trafficMachine.initialContext).toEqual({ cycles: 0 });
|
|
23
|
+
});
|
|
24
|
+
(0, vitest_1.it)('transitions red → green on NEXT', () => {
|
|
25
|
+
(0, vitest_1.expect)(trafficMachine.transition('red', 'NEXT')).toBe('green');
|
|
26
|
+
});
|
|
27
|
+
(0, vitest_1.it)('transitions green → yellow on NEXT', () => {
|
|
28
|
+
(0, vitest_1.expect)(trafficMachine.transition('green', 'NEXT')).toBe('yellow');
|
|
29
|
+
});
|
|
30
|
+
(0, vitest_1.it)('transitions yellow → red on NEXT (cycle completes)', () => {
|
|
31
|
+
(0, vitest_1.expect)(trafficMachine.transition('yellow', 'NEXT')).toBe('red');
|
|
32
|
+
});
|
|
33
|
+
(0, vitest_1.it)('returns the current state when no transition is defined', () => {
|
|
34
|
+
// Create a machine with a terminal state that has no outgoing transitions
|
|
35
|
+
const simple = (0, create_machine_js_1.createMachine)({
|
|
36
|
+
id: 'simple',
|
|
37
|
+
initial: 'off',
|
|
38
|
+
context: {},
|
|
39
|
+
states: {
|
|
40
|
+
off: { TOGGLE: 'on' },
|
|
41
|
+
on: { TOGGLE: 'off' },
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
// Sending an undefined event should return current state unchanged
|
|
45
|
+
// @ts-expect-error — intentional invalid event for test
|
|
46
|
+
(0, vitest_1.expect)(simple.transition('on', 'UNKNOWN')).toBe('on');
|
|
47
|
+
});
|
|
48
|
+
(0, vitest_1.it)('initial context is not mutated by multiple transitions', () => {
|
|
49
|
+
trafficMachine.transition('red', 'NEXT');
|
|
50
|
+
trafficMachine.transition('green', 'NEXT');
|
|
51
|
+
// initialContext must be unchanged
|
|
52
|
+
(0, vitest_1.expect)(trafficMachine.initialContext).toEqual({ cycles: 0 });
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
(0, vitest_1.describe)('assign helper', () => {
|
|
56
|
+
(0, vitest_1.it)('passes through a partial context object unchanged', () => {
|
|
57
|
+
const patch = { cycles: 5 };
|
|
58
|
+
(0, vitest_1.expect)((0, create_machine_js_1.assign)(patch)).toBe(patch);
|
|
59
|
+
});
|
|
60
|
+
(0, vitest_1.it)('passes through an updater function unchanged', () => {
|
|
61
|
+
const fn = (ctx) => ({ cycles: ctx.cycles + 1 });
|
|
62
|
+
(0, vitest_1.expect)((0, create_machine_js_1.assign)(fn)).toBe(fn);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
(0, vitest_1.describe)('setup helper', () => {
|
|
66
|
+
(0, vitest_1.it)('returns a createMachine function', () => {
|
|
67
|
+
const s = (0, create_machine_js_1.setup)({});
|
|
68
|
+
(0, vitest_1.expect)(typeof s.createMachine).toBe('function');
|
|
69
|
+
});
|
|
70
|
+
(0, vitest_1.it)('createMachine returned from setup produces a valid machine', () => {
|
|
71
|
+
const s = (0, create_machine_js_1.setup)({});
|
|
72
|
+
const m = s.createMachine({
|
|
73
|
+
id: 'test-setup',
|
|
74
|
+
initial: 'a',
|
|
75
|
+
context: {},
|
|
76
|
+
states: { a: { GO: 'b' }, b: {} },
|
|
77
|
+
});
|
|
78
|
+
(0, vitest_1.expect)(m.transition('a', 'GO')).toBe('b');
|
|
79
|
+
});
|
|
80
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const dialog_machine_js_1 = require("../primitives/dialog/dialog.machine");
|
|
5
|
+
// ─── Transition tests ──────────────────────────────────────────────────────
|
|
6
|
+
(0, vitest_1.describe)('dialogMachine transitions', () => {
|
|
7
|
+
const t = (state, event) => dialog_machine_js_1.dialogMachine.transition(state, event);
|
|
8
|
+
(0, vitest_1.it)('starts in the closed state', () => {
|
|
9
|
+
(0, vitest_1.expect)(dialog_machine_js_1.dialogMachine.initialState).toBe('closed');
|
|
10
|
+
});
|
|
11
|
+
(0, vitest_1.it)('closed + OPEN → opening', () => {
|
|
12
|
+
(0, vitest_1.expect)(t('closed', 'OPEN')).toBe('opening');
|
|
13
|
+
});
|
|
14
|
+
(0, vitest_1.it)('closed + TOGGLE → opening', () => {
|
|
15
|
+
(0, vitest_1.expect)(t('closed', 'TOGGLE')).toBe('opening');
|
|
16
|
+
});
|
|
17
|
+
(0, vitest_1.it)('opening + ANIMATION_END → open', () => {
|
|
18
|
+
(0, vitest_1.expect)(t('opening', 'ANIMATION_END')).toBe('open');
|
|
19
|
+
});
|
|
20
|
+
(0, vitest_1.it)('opening + CLOSE → closed (cancel before animation finishes)', () => {
|
|
21
|
+
(0, vitest_1.expect)(t('opening', 'CLOSE')).toBe('closed');
|
|
22
|
+
});
|
|
23
|
+
(0, vitest_1.it)('open + CLOSE → closing', () => {
|
|
24
|
+
(0, vitest_1.expect)(t('open', 'CLOSE')).toBe('closing');
|
|
25
|
+
});
|
|
26
|
+
(0, vitest_1.it)('open + TOGGLE → closing', () => {
|
|
27
|
+
(0, vitest_1.expect)(t('open', 'TOGGLE')).toBe('closing');
|
|
28
|
+
});
|
|
29
|
+
(0, vitest_1.it)('closing + ANIMATION_END → closed', () => {
|
|
30
|
+
(0, vitest_1.expect)(t('closing', 'ANIMATION_END')).toBe('closed');
|
|
31
|
+
});
|
|
32
|
+
(0, vitest_1.it)('closing + OPEN → opening (re-open during close animation)', () => {
|
|
33
|
+
(0, vitest_1.expect)(t('closing', 'OPEN')).toBe('opening');
|
|
34
|
+
});
|
|
35
|
+
(0, vitest_1.it)('closing + TOGGLE → opening', () => {
|
|
36
|
+
(0, vitest_1.expect)(t('closing', 'TOGGLE')).toBe('opening');
|
|
37
|
+
});
|
|
38
|
+
(0, vitest_1.it)('full open→close cycle via ANIMATION_END', () => {
|
|
39
|
+
let state = dialog_machine_js_1.dialogMachine.initialState;
|
|
40
|
+
state = dialog_machine_js_1.dialogMachine.transition(state, 'OPEN'); // → opening
|
|
41
|
+
state = dialog_machine_js_1.dialogMachine.transition(state, 'ANIMATION_END'); // → open
|
|
42
|
+
state = dialog_machine_js_1.dialogMachine.transition(state, 'CLOSE'); // → closing
|
|
43
|
+
state = dialog_machine_js_1.dialogMachine.transition(state, 'ANIMATION_END'); // → closed
|
|
44
|
+
(0, vitest_1.expect)(state).toBe('closed');
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
// ─── Derived boolean helpers ───────────────────────────────────────────────
|
|
48
|
+
(0, vitest_1.describe)('isDialogMounted', () => {
|
|
49
|
+
(0, vitest_1.it)('returns false when closed', () => {
|
|
50
|
+
(0, vitest_1.expect)((0, dialog_machine_js_1.isDialogMounted)('closed')).toBe(false);
|
|
51
|
+
});
|
|
52
|
+
(0, vitest_1.it)('returns true when opening (animation in progress)', () => {
|
|
53
|
+
(0, vitest_1.expect)((0, dialog_machine_js_1.isDialogMounted)('opening')).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
(0, vitest_1.it)('returns true when open', () => {
|
|
56
|
+
(0, vitest_1.expect)((0, dialog_machine_js_1.isDialogMounted)('open')).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
(0, vitest_1.it)('returns true when closing (DOM still present until animation ends)', () => {
|
|
59
|
+
(0, vitest_1.expect)((0, dialog_machine_js_1.isDialogMounted)('closing')).toBe(true);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
(0, vitest_1.describe)('isDialogOpen', () => {
|
|
63
|
+
(0, vitest_1.it)('returns false when closed', () => {
|
|
64
|
+
(0, vitest_1.expect)((0, dialog_machine_js_1.isDialogOpen)('closed')).toBe(false);
|
|
65
|
+
});
|
|
66
|
+
(0, vitest_1.it)('returns true when opening', () => {
|
|
67
|
+
(0, vitest_1.expect)((0, dialog_machine_js_1.isDialogOpen)('opening')).toBe(true);
|
|
68
|
+
});
|
|
69
|
+
(0, vitest_1.it)('returns true when open', () => {
|
|
70
|
+
(0, vitest_1.expect)((0, dialog_machine_js_1.isDialogOpen)('open')).toBe(true);
|
|
71
|
+
});
|
|
72
|
+
(0, vitest_1.it)('returns false when closing', () => {
|
|
73
|
+
(0, vitest_1.expect)((0, dialog_machine_js_1.isDialogOpen)('closing')).toBe(false);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const menu_machine_js_1 = require("../primitives/menu/menu.machine");
|
|
5
|
+
// ─── Fixtures ──────────────────────────────────────────────────────────────
|
|
6
|
+
const items = [
|
|
7
|
+
{ id: 'file', label: 'File', disabled: false },
|
|
8
|
+
{ id: 'edit', label: 'Edit', disabled: false },
|
|
9
|
+
{ id: 'view', label: 'View', disabled: true },
|
|
10
|
+
{ id: 'help', label: 'Help', disabled: false },
|
|
11
|
+
];
|
|
12
|
+
function makeCtx(overrides = {}) {
|
|
13
|
+
return {
|
|
14
|
+
triggerId: 'menu-trigger',
|
|
15
|
+
items,
|
|
16
|
+
activeItemId: null,
|
|
17
|
+
searchBuffer: '',
|
|
18
|
+
loop: true,
|
|
19
|
+
...overrides,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
// ─── State machine transitions ─────────────────────────────────────────────
|
|
23
|
+
(0, vitest_1.describe)('menuMachine transitions', () => {
|
|
24
|
+
(0, vitest_1.it)('starts closed', () => {
|
|
25
|
+
(0, vitest_1.expect)(menu_machine_js_1.menuMachine.initialState).toBe('closed');
|
|
26
|
+
});
|
|
27
|
+
(0, vitest_1.it)('closed + OPEN → open', () => {
|
|
28
|
+
(0, vitest_1.expect)(menu_machine_js_1.menuMachine.transition('closed', 'OPEN')).toBe('open');
|
|
29
|
+
});
|
|
30
|
+
(0, vitest_1.it)('closed + TOGGLE → open', () => {
|
|
31
|
+
(0, vitest_1.expect)(menu_machine_js_1.menuMachine.transition('closed', 'TOGGLE')).toBe('open');
|
|
32
|
+
});
|
|
33
|
+
(0, vitest_1.it)('open + CLOSE → closed', () => {
|
|
34
|
+
(0, vitest_1.expect)(menu_machine_js_1.menuMachine.transition('open', 'CLOSE')).toBe('closed');
|
|
35
|
+
});
|
|
36
|
+
(0, vitest_1.it)('open + TOGGLE → closed', () => {
|
|
37
|
+
(0, vitest_1.expect)(menu_machine_js_1.menuMachine.transition('open', 'TOGGLE')).toBe('closed');
|
|
38
|
+
});
|
|
39
|
+
(0, vitest_1.it)('open + SELECT_ITEM → closed (menu dismisses on selection)', () => {
|
|
40
|
+
(0, vitest_1.expect)(menu_machine_js_1.menuMachine.transition('open', 'SELECT_ITEM')).toBe('closed');
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
// ─── Context helpers ───────────────────────────────────────────────────────
|
|
44
|
+
(0, vitest_1.describe)('reduceMenuContext — OPEN', () => {
|
|
45
|
+
(0, vitest_1.it)('focuses the first enabled item on open', () => {
|
|
46
|
+
const ctx = makeCtx();
|
|
47
|
+
const patch = (0, menu_machine_js_1.reduceMenuContext)(ctx, 'OPEN');
|
|
48
|
+
(0, vitest_1.expect)(patch.activeItemId).toBe('file');
|
|
49
|
+
(0, vitest_1.expect)(patch.searchBuffer).toBe('');
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
(0, vitest_1.describe)('reduceMenuContext — CLOSE/TOGGLE', () => {
|
|
53
|
+
(0, vitest_1.it)('CLOSE clears activeItemId and searchBuffer', () => {
|
|
54
|
+
const ctx = makeCtx({ activeItemId: 'edit', searchBuffer: 'ed' });
|
|
55
|
+
const patch = (0, menu_machine_js_1.reduceMenuContext)(ctx, 'CLOSE');
|
|
56
|
+
(0, vitest_1.expect)(patch.activeItemId).toBeNull();
|
|
57
|
+
(0, vitest_1.expect)(patch.searchBuffer).toBe('');
|
|
58
|
+
});
|
|
59
|
+
(0, vitest_1.it)('TOGGLE clears activeItemId and searchBuffer', () => {
|
|
60
|
+
const ctx = makeCtx({ activeItemId: 'edit' });
|
|
61
|
+
const patch = (0, menu_machine_js_1.reduceMenuContext)(ctx, 'TOGGLE');
|
|
62
|
+
(0, vitest_1.expect)(patch.activeItemId).toBeNull();
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
(0, vitest_1.describe)('reduceMenuContext — NEXT_ITEM / PREV_ITEM', () => {
|
|
66
|
+
(0, vitest_1.it)('NEXT_ITEM skips disabled items', () => {
|
|
67
|
+
// file → edit (skip view which is disabled) → help → file (loop)
|
|
68
|
+
const ctx = makeCtx({ activeItemId: 'edit' });
|
|
69
|
+
const patch = (0, menu_machine_js_1.reduceMenuContext)(ctx, 'NEXT_ITEM');
|
|
70
|
+
// 'view' is disabled, so it should jump to 'help'
|
|
71
|
+
(0, vitest_1.expect)(patch.activeItemId).toBe('help');
|
|
72
|
+
});
|
|
73
|
+
(0, vitest_1.it)('NEXT_ITEM loops back to first enabled when at end (loop=true)', () => {
|
|
74
|
+
const ctx = makeCtx({ activeItemId: 'help', loop: true });
|
|
75
|
+
const patch = (0, menu_machine_js_1.reduceMenuContext)(ctx, 'NEXT_ITEM');
|
|
76
|
+
(0, vitest_1.expect)(patch.activeItemId).toBe('file');
|
|
77
|
+
});
|
|
78
|
+
(0, vitest_1.it)('NEXT_ITEM stays at last when loop=false', () => {
|
|
79
|
+
const ctx = makeCtx({ activeItemId: 'help', loop: false });
|
|
80
|
+
const patch = (0, menu_machine_js_1.reduceMenuContext)(ctx, 'NEXT_ITEM');
|
|
81
|
+
(0, vitest_1.expect)(patch.activeItemId).toBe('help');
|
|
82
|
+
});
|
|
83
|
+
(0, vitest_1.it)('PREV_ITEM moves to the previous enabled item', () => {
|
|
84
|
+
const ctx = makeCtx({ activeItemId: 'help' });
|
|
85
|
+
const patch = (0, menu_machine_js_1.reduceMenuContext)(ctx, 'PREV_ITEM');
|
|
86
|
+
// view is disabled, so should skip to 'edit'
|
|
87
|
+
(0, vitest_1.expect)(patch.activeItemId).toBe('edit');
|
|
88
|
+
});
|
|
89
|
+
(0, vitest_1.it)('PREV_ITEM wraps to last enabled item from first (loop=true)', () => {
|
|
90
|
+
const ctx = makeCtx({ activeItemId: 'file', loop: true });
|
|
91
|
+
const patch = (0, menu_machine_js_1.reduceMenuContext)(ctx, 'PREV_ITEM');
|
|
92
|
+
(0, vitest_1.expect)(patch.activeItemId).toBe('help');
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
(0, vitest_1.describe)('reduceMenuContext — FIRST_ITEM / LAST_ITEM', () => {
|
|
96
|
+
(0, vitest_1.it)('FIRST_ITEM focuses the first enabled item', () => {
|
|
97
|
+
const ctx = makeCtx({ activeItemId: 'help' });
|
|
98
|
+
const patch = (0, menu_machine_js_1.reduceMenuContext)(ctx, 'FIRST_ITEM');
|
|
99
|
+
(0, vitest_1.expect)(patch.activeItemId).toBe('file');
|
|
100
|
+
});
|
|
101
|
+
(0, vitest_1.it)('LAST_ITEM focuses the last enabled item', () => {
|
|
102
|
+
const ctx = makeCtx({ activeItemId: 'file' });
|
|
103
|
+
const patch = (0, menu_machine_js_1.reduceMenuContext)(ctx, 'LAST_ITEM');
|
|
104
|
+
(0, vitest_1.expect)(patch.activeItemId).toBe('help');
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
(0, vitest_1.describe)('reduceMenuContext — SEARCH (typeahead)', () => {
|
|
108
|
+
(0, vitest_1.it)('finds the first item starting with the typed character', () => {
|
|
109
|
+
const ctx = makeCtx({ activeItemId: null });
|
|
110
|
+
const patch = (0, menu_machine_js_1.reduceMenuContext)(ctx, 'SEARCH', { char: 'e' });
|
|
111
|
+
(0, vitest_1.expect)(patch.activeItemId).toBe('edit');
|
|
112
|
+
(0, vitest_1.expect)(patch.searchBuffer).toBe('e');
|
|
113
|
+
});
|
|
114
|
+
(0, vitest_1.it)('accumulates characters in searchBuffer', () => {
|
|
115
|
+
const ctx = makeCtx({ activeItemId: 'edit', searchBuffer: 'e' });
|
|
116
|
+
const patch = (0, menu_machine_js_1.reduceMenuContext)(ctx, 'SEARCH', { char: 'd' });
|
|
117
|
+
(0, vitest_1.expect)(patch.searchBuffer).toBe('ed');
|
|
118
|
+
(0, vitest_1.expect)(patch.activeItemId).toBe('edit');
|
|
119
|
+
});
|
|
120
|
+
(0, vitest_1.it)('is case-insensitive', () => {
|
|
121
|
+
const ctx = makeCtx();
|
|
122
|
+
const patch = (0, menu_machine_js_1.reduceMenuContext)(ctx, 'SEARCH', { char: 'H' });
|
|
123
|
+
(0, vitest_1.expect)(patch.activeItemId).toBe('help');
|
|
124
|
+
});
|
|
125
|
+
(0, vitest_1.it)('keeps current activeItemId when no match is found', () => {
|
|
126
|
+
const ctx = makeCtx({ activeItemId: 'file' });
|
|
127
|
+
const patch = (0, menu_machine_js_1.reduceMenuContext)(ctx, 'SEARCH', { char: 'z' });
|
|
128
|
+
(0, vitest_1.expect)(patch.activeItemId).toBe('file');
|
|
129
|
+
});
|
|
130
|
+
(0, vitest_1.it)('returns empty patch for empty char', () => {
|
|
131
|
+
const ctx = makeCtx();
|
|
132
|
+
const patch = (0, menu_machine_js_1.reduceMenuContext)(ctx, 'SEARCH', { char: '' });
|
|
133
|
+
(0, vitest_1.expect)(patch).toEqual({});
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
// ─── ARIA helpers ──────────────────────────────────────────────────────────
|
|
137
|
+
(0, vitest_1.describe)('getMenuTriggerAriaProps', () => {
|
|
138
|
+
(0, vitest_1.it)('returns aria-expanded=false when menu is closed', () => {
|
|
139
|
+
const ctx = makeCtx();
|
|
140
|
+
const props = (0, menu_machine_js_1.getMenuTriggerAriaProps)('closed', ctx, 'menu-list');
|
|
141
|
+
(0, vitest_1.expect)(props['aria-expanded']).toBe(false);
|
|
142
|
+
(0, vitest_1.expect)(props['aria-haspopup']).toBe('menu');
|
|
143
|
+
(0, vitest_1.expect)(props['aria-controls']).toBe('menu-list');
|
|
144
|
+
});
|
|
145
|
+
(0, vitest_1.it)('returns aria-expanded=true when menu is open', () => {
|
|
146
|
+
const ctx = makeCtx();
|
|
147
|
+
const props = (0, menu_machine_js_1.getMenuTriggerAriaProps)('open', ctx, 'menu-list');
|
|
148
|
+
(0, vitest_1.expect)(props['aria-expanded']).toBe(true);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
(0, vitest_1.describe)('getMenuAriaProps', () => {
|
|
152
|
+
(0, vitest_1.it)('returns role=menu with aria-labelledby pointing to trigger', () => {
|
|
153
|
+
const ctx = makeCtx({ triggerId: 'my-trigger' });
|
|
154
|
+
const props = (0, menu_machine_js_1.getMenuAriaProps)(ctx, 'my-menu');
|
|
155
|
+
(0, vitest_1.expect)(props.role).toBe('menu');
|
|
156
|
+
(0, vitest_1.expect)(props['aria-labelledby']).toBe('my-trigger');
|
|
157
|
+
(0, vitest_1.expect)(props.id).toBe('my-menu');
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
(0, vitest_1.describe)('getMenuItemAriaProps', () => {
|
|
161
|
+
(0, vitest_1.it)('marks the active item with tabIndex=0', () => {
|
|
162
|
+
const ctx = makeCtx({ activeItemId: 'file' });
|
|
163
|
+
const props = (0, menu_machine_js_1.getMenuItemAriaProps)(ctx, items[0]);
|
|
164
|
+
(0, vitest_1.expect)(props.role).toBe('menuitem');
|
|
165
|
+
(0, vitest_1.expect)(props.tabIndex).toBe(0);
|
|
166
|
+
(0, vitest_1.expect)(props['aria-disabled']).toBe(false);
|
|
167
|
+
});
|
|
168
|
+
(0, vitest_1.it)('marks non-active items with tabIndex=-1', () => {
|
|
169
|
+
const ctx = makeCtx({ activeItemId: 'file' });
|
|
170
|
+
const props = (0, menu_machine_js_1.getMenuItemAriaProps)(ctx, items[1]); // 'edit'
|
|
171
|
+
(0, vitest_1.expect)(props.tabIndex).toBe(-1);
|
|
172
|
+
});
|
|
173
|
+
(0, vitest_1.it)('marks disabled items with aria-disabled=true', () => {
|
|
174
|
+
const ctx = makeCtx({ activeItemId: 'view' });
|
|
175
|
+
const props = (0, menu_machine_js_1.getMenuItemAriaProps)(ctx, items[2]); // 'view' is disabled
|
|
176
|
+
(0, vitest_1.expect)(props['aria-disabled']).toBe(true);
|
|
177
|
+
});
|
|
178
|
+
});
|