@primer/components 30.3.0-rc.2010c7d4 → 31.0.0-rc.15aa0a10
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/.eslintrc.json +2 -1
- package/.storybook/preview.js +4 -4
- package/CHANGELOG.md +12 -2
- package/codemods/deprecateUtilityComponents.js +1 -1
- package/contributor-docs/adrs/adr-003-prop-norms.md +72 -0
- package/dist/browser.esm.js +798 -794
- package/dist/browser.esm.js.map +1 -1
- package/dist/browser.umd.js +801 -797
- package/dist/browser.umd.js.map +1 -1
- package/docs/content/Autocomplete.mdx +627 -0
- package/docs/content/TextInputTokens.mdx +89 -0
- package/docs/content/getting-started.md +1 -1
- package/docs/content/overriding-styles.mdx +7 -6
- package/docs/content/theming.md +5 -5
- package/docs/package-lock.json +288 -511
- package/docs/package.json +1 -1
- package/docs/src/@primer/gatsby-theme-doctocat/components/hero.js +14 -12
- package/docs/src/@primer/gatsby-theme-doctocat/nav.yml +2 -0
- package/docs/src/@primer/gatsby-theme-doctocat/primer-components-hero.svg +7 -7
- package/lib/ActionList/Item.js +1 -1
- package/lib/AnchoredOverlay/AnchoredOverlay.d.ts +2 -1
- package/lib/AnchoredOverlay/AnchoredOverlay.js +11 -3
- package/lib/Autocomplete/Autocomplete.d.ts +304 -0
- package/lib/Autocomplete/Autocomplete.js +145 -0
- package/lib/Autocomplete/AutocompleteContext.d.ts +17 -0
- package/lib/Autocomplete/AutocompleteContext.js +11 -0
- package/lib/Autocomplete/AutocompleteInput.d.ts +292 -0
- package/lib/Autocomplete/AutocompleteInput.js +157 -0
- package/lib/Autocomplete/AutocompleteMenu.d.ts +72 -0
- package/lib/Autocomplete/AutocompleteMenu.js +224 -0
- package/lib/Autocomplete/AutocompleteOverlay.d.ts +20 -0
- package/lib/Autocomplete/AutocompleteOverlay.js +80 -0
- package/lib/Autocomplete/index.d.ts +2 -0
- package/lib/Autocomplete/index.js +15 -0
- package/lib/BaseStyles.js +1 -1
- package/lib/BorderBox.js +1 -1
- package/lib/Button/ButtonInvisible.js +1 -1
- package/lib/Caret.js +2 -2
- package/lib/Dialog.js +1 -1
- package/lib/FilteredActionList/FilteredActionList.js +5 -31
- package/lib/Flash.js +16 -16
- package/lib/Label.js +1 -1
- package/lib/Overlay.d.ts +1 -0
- package/lib/Overlay.js +3 -1
- package/lib/ProgressBar.js +1 -1
- package/lib/StateLabel.js +13 -19
- package/lib/Token/_RemoveTokenButton.js +1 -1
- package/lib/__tests__/Autocomplete.test.d.ts +1 -0
- package/lib/__tests__/Autocomplete.test.js +528 -0
- package/lib/__tests__/BorderBox.test.js +1 -1
- package/lib/__tests__/CircleOcticon.test.js +1 -1
- package/lib/__tests__/CounterLabel.test.js +4 -4
- package/lib/__tests__/Flash.test.js +4 -4
- package/lib/__tests__/Link.test.js +1 -1
- package/lib/__tests__/behaviors/scrollIntoViewingArea.test.d.ts +1 -0
- package/lib/__tests__/behaviors/scrollIntoViewingArea.test.js +226 -0
- package/lib/behaviors/scrollIntoViewingArea.d.ts +1 -0
- package/lib/behaviors/scrollIntoViewingArea.js +39 -0
- package/lib/hooks/useOpenAndCloseFocus.d.ts +2 -1
- package/lib/hooks/useOpenAndCloseFocus.js +7 -2
- package/lib/hooks/useOverlay.d.ts +2 -1
- package/lib/hooks/useOverlay.js +4 -2
- package/lib/index.d.ts +2 -0
- package/lib/index.js +8 -0
- package/lib/stories/Autocomplete.stories.js +608 -0
- package/lib/stories/Dialog.stories.js +3 -3
- package/lib/stories/IssueLabelToken.stories.js +1 -1
- package/lib/stories/ProfileToken.stories.js +1 -1
- package/lib/theme-preval.js +370 -3100
- package/lib/utils/testing.d.ts +50 -493
- package/lib/utils/types/MandateProps.d.ts +3 -0
- package/lib/utils/types/MandateProps.js +1 -0
- package/lib/utils/types/index.d.ts +1 -0
- package/lib/utils/types/index.js +13 -0
- package/lib-esm/ActionList/Item.js +1 -1
- package/lib-esm/AnchoredOverlay/AnchoredOverlay.d.ts +2 -1
- package/lib-esm/AnchoredOverlay/AnchoredOverlay.js +11 -3
- package/lib-esm/Autocomplete/Autocomplete.d.ts +304 -0
- package/lib-esm/Autocomplete/Autocomplete.js +123 -0
- package/lib-esm/Autocomplete/AutocompleteContext.d.ts +17 -0
- package/lib-esm/Autocomplete/AutocompleteContext.js +2 -0
- package/lib-esm/Autocomplete/AutocompleteInput.d.ts +292 -0
- package/lib-esm/Autocomplete/AutocompleteInput.js +138 -0
- package/lib-esm/Autocomplete/AutocompleteMenu.d.ts +72 -0
- package/lib-esm/Autocomplete/AutocompleteMenu.js +205 -0
- package/lib-esm/Autocomplete/AutocompleteOverlay.d.ts +20 -0
- package/lib-esm/Autocomplete/AutocompleteOverlay.js +62 -0
- package/lib-esm/Autocomplete/index.d.ts +2 -0
- package/lib-esm/Autocomplete/index.js +1 -0
- package/lib-esm/BaseStyles.js +1 -1
- package/lib-esm/BorderBox.js +1 -1
- package/lib-esm/Button/ButtonInvisible.js +1 -1
- package/lib-esm/Caret.js +2 -2
- package/lib-esm/Dialog.js +1 -1
- package/lib-esm/FilteredActionList/FilteredActionList.js +3 -31
- package/lib-esm/Flash.js +16 -16
- package/lib-esm/Label.js +1 -1
- package/lib-esm/Overlay.d.ts +1 -0
- package/lib-esm/Overlay.js +3 -1
- package/lib-esm/ProgressBar.js +1 -1
- package/lib-esm/StateLabel.js +13 -19
- package/lib-esm/Token/_RemoveTokenButton.js +1 -1
- package/lib-esm/__tests__/Autocomplete.test.d.ts +1 -0
- package/lib-esm/__tests__/Autocomplete.test.js +494 -0
- package/lib-esm/__tests__/BorderBox.test.js +1 -1
- package/lib-esm/__tests__/CircleOcticon.test.js +1 -1
- package/lib-esm/__tests__/CounterLabel.test.js +4 -4
- package/lib-esm/__tests__/Flash.test.js +4 -4
- package/lib-esm/__tests__/Link.test.js +1 -1
- package/lib-esm/__tests__/behaviors/scrollIntoViewingArea.test.d.ts +1 -0
- package/lib-esm/__tests__/behaviors/scrollIntoViewingArea.test.js +224 -0
- package/lib-esm/behaviors/scrollIntoViewingArea.d.ts +1 -0
- package/lib-esm/behaviors/scrollIntoViewingArea.js +30 -0
- package/lib-esm/hooks/useOpenAndCloseFocus.d.ts +2 -1
- package/lib-esm/hooks/useOpenAndCloseFocus.js +7 -2
- package/lib-esm/hooks/useOverlay.d.ts +2 -1
- package/lib-esm/hooks/useOverlay.js +4 -2
- package/lib-esm/index.d.ts +2 -0
- package/lib-esm/index.js +1 -0
- package/lib-esm/stories/Autocomplete.stories.js +549 -0
- package/lib-esm/stories/Dialog.stories.js +3 -3
- package/lib-esm/stories/IssueLabelToken.stories.js +1 -1
- package/lib-esm/stories/ProfileToken.stories.js +1 -1
- package/lib-esm/theme-preval.js +370 -3100
- package/lib-esm/utils/testing.d.ts +50 -493
- package/lib-esm/utils/types/MandateProps.d.ts +3 -0
- package/lib-esm/utils/types/MandateProps.js +1 -0
- package/lib-esm/utils/types/index.d.ts +1 -0
- package/lib-esm/utils/types/index.js +2 -1
- package/package-lock.json +11 -8
- package/package.json +3 -3
- package/src/ActionList/Item.tsx +1 -1
- package/src/AnchoredOverlay/AnchoredOverlay.tsx +14 -3
- package/src/Autocomplete/Autocomplete.tsx +103 -0
- package/src/Autocomplete/AutocompleteContext.tsx +19 -0
- package/src/Autocomplete/AutocompleteInput.tsx +179 -0
- package/src/Autocomplete/AutocompleteMenu.tsx +341 -0
- package/src/Autocomplete/AutocompleteOverlay.tsx +68 -0
- package/src/Autocomplete/index.ts +2 -0
- package/src/BaseStyles.tsx +1 -1
- package/src/BorderBox.tsx +1 -1
- package/src/Button/ButtonInvisible.tsx +7 -2
- package/src/Caret.tsx +2 -2
- package/src/Dialog.tsx +1 -1
- package/src/FilteredActionList/FilteredActionList.tsx +10 -25
- package/src/Flash.tsx +16 -16
- package/src/Label.tsx +1 -1
- package/src/Overlay.tsx +4 -1
- package/src/ProgressBar.tsx +1 -1
- package/src/StateLabel.tsx +12 -20
- package/src/Token/_RemoveTokenButton.tsx +4 -2
- package/src/__tests__/Autocomplete.test.tsx +444 -0
- package/src/__tests__/BorderBox.test.tsx +1 -1
- package/src/__tests__/CircleOcticon.test.tsx +1 -1
- package/src/__tests__/CounterLabel.test.tsx +4 -4
- package/src/__tests__/Flash.test.tsx +4 -4
- package/src/__tests__/Link.test.tsx +1 -1
- package/src/__tests__/__snapshots__/AnchoredOverlay.test.tsx.snap +3 -3
- package/src/__tests__/__snapshots__/Autocomplete.test.tsx.snap +3414 -0
- package/src/__tests__/__snapshots__/Button.test.tsx.snap +9 -1
- package/src/__tests__/__snapshots__/ConfirmationDialog.test.tsx.snap +1 -1
- package/src/__tests__/__snapshots__/SelectPanel.test.tsx.snap +1 -1
- package/src/__tests__/__snapshots__/StateLabel.test.tsx.snap +0 -21
- package/src/__tests__/__snapshots__/TextInputWithTokens.test.tsx.snap +16 -16
- package/src/__tests__/__snapshots__/Token.test.tsx.snap +34 -34
- package/src/__tests__/behaviors/scrollIntoViewingArea.test.ts +195 -0
- package/src/behaviors/scrollIntoViewingArea.ts +27 -0
- package/src/hooks/useOpenAndCloseFocus.ts +7 -2
- package/src/hooks/useOverlay.tsx +4 -2
- package/src/index.ts +2 -0
- package/src/stories/Autocomplete.stories.tsx +572 -0
- package/src/stories/Dialog.stories.tsx +3 -3
- package/src/stories/IssueLabelToken.stories.tsx +1 -1
- package/src/stories/ProfileToken.stories.tsx +1 -1
- package/src/utils/types/MandateProps.ts +19 -0
- package/src/utils/types/index.ts +1 -0
- package/stats.html +1 -1
- package/docs/src/@primer/gatsby-theme-doctocat/components/live-code.js +0 -84
- package/docs/src/@primer/gatsby-theme-doctocat/components/nav-dropdown.js +0 -48
- package/docs/src/@primer/gatsby-theme-doctocat/components/wrap-page-element.js +0 -25
@@ -0,0 +1,444 @@
|
|
1
|
+
import React from 'react'
|
2
|
+
import {render} from '../utils/testing'
|
3
|
+
import {render as HTMLRender, fireEvent} from '@testing-library/react'
|
4
|
+
import {toHaveNoViolations} from 'jest-axe'
|
5
|
+
import 'babel-polyfill'
|
6
|
+
import Autocomplete, {AutocompleteInputProps} from '../Autocomplete'
|
7
|
+
import {SSRProvider} from '../index'
|
8
|
+
import theme from '../theme'
|
9
|
+
import BaseStyles from '../BaseStyles'
|
10
|
+
import {ThemeProvider} from '../ThemeProvider'
|
11
|
+
import userEvent from '@testing-library/user-event'
|
12
|
+
import {AutocompleteMenuInternalProps} from '../Autocomplete/AutocompleteMenu'
|
13
|
+
import {ItemProps} from '../ActionList'
|
14
|
+
import {MandateProps} from '../utils/types'
|
15
|
+
expect.extend(toHaveNoViolations)
|
16
|
+
|
17
|
+
const mockItems = [
|
18
|
+
{text: 'zero', id: 0},
|
19
|
+
{text: 'one', id: 1},
|
20
|
+
{text: 'two', id: 2},
|
21
|
+
{text: 'three', id: 3},
|
22
|
+
{text: 'four', id: 4},
|
23
|
+
{text: 'five', id: 5},
|
24
|
+
{text: 'six', id: 6},
|
25
|
+
{text: 'seven', id: 7},
|
26
|
+
{text: 'twenty', id: 20},
|
27
|
+
{text: 'twentyone', id: 21}
|
28
|
+
]
|
29
|
+
|
30
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
31
|
+
type AutocompleteItemProps<T = Record<string, any>> = MandateProps<ItemProps, 'id'> & {metadata?: T}
|
32
|
+
|
33
|
+
const AUTOCOMPLETE_LABEL = 'Autocomplete field'
|
34
|
+
const LabelledAutocomplete = <T extends AutocompleteItemProps>({
|
35
|
+
inputProps = {},
|
36
|
+
menuProps
|
37
|
+
}: {
|
38
|
+
inputProps?: AutocompleteInputProps
|
39
|
+
menuProps: AutocompleteMenuInternalProps<T>
|
40
|
+
}) => {
|
41
|
+
const {['aria-labelledby']: ariaLabelledBy = 'autocompleteLabel', ...menuPropsRest} = menuProps
|
42
|
+
const {id = 'autocompleteInput', ...inputPropsRest} = inputProps
|
43
|
+
return (
|
44
|
+
<ThemeProvider theme={theme}>
|
45
|
+
<SSRProvider>
|
46
|
+
<BaseStyles>
|
47
|
+
{/* eslint-disable-next-line jsx-a11y/label-has-for */}
|
48
|
+
<label htmlFor={id} id={ariaLabelledBy}>
|
49
|
+
Autocomplete field
|
50
|
+
</label>
|
51
|
+
<Autocomplete id="autocompleteId">
|
52
|
+
<Autocomplete.Input id={id} {...inputPropsRest} />
|
53
|
+
<Autocomplete.Overlay>
|
54
|
+
<Autocomplete.Menu aria-labelledby={ariaLabelledBy} {...menuPropsRest} />
|
55
|
+
</Autocomplete.Overlay>
|
56
|
+
</Autocomplete>
|
57
|
+
</BaseStyles>
|
58
|
+
</SSRProvider>
|
59
|
+
</ThemeProvider>
|
60
|
+
)
|
61
|
+
}
|
62
|
+
|
63
|
+
describe('Autocomplete', () => {
|
64
|
+
describe('snapshots', () => {
|
65
|
+
it('renders a single select input', () => {
|
66
|
+
expect(
|
67
|
+
render(
|
68
|
+
<SSRProvider>
|
69
|
+
<Autocomplete id="autocompleteId">
|
70
|
+
<Autocomplete.Input />
|
71
|
+
<Autocomplete.Menu aria-labelledby="labelId" items={mockItems} selectedItemIds={[]} />
|
72
|
+
</Autocomplete>
|
73
|
+
</SSRProvider>
|
74
|
+
)
|
75
|
+
).toMatchSnapshot()
|
76
|
+
})
|
77
|
+
|
78
|
+
it('renders a multiselect input', () => {
|
79
|
+
expect(
|
80
|
+
render(
|
81
|
+
<SSRProvider>
|
82
|
+
<Autocomplete id="autocompleteId">
|
83
|
+
<Autocomplete.Input />
|
84
|
+
<Autocomplete.Menu
|
85
|
+
aria-labelledby="labelId"
|
86
|
+
items={mockItems}
|
87
|
+
selectedItemIds={[]}
|
88
|
+
selectionVariant="multiple"
|
89
|
+
/>
|
90
|
+
</Autocomplete>
|
91
|
+
</SSRProvider>
|
92
|
+
)
|
93
|
+
).toMatchSnapshot()
|
94
|
+
})
|
95
|
+
|
96
|
+
it('renders a multiselect input with selected menu items', () => {
|
97
|
+
expect(
|
98
|
+
render(
|
99
|
+
<SSRProvider>
|
100
|
+
<Autocomplete id="autocompleteId">
|
101
|
+
<Autocomplete.Input />
|
102
|
+
<Autocomplete.Menu
|
103
|
+
aria-labelledby="labelId"
|
104
|
+
items={mockItems}
|
105
|
+
selectedItemIds={[0, 1, 2]}
|
106
|
+
selectionVariant="multiple"
|
107
|
+
/>
|
108
|
+
</Autocomplete>
|
109
|
+
</SSRProvider>
|
110
|
+
)
|
111
|
+
).toMatchSnapshot()
|
112
|
+
})
|
113
|
+
|
114
|
+
it('renders a menu that contains an item to add to the menu', () => {
|
115
|
+
const handleAddItemMock = jest.fn()
|
116
|
+
expect(
|
117
|
+
render(
|
118
|
+
<SSRProvider>
|
119
|
+
<Autocomplete id="autocompleteId">
|
120
|
+
<Autocomplete.Input />
|
121
|
+
<Autocomplete.Menu
|
122
|
+
aria-labelledby="labelId"
|
123
|
+
items={mockItems}
|
124
|
+
selectionVariant="multiple"
|
125
|
+
selectedItemIds={[]}
|
126
|
+
addNewItem={{
|
127
|
+
text: 'Add new item',
|
128
|
+
handleAddItem: handleAddItemMock
|
129
|
+
}}
|
130
|
+
/>
|
131
|
+
</Autocomplete>
|
132
|
+
</SSRProvider>
|
133
|
+
)
|
134
|
+
).toMatchSnapshot()
|
135
|
+
})
|
136
|
+
|
137
|
+
it('renders a custom empty state message', () => {
|
138
|
+
expect(
|
139
|
+
render(
|
140
|
+
<SSRProvider>
|
141
|
+
<Autocomplete id="autocompleteId">
|
142
|
+
<Autocomplete.Input />
|
143
|
+
<Autocomplete.Menu
|
144
|
+
aria-labelledby="labelId"
|
145
|
+
items={[]}
|
146
|
+
selectedItemIds={[]}
|
147
|
+
emptyStateText="No results"
|
148
|
+
/>
|
149
|
+
</Autocomplete>
|
150
|
+
</SSRProvider>
|
151
|
+
)
|
152
|
+
).toMatchSnapshot()
|
153
|
+
})
|
154
|
+
|
155
|
+
it('renders a loading state', () => {
|
156
|
+
expect(
|
157
|
+
render(
|
158
|
+
<SSRProvider>
|
159
|
+
<Autocomplete id="autocompleteId">
|
160
|
+
<Autocomplete.Input />
|
161
|
+
<Autocomplete.Menu aria-labelledby="labelId" loading items={[]} selectedItemIds={[]} />
|
162
|
+
</Autocomplete>
|
163
|
+
</SSRProvider>
|
164
|
+
)
|
165
|
+
).toMatchSnapshot()
|
166
|
+
})
|
167
|
+
|
168
|
+
it('renders with a custom text input component', () => {
|
169
|
+
expect(
|
170
|
+
render(
|
171
|
+
<SSRProvider>
|
172
|
+
<Autocomplete id="autocompleteId">
|
173
|
+
<Autocomplete.Input as={() => <input type="text" id="customInput" />} />
|
174
|
+
<Autocomplete.Menu aria-labelledby="labelId" items={mockItems} selectedItemIds={[]} />
|
175
|
+
</Autocomplete>
|
176
|
+
</SSRProvider>
|
177
|
+
)
|
178
|
+
).toMatchSnapshot()
|
179
|
+
})
|
180
|
+
|
181
|
+
it('renders with an input value', () => {
|
182
|
+
expect(
|
183
|
+
render(
|
184
|
+
<SSRProvider>
|
185
|
+
<Autocomplete id="autocompleteId">
|
186
|
+
<Autocomplete.Input value="test" />
|
187
|
+
<Autocomplete.Menu aria-labelledby="labelId" items={mockItems} selectedItemIds={[]} />
|
188
|
+
</Autocomplete>
|
189
|
+
</SSRProvider>
|
190
|
+
)
|
191
|
+
).toMatchSnapshot()
|
192
|
+
})
|
193
|
+
})
|
194
|
+
|
195
|
+
describe('Autocomplete.Input', () => {
|
196
|
+
it('calls onChange', () => {
|
197
|
+
const onChangeMock = jest.fn()
|
198
|
+
const {container} = HTMLRender(
|
199
|
+
<LabelledAutocomplete
|
200
|
+
inputProps={{onChange: onChangeMock}}
|
201
|
+
menuProps={{items: mockItems, selectedItemIds: []}}
|
202
|
+
/>
|
203
|
+
)
|
204
|
+
const inputNode = container.querySelector('#autocompleteInput')
|
205
|
+
|
206
|
+
expect(onChangeMock).not.toHaveBeenCalled()
|
207
|
+
inputNode && userEvent.type(inputNode, 'z')
|
208
|
+
expect(onChangeMock).toHaveBeenCalled()
|
209
|
+
})
|
210
|
+
|
211
|
+
it('calls onFocus', () => {
|
212
|
+
const onFocusMock = jest.fn()
|
213
|
+
const {container} = HTMLRender(
|
214
|
+
<LabelledAutocomplete inputProps={{onFocus: onFocusMock}} menuProps={{items: mockItems, selectedItemIds: []}} />
|
215
|
+
)
|
216
|
+
const inputNode = container.querySelector('#autocompleteInput')
|
217
|
+
|
218
|
+
expect(onFocusMock).not.toHaveBeenCalled()
|
219
|
+
inputNode && fireEvent.focus(inputNode)
|
220
|
+
expect(onFocusMock).toHaveBeenCalled()
|
221
|
+
})
|
222
|
+
|
223
|
+
it('calls onKeyDown', () => {
|
224
|
+
const onKeyDownMock = jest.fn()
|
225
|
+
const {getByLabelText} = HTMLRender(
|
226
|
+
<LabelledAutocomplete inputProps={{onKeyDown: onKeyDownMock}} menuProps={{items: [], selectedItemIds: []}} />
|
227
|
+
)
|
228
|
+
const inputNode = getByLabelText(AUTOCOMPLETE_LABEL)
|
229
|
+
|
230
|
+
expect(onKeyDownMock).not.toHaveBeenCalled()
|
231
|
+
fireEvent.keyDown(inputNode, {key: 'Shift'})
|
232
|
+
expect(onKeyDownMock).toHaveBeenCalled()
|
233
|
+
})
|
234
|
+
|
235
|
+
it('calls onKeyUp', () => {
|
236
|
+
const onKeyUpMock = jest.fn()
|
237
|
+
const {getByLabelText} = HTMLRender(
|
238
|
+
<LabelledAutocomplete inputProps={{onKeyUp: onKeyUpMock}} menuProps={{items: [], selectedItemIds: []}} />
|
239
|
+
)
|
240
|
+
const inputNode = getByLabelText(AUTOCOMPLETE_LABEL)
|
241
|
+
|
242
|
+
expect(onKeyUpMock).not.toHaveBeenCalled()
|
243
|
+
fireEvent.keyUp(inputNode, {key: 'Shift'})
|
244
|
+
expect(onKeyUpMock).toHaveBeenCalled()
|
245
|
+
})
|
246
|
+
|
247
|
+
it('calls onKeyPress', () => {
|
248
|
+
const onKeyPressMock = jest.fn()
|
249
|
+
const {getByLabelText} = HTMLRender(
|
250
|
+
<LabelledAutocomplete inputProps={{onKeyPress: onKeyPressMock}} menuProps={{items: [], selectedItemIds: []}} />
|
251
|
+
)
|
252
|
+
const inputNode = getByLabelText(AUTOCOMPLETE_LABEL)
|
253
|
+
|
254
|
+
expect(onKeyPressMock).not.toHaveBeenCalled()
|
255
|
+
userEvent.type(inputNode, '{enter}')
|
256
|
+
expect(onKeyPressMock).toHaveBeenCalled()
|
257
|
+
})
|
258
|
+
|
259
|
+
it('opens the menu when the input is focused', () => {
|
260
|
+
const {getByLabelText} = HTMLRender(<LabelledAutocomplete menuProps={{items: [], selectedItemIds: []}} />)
|
261
|
+
const inputNode = getByLabelText(AUTOCOMPLETE_LABEL)
|
262
|
+
|
263
|
+
expect(inputNode.getAttribute('aria-expanded')).not.toBe('true')
|
264
|
+
fireEvent.focus(inputNode)
|
265
|
+
expect(inputNode.getAttribute('aria-expanded')).toBe('true')
|
266
|
+
})
|
267
|
+
|
268
|
+
it('closes the menu when the input is blurred', () => {
|
269
|
+
const {getByLabelText} = HTMLRender(<LabelledAutocomplete menuProps={{items: [], selectedItemIds: []}} />)
|
270
|
+
const inputNode = getByLabelText(AUTOCOMPLETE_LABEL)
|
271
|
+
|
272
|
+
expect(inputNode.getAttribute('aria-expanded')).not.toBe('true')
|
273
|
+
fireEvent.focus(inputNode)
|
274
|
+
expect(inputNode.getAttribute('aria-expanded')).toBe('true')
|
275
|
+
// eslint-disable-next-line github/no-blur
|
276
|
+
fireEvent.blur(inputNode)
|
277
|
+
|
278
|
+
// wait a tick for blur to finish
|
279
|
+
setTimeout(() => {
|
280
|
+
expect(inputNode.getAttribute('aria-expanded')).not.toBe('true')
|
281
|
+
}, 0)
|
282
|
+
})
|
283
|
+
|
284
|
+
it('sets the input value to the suggested item text and highlights the untyped part of the word', () => {
|
285
|
+
const {container, getByDisplayValue} = HTMLRender(
|
286
|
+
<LabelledAutocomplete menuProps={{items: mockItems, selectedItemIds: []}} />
|
287
|
+
)
|
288
|
+
const inputNode = container.querySelector('#autocompleteInput')
|
289
|
+
|
290
|
+
inputNode && userEvent.type(inputNode, 'ze')
|
291
|
+
expect(getByDisplayValue('zero')).toBeDefined()
|
292
|
+
})
|
293
|
+
|
294
|
+
it('does not show or highlight suggestion text after the user hits Backspace until they hit another key', () => {
|
295
|
+
const {container, getByDisplayValue} = HTMLRender(
|
296
|
+
<LabelledAutocomplete menuProps={{items: mockItems, selectedItemIds: []}} />
|
297
|
+
)
|
298
|
+
const inputNode = container.querySelector('#autocompleteInput')
|
299
|
+
|
300
|
+
expect((inputNode as HTMLInputElement).selectionStart).toBe(0)
|
301
|
+
inputNode && userEvent.type(inputNode, 'ze')
|
302
|
+
expect(getByDisplayValue('zero')).toBeDefined()
|
303
|
+
expect((inputNode as HTMLInputElement).selectionStart).toBe(2)
|
304
|
+
expect((inputNode as HTMLInputElement).selectionEnd).toBe(4)
|
305
|
+
inputNode && userEvent.type(inputNode, '{backspace}')
|
306
|
+
expect((inputNode as HTMLInputElement).selectionStart).toBe(2)
|
307
|
+
expect(getByDisplayValue('ze')).toBeDefined()
|
308
|
+
inputNode && userEvent.type(inputNode, 'r')
|
309
|
+
expect((inputNode as HTMLInputElement).selectionStart).toBe(3)
|
310
|
+
expect((inputNode as HTMLInputElement).selectionEnd).toBe(4)
|
311
|
+
expect(getByDisplayValue('zero')).toBeDefined()
|
312
|
+
})
|
313
|
+
|
314
|
+
it('clears the input value when when the user hits Escape', () => {
|
315
|
+
const {container} = HTMLRender(<LabelledAutocomplete menuProps={{items: mockItems, selectedItemIds: []}} />)
|
316
|
+
const inputNode = container.querySelector('#autocompleteInput')
|
317
|
+
|
318
|
+
expect(inputNode?.getAttribute('aria-expanded')).not.toBe('true')
|
319
|
+
inputNode && userEvent.type(inputNode, 'ze')
|
320
|
+
expect(inputNode?.getAttribute('aria-expanded')).toBe('true')
|
321
|
+
inputNode && userEvent.type(inputNode, '{esc}')
|
322
|
+
expect(inputNode?.getAttribute('aria-expanded')).not.toBe('true')
|
323
|
+
})
|
324
|
+
})
|
325
|
+
|
326
|
+
describe('Autocomplete.Menu', () => {
|
327
|
+
it('calls a custom filter function', () => {
|
328
|
+
const filterFnMock = jest.fn()
|
329
|
+
const {container} = HTMLRender(
|
330
|
+
<LabelledAutocomplete menuProps={{items: mockItems, selectedItemIds: [], filterFn: filterFnMock}} />
|
331
|
+
)
|
332
|
+
const inputNode = container.querySelector('#autocompleteInput')
|
333
|
+
|
334
|
+
inputNode && userEvent.type(inputNode, 'ze')
|
335
|
+
expect(filterFnMock).toHaveBeenCalled()
|
336
|
+
})
|
337
|
+
|
338
|
+
it('calls a custom sort function when the menu closes', () => {
|
339
|
+
const sortOnCloseFnMock = jest.fn()
|
340
|
+
const {container} = HTMLRender(
|
341
|
+
<LabelledAutocomplete menuProps={{items: mockItems, selectedItemIds: [], sortOnCloseFn: sortOnCloseFnMock}} />
|
342
|
+
)
|
343
|
+
const inputNode = container.querySelector('#autocompleteInput')
|
344
|
+
|
345
|
+
// `sortOnCloseFnMock` will be called in a `.sort()` on render to check if the
|
346
|
+
// current sort order matches the result of `sortOnCloseFnMock`
|
347
|
+
expect(sortOnCloseFnMock).toHaveBeenCalledTimes(mockItems.length - 1)
|
348
|
+
if (inputNode) {
|
349
|
+
userEvent.type(inputNode, 'ze')
|
350
|
+
// eslint-disable-next-line github/no-blur
|
351
|
+
fireEvent.blur(inputNode)
|
352
|
+
}
|
353
|
+
|
354
|
+
// wait a tick for blur to finish
|
355
|
+
setTimeout(() => {
|
356
|
+
expect(sortOnCloseFnMock).toHaveBeenCalledTimes(mockItems.length)
|
357
|
+
}, 0)
|
358
|
+
})
|
359
|
+
|
360
|
+
it("calls onOpenChange with the menu's open state", () => {
|
361
|
+
const onOpenChangeMock = jest.fn()
|
362
|
+
const {container} = HTMLRender(
|
363
|
+
<LabelledAutocomplete menuProps={{items: mockItems, selectedItemIds: [], onOpenChange: onOpenChangeMock}} />
|
364
|
+
)
|
365
|
+
const inputNode = container.querySelector('#autocompleteInput')
|
366
|
+
|
367
|
+
inputNode && userEvent.type(inputNode, 'ze')
|
368
|
+
expect(onOpenChangeMock).toHaveBeenCalled()
|
369
|
+
})
|
370
|
+
|
371
|
+
it('calls onSelectedChange with the data for the selected items', () => {
|
372
|
+
const onSelectedChangeMock = jest.fn()
|
373
|
+
const {container} = HTMLRender(
|
374
|
+
<LabelledAutocomplete
|
375
|
+
menuProps={{items: mockItems, selectedItemIds: [], onSelectedChange: onSelectedChangeMock}}
|
376
|
+
/>
|
377
|
+
)
|
378
|
+
const inputNode = container.querySelector('#autocompleteInput')
|
379
|
+
|
380
|
+
expect(onSelectedChangeMock).not.toHaveBeenCalled()
|
381
|
+
if (inputNode) {
|
382
|
+
fireEvent.focus(inputNode)
|
383
|
+
userEvent.type(inputNode, '{enter}')
|
384
|
+
}
|
385
|
+
|
386
|
+
// wait a tick for the keyboard event to be dispatched to the menu item
|
387
|
+
setTimeout(() => {
|
388
|
+
expect(onSelectedChangeMock).toHaveBeenCalledWith([mockItems[0]])
|
389
|
+
}, 0)
|
390
|
+
})
|
391
|
+
|
392
|
+
it('does not close the menu when clicking an item in the menu if selectionVariant=multiple', () => {
|
393
|
+
const {getByText, container} = HTMLRender(
|
394
|
+
<LabelledAutocomplete menuProps={{items: mockItems, selectedItemIds: [], selectionVariant: 'multiple'}} />
|
395
|
+
)
|
396
|
+
const inputNode = container.querySelector('#autocompleteInput')
|
397
|
+
const itemToClickNode = getByText(mockItems[1].text)
|
398
|
+
|
399
|
+
expect(inputNode?.getAttribute('aria-expanded')).not.toBe('true')
|
400
|
+
inputNode && fireEvent.focus(inputNode)
|
401
|
+
expect(inputNode?.getAttribute('aria-expanded')).toBe('true')
|
402
|
+
fireEvent.click(itemToClickNode)
|
403
|
+
inputNode && userEvent.type(inputNode, '{enter}')
|
404
|
+
expect(inputNode?.getAttribute('aria-expanded')).toBe('true')
|
405
|
+
})
|
406
|
+
|
407
|
+
it('closes the menu when clicking an item in the menu if selectionVariant=single', () => {
|
408
|
+
const {getByText, container} = HTMLRender(
|
409
|
+
<LabelledAutocomplete menuProps={{items: mockItems, selectedItemIds: [], selectionVariant: 'single'}} />
|
410
|
+
)
|
411
|
+
const inputNode = container.querySelector('#autocompleteInput')
|
412
|
+
const itemToClickNode = getByText(mockItems[1].text)
|
413
|
+
|
414
|
+
expect(inputNode?.getAttribute('aria-expanded')).not.toBe('true')
|
415
|
+
inputNode && fireEvent.focus(inputNode)
|
416
|
+
expect(inputNode?.getAttribute('aria-expanded')).toBe('true')
|
417
|
+
fireEvent.click(itemToClickNode)
|
418
|
+
expect(inputNode?.getAttribute('aria-expanded')).not.toBe('true')
|
419
|
+
})
|
420
|
+
|
421
|
+
it('calls handleAddItem with new item data when passing addNewItem', () => {
|
422
|
+
const handleAddItemMock = jest.fn()
|
423
|
+
const {getByText} = HTMLRender(
|
424
|
+
<LabelledAutocomplete
|
425
|
+
menuProps={{
|
426
|
+
items: mockItems,
|
427
|
+
selectedItemIds: [],
|
428
|
+
selectionVariant: 'multiple',
|
429
|
+
addNewItem: {
|
430
|
+
text: 'Add new item',
|
431
|
+
handleAddItem: handleAddItemMock
|
432
|
+
}
|
433
|
+
}}
|
434
|
+
/>
|
435
|
+
)
|
436
|
+
|
437
|
+
const addNewItemNode = getByText('Add new item')
|
438
|
+
|
439
|
+
expect(handleAddItemMock).not.toHaveBeenCalled()
|
440
|
+
fireEvent.click(addNewItemNode)
|
441
|
+
expect(handleAddItemMock).toHaveBeenCalled()
|
442
|
+
})
|
443
|
+
})
|
444
|
+
})
|
@@ -24,7 +24,7 @@ describe('BorderBox', () => {
|
|
24
24
|
it('renders borders', () => {
|
25
25
|
expect(render(<BorderBox borderColor="success.emphasis" />)).toHaveStyleRule(
|
26
26
|
'border-color',
|
27
|
-
theme.colorSchemes.light.colors.
|
27
|
+
theme.colorSchemes.light.colors.success?.emphasis
|
28
28
|
)
|
29
29
|
expect(render(<BorderBox borderBottom={0} />)).toHaveStyleRule('border-bottom', '0')
|
30
30
|
})
|
@@ -38,7 +38,7 @@ describe('CircleOcticon', () => {
|
|
38
38
|
it('respects the bg prop', () => {
|
39
39
|
expect(render(<CircleOcticon icon={CheckIcon} bg="danger.subtle" />)).toHaveStyleRule(
|
40
40
|
'background-color',
|
41
|
-
theme.colorSchemes.light.colors.
|
41
|
+
theme.colorSchemes.light.colors.danger?.subtle
|
42
42
|
)
|
43
43
|
})
|
44
44
|
|
@@ -29,22 +29,22 @@ describe('CounterLabel', () => {
|
|
29
29
|
it('respects the primary "scheme" prop', () => {
|
30
30
|
expect(render(<CounterLabel scheme="primary" />)).toHaveStyleRule(
|
31
31
|
'color',
|
32
|
-
theme.colorSchemes.light.colors.
|
32
|
+
theme.colorSchemes.light.colors.fg?.onEmphasis.trim()
|
33
33
|
)
|
34
34
|
expect(render(<CounterLabel scheme="primary" />)).toHaveStyleRule(
|
35
35
|
'background-color',
|
36
|
-
theme.colorSchemes.light.colors.
|
36
|
+
theme.colorSchemes.light.colors.neutral?.emphasis.trim()
|
37
37
|
)
|
38
38
|
})
|
39
39
|
|
40
40
|
it('respects the secondary "scheme" prop', () => {
|
41
41
|
expect(render(<CounterLabel scheme="secondary" />)).toHaveStyleRule(
|
42
42
|
'color',
|
43
|
-
theme.colorSchemes.light.colors.
|
43
|
+
theme.colorSchemes.light.colors.fg?.default.trim()
|
44
44
|
)
|
45
45
|
expect(render(<CounterLabel scheme="secondary" />)).toHaveStyleRule(
|
46
46
|
'background-color',
|
47
|
-
theme.colorSchemes.light.colors.
|
47
|
+
theme.colorSchemes.light.colors.neutral?.muted
|
48
48
|
)
|
49
49
|
})
|
50
50
|
})
|
@@ -30,16 +30,16 @@ describe('Flash', () => {
|
|
30
30
|
it('respects the "variant" prop', () => {
|
31
31
|
expect(render(<Flash variant="warning" />)).toHaveStyleRule(
|
32
32
|
'background-color',
|
33
|
-
theme.colorSchemes.light.colors.
|
33
|
+
theme.colorSchemes.light.colors.attention?.subtle
|
34
34
|
)
|
35
35
|
expect(render(<Flash variant="danger" />)).toHaveStyleRule(
|
36
36
|
'background-color',
|
37
|
-
theme.colorSchemes.light.colors.
|
37
|
+
theme.colorSchemes.light.colors.danger?.subtle
|
38
38
|
)
|
39
39
|
expect(render(<Flash variant="success" />)).toHaveStyleRule(
|
40
40
|
'background-color',
|
41
|
-
theme.colorSchemes.light.colors.
|
41
|
+
theme.colorSchemes.light.colors.success?.subtle
|
42
42
|
)
|
43
|
-
expect(render(<Flash />)).toHaveStyleRule('background-color', theme.colorSchemes.light.colors.
|
43
|
+
expect(render(<Flash />)).toHaveStyleRule('background-color', theme.colorSchemes.light.colors.accent?.subtle)
|
44
44
|
})
|
45
45
|
})
|
@@ -25,7 +25,7 @@ describe('Link', () => {
|
|
25
25
|
})
|
26
26
|
|
27
27
|
it('respects hoverColor prop', () => {
|
28
|
-
expect(render(<Link hoverColor="
|
28
|
+
expect(render(<Link hoverColor="accent.fg" />)).toMatchSnapshot()
|
29
29
|
})
|
30
30
|
|
31
31
|
it('respects the "fontStyle" prop', () => {
|
@@ -75,7 +75,7 @@ exports[`AnchoredOverlay renders consistently 1`] = `
|
|
75
75
|
|
76
76
|
<div
|
77
77
|
className="c0"
|
78
|
-
color="
|
78
|
+
color="fg.default"
|
79
79
|
data-portal-root={true}
|
80
80
|
fontFamily="normal"
|
81
81
|
>
|
@@ -191,7 +191,7 @@ Object {
|
|
191
191
|
<div>
|
192
192
|
<div
|
193
193
|
class="c0"
|
194
|
-
color="
|
194
|
+
color="fg.default"
|
195
195
|
data-portal-root="true"
|
196
196
|
font-family="normal"
|
197
197
|
>
|
@@ -236,7 +236,7 @@ Object {
|
|
236
236
|
"container": <div>
|
237
237
|
<div
|
238
238
|
class="BaseStyles__Base-qvuaww-0 ihFkAM"
|
239
|
-
color="
|
239
|
+
color="fg.default"
|
240
240
|
data-portal-root="true"
|
241
241
|
font-family="normal"
|
242
242
|
>
|