@instructure/ui-simple-select 10.3.1-snapshot-7 → 10.3.1-snapshot-9

Sign up to get free protection for your applications and to get access to all the features.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instructure/ui-simple-select",
3
- "version": "10.3.1-snapshot-7",
3
+ "version": "10.3.1-snapshot-9",
4
4
  "description": "A component for standard select element behavior.",
5
5
  "author": "Instructure, Inc. Engineering and Product Design",
6
6
  "module": "./es/index.js",
@@ -24,25 +24,26 @@
24
24
  "license": "MIT",
25
25
  "dependencies": {
26
26
  "@babel/runtime": "^7.25.6",
27
- "@instructure/console": "10.3.1-snapshot-7",
28
- "@instructure/shared-types": "10.3.1-snapshot-7",
29
- "@instructure/ui-form-field": "10.3.1-snapshot-7",
30
- "@instructure/ui-position": "10.3.1-snapshot-7",
31
- "@instructure/ui-prop-types": "10.3.1-snapshot-7",
32
- "@instructure/ui-react-utils": "10.3.1-snapshot-7",
33
- "@instructure/ui-select": "10.3.1-snapshot-7",
34
- "@instructure/ui-testable": "10.3.1-snapshot-7",
27
+ "@instructure/console": "10.3.1-snapshot-9",
28
+ "@instructure/shared-types": "10.3.1-snapshot-9",
29
+ "@instructure/ui-form-field": "10.3.1-snapshot-9",
30
+ "@instructure/ui-position": "10.3.1-snapshot-9",
31
+ "@instructure/ui-prop-types": "10.3.1-snapshot-9",
32
+ "@instructure/ui-react-utils": "10.3.1-snapshot-9",
33
+ "@instructure/ui-select": "10.3.1-snapshot-9",
34
+ "@instructure/ui-testable": "10.3.1-snapshot-9",
35
35
  "prop-types": "^15.8.1"
36
36
  },
37
37
  "devDependencies": {
38
- "@instructure/ui-babel-preset": "10.3.1-snapshot-7",
39
- "@instructure/ui-color-utils": "10.3.1-snapshot-7",
40
- "@instructure/ui-icons": "10.3.1-snapshot-7",
41
- "@instructure/ui-test-locator": "10.3.1-snapshot-7",
42
- "@instructure/ui-test-utils": "10.3.1-snapshot-7",
43
- "@instructure/ui-utils": "10.3.1-snapshot-7",
38
+ "@instructure/ui-axe-check": "10.3.1-snapshot-9",
39
+ "@instructure/ui-babel-preset": "10.3.1-snapshot-9",
40
+ "@instructure/ui-color-utils": "10.3.1-snapshot-9",
41
+ "@instructure/ui-icons": "10.3.1-snapshot-9",
42
+ "@instructure/ui-test-utils": "10.3.1-snapshot-9",
43
+ "@instructure/ui-utils": "10.3.1-snapshot-9",
44
44
  "@testing-library/jest-dom": "^6.4.6",
45
45
  "@testing-library/react": "^16.0.1",
46
+ "@testing-library/user-event": "^14.5.2",
46
47
  "vitest": "^2.1.1"
47
48
  },
48
49
  "peerDependencies": {
@@ -22,56 +22,330 @@
22
22
  * SOFTWARE.
23
23
  */
24
24
  import React from 'react'
25
- import { render, fireEvent, screen } from '@testing-library/react'
26
- import { vi } from 'vitest'
25
+ import { fireEvent, render, screen, waitFor } from '@testing-library/react'
26
+ import { vi, MockInstance, it } from 'vitest'
27
+ import userEvent from '@testing-library/user-event'
27
28
  import '@testing-library/jest-dom'
29
+
30
+ // eslint-disable-next-line no-restricted-imports
31
+ import { generateA11yTests } from '@instructure/ui-scripts/lib/test/generateA11yTests'
32
+ import { IconCheckSolid } from '@instructure/ui-icons'
33
+ import { runAxeCheck } from '@instructure/ui-axe-check'
34
+
35
+ import SimpleSelectExamples from '../__examples__/SimpleSelect.examples'
28
36
  import SimpleSelect from '../index'
29
- import * as utils from '@instructure/ui-utils'
30
37
 
31
38
  type ExampleOption = 'foo' | 'bar' | 'baz'
32
- vi.mock('@instructure/ui-utils', async (importOriginal) => {
33
- const originalModule = (await importOriginal()) as any
34
- return {
35
- __esModule: true,
36
- ...originalModule,
37
- isSafari: vi.fn(() => true)
38
- }
39
- })
39
+ const defaultOptions: ExampleOption[] = ['foo', 'bar', 'baz']
40
40
 
41
- const mockUtils = utils as any
41
+ const getOptions = (disabled?: ExampleOption) =>
42
+ defaultOptions.map((opt) => (
43
+ <SimpleSelect.Option
44
+ id={opt}
45
+ key={opt}
46
+ value={opt}
47
+ isDisabled={opt === disabled}
48
+ >
49
+ {opt}
50
+ </SimpleSelect.Option>
51
+ ))
42
52
 
43
53
  describe('<SimpleSelect />', () => {
44
- const defaultOptions: ExampleOption[] = ['foo', 'bar', 'baz']
45
-
46
- const getOptions = (disabled?: ExampleOption) =>
47
- defaultOptions.map((opt) => (
48
- <SimpleSelect.Option
49
- id={opt}
50
- key={opt}
51
- value={opt}
52
- isDisabled={opt === disabled}
53
- >
54
- {opt}
55
- </SimpleSelect.Option>
56
- ))
54
+ let consoleErrorMock: ReturnType<typeof vi.spyOn>
57
55
 
58
- // convert to e2e fail in vitest
59
- // it('should have role button in Safari', async () => {
60
- // const { container } = render(
61
- // <SimpleSelect renderLabel="Choose an option">{getOptions()}</SimpleSelect>
62
- // )
63
- // const input = container.querySelector('input')
64
- // expect(input).toHaveAttribute('role', 'button')
65
- // })
56
+ beforeEach(() => {
57
+ // Mocking console to prevent test output pollution and expect for messages
58
+ consoleErrorMock = vi
59
+ .spyOn(console, 'error')
60
+ .mockImplementation(() => {}) as MockInstance
61
+ })
66
62
 
67
- it('should have role combobox in different browsers than Safari', async () => {
68
- mockUtils.isSafari = vi.fn(() => false)
63
+ afterEach(() => {
64
+ consoleErrorMock.mockRestore()
65
+ })
69
66
 
70
- const { container } = render(
67
+ it('should render an input and a list', async () => {
68
+ render(
71
69
  <SimpleSelect renderLabel="Choose an option">{getOptions()}</SimpleSelect>
72
70
  )
73
- const input = container.querySelector('input')
74
- expect(input).toHaveAttribute('role', 'combobox')
71
+ const input = screen.getByLabelText('Choose an option')
72
+ const listInitial = screen.queryByRole('listbox')
73
+
74
+ expect(listInitial).not.toBeInTheDocument()
75
+ expect(input).toBeInTheDocument()
76
+
77
+ await userEvent.click(input)
78
+
79
+ await waitFor(() => {
80
+ const list = screen.queryByRole('listbox')
81
+
82
+ expect(list).toBeInTheDocument()
83
+ })
84
+ })
85
+
86
+ it('should render groups', async () => {
87
+ render(
88
+ <SimpleSelect renderLabel="Choose an option">
89
+ <SimpleSelect.Option id="0" value="0">
90
+ ungrouped option one
91
+ </SimpleSelect.Option>
92
+ <SimpleSelect.Group renderLabel="Group one">
93
+ <SimpleSelect.Option id="1" value="1">
94
+ grouped option one
95
+ </SimpleSelect.Option>
96
+ </SimpleSelect.Group>
97
+ <SimpleSelect.Group renderLabel="Group two">
98
+ <SimpleSelect.Option id="2" value="2">
99
+ grouped option two
100
+ </SimpleSelect.Option>
101
+ </SimpleSelect.Group>
102
+ <SimpleSelect.Option id="3" value="3">
103
+ ungrouped option two
104
+ </SimpleSelect.Option>
105
+ </SimpleSelect>
106
+ )
107
+ const input = screen.getByLabelText('Choose an option')
108
+
109
+ await userEvent.click(input)
110
+
111
+ await waitFor(() => {
112
+ const groups = screen.getAllByRole('group')
113
+ const labelOne = screen.getByText('Group one')
114
+ const labelOneID = labelOne.getAttribute('id')
115
+
116
+ expect(groups.length).toBe(2)
117
+ expect(groups[0]).toHaveAttribute('aria-labelledby', labelOneID)
118
+ expect(labelOne).toHaveAttribute('role', 'presentation')
119
+ })
120
+ })
121
+
122
+ it('should ignore invalid children', async () => {
123
+ render(
124
+ <SimpleSelect renderLabel="Choose an option">
125
+ <SimpleSelect.Option id="0" value={0}>
126
+ valid
127
+ </SimpleSelect.Option>
128
+ <div>invalid</div>
129
+ </SimpleSelect>
130
+ )
131
+ const input = screen.getByLabelText('Choose an option')
132
+
133
+ await userEvent.click(input)
134
+
135
+ await waitFor(() => {
136
+ const invalidChild = screen.queryByText('invalid')
137
+
138
+ expect(invalidChild).not.toBeInTheDocument()
139
+ expect(consoleErrorMock).toHaveBeenCalledWith(
140
+ expect.any(String),
141
+ expect.any(String),
142
+ expect.stringContaining('Expected one of Group, Option'),
143
+ expect.any(String)
144
+ )
145
+ })
146
+ })
147
+
148
+ it('should fire onFocus when input gains focus', async () => {
149
+ const onFocus = vi.fn()
150
+ render(
151
+ <SimpleSelect renderLabel="Choose an option" onFocus={onFocus}>
152
+ {getOptions()}
153
+ </SimpleSelect>
154
+ )
155
+ const input = screen.getByLabelText('Choose an option')
156
+
157
+ input.focus()
158
+
159
+ await waitFor(() => {
160
+ expect(onFocus).toHaveBeenCalled()
161
+ })
162
+ })
163
+
164
+ describe('input', () => {
165
+ it('should render with a custom id if given', async () => {
166
+ render(<SimpleSelect renderLabel="Choose an option" id="customSelect" />)
167
+ const input = screen.getByLabelText('Choose an option')
168
+
169
+ expect(input).toHaveAttribute('id', 'customSelect')
170
+ })
171
+
172
+ it('should always render readonly', async () => {
173
+ render(
174
+ <SimpleSelect renderLabel="Choose an option" interaction="enabled" />
175
+ )
176
+ const input = screen.getByLabelText('Choose an option')
177
+
178
+ expect(input).toHaveAttribute('readonly')
179
+ expect(input).not.toHaveAttribute('disabled')
180
+ })
181
+
182
+ it('should render disabled when interaction="disabled"', async () => {
183
+ render(
184
+ <SimpleSelect renderLabel="Choose an option" interaction="disabled" />
185
+ )
186
+ const input = screen.getByLabelText('Choose an option')
187
+
188
+ expect(input).toHaveAttribute('disabled')
189
+ expect(input).not.toHaveAttribute('readonly')
190
+ })
191
+
192
+ it('should render required when isRequired={true}', async () => {
193
+ render(<SimpleSelect renderLabel="Choose an option" isRequired />)
194
+ const input = screen.getByLabelText('Choose an option')
195
+
196
+ expect(input).toHaveAttribute('required')
197
+ })
198
+
199
+ it('should allow assistive text', async () => {
200
+ render(
201
+ <SimpleSelect
202
+ renderLabel="Choose an option"
203
+ assistiveText="hello world"
204
+ >
205
+ {getOptions()}
206
+ </SimpleSelect>
207
+ )
208
+ const input = screen.getByLabelText('Choose an option')
209
+ const assistiveText = screen.getByText('hello world')
210
+ const assistiveTextID = assistiveText.getAttribute('id')
211
+
212
+ expect(input).toHaveAttribute('aria-describedby', assistiveTextID)
213
+ })
214
+
215
+ it('should allow custom props to pass through', async () => {
216
+ render(
217
+ <SimpleSelect renderLabel="Choose an option" data-custom-attr="true">
218
+ {getOptions()}
219
+ </SimpleSelect>
220
+ )
221
+ const input = screen.getByLabelText('Choose an option')
222
+
223
+ expect(input).toHaveAttribute('data-custom-attr', 'true')
224
+ })
225
+
226
+ it('should provide a ref to the input element', async () => {
227
+ const inputRef = vi.fn()
228
+
229
+ render(
230
+ <SimpleSelect renderLabel="Choose an option" inputRef={inputRef}>
231
+ {getOptions()}
232
+ </SimpleSelect>
233
+ )
234
+ const input = screen.getByLabelText('Choose an option')
235
+
236
+ expect(inputRef).toHaveBeenCalledWith(input)
237
+ })
238
+ })
239
+
240
+ it('should render icons before option and call renderBeforeLabel callback with necessary props', async () => {
241
+ const renderBeforeLabel = vi.fn(() => (
242
+ <IconCheckSolid data-testid="option-icon" />
243
+ ))
244
+
245
+ render(
246
+ <SimpleSelect renderLabel="Choose an option">
247
+ <SimpleSelect.Option
248
+ id="option-1"
249
+ value="1"
250
+ isDisabled
251
+ renderBeforeLabel={renderBeforeLabel}
252
+ >
253
+ option one
254
+ </SimpleSelect.Option>
255
+ <SimpleSelect.Option
256
+ id="option-2"
257
+ value="2"
258
+ renderBeforeLabel={renderBeforeLabel}
259
+ >
260
+ option two
261
+ </SimpleSelect.Option>
262
+ </SimpleSelect>
263
+ )
264
+ const input = screen.getByLabelText('Choose an option')
265
+
266
+ await userEvent.click(input)
267
+
268
+ await waitFor(() => {
269
+ const optionIcons = screen.getAllByTestId('option-icon')
270
+ expect(optionIcons.length).toBe(2)
271
+
272
+ expect(renderBeforeLabel).toHaveBeenCalledTimes(2)
273
+
274
+ type MockCallType = Parameters<(...args: any) => any>[]
275
+ const [[argsOption1], [argsOption2]] = renderBeforeLabel.mock
276
+ .calls as MockCallType
277
+
278
+ expect(argsOption1).toMatchObject({
279
+ id: 'option-1',
280
+ isDisabled: true,
281
+ isSelected: true,
282
+ isHighlighted: true,
283
+ children: 'option one'
284
+ })
285
+
286
+ expect(argsOption2).toMatchObject({
287
+ id: 'option-2',
288
+ isDisabled: false,
289
+ isSelected: false,
290
+ isHighlighted: false,
291
+ children: 'option two'
292
+ })
293
+ })
294
+ })
295
+
296
+ describe('list', () => {
297
+ it('should set aria-disabled on options when isDisabled={true}', async () => {
298
+ render(
299
+ <SimpleSelect renderLabel="Choose an option">
300
+ {getOptions(defaultOptions[2])}
301
+ </SimpleSelect>
302
+ )
303
+ const input = screen.getByLabelText('Choose an option')
304
+
305
+ await userEvent.click(input)
306
+
307
+ await waitFor(() => {
308
+ const options = screen.getAllByRole('option')
309
+
310
+ expect(options[0]).not.toHaveAttribute('aria-disabled')
311
+ expect(options[2]).toHaveAttribute('aria-disabled', 'true')
312
+ })
313
+ })
314
+
315
+ it('should provide a ref to the list element', async () => {
316
+ const listRef = vi.fn()
317
+
318
+ render(
319
+ <SimpleSelect renderLabel="Choose an option" listRef={listRef}>
320
+ {getOptions()}
321
+ </SimpleSelect>
322
+ )
323
+ const input = screen.getByLabelText('Choose an option')
324
+
325
+ await userEvent.click(input)
326
+
327
+ await waitFor(() => {
328
+ const listbox = screen.getByRole('listbox')
329
+
330
+ expect(listRef).toHaveBeenCalledWith(listbox)
331
+ })
332
+ })
333
+ })
334
+
335
+ describe('with generated examples', () => {
336
+ const generatedComponents = generateA11yTests(
337
+ SimpleSelect,
338
+ SimpleSelectExamples
339
+ )
340
+
341
+ it.each(generatedComponents)(
342
+ 'should be accessible with example: $description',
343
+ async ({ content }) => {
344
+ const { container } = render(content)
345
+ const axeCheck = await runAxeCheck(container)
346
+ expect(axeCheck).toBe(true)
347
+ }
348
+ )
75
349
  })
76
350
 
77
351
  describe('children', () => {
@@ -7,6 +7,7 @@
7
7
  },
8
8
  "include": ["src"],
9
9
  "references": [
10
+ { "path": "../ui-axe-check/tsconfig.build.json" },
10
11
  { "path": "../console/tsconfig.build.json" },
11
12
  { "path": "../ui-form-field/tsconfig.build.json" },
12
13
  { "path": "../ui-position/tsconfig.build.json" },
@@ -17,7 +18,6 @@
17
18
  { "path": "../ui-babel-preset/tsconfig.build.json" },
18
19
  { "path": "../ui-color-utils/tsconfig.build.json" },
19
20
  { "path": "../ui-icons/tsconfig.build.json" },
20
- { "path": "../ui-test-locator/tsconfig.build.json" },
21
21
  { "path": "../ui-test-utils/tsconfig.build.json" },
22
22
  { "path": "../shared-types/tsconfig.build.json" },
23
23
  { "path": "../ui-utils/tsconfig.build.json" }