@transferwise/components 0.0.0-experimental-e9426b6 → 0.0.0-experimental-ce46fbc
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/build/dateInput/DateInput.js +3 -6
- package/build/dateInput/DateInput.js.map +1 -1
- package/build/dateInput/DateInput.mjs +2 -5
- package/build/dateInput/DateInput.mjs.map +1 -1
- package/build/expressiveMoneyInput/currencySelector/CurrencySelector.js +3 -5
- package/build/expressiveMoneyInput/currencySelector/CurrencySelector.js.map +1 -1
- package/build/expressiveMoneyInput/currencySelector/CurrencySelector.mjs +1 -3
- package/build/expressiveMoneyInput/currencySelector/CurrencySelector.mjs.map +1 -1
- package/build/index.js +3 -5
- package/build/index.js.map +1 -1
- package/build/index.mjs +1 -3
- package/build/index.mjs.map +1 -1
- package/build/inputs/SelectInput.js +821 -0
- package/build/inputs/SelectInput.js.map +1 -0
- package/build/inputs/SelectInput.messages.js.map +1 -0
- package/build/inputs/SelectInput.messages.mjs.map +1 -0
- package/build/inputs/SelectInput.mjs +813 -0
- package/build/inputs/SelectInput.mjs.map +1 -0
- package/build/main.css +47 -47
- package/build/moneyInput/MoneyInput.js +2 -5
- package/build/moneyInput/MoneyInput.js.map +1 -1
- package/build/moneyInput/MoneyInput.mjs +1 -4
- package/build/moneyInput/MoneyInput.mjs.map +1 -1
- package/build/phoneNumberInput/PhoneNumberInput.js +2 -5
- package/build/phoneNumberInput/PhoneNumberInput.js.map +1 -1
- package/build/phoneNumberInput/PhoneNumberInput.mjs +1 -4
- package/build/phoneNumberInput/PhoneNumberInput.mjs.map +1 -1
- package/build/styles/main.css +47 -47
- package/build/types/inputs/{SelectInput/SelectInput.types.d.ts → SelectInput.d.ts} +7 -4
- package/build/types/inputs/SelectInput.d.ts.map +1 -0
- package/build/types/inputs/SelectInput.messages.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/inputs/{SelectInput/SelectInput.docs.mdx → SelectInput.docs.mdx} +1 -0
- package/src/inputs/SelectInput.less +219 -0
- package/src/inputs/{SelectInput/SelectInput.story.tsx → SelectInput.story.tsx} +7 -7
- package/src/inputs/SelectInput.tsx +1190 -0
- package/src/listItem/_stories/ListItem.story.tsx +76 -1
- package/src/main.css +47 -47
- package/src/main.less +1 -1
- package/build/inputs/SelectInput/SelectInput.helpers.js +0 -115
- package/build/inputs/SelectInput/SelectInput.helpers.js.map +0 -1
- package/build/inputs/SelectInput/SelectInput.helpers.mjs +0 -109
- package/build/inputs/SelectInput/SelectInput.helpers.mjs.map +0 -1
- package/build/inputs/SelectInput/SelectInput.js +0 -216
- package/build/inputs/SelectInput/SelectInput.js.map +0 -1
- package/build/inputs/SelectInput/SelectInput.messages.js.map +0 -1
- package/build/inputs/SelectInput/SelectInput.messages.mjs.map +0 -1
- package/build/inputs/SelectInput/SelectInput.mjs +0 -210
- package/build/inputs/SelectInput/SelectInput.mjs.map +0 -1
- package/build/inputs/SelectInput/components/SelectInputClearButton/SelectInputClearButton.js +0 -26
- package/build/inputs/SelectInput/components/SelectInputClearButton/SelectInputClearButton.js.map +0 -1
- package/build/inputs/SelectInput/components/SelectInputClearButton/SelectInputClearButton.mjs +0 -24
- package/build/inputs/SelectInput/components/SelectInputClearButton/SelectInputClearButton.mjs.map +0 -1
- package/build/inputs/SelectInput/components/SelectInputDefaultTrigger/SelectInputDefaultTrigger.js +0 -54
- package/build/inputs/SelectInput/components/SelectInputDefaultTrigger/SelectInputDefaultTrigger.js.map +0 -1
- package/build/inputs/SelectInput/components/SelectInputDefaultTrigger/SelectInputDefaultTrigger.mjs +0 -52
- package/build/inputs/SelectInput/components/SelectInputDefaultTrigger/SelectInputDefaultTrigger.mjs.map +0 -1
- package/build/inputs/SelectInput/components/SelectInputGroupItemView/SelectInputGroupItemView.js +0 -50
- package/build/inputs/SelectInput/components/SelectInputGroupItemView/SelectInputGroupItemView.js.map +0 -1
- package/build/inputs/SelectInput/components/SelectInputGroupItemView/SelectInputGroupItemView.mjs +0 -48
- package/build/inputs/SelectInput/components/SelectInputGroupItemView/SelectInputGroupItemView.mjs.map +0 -1
- package/build/inputs/SelectInput/components/SelectInputItemView/SelectInputItemView.js +0 -47
- package/build/inputs/SelectInput/components/SelectInputItemView/SelectInputItemView.js.map +0 -1
- package/build/inputs/SelectInput/components/SelectInputItemView/SelectInputItemView.mjs +0 -45
- package/build/inputs/SelectInput/components/SelectInputItemView/SelectInputItemView.mjs.map +0 -1
- package/build/inputs/SelectInput/components/SelectInputOption/SelectInputOption.js +0 -45
- package/build/inputs/SelectInput/components/SelectInputOption/SelectInputOption.js.map +0 -1
- package/build/inputs/SelectInput/components/SelectInputOption/SelectInputOption.mjs +0 -41
- package/build/inputs/SelectInput/components/SelectInputOption/SelectInputOption.mjs.map +0 -1
- package/build/inputs/SelectInput/components/SelectInputOptionContent/SelectInputOptionContent.js +0 -41
- package/build/inputs/SelectInput/components/SelectInputOptionContent/SelectInputOptionContent.js.map +0 -1
- package/build/inputs/SelectInput/components/SelectInputOptionContent/SelectInputOptionContent.mjs +0 -38
- package/build/inputs/SelectInput/components/SelectInputOptionContent/SelectInputOptionContent.mjs.map +0 -1
- package/build/inputs/SelectInput/components/SelectInputOptions/SelectInputOptions.js +0 -270
- package/build/inputs/SelectInput/components/SelectInputOptions/SelectInputOptions.js.map +0 -1
- package/build/inputs/SelectInput/components/SelectInputOptions/SelectInputOptions.mjs +0 -268
- package/build/inputs/SelectInput/components/SelectInputOptions/SelectInputOptions.mjs.map +0 -1
- package/build/inputs/SelectInput/components/SelectInputOptionsContainer/SelectInputOptionsContainer.js +0 -48
- package/build/inputs/SelectInput/components/SelectInputOptionsContainer/SelectInputOptionsContainer.js.map +0 -1
- package/build/inputs/SelectInput/components/SelectInputOptionsContainer/SelectInputOptionsContainer.mjs +0 -46
- package/build/inputs/SelectInput/components/SelectInputOptionsContainer/SelectInputOptionsContainer.mjs.map +0 -1
- package/build/inputs/SelectInput/components/SelectInputTriggerButton/SelectInputTriggerButton.js +0 -41
- package/build/inputs/SelectInput/components/SelectInputTriggerButton/SelectInputTriggerButton.js.map +0 -1
- package/build/inputs/SelectInput/components/SelectInputTriggerButton/SelectInputTriggerButton.mjs +0 -34
- package/build/inputs/SelectInput/components/SelectInputTriggerButton/SelectInputTriggerButton.mjs.map +0 -1
- package/build/styles/inputs/SelectInput/components/SelectInputDefaultTrigger/SelectInputDefaultTrigger.css +0 -17
- package/build/styles/inputs/SelectInput/components/SelectInputItemView/SelectInputItemView.css +0 -16
- package/build/styles/inputs/SelectInput/components/SelectInputOption/SelectInputOption.css +0 -33
- package/build/styles/inputs/SelectInput/components/SelectInputOptionContent/SelectInputOptionContent.css +0 -37
- package/build/types/inputs/SelectInput/SelectInput.d.ts +0 -3
- package/build/types/inputs/SelectInput/SelectInput.d.ts.map +0 -1
- package/build/types/inputs/SelectInput/SelectInput.helpers.d.ts +0 -28
- package/build/types/inputs/SelectInput/SelectInput.helpers.d.ts.map +0 -1
- package/build/types/inputs/SelectInput/SelectInput.messages.d.ts.map +0 -1
- package/build/types/inputs/SelectInput/SelectInput.types.d.ts.map +0 -1
- package/build/types/inputs/SelectInput/components/SelectInputClearButton/SelectInputClearButton.d.ts +0 -5
- package/build/types/inputs/SelectInput/components/SelectInputClearButton/SelectInputClearButton.d.ts.map +0 -1
- package/build/types/inputs/SelectInput/components/SelectInputClearButton/index.d.ts +0 -2
- package/build/types/inputs/SelectInput/components/SelectInputClearButton/index.d.ts.map +0 -1
- package/build/types/inputs/SelectInput/components/SelectInputDefaultTrigger/SelectInputDefaultTrigger.d.ts +0 -9
- package/build/types/inputs/SelectInput/components/SelectInputDefaultTrigger/SelectInputDefaultTrigger.d.ts.map +0 -1
- package/build/types/inputs/SelectInput/components/SelectInputDefaultTrigger/index.d.ts +0 -2
- package/build/types/inputs/SelectInput/components/SelectInputDefaultTrigger/index.d.ts.map +0 -1
- package/build/types/inputs/SelectInput/components/SelectInputGroupItemView/SelectInputGroupItemView.d.ts +0 -9
- package/build/types/inputs/SelectInput/components/SelectInputGroupItemView/SelectInputGroupItemView.d.ts.map +0 -1
- package/build/types/inputs/SelectInput/components/SelectInputGroupItemView/index.d.ts +0 -2
- package/build/types/inputs/SelectInput/components/SelectInputGroupItemView/index.d.ts.map +0 -1
- package/build/types/inputs/SelectInput/components/SelectInputItemView/SelectInputItemView.d.ts +0 -8
- package/build/types/inputs/SelectInput/components/SelectInputItemView/SelectInputItemView.d.ts.map +0 -1
- package/build/types/inputs/SelectInput/components/SelectInputItemView/index.d.ts +0 -2
- package/build/types/inputs/SelectInput/components/SelectInputItemView/index.d.ts.map +0 -1
- package/build/types/inputs/SelectInput/components/SelectInputOption/SelectInputOption.d.ts +0 -10
- package/build/types/inputs/SelectInput/components/SelectInputOption/SelectInputOption.d.ts.map +0 -1
- package/build/types/inputs/SelectInput/components/SelectInputOption/index.d.ts +0 -2
- package/build/types/inputs/SelectInput/components/SelectInputOption/index.d.ts.map +0 -1
- package/build/types/inputs/SelectInput/components/SelectInputOptionContent/SelectInputOptionContent.d.ts +0 -9
- package/build/types/inputs/SelectInput/components/SelectInputOptionContent/SelectInputOptionContent.d.ts.map +0 -1
- package/build/types/inputs/SelectInput/components/SelectInputOptionContent/index.d.ts +0 -3
- package/build/types/inputs/SelectInput/components/SelectInputOptionContent/index.d.ts.map +0 -1
- package/build/types/inputs/SelectInput/components/SelectInputOptions/SelectInputOptions.d.ts +0 -15
- package/build/types/inputs/SelectInput/components/SelectInputOptions/SelectInputOptions.d.ts.map +0 -1
- package/build/types/inputs/SelectInput/components/SelectInputOptions/index.d.ts +0 -2
- package/build/types/inputs/SelectInput/components/SelectInputOptions/index.d.ts.map +0 -1
- package/build/types/inputs/SelectInput/components/SelectInputOptionsContainer/SelectInputOptionsContainer.d.ts +0 -6
- package/build/types/inputs/SelectInput/components/SelectInputOptionsContainer/SelectInputOptionsContainer.d.ts.map +0 -1
- package/build/types/inputs/SelectInput/components/SelectInputOptionsContainer/index.d.ts +0 -2
- package/build/types/inputs/SelectInput/components/SelectInputOptionsContainer/index.d.ts.map +0 -1
- package/build/types/inputs/SelectInput/components/SelectInputTriggerButton/SelectInputTriggerButton.d.ts +0 -15
- package/build/types/inputs/SelectInput/components/SelectInputTriggerButton/SelectInputTriggerButton.d.ts.map +0 -1
- package/build/types/inputs/SelectInput/components/SelectInputTriggerButton/index.d.ts +0 -3
- package/build/types/inputs/SelectInput/components/SelectInputTriggerButton/index.d.ts.map +0 -1
- package/build/types/inputs/SelectInput/index.d.ts +0 -5
- package/build/types/inputs/SelectInput/index.d.ts.map +0 -1
- package/src/inputs/SelectInput/SelectInput.helpers.ts +0 -152
- package/src/inputs/SelectInput/SelectInput.less +0 -42
- package/src/inputs/SelectInput/SelectInput.test.tsx +0 -606
- package/src/inputs/SelectInput/SelectInput.tsx +0 -247
- package/src/inputs/SelectInput/SelectInput.types.ts +0 -114
- package/src/inputs/SelectInput/components/SelectInputClearButton/SelectInputClearButton.tsx +0 -25
- package/src/inputs/SelectInput/components/SelectInputClearButton/index.ts +0 -1
- package/src/inputs/SelectInput/components/SelectInputDefaultTrigger/SelectInputDefaultTrigger.css +0 -17
- package/src/inputs/SelectInput/components/SelectInputDefaultTrigger/SelectInputDefaultTrigger.less +0 -15
- package/src/inputs/SelectInput/components/SelectInputDefaultTrigger/SelectInputDefaultTrigger.tsx +0 -56
- package/src/inputs/SelectInput/components/SelectInputDefaultTrigger/index.ts +0 -1
- package/src/inputs/SelectInput/components/SelectInputGroupItemView/SelectInputGroupItemView.tsx +0 -64
- package/src/inputs/SelectInput/components/SelectInputGroupItemView/index.ts +0 -1
- package/src/inputs/SelectInput/components/SelectInputItemView/SelectInputItemView.css +0 -16
- package/src/inputs/SelectInput/components/SelectInputItemView/SelectInputItemView.less +0 -17
- package/src/inputs/SelectInput/components/SelectInputItemView/SelectInputItemView.tsx +0 -55
- package/src/inputs/SelectInput/components/SelectInputItemView/index.ts +0 -1
- package/src/inputs/SelectInput/components/SelectInputOption/SelectInputOption.css +0 -33
- package/src/inputs/SelectInput/components/SelectInputOption/SelectInputOption.less +0 -32
- package/src/inputs/SelectInput/components/SelectInputOption/SelectInputOption.tsx +0 -51
- package/src/inputs/SelectInput/components/SelectInputOption/index.ts +0 -5
- package/src/inputs/SelectInput/components/SelectInputOptionContent/SelectInputOptionContent.css +0 -37
- package/src/inputs/SelectInput/components/SelectInputOptionContent/SelectInputOptionContent.less +0 -38
- package/src/inputs/SelectInput/components/SelectInputOptionContent/SelectInputOptionContent.tsx +0 -67
- package/src/inputs/SelectInput/components/SelectInputOptionContent/index.ts +0 -5
- package/src/inputs/SelectInput/components/SelectInputOptions/SelectInputOptions.less +0 -75
- package/src/inputs/SelectInput/components/SelectInputOptions/SelectInputOptions.tsx +0 -369
- package/src/inputs/SelectInput/components/SelectInputOptions/index.ts +0 -1
- package/src/inputs/SelectInput/components/SelectInputOptionsContainer/SelectInputOptionsContainer.tsx +0 -56
- package/src/inputs/SelectInput/components/SelectInputOptionsContainer/index.ts +0 -1
- package/src/inputs/SelectInput/components/SelectInputTriggerButton/SelectInputTriggerButton.tsx +0 -39
- package/src/inputs/SelectInput/components/SelectInputTriggerButton/index.ts +0 -5
- package/src/inputs/SelectInput/index.ts +0 -13
- package/build/inputs/{SelectInput/SelectInput.messages.js → SelectInput.messages.js} +0 -0
- package/build/inputs/{SelectInput/SelectInput.messages.mjs → SelectInput.messages.mjs} +0 -0
- package/{src/inputs/SelectInput → build/styles/inputs}/SelectInput.css +47 -47
- package/build/types/inputs/{SelectInput/SelectInput.messages.d.ts → SelectInput.messages.d.ts} +0 -0
- package/{build/styles/inputs/SelectInput → src/inputs}/SelectInput.css +47 -47
- /package/src/inputs/{SelectInput/SelectInput.messages.ts → SelectInput.messages.ts} +0 -0
|
@@ -1,606 +0,0 @@
|
|
|
1
|
-
import { screen, waitFor, within } from '@testing-library/react';
|
|
2
|
-
import { userEvent } from '@testing-library/user-event';
|
|
3
|
-
import { mockAnimationsApi } from 'jsdom-testing-mocks';
|
|
4
|
-
|
|
5
|
-
import { render, mockMatchMedia, mockResizeObserver } from '../../test-utils';
|
|
6
|
-
|
|
7
|
-
import { SelectInput, type SelectInputOptionItem, type SelectInputProps } from '.';
|
|
8
|
-
import { Field } from '../../field/Field';
|
|
9
|
-
|
|
10
|
-
mockMatchMedia();
|
|
11
|
-
mockResizeObserver();
|
|
12
|
-
mockAnimationsApi();
|
|
13
|
-
|
|
14
|
-
describe('SelectInput', () => {
|
|
15
|
-
it('renders placeholder', () => {
|
|
16
|
-
render(
|
|
17
|
-
<SelectInput
|
|
18
|
-
placeholder="Currency"
|
|
19
|
-
items={[
|
|
20
|
-
{ type: 'option', value: 'USD' },
|
|
21
|
-
{ type: 'option', value: 'EUR' },
|
|
22
|
-
]}
|
|
23
|
-
/>,
|
|
24
|
-
);
|
|
25
|
-
|
|
26
|
-
expect(screen.getByText('Currency')).toBeInTheDocument();
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('renders footer', async () => {
|
|
30
|
-
render(
|
|
31
|
-
<SelectInput
|
|
32
|
-
items={[
|
|
33
|
-
{ type: 'option', value: 'USD' },
|
|
34
|
-
{ type: 'option', value: 'EUR' },
|
|
35
|
-
]}
|
|
36
|
-
renderFooter={({ queryNormalized: normalizedQuery }) =>
|
|
37
|
-
normalizedQuery != null ? (
|
|
38
|
-
<>Showing results for ‘{normalizedQuery}’</>
|
|
39
|
-
) : (
|
|
40
|
-
<>All items shown</>
|
|
41
|
-
)
|
|
42
|
-
}
|
|
43
|
-
filterable
|
|
44
|
-
/>,
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
await userEvent.tab();
|
|
48
|
-
await userEvent.keyboard(' ');
|
|
49
|
-
|
|
50
|
-
const footer = screen.getByText('All items shown');
|
|
51
|
-
expect(footer).toBeInTheDocument();
|
|
52
|
-
|
|
53
|
-
await userEvent.keyboard('u');
|
|
54
|
-
expect(footer).toHaveTextContent(/‘u’$/);
|
|
55
|
-
|
|
56
|
-
await userEvent.keyboard('r');
|
|
57
|
-
expect(footer).toHaveTextContent(/‘ur’$/);
|
|
58
|
-
|
|
59
|
-
await userEvent.keyboard('x');
|
|
60
|
-
expect(footer).toHaveTextContent(/‘urx’$/);
|
|
61
|
-
|
|
62
|
-
await userEvent.keyboard('{Backspace}');
|
|
63
|
-
expect(footer).toHaveTextContent(/‘ur’$/);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it('allows navigating the listbox with cursors', async () => {
|
|
67
|
-
render(
|
|
68
|
-
<SelectInput
|
|
69
|
-
items={[
|
|
70
|
-
{ type: 'option', value: 'GBP' },
|
|
71
|
-
{ type: 'option', value: 'EUR' },
|
|
72
|
-
{ type: 'option', value: 'USD' },
|
|
73
|
-
]}
|
|
74
|
-
renderFooter={({ queryNormalized: normalizedQuery }) => (
|
|
75
|
-
<button type="button">Footer button</button>
|
|
76
|
-
)}
|
|
77
|
-
filterable
|
|
78
|
-
/>,
|
|
79
|
-
);
|
|
80
|
-
|
|
81
|
-
// opened the dropbox, with search focused
|
|
82
|
-
await userEvent.tab();
|
|
83
|
-
await userEvent.keyboard(' ');
|
|
84
|
-
expect(screen.getByRole('combobox')).toHaveFocus();
|
|
85
|
-
|
|
86
|
-
// search still focused but listbox can be navigated via keyboard
|
|
87
|
-
await userEvent.keyboard('{ArrowDown}');
|
|
88
|
-
expect(screen.getByRole('combobox')).toHaveFocus();
|
|
89
|
-
expect(screen.getByRole('option', { name: 'EUR' })).toHaveClass(
|
|
90
|
-
'np-select-input-option-container--active',
|
|
91
|
-
);
|
|
92
|
-
|
|
93
|
-
// tab moves focus to listbox
|
|
94
|
-
await userEvent.tab();
|
|
95
|
-
expect(screen.getByRole('listbox')).toHaveFocus();
|
|
96
|
-
expect(screen.getByRole('combobox')).not.toHaveFocus();
|
|
97
|
-
|
|
98
|
-
// arrows still navigate within listbox
|
|
99
|
-
await userEvent.keyboard('{ArrowDown}');
|
|
100
|
-
expect(screen.getByRole('option', { name: 'USD' })).toHaveClass(
|
|
101
|
-
'np-select-input-option-container--active',
|
|
102
|
-
);
|
|
103
|
-
|
|
104
|
-
// tab moves focus to footer but highlighted option is retained
|
|
105
|
-
await userEvent.tab();
|
|
106
|
-
expect(screen.getByRole('listbox')).not.toHaveFocus();
|
|
107
|
-
expect(screen.getByRole('combobox')).not.toHaveFocus();
|
|
108
|
-
expect(screen.getByText('Footer button')).toHaveFocus();
|
|
109
|
-
expect(screen.getByRole('option', { name: 'USD' })).toHaveClass(
|
|
110
|
-
'np-select-input-option-container--active',
|
|
111
|
-
);
|
|
112
|
-
|
|
113
|
-
// shift+tab moves focus back to listbox
|
|
114
|
-
await userEvent.tab({ shift: true });
|
|
115
|
-
expect(screen.getByRole('listbox')).toHaveFocus();
|
|
116
|
-
expect(screen.getByRole('combobox')).not.toHaveFocus();
|
|
117
|
-
expect(screen.getByText('Footer button')).not.toHaveFocus();
|
|
118
|
-
|
|
119
|
-
// previously highlighted option is still active within listbox
|
|
120
|
-
expect(screen.getByRole('option', { name: 'USD' })).toHaveClass(
|
|
121
|
-
'np-select-input-option-container--active',
|
|
122
|
-
);
|
|
123
|
-
|
|
124
|
-
// arrows continue to navigate within listbox
|
|
125
|
-
await userEvent.keyboard('{ArrowUp}');
|
|
126
|
-
expect(screen.getByRole('option', { name: 'EUR' })).toHaveClass(
|
|
127
|
-
'np-select-input-option-container--active',
|
|
128
|
-
);
|
|
129
|
-
|
|
130
|
-
// shift+tab moves focus back to search input
|
|
131
|
-
await userEvent.tab({ shift: true });
|
|
132
|
-
expect(screen.getByRole('combobox')).toHaveFocus();
|
|
133
|
-
|
|
134
|
-
// arrows continue to navigate within listbox
|
|
135
|
-
await userEvent.keyboard('{ArrowUp}');
|
|
136
|
-
expect(screen.getByRole('option', { name: 'GBP' })).toHaveClass(
|
|
137
|
-
'np-select-input-option-container--active',
|
|
138
|
-
);
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it('shows item selected via mouse', async () => {
|
|
142
|
-
const handleClose = jest.fn();
|
|
143
|
-
|
|
144
|
-
render(
|
|
145
|
-
<SelectInput
|
|
146
|
-
items={[
|
|
147
|
-
{ type: 'option', value: 'USD' },
|
|
148
|
-
{ type: 'option', value: 'EUR' },
|
|
149
|
-
]}
|
|
150
|
-
onClose={handleClose}
|
|
151
|
-
/>,
|
|
152
|
-
);
|
|
153
|
-
|
|
154
|
-
expect(screen.queryByText('EUR')).not.toBeInTheDocument();
|
|
155
|
-
|
|
156
|
-
const trigger = screen.getByRole('combobox');
|
|
157
|
-
await userEvent.click(trigger);
|
|
158
|
-
|
|
159
|
-
expect(handleClose).not.toHaveBeenCalled();
|
|
160
|
-
|
|
161
|
-
const listbox = screen.getByRole('listbox');
|
|
162
|
-
const option = within(listbox).getByRole('option', { name: 'EUR' });
|
|
163
|
-
await userEvent.click(option);
|
|
164
|
-
|
|
165
|
-
expect(handleClose).toHaveBeenCalledTimes(1);
|
|
166
|
-
expect(trigger).toHaveTextContent('EUR');
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
it('filters items via keyboard', async () => {
|
|
170
|
-
const handleClose = jest.fn();
|
|
171
|
-
|
|
172
|
-
render(
|
|
173
|
-
<SelectInput
|
|
174
|
-
items={[
|
|
175
|
-
{
|
|
176
|
-
type: 'group',
|
|
177
|
-
label: 'Popular currencies',
|
|
178
|
-
options: [
|
|
179
|
-
{ type: 'option', value: 'USD' },
|
|
180
|
-
{ type: 'option', value: 'EUR' },
|
|
181
|
-
{ type: 'option', value: 'GBP' },
|
|
182
|
-
],
|
|
183
|
-
},
|
|
184
|
-
]}
|
|
185
|
-
filterable
|
|
186
|
-
onClose={handleClose}
|
|
187
|
-
/>,
|
|
188
|
-
);
|
|
189
|
-
|
|
190
|
-
const trigger = screen.getByRole('combobox');
|
|
191
|
-
await userEvent.tab();
|
|
192
|
-
await userEvent.keyboard(' ');
|
|
193
|
-
|
|
194
|
-
expect(handleClose).not.toHaveBeenCalled();
|
|
195
|
-
|
|
196
|
-
const listbox = screen.getByRole('listbox');
|
|
197
|
-
expect(within(listbox).getAllByRole('option')).toHaveLength(3);
|
|
198
|
-
|
|
199
|
-
await userEvent.keyboard('u');
|
|
200
|
-
expect(within(listbox).getAllByRole('option')).toHaveLength(2);
|
|
201
|
-
|
|
202
|
-
await userEvent.keyboard('r');
|
|
203
|
-
expect(within(listbox).getByRole('option')).toBeInTheDocument();
|
|
204
|
-
|
|
205
|
-
await userEvent.keyboard('x');
|
|
206
|
-
expect(within(listbox).queryByRole('option')).not.toBeInTheDocument();
|
|
207
|
-
|
|
208
|
-
await userEvent.keyboard('{Backspace}');
|
|
209
|
-
expect(within(listbox).getByRole('option')).toBeInTheDocument();
|
|
210
|
-
|
|
211
|
-
const option = within(listbox).getAllByRole('option')[0];
|
|
212
|
-
await userEvent.click(option);
|
|
213
|
-
|
|
214
|
-
expect(handleClose).toHaveBeenCalledTimes(1);
|
|
215
|
-
expect(trigger).toHaveTextContent('EUR');
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
it('clears filter query on close', async () => {
|
|
219
|
-
const handleFilterChange = jest.fn();
|
|
220
|
-
|
|
221
|
-
render(
|
|
222
|
-
<SelectInput
|
|
223
|
-
items={[
|
|
224
|
-
{ type: 'option', value: 'USD' },
|
|
225
|
-
{ type: 'option', value: 'EUR' },
|
|
226
|
-
]}
|
|
227
|
-
filterable
|
|
228
|
-
onFilterChange={handleFilterChange}
|
|
229
|
-
/>,
|
|
230
|
-
);
|
|
231
|
-
|
|
232
|
-
const trigger = screen.getByRole('combobox');
|
|
233
|
-
await userEvent.tab();
|
|
234
|
-
await userEvent.keyboard(' ');
|
|
235
|
-
|
|
236
|
-
expect(handleFilterChange).not.toHaveBeenCalled();
|
|
237
|
-
|
|
238
|
-
await userEvent.keyboard(' x');
|
|
239
|
-
expect(handleFilterChange).toHaveBeenLastCalledWith({
|
|
240
|
-
query: ' x',
|
|
241
|
-
queryNormalized: 'x',
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
await userEvent.keyboard('{Escape}');
|
|
245
|
-
await waitFor(() => {
|
|
246
|
-
expect(handleFilterChange).toHaveBeenLastCalledWith({
|
|
247
|
-
query: '',
|
|
248
|
-
queryNormalized: null,
|
|
249
|
-
});
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
await userEvent.click(trigger);
|
|
253
|
-
|
|
254
|
-
const listbox = screen.getByRole('listbox');
|
|
255
|
-
expect(within(listbox).getAllByRole('option')).toHaveLength(2);
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
it('filters items ignoring diacritics/accents', async () => {
|
|
259
|
-
render(
|
|
260
|
-
<SelectInput
|
|
261
|
-
items={[
|
|
262
|
-
{ type: 'option', value: 'AX', filterMatchers: ['Åland Islands'] },
|
|
263
|
-
{ type: 'option', value: 'AL', filterMatchers: ['Albania'] },
|
|
264
|
-
{ type: 'option', value: 'DZ', filterMatchers: ['Algeria'] },
|
|
265
|
-
{ type: 'option', value: 'RE', filterMatchers: ['Réunion'] },
|
|
266
|
-
]}
|
|
267
|
-
filterable
|
|
268
|
-
/>,
|
|
269
|
-
);
|
|
270
|
-
|
|
271
|
-
await userEvent.tab();
|
|
272
|
-
await userEvent.keyboard(' ');
|
|
273
|
-
|
|
274
|
-
const listbox = screen.getByRole('listbox');
|
|
275
|
-
expect(within(listbox).getAllByRole('option')).toHaveLength(4);
|
|
276
|
-
|
|
277
|
-
await userEvent.keyboard('aland');
|
|
278
|
-
expect(within(listbox).getAllByRole('option')).toHaveLength(1);
|
|
279
|
-
expect(within(listbox).getByRole('option')).toHaveTextContent('AX');
|
|
280
|
-
|
|
281
|
-
const searchInput = screen.getByRole('combobox', { expanded: true });
|
|
282
|
-
await userEvent.clear(searchInput);
|
|
283
|
-
await userEvent.keyboard('reunion');
|
|
284
|
-
expect(within(listbox).getAllByRole('option')).toHaveLength(1);
|
|
285
|
-
expect(within(listbox).getByRole('option')).toHaveTextContent('RE');
|
|
286
|
-
|
|
287
|
-
await userEvent.clear(searchInput);
|
|
288
|
-
await userEvent.keyboard('Åland');
|
|
289
|
-
expect(within(listbox).getAllByRole('option')).toHaveLength(1);
|
|
290
|
-
expect(within(listbox).getByRole('option')).toHaveTextContent('AX');
|
|
291
|
-
|
|
292
|
-
await userEvent.clear(searchInput);
|
|
293
|
-
await userEvent.keyboard('Rèunion');
|
|
294
|
-
expect(within(listbox).getAllByRole('option')).toHaveLength(1);
|
|
295
|
-
expect(within(listbox).getByRole('option')).toHaveTextContent('RE');
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
it('selects multiple options', async () => {
|
|
299
|
-
render(
|
|
300
|
-
<SelectInput
|
|
301
|
-
multiple
|
|
302
|
-
items={[
|
|
303
|
-
{ type: 'option', value: 'USD' },
|
|
304
|
-
{ type: 'option', value: 'EUR' },
|
|
305
|
-
]}
|
|
306
|
-
/>,
|
|
307
|
-
);
|
|
308
|
-
|
|
309
|
-
const trigger = screen.getByRole('combobox');
|
|
310
|
-
await userEvent.click(trigger);
|
|
311
|
-
|
|
312
|
-
const listbox = screen.getByRole('listbox');
|
|
313
|
-
const options = within(listbox).getAllByRole('option');
|
|
314
|
-
for (const option of options) {
|
|
315
|
-
await userEvent.click(option);
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
expect(trigger).toHaveTextContent('USD, EUR');
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
it('supports custom `id` attribute', () => {
|
|
322
|
-
render(<SelectInput id="custom" items={[]} />);
|
|
323
|
-
|
|
324
|
-
const trigger = screen.getByRole('combobox');
|
|
325
|
-
expect(trigger).toHaveAttribute('id', 'custom');
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
it('supports `Field` for labeling', () => {
|
|
329
|
-
render(
|
|
330
|
-
<Field label="Currency">
|
|
331
|
-
<SelectInput items={[{ type: 'option', value: 'USD' }]} value="USD" />
|
|
332
|
-
</Field>,
|
|
333
|
-
);
|
|
334
|
-
expect(screen.getByLabelText(/Currency/)).toHaveAttribute('aria-haspopup');
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
it('deduplicates search results across groups using compareValues as key', async () => {
|
|
338
|
-
interface Currency {
|
|
339
|
-
code: string;
|
|
340
|
-
name: string;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
const usdInGroup1: Currency = { code: 'USD', name: 'US Dollar' };
|
|
344
|
-
const usdInGroup2: Currency = { code: 'USD', name: 'US Dollar' };
|
|
345
|
-
const eur: Currency = { code: 'EUR', name: 'Euro' };
|
|
346
|
-
const gbp: Currency = { code: 'GBP', name: 'British Pound' };
|
|
347
|
-
|
|
348
|
-
render(
|
|
349
|
-
<SelectInput<Currency>
|
|
350
|
-
items={[
|
|
351
|
-
{
|
|
352
|
-
type: 'group',
|
|
353
|
-
label: 'Popular',
|
|
354
|
-
options: [
|
|
355
|
-
{ type: 'option', value: usdInGroup1 },
|
|
356
|
-
{ type: 'option', value: eur },
|
|
357
|
-
],
|
|
358
|
-
},
|
|
359
|
-
{
|
|
360
|
-
type: 'group',
|
|
361
|
-
label: 'All currencies',
|
|
362
|
-
options: [
|
|
363
|
-
{ type: 'option', value: usdInGroup2 },
|
|
364
|
-
{ type: 'option', value: gbp },
|
|
365
|
-
],
|
|
366
|
-
},
|
|
367
|
-
]}
|
|
368
|
-
compareValues="code"
|
|
369
|
-
renderValue={(currency) => currency.name}
|
|
370
|
-
filterable
|
|
371
|
-
/>,
|
|
372
|
-
);
|
|
373
|
-
|
|
374
|
-
const trigger = screen.getByRole('combobox');
|
|
375
|
-
await userEvent.click(trigger);
|
|
376
|
-
|
|
377
|
-
const listbox = screen.getByRole('listbox');
|
|
378
|
-
|
|
379
|
-
// Before filtering, should show all 4 options (no deduplication yet)
|
|
380
|
-
let options = within(listbox).getAllByRole('option');
|
|
381
|
-
expect(options).toHaveLength(4);
|
|
382
|
-
|
|
383
|
-
const usdOptions = within(listbox).getAllByText('US Dollar');
|
|
384
|
-
expect(usdOptions).toHaveLength(2);
|
|
385
|
-
|
|
386
|
-
// Start filtering - type a search query to trigger deduplication
|
|
387
|
-
const searchInput = screen.getByRole('combobox', { expanded: true });
|
|
388
|
-
await userEvent.type(searchInput, 'u');
|
|
389
|
-
|
|
390
|
-
// After filtering with 'u', should show 3 unique options (USD deduplicated, EUR, GBP)
|
|
391
|
-
options = within(listbox).getAllByRole('option');
|
|
392
|
-
expect(options).toHaveLength(3);
|
|
393
|
-
expect(within(listbox).getByText('Euro')).toBeInTheDocument();
|
|
394
|
-
expect(within(listbox).getByText('British Pound')).toBeInTheDocument();
|
|
395
|
-
|
|
396
|
-
// Filter more specifically for 'dollar'
|
|
397
|
-
await userEvent.clear(searchInput);
|
|
398
|
-
await userEvent.type(searchInput, 'dollar');
|
|
399
|
-
|
|
400
|
-
const filteredOptions = within(listbox).getAllByRole('option');
|
|
401
|
-
// Should only show 1 USD option, not 2
|
|
402
|
-
expect(filteredOptions).toHaveLength(1);
|
|
403
|
-
expect(within(listbox).getByText('US Dollar')).toBeInTheDocument();
|
|
404
|
-
});
|
|
405
|
-
|
|
406
|
-
it('deduplicates search results across groups using compareValues as function', async () => {
|
|
407
|
-
interface Item {
|
|
408
|
-
id: number;
|
|
409
|
-
label: string;
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
const item1Group1: Item = { id: 1, label: 'Item One' };
|
|
413
|
-
const item2Group1: Item = { id: 2, label: 'Item Two' };
|
|
414
|
-
|
|
415
|
-
const item1Group2: Item = { id: 1, label: 'Item One' };
|
|
416
|
-
|
|
417
|
-
render(
|
|
418
|
-
<SelectInput<Item>
|
|
419
|
-
items={[
|
|
420
|
-
{
|
|
421
|
-
type: 'group',
|
|
422
|
-
label: 'Group A',
|
|
423
|
-
options: [
|
|
424
|
-
{ type: 'option', value: item1Group1 },
|
|
425
|
-
{ type: 'option', value: item2Group1 },
|
|
426
|
-
],
|
|
427
|
-
},
|
|
428
|
-
{
|
|
429
|
-
type: 'group',
|
|
430
|
-
label: 'Group B',
|
|
431
|
-
options: [{ type: 'option', value: item1Group2 }],
|
|
432
|
-
},
|
|
433
|
-
]}
|
|
434
|
-
compareValues={(a, b) => a?.id === b?.id}
|
|
435
|
-
renderValue={(item) => item.label}
|
|
436
|
-
filterable
|
|
437
|
-
/>,
|
|
438
|
-
);
|
|
439
|
-
|
|
440
|
-
const trigger = screen.getByRole('combobox');
|
|
441
|
-
await userEvent.click(trigger);
|
|
442
|
-
|
|
443
|
-
const listbox = screen.getByRole('listbox');
|
|
444
|
-
|
|
445
|
-
// Before filtering, should show all 3 options (no deduplication yet)
|
|
446
|
-
let options = within(listbox).getAllByRole('option');
|
|
447
|
-
expect(options).toHaveLength(3);
|
|
448
|
-
|
|
449
|
-
// Start filtering - type a search query to trigger deduplication
|
|
450
|
-
const searchInput = screen.getByRole('combobox', { expanded: true });
|
|
451
|
-
await userEvent.type(searchInput, 'item');
|
|
452
|
-
|
|
453
|
-
// After filtering, should show 2 unique options (item with id:1 deduplicated, item with id:2)
|
|
454
|
-
options = within(listbox).getAllByRole('option');
|
|
455
|
-
expect(options).toHaveLength(2);
|
|
456
|
-
expect(within(listbox).getByText('Item One')).toBeInTheDocument();
|
|
457
|
-
expect(within(listbox).getByText('Item Two')).toBeInTheDocument();
|
|
458
|
-
});
|
|
459
|
-
|
|
460
|
-
it('sorts filtered options using sortFilteredOptions prop', async () => {
|
|
461
|
-
interface Country {
|
|
462
|
-
code: string;
|
|
463
|
-
name: string;
|
|
464
|
-
keywords: string[];
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
const countries: Country[] = [
|
|
468
|
-
{ code: 'AD', name: 'Andorra', keywords: ['united states dollar'] },
|
|
469
|
-
{ code: 'DE', name: 'Germany', keywords: ['EUR'] },
|
|
470
|
-
{ code: 'US', name: 'United States', keywords: ['United States dollar', 'USD'] },
|
|
471
|
-
{ code: 'ZM', name: 'Zambia', keywords: ['USD', 'united states dollar'] },
|
|
472
|
-
];
|
|
473
|
-
|
|
474
|
-
render(
|
|
475
|
-
<SelectInput<Country>
|
|
476
|
-
items={countries.map((country) => ({
|
|
477
|
-
type: 'option',
|
|
478
|
-
value: country,
|
|
479
|
-
filterMatchers: country.keywords,
|
|
480
|
-
}))}
|
|
481
|
-
renderValue={(country) => country.name}
|
|
482
|
-
filterable
|
|
483
|
-
sortFilteredOptions={(a, b, searchQuery) => {
|
|
484
|
-
const query = searchQuery.toLowerCase();
|
|
485
|
-
const nameA = a.value.name.toLowerCase();
|
|
486
|
-
const nameB = b.value.name.toLowerCase();
|
|
487
|
-
|
|
488
|
-
const aMatch = nameA.includes(query);
|
|
489
|
-
const bMatch = nameB.includes(query);
|
|
490
|
-
|
|
491
|
-
if (aMatch && !bMatch) return -1;
|
|
492
|
-
if (!aMatch && bMatch) return 1;
|
|
493
|
-
|
|
494
|
-
return nameA.localeCompare(nameB);
|
|
495
|
-
}}
|
|
496
|
-
/>,
|
|
497
|
-
);
|
|
498
|
-
|
|
499
|
-
const trigger = screen.getByRole('combobox');
|
|
500
|
-
await userEvent.click(trigger);
|
|
501
|
-
|
|
502
|
-
const searchInput = screen.getByRole('combobox', { expanded: true });
|
|
503
|
-
await userEvent.type(searchInput, 'united');
|
|
504
|
-
|
|
505
|
-
const listbox = screen.getByRole('listbox');
|
|
506
|
-
const options = within(listbox).getAllByRole('option');
|
|
507
|
-
|
|
508
|
-
expect(options).toHaveLength(3);
|
|
509
|
-
expect(options[0]).toHaveTextContent('United States');
|
|
510
|
-
expect(options[1]).toHaveTextContent('Andorra');
|
|
511
|
-
expect(options[2]).toHaveTextContent('Zambia');
|
|
512
|
-
});
|
|
513
|
-
|
|
514
|
-
describe('listbox label', () => {
|
|
515
|
-
const fieldLabel = 'Fruits';
|
|
516
|
-
const triggerLabel = 'Select fruit';
|
|
517
|
-
const options: SelectInputOptionItem[] = [
|
|
518
|
-
{ type: 'option', value: 'Banana' },
|
|
519
|
-
{ type: 'option', value: 'Orange' },
|
|
520
|
-
{ type: 'option', value: 'Olive' },
|
|
521
|
-
];
|
|
522
|
-
const requiredTriggerButtonProps = {
|
|
523
|
-
id: undefined,
|
|
524
|
-
'aria-labelledby': undefined,
|
|
525
|
-
'aria-describedby': undefined,
|
|
526
|
-
'aria-invalid': undefined,
|
|
527
|
-
'aria-label': undefined,
|
|
528
|
-
};
|
|
529
|
-
|
|
530
|
-
const renderSelectInput = (props: Omit<SelectInputProps<string | null>, 'items'> = {}) =>
|
|
531
|
-
render(
|
|
532
|
-
<Field label={fieldLabel} id="selectId">
|
|
533
|
-
<SelectInput {...props} items={options} />
|
|
534
|
-
</Field>,
|
|
535
|
-
);
|
|
536
|
-
|
|
537
|
-
it("should propagate trigger's label if nothing is selected", async () => {
|
|
538
|
-
renderSelectInput({
|
|
539
|
-
UNSAFE_triggerButtonProps: {
|
|
540
|
-
...requiredTriggerButtonProps,
|
|
541
|
-
'aria-label': triggerLabel,
|
|
542
|
-
},
|
|
543
|
-
});
|
|
544
|
-
const trigger = screen.getByRole('combobox');
|
|
545
|
-
await userEvent.click(trigger);
|
|
546
|
-
expect(screen.getByRole('listbox', { name: triggerLabel })).toBeInTheDocument();
|
|
547
|
-
});
|
|
548
|
-
|
|
549
|
-
it("should propagate trigger's label if an option is selected", async () => {
|
|
550
|
-
renderSelectInput({
|
|
551
|
-
UNSAFE_triggerButtonProps: {
|
|
552
|
-
...requiredTriggerButtonProps,
|
|
553
|
-
'aria-label': triggerLabel,
|
|
554
|
-
},
|
|
555
|
-
value: options[1].value,
|
|
556
|
-
});
|
|
557
|
-
const trigger = screen.getByRole('combobox');
|
|
558
|
-
await userEvent.click(trigger);
|
|
559
|
-
expect(screen.getByRole('listbox', { name: triggerLabel })).toBeInTheDocument();
|
|
560
|
-
});
|
|
561
|
-
|
|
562
|
-
it("should propagate trigger's label by id", async () => {
|
|
563
|
-
const customLabelId = 'customLabelId';
|
|
564
|
-
renderSelectInput({
|
|
565
|
-
UNSAFE_triggerButtonProps: {
|
|
566
|
-
...requiredTriggerButtonProps,
|
|
567
|
-
'aria-labelledby': customLabelId,
|
|
568
|
-
},
|
|
569
|
-
});
|
|
570
|
-
const trigger = screen.getByRole('combobox');
|
|
571
|
-
await userEvent.click(trigger);
|
|
572
|
-
expect(screen.getByRole('listbox')).toHaveAttribute('aria-labelledby', customLabelId);
|
|
573
|
-
});
|
|
574
|
-
|
|
575
|
-
it("should propagate input's label by id", async () => {
|
|
576
|
-
renderSelectInput();
|
|
577
|
-
const trigger = screen.getByRole('combobox');
|
|
578
|
-
await userEvent.click(trigger);
|
|
579
|
-
expect(screen.getByRole('listbox', { name: fieldLabel })).toBeInTheDocument();
|
|
580
|
-
});
|
|
581
|
-
|
|
582
|
-
it('should prefer explicit label over label ids', async () => {
|
|
583
|
-
const customLabelId = 'customLabelId';
|
|
584
|
-
renderSelectInput({
|
|
585
|
-
UNSAFE_triggerButtonProps: {
|
|
586
|
-
...requiredTriggerButtonProps,
|
|
587
|
-
'aria-labelledby': customLabelId,
|
|
588
|
-
'aria-label': triggerLabel,
|
|
589
|
-
},
|
|
590
|
-
});
|
|
591
|
-
const trigger = screen.getByRole('combobox');
|
|
592
|
-
await userEvent.click(trigger);
|
|
593
|
-
expect(screen.getByRole('listbox', { name: triggerLabel })).toBeInTheDocument();
|
|
594
|
-
expect(screen.getByRole('listbox')).not.toHaveAttribute('aria-labelledby');
|
|
595
|
-
});
|
|
596
|
-
|
|
597
|
-
it('should have no label if none of the above are provided', async () => {
|
|
598
|
-
render(<SelectInput items={options} />);
|
|
599
|
-
const trigger = screen.getByRole('combobox');
|
|
600
|
-
await userEvent.click(trigger);
|
|
601
|
-
const listBox = screen.getByRole('listbox');
|
|
602
|
-
expect(listBox).not.toHaveAttribute('aria-label');
|
|
603
|
-
expect(listBox).not.toHaveAttribute('aria-labelledby');
|
|
604
|
-
});
|
|
605
|
-
});
|
|
606
|
-
});
|