instant-cli 1.0.33 → 1.0.34-branch-webhooks-docs.25934100514.1
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/.turbo/turbo-build.log +1 -1
- package/__tests__/multiSelect.test.ts +236 -0
- package/__tests__/select.test.ts +224 -0
- package/__tests__/webhooks.test.ts +728 -0
- package/dist/commands/webhooks/add.d.ts +9 -0
- package/dist/commands/webhooks/add.d.ts.map +1 -0
- package/dist/commands/webhooks/add.js +75 -0
- package/dist/commands/webhooks/add.js.map +1 -0
- package/dist/commands/webhooks/delete.d.ts +6 -0
- package/dist/commands/webhooks/delete.d.ts.map +1 -0
- package/dist/commands/webhooks/delete.js +17 -0
- package/dist/commands/webhooks/delete.js.map +1 -0
- package/dist/commands/webhooks/disable.d.ts +7 -0
- package/dist/commands/webhooks/disable.d.ts.map +1 -0
- package/dist/commands/webhooks/disable.js +18 -0
- package/dist/commands/webhooks/disable.js.map +1 -0
- package/dist/commands/webhooks/enable.d.ts +6 -0
- package/dist/commands/webhooks/enable.d.ts.map +1 -0
- package/dist/commands/webhooks/enable.js +18 -0
- package/dist/commands/webhooks/enable.js.map +1 -0
- package/dist/commands/webhooks/events/list.d.ts +7 -0
- package/dist/commands/webhooks/events/list.d.ts.map +1 -0
- package/dist/commands/webhooks/events/list.js +31 -0
- package/dist/commands/webhooks/events/list.js.map +1 -0
- package/dist/commands/webhooks/events/payload.d.ts +8 -0
- package/dist/commands/webhooks/events/payload.d.ts.map +1 -0
- package/dist/commands/webhooks/events/payload.js +39 -0
- package/dist/commands/webhooks/events/payload.js.map +1 -0
- package/dist/commands/webhooks/events/resend.d.ts +8 -0
- package/dist/commands/webhooks/events/resend.d.ts.map +1 -0
- package/dist/commands/webhooks/events/resend.js +43 -0
- package/dist/commands/webhooks/events/resend.js.map +1 -0
- package/dist/commands/webhooks/list.d.ts +8 -0
- package/dist/commands/webhooks/list.d.ts.map +1 -0
- package/dist/commands/webhooks/list.js +29 -0
- package/dist/commands/webhooks/list.js.map +1 -0
- package/dist/commands/webhooks/shared.d.ts +40 -0
- package/dist/commands/webhooks/shared.d.ts.map +1 -0
- package/dist/commands/webhooks/shared.js +248 -0
- package/dist/commands/webhooks/shared.js.map +1 -0
- package/dist/commands/webhooks/update.d.ts +10 -0
- package/dist/commands/webhooks/update.d.ts.map +1 -0
- package/dist/commands/webhooks/update.js +189 -0
- package/dist/commands/webhooks/update.js.map +1 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +141 -0
- package/dist/index.js.map +1 -1
- package/dist/layer.d.ts +2 -2
- package/dist/layer.d.ts.map +1 -1
- package/dist/layer.js +30 -1
- package/dist/layer.js.map +1 -1
- package/dist/lib/webhooks.d.ts +28 -0
- package/dist/lib/webhooks.d.ts.map +1 -0
- package/dist/lib/webhooks.js +102 -0
- package/dist/lib/webhooks.js.map +1 -0
- package/dist/ui/index.d.ts +39 -1
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/index.js +387 -25
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/lib.d.ts +7 -0
- package/dist/ui/lib.d.ts.map +1 -1
- package/dist/ui/lib.js +40 -1
- package/dist/ui/lib.js.map +1 -1
- package/package.json +4 -4
- package/src/commands/webhooks/add.ts +111 -0
- package/src/commands/webhooks/delete.ts +23 -0
- package/src/commands/webhooks/disable.ts +24 -0
- package/src/commands/webhooks/enable.ts +24 -0
- package/src/commands/webhooks/events/list.ts +38 -0
- package/src/commands/webhooks/events/payload.ts +56 -0
- package/src/commands/webhooks/events/resend.ts +66 -0
- package/src/commands/webhooks/list.ts +41 -0
- package/src/commands/webhooks/shared.ts +339 -0
- package/src/commands/webhooks/update.ts +276 -0
- package/src/index.ts +242 -0
- package/src/layer.ts +33 -1
- package/src/lib/webhooks.ts +127 -0
- package/src/ui/index.ts +465 -32
- package/src/ui/lib.ts +41 -1
package/.turbo/turbo-build.log
CHANGED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { test, expect, describe } from 'vitest';
|
|
2
|
+
import { UI } from '../src/ui/index.ts';
|
|
3
|
+
|
|
4
|
+
const key = (
|
|
5
|
+
name: string,
|
|
6
|
+
overrides: { ctrl?: boolean; meta?: boolean; shift?: boolean } = {},
|
|
7
|
+
) => ({
|
|
8
|
+
sequence: name,
|
|
9
|
+
name,
|
|
10
|
+
ctrl: overrides.ctrl ?? false,
|
|
11
|
+
meta: overrides.meta ?? false,
|
|
12
|
+
shift: overrides.shift ?? false,
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const space = key('space');
|
|
16
|
+
const down = key('down');
|
|
17
|
+
const up = key('up');
|
|
18
|
+
const pageDown = key('pagedown');
|
|
19
|
+
const pageUp = key('pageup');
|
|
20
|
+
const ret = key('return');
|
|
21
|
+
const esc = key('escape');
|
|
22
|
+
const back = key('backspace');
|
|
23
|
+
const ch = (c: string) => ({
|
|
24
|
+
sequence: c,
|
|
25
|
+
name: c,
|
|
26
|
+
ctrl: false,
|
|
27
|
+
meta: false,
|
|
28
|
+
shift: false,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
class FakeTerminal {
|
|
32
|
+
resolved: { data: unknown; status: string } | null = null;
|
|
33
|
+
toggleCursor(_state: 'hide' | 'show') {}
|
|
34
|
+
requestLayout() {}
|
|
35
|
+
setAllowInteraction(_v: boolean) {}
|
|
36
|
+
resolve(value: any) {
|
|
37
|
+
this.resolved = value;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const setup = <T>(props: any) => {
|
|
42
|
+
const ms = new UI.MultiSelect<T>(props);
|
|
43
|
+
const term = new FakeTerminal();
|
|
44
|
+
ms.attach(term as any);
|
|
45
|
+
return { ms, term };
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const strOpts = (...values: string[]) =>
|
|
49
|
+
values.map((v) => ({ value: v, label: v }));
|
|
50
|
+
|
|
51
|
+
describe('MultiSelect navigation', () => {
|
|
52
|
+
test('space toggles current item', () => {
|
|
53
|
+
const { ms } = setup<string>({
|
|
54
|
+
options: strOpts('a', 'b'),
|
|
55
|
+
promptText: 'p',
|
|
56
|
+
});
|
|
57
|
+
ms.input(' ', space);
|
|
58
|
+
expect(ms.result()).toEqual(['a']);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('down moves cursor forward', () => {
|
|
62
|
+
const { ms } = setup<string>({
|
|
63
|
+
options: strOpts('a', 'b'),
|
|
64
|
+
promptText: 'p',
|
|
65
|
+
});
|
|
66
|
+
ms.input(undefined, down);
|
|
67
|
+
ms.input(' ', space);
|
|
68
|
+
expect(ms.result()).toEqual(['b']);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('down wraps from last to first', () => {
|
|
72
|
+
const { ms } = setup<string>({
|
|
73
|
+
options: strOpts('a', 'b'),
|
|
74
|
+
promptText: 'p',
|
|
75
|
+
});
|
|
76
|
+
ms.input(undefined, down);
|
|
77
|
+
ms.input(undefined, down);
|
|
78
|
+
ms.input(' ', space);
|
|
79
|
+
expect(ms.result()).toEqual(['a']);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('up wraps from first to last', () => {
|
|
83
|
+
const { ms } = setup<string>({
|
|
84
|
+
options: strOpts('a', 'b', 'c'),
|
|
85
|
+
promptText: 'p',
|
|
86
|
+
});
|
|
87
|
+
ms.input(undefined, up);
|
|
88
|
+
ms.input(' ', space);
|
|
89
|
+
expect(ms.result()).toEqual(['c']);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('pageDown moves cursor by pageSize', () => {
|
|
93
|
+
const opts = Array.from({ length: 15 }, (_, i) => ({
|
|
94
|
+
value: `o${i}`,
|
|
95
|
+
label: `o${i}`,
|
|
96
|
+
}));
|
|
97
|
+
const { ms } = setup<string>({
|
|
98
|
+
options: opts,
|
|
99
|
+
promptText: 'p',
|
|
100
|
+
pageSize: 5,
|
|
101
|
+
});
|
|
102
|
+
ms.input(undefined, pageDown); // 0 → 5
|
|
103
|
+
ms.input(' ', space);
|
|
104
|
+
expect(ms.result()).toEqual(['o5']);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('pageUp clamps at 0', () => {
|
|
108
|
+
const opts = Array.from({ length: 5 }, (_, i) => ({
|
|
109
|
+
value: `o${i}`,
|
|
110
|
+
label: `o${i}`,
|
|
111
|
+
}));
|
|
112
|
+
const { ms } = setup<string>({
|
|
113
|
+
options: opts,
|
|
114
|
+
promptText: 'p',
|
|
115
|
+
pageSize: 10,
|
|
116
|
+
});
|
|
117
|
+
ms.input(undefined, pageUp);
|
|
118
|
+
ms.input(' ', space);
|
|
119
|
+
expect(ms.result()).toEqual(['o0']);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe('MultiSelect selection', () => {
|
|
124
|
+
test('initialSelected reflects in result', () => {
|
|
125
|
+
const { ms } = setup<string>({
|
|
126
|
+
options: strOpts('a', 'b', 'c'),
|
|
127
|
+
promptText: 'p',
|
|
128
|
+
initialSelected: ['a', 'c'],
|
|
129
|
+
});
|
|
130
|
+
expect(ms.result()).toEqual(['a', 'c']);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test('space twice deselects', () => {
|
|
134
|
+
const { ms } = setup<string>({
|
|
135
|
+
options: strOpts('a'),
|
|
136
|
+
promptText: 'p',
|
|
137
|
+
});
|
|
138
|
+
ms.input(' ', space);
|
|
139
|
+
ms.input(' ', space);
|
|
140
|
+
expect(ms.result()).toEqual([]);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test('result returns in original option order, not selection order', () => {
|
|
144
|
+
const { ms } = setup<string>({
|
|
145
|
+
options: strOpts('a', 'b', 'c'),
|
|
146
|
+
promptText: 'p',
|
|
147
|
+
});
|
|
148
|
+
ms.input(undefined, down);
|
|
149
|
+
ms.input(undefined, down); // cursor at c
|
|
150
|
+
ms.input(' ', space); // select c
|
|
151
|
+
ms.input(undefined, up);
|
|
152
|
+
ms.input(undefined, up); // cursor at a
|
|
153
|
+
ms.input(' ', space); // select a
|
|
154
|
+
ms.input(undefined, down);
|
|
155
|
+
ms.input(' ', space); // select b
|
|
156
|
+
expect(ms.result()).toEqual(['a', 'b', 'c']);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
describe('MultiSelect filtering', () => {
|
|
161
|
+
test('typing narrows visible options; space toggles a visible match', () => {
|
|
162
|
+
const { ms } = setup<string>({
|
|
163
|
+
options: strOpts('posts', 'comments', 'authors'),
|
|
164
|
+
promptText: 'p',
|
|
165
|
+
});
|
|
166
|
+
'posts'.split('').forEach((c) => ms.input(c, ch(c)));
|
|
167
|
+
ms.input(' ', space);
|
|
168
|
+
expect(ms.result()).toEqual(['posts']);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test('filter with no matches: space is a no-op', () => {
|
|
172
|
+
const { ms } = setup<string>({
|
|
173
|
+
options: strOpts('a', 'b'),
|
|
174
|
+
promptText: 'p',
|
|
175
|
+
});
|
|
176
|
+
ms.input('z', ch('z'));
|
|
177
|
+
ms.input(' ', space);
|
|
178
|
+
expect(ms.result()).toEqual([]);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test('backspace shortens filter and restores options', () => {
|
|
182
|
+
const { ms } = setup<string>({
|
|
183
|
+
options: strOpts('posts', 'comments'),
|
|
184
|
+
promptText: 'p',
|
|
185
|
+
});
|
|
186
|
+
ms.input('z', ch('z'));
|
|
187
|
+
ms.input(undefined, back); // filter empty again
|
|
188
|
+
ms.input(' ', space); // toggles posts (first visible)
|
|
189
|
+
expect(ms.result()).toEqual(['posts']);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test('case-insensitive filter by default', () => {
|
|
193
|
+
const { ms } = setup<string>({
|
|
194
|
+
options: strOpts('Posts', 'Comments'),
|
|
195
|
+
promptText: 'p',
|
|
196
|
+
});
|
|
197
|
+
ms.input('p', ch('p'));
|
|
198
|
+
ms.input(' ', space);
|
|
199
|
+
expect(ms.result()).toEqual(['Posts']);
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
describe('MultiSelect submit / cancel', () => {
|
|
204
|
+
test('return submits result when minSelected is satisfied', () => {
|
|
205
|
+
const { ms, term } = setup<string>({
|
|
206
|
+
options: strOpts('a'),
|
|
207
|
+
promptText: 'p',
|
|
208
|
+
minSelected: 1,
|
|
209
|
+
});
|
|
210
|
+
ms.input(' ', space);
|
|
211
|
+
ms.input(undefined, ret);
|
|
212
|
+
expect(term.resolved).toEqual({ data: ['a'], status: 'submitted' });
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test('return does not submit when below minSelected', () => {
|
|
216
|
+
const { ms, term } = setup<string>({
|
|
217
|
+
options: strOpts('a'),
|
|
218
|
+
promptText: 'p',
|
|
219
|
+
minSelected: 1,
|
|
220
|
+
});
|
|
221
|
+
ms.input(undefined, ret);
|
|
222
|
+
expect(term.resolved).toBeNull();
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
test('escape aborts', () => {
|
|
226
|
+
const { ms, term } = setup<string>({
|
|
227
|
+
options: strOpts('a'),
|
|
228
|
+
promptText: 'p',
|
|
229
|
+
});
|
|
230
|
+
ms.input(undefined, esc);
|
|
231
|
+
expect(term.resolved).toEqual({
|
|
232
|
+
data: undefined,
|
|
233
|
+
status: 'aborted',
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
});
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { test, expect, describe } from 'vitest';
|
|
2
|
+
import stripAnsi from 'strip-ansi';
|
|
3
|
+
import { UI } from '../src/ui/index.ts';
|
|
4
|
+
|
|
5
|
+
const key = (name: string) => ({
|
|
6
|
+
sequence: name,
|
|
7
|
+
name,
|
|
8
|
+
ctrl: false,
|
|
9
|
+
meta: false,
|
|
10
|
+
shift: false,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const tab = key('tab');
|
|
14
|
+
const down = key('down');
|
|
15
|
+
const up = key('up');
|
|
16
|
+
|
|
17
|
+
class FakeTerminal {
|
|
18
|
+
toggleCursor(_state: 'hide' | 'show') {}
|
|
19
|
+
requestLayout() {}
|
|
20
|
+
setAllowInteraction(_v: boolean) {}
|
|
21
|
+
resolve(_v: any) {}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const setup = <T>(props: any) => {
|
|
25
|
+
const sel = new UI.Select<T>(props);
|
|
26
|
+
const term = new FakeTerminal();
|
|
27
|
+
sel.attach(term as any);
|
|
28
|
+
return { sel, term };
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const tick = () => new Promise<void>((r) => setImmediate(r));
|
|
32
|
+
|
|
33
|
+
describe('Select expansion', () => {
|
|
34
|
+
test('Tab is a no-op when option has no expandableLabel', () => {
|
|
35
|
+
const { sel } = setup({
|
|
36
|
+
options: [{ value: 'a', label: 'a' }],
|
|
37
|
+
promptText: 'p',
|
|
38
|
+
});
|
|
39
|
+
sel.input(undefined, tab);
|
|
40
|
+
expect(stripAnsi(sel.render('idle'))).not.toContain('expanded');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('Tab shows a static expandableLabel string', () => {
|
|
44
|
+
const { sel } = setup({
|
|
45
|
+
options: [
|
|
46
|
+
{
|
|
47
|
+
value: 'a',
|
|
48
|
+
label: 'a',
|
|
49
|
+
expandableLabel: ' static-detail-line',
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
promptText: 'p',
|
|
53
|
+
});
|
|
54
|
+
expect(stripAnsi(sel.render('idle'))).not.toContain('static-detail-line');
|
|
55
|
+
sel.input(undefined, tab);
|
|
56
|
+
expect(stripAnsi(sel.render('idle'))).toContain('static-detail-line');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('Tab twice collapses', () => {
|
|
60
|
+
const { sel } = setup({
|
|
61
|
+
options: [
|
|
62
|
+
{ value: 'a', label: 'a', expandableLabel: ' static-detail' },
|
|
63
|
+
],
|
|
64
|
+
promptText: 'p',
|
|
65
|
+
});
|
|
66
|
+
sel.input(undefined, tab);
|
|
67
|
+
sel.input(undefined, tab);
|
|
68
|
+
expect(stripAnsi(sel.render('idle'))).not.toContain('static-detail');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('Sticky mode: navigating to another expandable auto-expands it', () => {
|
|
72
|
+
const { sel } = setup({
|
|
73
|
+
options: [
|
|
74
|
+
{ value: 'a', label: 'a-row', expandableLabel: ' a-detail' },
|
|
75
|
+
{ value: 'b', label: 'b-row', expandableLabel: ' b-detail' },
|
|
76
|
+
],
|
|
77
|
+
promptText: 'p',
|
|
78
|
+
});
|
|
79
|
+
sel.input(undefined, tab);
|
|
80
|
+
expect(stripAnsi(sel.render('idle'))).toContain('a-detail');
|
|
81
|
+
sel.input(undefined, down);
|
|
82
|
+
const out = stripAnsi(sel.render('idle'));
|
|
83
|
+
expect(out).not.toContain('a-detail');
|
|
84
|
+
expect(out).toContain('b-detail');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('Sticky mode: navigating to non-expandable hides expansion but stays sticky', () => {
|
|
88
|
+
const { sel } = setup({
|
|
89
|
+
options: [
|
|
90
|
+
{ value: 'a', label: 'a', expandableLabel: ' a-detail' },
|
|
91
|
+
{ value: 'b', label: 'b' },
|
|
92
|
+
],
|
|
93
|
+
promptText: 'p',
|
|
94
|
+
});
|
|
95
|
+
sel.input(undefined, tab);
|
|
96
|
+
sel.input(undefined, down); // move to b, no expansion
|
|
97
|
+
let out = stripAnsi(sel.render('idle'));
|
|
98
|
+
expect(out).not.toContain('a-detail');
|
|
99
|
+
expect(out).toContain('(tab to collapse)');
|
|
100
|
+
sel.input(undefined, up); // back to a — should auto-expand again
|
|
101
|
+
out = stripAnsi(sel.render('idle'));
|
|
102
|
+
expect(out).toContain('a-detail');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('Without Tab, navigation does not auto-expand', () => {
|
|
106
|
+
const { sel } = setup({
|
|
107
|
+
options: [
|
|
108
|
+
{ value: 'a', label: 'a', expandableLabel: ' a-detail' },
|
|
109
|
+
{ value: 'b', label: 'b', expandableLabel: ' b-detail' },
|
|
110
|
+
],
|
|
111
|
+
promptText: 'p',
|
|
112
|
+
});
|
|
113
|
+
sel.input(undefined, down);
|
|
114
|
+
const out = stripAnsi(sel.render('idle'));
|
|
115
|
+
expect(out).not.toContain('a-detail');
|
|
116
|
+
expect(out).not.toContain('b-detail');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test('Async expandableLabel shows Loading then resolves', async () => {
|
|
120
|
+
let resolveFn!: (s: string) => void;
|
|
121
|
+
const promise = new Promise<string>((r) => {
|
|
122
|
+
resolveFn = r;
|
|
123
|
+
});
|
|
124
|
+
const { sel } = setup({
|
|
125
|
+
options: [
|
|
126
|
+
{
|
|
127
|
+
value: 'a',
|
|
128
|
+
label: 'a',
|
|
129
|
+
expandableLabel: () => promise,
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
promptText: 'p',
|
|
133
|
+
});
|
|
134
|
+
sel.input(undefined, tab);
|
|
135
|
+
expect(stripAnsi(sel.render('idle'))).toContain('Loading');
|
|
136
|
+
resolveFn(' resolved-content-here');
|
|
137
|
+
await tick();
|
|
138
|
+
await tick();
|
|
139
|
+
expect(stripAnsi(sel.render('idle'))).toContain('resolved-content-here');
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test('Async expandableLabel caches result across collapse/re-expand', async () => {
|
|
143
|
+
let calls = 0;
|
|
144
|
+
const { sel } = setup({
|
|
145
|
+
options: [
|
|
146
|
+
{
|
|
147
|
+
value: 'a',
|
|
148
|
+
label: 'a',
|
|
149
|
+
expandableLabel: () => {
|
|
150
|
+
calls++;
|
|
151
|
+
return Promise.resolve(' cached');
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
],
|
|
155
|
+
promptText: 'p',
|
|
156
|
+
});
|
|
157
|
+
sel.input(undefined, tab);
|
|
158
|
+
await tick();
|
|
159
|
+
expect(calls).toBe(1);
|
|
160
|
+
sel.input(undefined, tab); // collapse
|
|
161
|
+
sel.input(undefined, tab); // re-expand
|
|
162
|
+
expect(calls).toBe(1);
|
|
163
|
+
expect(stripAnsi(sel.render('idle'))).toContain('cached');
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test('Async expandableLabel rejection shows error in expansion', async () => {
|
|
167
|
+
const { sel } = setup({
|
|
168
|
+
options: [
|
|
169
|
+
{
|
|
170
|
+
value: 'a',
|
|
171
|
+
label: 'a',
|
|
172
|
+
expandableLabel: () => Promise.reject(new Error('boom')),
|
|
173
|
+
},
|
|
174
|
+
],
|
|
175
|
+
promptText: 'p',
|
|
176
|
+
});
|
|
177
|
+
sel.input(undefined, tab);
|
|
178
|
+
await tick();
|
|
179
|
+
await tick();
|
|
180
|
+
expect(stripAnsi(sel.render('idle'))).toMatch(/Error loading.*boom/);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test('Hint shown whenever any option has expandableLabel', () => {
|
|
184
|
+
const { sel } = setup({
|
|
185
|
+
options: [
|
|
186
|
+
{ value: 'a', label: 'a', expandableLabel: ' detail' },
|
|
187
|
+
{ value: 'b', label: 'b' },
|
|
188
|
+
],
|
|
189
|
+
promptText: 'p',
|
|
190
|
+
});
|
|
191
|
+
expect(stripAnsi(sel.render('idle'))).toContain('(tab to expand)');
|
|
192
|
+
sel.input(undefined, tab);
|
|
193
|
+
expect(stripAnsi(sel.render('idle'))).toContain('(tab to collapse)');
|
|
194
|
+
// Moving to a non-expandable row keeps the hint (sticky mode is active)
|
|
195
|
+
sel.input(undefined, down);
|
|
196
|
+
expect(stripAnsi(sel.render('idle'))).toContain('(tab to collapse)');
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test('Hint not shown when no option has expandableLabel', () => {
|
|
200
|
+
const { sel } = setup({
|
|
201
|
+
options: [
|
|
202
|
+
{ value: 'a', label: 'a' },
|
|
203
|
+
{ value: 'b', label: 'b' },
|
|
204
|
+
],
|
|
205
|
+
promptText: 'p',
|
|
206
|
+
});
|
|
207
|
+
expect(stripAnsi(sel.render('idle'))).not.toMatch(/tab to/);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test('Tab only expands the focused row', async () => {
|
|
211
|
+
const { sel } = setup({
|
|
212
|
+
options: [
|
|
213
|
+
{ value: 'a', label: 'a-row', expandableLabel: ' detail-a' },
|
|
214
|
+
{ value: 'b', label: 'b-row', expandableLabel: ' detail-b' },
|
|
215
|
+
],
|
|
216
|
+
promptText: 'p',
|
|
217
|
+
});
|
|
218
|
+
sel.input(undefined, down); // focus b
|
|
219
|
+
sel.input(undefined, tab);
|
|
220
|
+
const out = stripAnsi(sel.render('idle'));
|
|
221
|
+
expect(out).toContain('detail-b');
|
|
222
|
+
expect(out).not.toContain('detail-a');
|
|
223
|
+
});
|
|
224
|
+
});
|