govuk_publishing_components 43.4.0 → 43.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/views/govuk_publishing_components/components/_radio.html.erb +2 -0
- data/app/views/govuk_publishing_components/components/docs/inverse_header.yml +2 -30
- data/app/views/govuk_publishing_components/components/docs/radio.yml +15 -0
- data/lib/govuk_publishing_components/version.rb +1 -1
- data/node_modules/accessible-autocomplete/CHANGELOG.md +398 -0
- data/node_modules/accessible-autocomplete/CODEOWNERS +2 -0
- data/node_modules/accessible-autocomplete/CONTRIBUTING.md +161 -0
- data/node_modules/accessible-autocomplete/LICENSE.txt +20 -0
- data/node_modules/accessible-autocomplete/Procfile +1 -0
- data/node_modules/accessible-autocomplete/README.md +490 -0
- data/node_modules/accessible-autocomplete/accessibility-criteria.md +43 -0
- data/node_modules/accessible-autocomplete/app.json +15 -0
- data/node_modules/accessible-autocomplete/babel.config.js +29 -0
- data/node_modules/accessible-autocomplete/dist/accessible-autocomplete.min.css +3 -0
- data/node_modules/accessible-autocomplete/dist/accessible-autocomplete.min.css.map +1 -0
- data/node_modules/accessible-autocomplete/dist/accessible-autocomplete.min.js +2 -0
- data/node_modules/accessible-autocomplete/dist/accessible-autocomplete.min.js.map +1 -0
- data/node_modules/accessible-autocomplete/dist/lib/accessible-autocomplete.preact.min.js +2 -0
- data/node_modules/accessible-autocomplete/dist/lib/accessible-autocomplete.preact.min.js.map +1 -0
- data/node_modules/accessible-autocomplete/dist/lib/accessible-autocomplete.react.min.js +2 -0
- data/node_modules/accessible-autocomplete/dist/lib/accessible-autocomplete.react.min.js.map +1 -0
- data/node_modules/accessible-autocomplete/examples/ajax-source.html +300 -0
- data/node_modules/accessible-autocomplete/examples/form-single.html +381 -0
- data/node_modules/accessible-autocomplete/examples/form.html +673 -0
- data/node_modules/accessible-autocomplete/examples/index.html +693 -0
- data/node_modules/accessible-autocomplete/examples/preact/index.html +346 -0
- data/node_modules/accessible-autocomplete/examples/react/index.html +347 -0
- data/node_modules/accessible-autocomplete/examples/suggestions.json +258 -0
- data/node_modules/accessible-autocomplete/package.json +93 -0
- data/node_modules/accessible-autocomplete/postcss.config.js +16 -0
- data/node_modules/accessible-autocomplete/preact.js +1 -0
- data/node_modules/accessible-autocomplete/react.js +1 -0
- data/node_modules/accessible-autocomplete/scripts/check-staged.mjs +16 -0
- data/node_modules/accessible-autocomplete/src/autocomplete.css +167 -0
- data/node_modules/accessible-autocomplete/src/autocomplete.js +610 -0
- data/node_modules/accessible-autocomplete/src/dropdown-arrow-down.js +11 -0
- data/node_modules/accessible-autocomplete/src/status.js +125 -0
- data/node_modules/accessible-autocomplete/src/wrapper.js +60 -0
- data/node_modules/accessible-autocomplete/test/functional/dropdown-arrow-down.js +46 -0
- data/node_modules/accessible-autocomplete/test/functional/index.js +809 -0
- data/node_modules/accessible-autocomplete/test/functional/wrapper.js +339 -0
- data/node_modules/accessible-autocomplete/test/integration/index.js +309 -0
- data/node_modules/accessible-autocomplete/test/karma.config.js +46 -0
- data/node_modules/accessible-autocomplete/test/wdio.config.js +123 -0
- data/node_modules/accessible-autocomplete/webpack.config.mjs +244 -0
- metadata +57 -2
@@ -0,0 +1,339 @@
|
|
1
|
+
/* global before, beforeEach, after, describe, it */
|
2
|
+
|
3
|
+
import { expect } from 'chai'
|
4
|
+
import accessibleAutocomplete from '../../src/wrapper'
|
5
|
+
|
6
|
+
const DEFAULT_OPTIONS = {
|
7
|
+
'': 'Select',
|
8
|
+
fr: 'France',
|
9
|
+
de: 'Germany',
|
10
|
+
gb: 'United Kingdom of Great Britain & Northern Ireland'
|
11
|
+
}
|
12
|
+
|
13
|
+
const injectSelectToEnhanceIntoDOM = (element, settings) => {
|
14
|
+
settings = settings || {}
|
15
|
+
settings.options = settings.options || DEFAULT_OPTIONS
|
16
|
+
settings.id = settings.id !== undefined ? settings.id : 'location-picker-id'
|
17
|
+
settings.name = settings.name !== undefined ? settings.name : 'location-picker-name'
|
18
|
+
const $select = document.createElement('select')
|
19
|
+
if (settings.id) {
|
20
|
+
$select.id = settings.id
|
21
|
+
}
|
22
|
+
if (settings.name) {
|
23
|
+
$select.name = settings.name
|
24
|
+
}
|
25
|
+
|
26
|
+
Object.keys(settings.options)
|
27
|
+
.map(optionKey => {
|
28
|
+
const option = document.createElement('option')
|
29
|
+
option.value = optionKey
|
30
|
+
option.text = settings.options[optionKey]
|
31
|
+
option.selected = (settings.selected === optionKey)
|
32
|
+
return option
|
33
|
+
})
|
34
|
+
.forEach(option => $select.appendChild(option))
|
35
|
+
|
36
|
+
element.appendChild($select)
|
37
|
+
|
38
|
+
return $select
|
39
|
+
}
|
40
|
+
|
41
|
+
describe('Wrapper', () => {
|
42
|
+
let scratch
|
43
|
+
before(() => {
|
44
|
+
scratch = document.createElement('div');
|
45
|
+
(document.body || document.documentElement).appendChild(scratch)
|
46
|
+
})
|
47
|
+
|
48
|
+
beforeEach(() => {
|
49
|
+
scratch.innerHTML = ''
|
50
|
+
})
|
51
|
+
|
52
|
+
after(() => {
|
53
|
+
scratch.parentNode.removeChild(scratch)
|
54
|
+
scratch = null
|
55
|
+
})
|
56
|
+
|
57
|
+
it('throws an error when called on nonexistent element', () => {
|
58
|
+
expect(
|
59
|
+
accessibleAutocomplete.bind(null, {
|
60
|
+
element: document.querySelector('#nothing-container'),
|
61
|
+
id: 'scratch',
|
62
|
+
source: () => {}
|
63
|
+
})
|
64
|
+
).to.throw('element is not defined')
|
65
|
+
})
|
66
|
+
|
67
|
+
it('throws an error when called without an id ', () => {
|
68
|
+
expect(
|
69
|
+
accessibleAutocomplete.bind(null, {
|
70
|
+
element: scratch,
|
71
|
+
source: () => {}
|
72
|
+
})
|
73
|
+
).to.throw('id is not defined')
|
74
|
+
})
|
75
|
+
|
76
|
+
it('throws an error when called without a source', () => {
|
77
|
+
expect(
|
78
|
+
accessibleAutocomplete.bind(null, {
|
79
|
+
element: scratch,
|
80
|
+
id: 'scratch'
|
81
|
+
})
|
82
|
+
).to.throw('source is not defined')
|
83
|
+
})
|
84
|
+
|
85
|
+
it('throws an error when called on nonexistent selectElement', () => {
|
86
|
+
expect(
|
87
|
+
accessibleAutocomplete.enhanceSelectElement.bind(null, {
|
88
|
+
selectElement: document.querySelector('#nothing')
|
89
|
+
})
|
90
|
+
).to.throw('selectElement is not defined')
|
91
|
+
})
|
92
|
+
|
93
|
+
it('can enhance a select element', () => {
|
94
|
+
const select = injectSelectToEnhanceIntoDOM(scratch)
|
95
|
+
const id = select.id
|
96
|
+
|
97
|
+
accessibleAutocomplete.enhanceSelectElement({
|
98
|
+
selectElement: select
|
99
|
+
})
|
100
|
+
|
101
|
+
const autocompleteInstances = document.querySelectorAll('.autocomplete__wrapper')
|
102
|
+
expect(autocompleteInstances.length).to.equal(1)
|
103
|
+
|
104
|
+
const autocompleteInstance = autocompleteInstances[0]
|
105
|
+
|
106
|
+
const autocompleteInput = autocompleteInstance.querySelector('.autocomplete__input')
|
107
|
+
expect(autocompleteInput.tagName.toLowerCase()).to.equal('input')
|
108
|
+
expect(autocompleteInput.id).to.equal(id)
|
109
|
+
})
|
110
|
+
|
111
|
+
it('uses the defaultValue setting to populate the input field if no option is selected', () => {
|
112
|
+
const select = injectSelectToEnhanceIntoDOM(scratch, { selected: '' })
|
113
|
+
accessibleAutocomplete.enhanceSelectElement({
|
114
|
+
defaultValue: '',
|
115
|
+
selectElement: select
|
116
|
+
})
|
117
|
+
|
118
|
+
const autocompleteInstances = document.querySelectorAll('.autocomplete__wrapper')
|
119
|
+
const autocompleteInstance = autocompleteInstances[0]
|
120
|
+
|
121
|
+
const autocompleteInput = autocompleteInstance.querySelector('.autocomplete__input')
|
122
|
+
expect(autocompleteInput.value).to.equal('')
|
123
|
+
})
|
124
|
+
|
125
|
+
it('uses the option label as the default input element value if an option is selected', () => {
|
126
|
+
const select = injectSelectToEnhanceIntoDOM(scratch, { selected: 'de' })
|
127
|
+
accessibleAutocomplete.enhanceSelectElement({
|
128
|
+
defaultValue: '',
|
129
|
+
selectElement: select
|
130
|
+
})
|
131
|
+
|
132
|
+
const autocompleteInstances = document.querySelectorAll('.autocomplete__wrapper')
|
133
|
+
const autocompleteInstance = autocompleteInstances[0]
|
134
|
+
|
135
|
+
const autocompleteInput = autocompleteInstance.querySelector('.autocomplete__input')
|
136
|
+
expect(autocompleteInput.value).to.equal('Germany')
|
137
|
+
})
|
138
|
+
|
139
|
+
it('gives the autocomplete element a blank name attribute by default', () => {
|
140
|
+
const select = injectSelectToEnhanceIntoDOM(scratch)
|
141
|
+
|
142
|
+
accessibleAutocomplete.enhanceSelectElement({
|
143
|
+
selectElement: select
|
144
|
+
})
|
145
|
+
|
146
|
+
const autocompleteInstances = document.querySelectorAll('.autocomplete__wrapper')
|
147
|
+
|
148
|
+
const autocompleteInstance = autocompleteInstances[0]
|
149
|
+
|
150
|
+
const autocompleteInput = autocompleteInstance.querySelector('.autocomplete__input')
|
151
|
+
expect(autocompleteInput.name).to.equal('')
|
152
|
+
})
|
153
|
+
|
154
|
+
it('can define a name for the autocomplete element', () => {
|
155
|
+
const select = injectSelectToEnhanceIntoDOM(scratch)
|
156
|
+
|
157
|
+
accessibleAutocomplete.enhanceSelectElement({
|
158
|
+
name: 'location-picker-autocomplete',
|
159
|
+
selectElement: select
|
160
|
+
})
|
161
|
+
|
162
|
+
const autocompleteInstances = document.querySelectorAll('.autocomplete__wrapper')
|
163
|
+
|
164
|
+
const autocompleteInstance = autocompleteInstances[0]
|
165
|
+
|
166
|
+
const autocompleteInput = autocompleteInstance.querySelector('.autocomplete__input')
|
167
|
+
expect(autocompleteInput.name).to.equal('location-picker-autocomplete')
|
168
|
+
})
|
169
|
+
|
170
|
+
it('does not include "null" options in autocomplete', (done) => {
|
171
|
+
const select = injectSelectToEnhanceIntoDOM(scratch)
|
172
|
+
|
173
|
+
accessibleAutocomplete.enhanceSelectElement({
|
174
|
+
selectElement: select
|
175
|
+
})
|
176
|
+
|
177
|
+
const autocompleteInstances = document.querySelectorAll('.autocomplete__wrapper')
|
178
|
+
const autocompleteInstance = autocompleteInstances[0]
|
179
|
+
const autocompleteInput = autocompleteInstance.querySelector('.autocomplete__input')
|
180
|
+
|
181
|
+
// Using setTimeouts here since changes in values take a while to reflect in lists
|
182
|
+
autocompleteInput.value = 'e'
|
183
|
+
setTimeout(() => {
|
184
|
+
const autocompleteOptions = autocompleteInstance.querySelectorAll('.autocomplete__option')
|
185
|
+
expect(autocompleteOptions.length).to.equal(3)
|
186
|
+
expect([].map.call(autocompleteOptions, o => o.textContent)).not.to.contain('Select')
|
187
|
+
done()
|
188
|
+
}, 250)
|
189
|
+
})
|
190
|
+
|
191
|
+
it('includes "null" options in autocomplete if `preserveNullOptions` flag is true', (done) => {
|
192
|
+
const select = injectSelectToEnhanceIntoDOM(scratch)
|
193
|
+
|
194
|
+
accessibleAutocomplete.enhanceSelectElement({
|
195
|
+
preserveNullOptions: true,
|
196
|
+
selectElement: select
|
197
|
+
})
|
198
|
+
|
199
|
+
const autocompleteInstances = document.querySelectorAll('.autocomplete__wrapper')
|
200
|
+
const autocompleteInstance = autocompleteInstances[0]
|
201
|
+
const autocompleteInput = autocompleteInstance.querySelector('.autocomplete__input')
|
202
|
+
|
203
|
+
// Using setTimeouts here since changes in values take a while to reflect in lists
|
204
|
+
autocompleteInput.value = 'e'
|
205
|
+
setTimeout(() => {
|
206
|
+
const autocompleteOptions = autocompleteInstance.querySelectorAll('.autocomplete__option')
|
207
|
+
expect(autocompleteOptions.length).to.equal(4)
|
208
|
+
expect([].map.call(autocompleteOptions, o => o.textContent)).to.contain('Select')
|
209
|
+
done()
|
210
|
+
}, 250)
|
211
|
+
})
|
212
|
+
|
213
|
+
it('has all options when typing', (done) => {
|
214
|
+
const select = injectSelectToEnhanceIntoDOM(scratch)
|
215
|
+
|
216
|
+
accessibleAutocomplete.enhanceSelectElement({
|
217
|
+
selectElement: select
|
218
|
+
})
|
219
|
+
|
220
|
+
const autocompleteInstances = document.querySelectorAll('.autocomplete__wrapper')
|
221
|
+
const autocompleteInstance = autocompleteInstances[0]
|
222
|
+
const autocompleteInput = autocompleteInstance.querySelector('.autocomplete__input')
|
223
|
+
const autocompleteOption = autocompleteInstance.querySelector('.autocomplete__option')
|
224
|
+
|
225
|
+
// Using setTimeouts here since changes in values take a while to reflect in lists
|
226
|
+
autocompleteInput.value = 'Fran'
|
227
|
+
setTimeout(() => {
|
228
|
+
expect(autocompleteOption.textContent).to.equal('France')
|
229
|
+
autocompleteInput.value = 'Ger'
|
230
|
+
setTimeout(() => {
|
231
|
+
expect(autocompleteOption.textContent).to.equal('Germany')
|
232
|
+
autocompleteInput.value = 'United'
|
233
|
+
setTimeout(() => {
|
234
|
+
const autocompleteHint = autocompleteInstance.querySelector('.autocomplete__hint')
|
235
|
+
expect(autocompleteOption.textContent).to.equal('United Kingdom of Great Britain & Northern Ireland')
|
236
|
+
expect(autocompleteHint.value).to.equal('United Kingdom of Great Britain & Northern Ireland')
|
237
|
+
done()
|
238
|
+
}, 250)
|
239
|
+
}, 250)
|
240
|
+
}, 250)
|
241
|
+
})
|
242
|
+
|
243
|
+
it('includes aria attributes on each option, to indicate position within the full set of list item elements', (done) => {
|
244
|
+
const select = injectSelectToEnhanceIntoDOM(scratch)
|
245
|
+
|
246
|
+
accessibleAutocomplete.enhanceSelectElement({
|
247
|
+
selectElement: select
|
248
|
+
})
|
249
|
+
|
250
|
+
const autocompleteInstances = document.querySelectorAll('.autocomplete__wrapper')
|
251
|
+
const autocompleteInstance = autocompleteInstances[0]
|
252
|
+
const autocompleteInput = autocompleteInstance.querySelector('.autocomplete__input')
|
253
|
+
autocompleteInput.value = 'e'
|
254
|
+
setTimeout(() => {
|
255
|
+
const autocompleteOptions = autocompleteInstance.querySelectorAll('.autocomplete__option')
|
256
|
+
expect(autocompleteOptions.length).to.equal(3)
|
257
|
+
expect(autocompleteOptions[0].getAttribute('aria-posinset')).to.equal('1')
|
258
|
+
expect(autocompleteOptions[0].getAttribute('aria-setsize')).to.equal('3')
|
259
|
+
expect(autocompleteOptions[1].getAttribute('aria-posinset')).to.equal('2')
|
260
|
+
expect(autocompleteOptions[1].getAttribute('aria-setsize')).to.equal('3')
|
261
|
+
expect(autocompleteOptions[2].getAttribute('aria-posinset')).to.equal('3')
|
262
|
+
expect(autocompleteOptions[2].getAttribute('aria-setsize')).to.equal('3')
|
263
|
+
done()
|
264
|
+
}, 250)
|
265
|
+
})
|
266
|
+
|
267
|
+
it('includes an explicit position suffix on each list item option when iOS is detected', (done) => {
|
268
|
+
Object.defineProperty(global.navigator, 'userAgent', { value: 'iPhone AppleWebKit', configurable: true })
|
269
|
+
|
270
|
+
const select = injectSelectToEnhanceIntoDOM(scratch)
|
271
|
+
|
272
|
+
accessibleAutocomplete.enhanceSelectElement({
|
273
|
+
selectElement: select
|
274
|
+
})
|
275
|
+
|
276
|
+
const autocompleteInstances = document.querySelectorAll('.autocomplete__wrapper')
|
277
|
+
const autocompleteInstance = autocompleteInstances[0]
|
278
|
+
const autocompleteInput = autocompleteInstance.querySelector('.autocomplete__input')
|
279
|
+
const autocompleteOption = autocompleteInstance.querySelector('.autocomplete__option')
|
280
|
+
|
281
|
+
autocompleteInput.value = 'Fran'
|
282
|
+
setTimeout(() => {
|
283
|
+
expect(autocompleteOption.textContent).to.equal('France 1 of 1')
|
284
|
+
const iosSuffixSpan = autocompleteOption.querySelector('#location-picker-id__option-suffix--0')
|
285
|
+
expect(iosSuffixSpan.textContent).to.equal(' 1 of 1')
|
286
|
+
done()
|
287
|
+
}, 250)
|
288
|
+
})
|
289
|
+
|
290
|
+
it('does not include a position suffix on each list item option, when iOS is not detected', (done) => {
|
291
|
+
Object.defineProperty(global.navigator, 'userAgent', { value: 'definitely not an iDevice', configurable: true })
|
292
|
+
|
293
|
+
const select = injectSelectToEnhanceIntoDOM(scratch)
|
294
|
+
|
295
|
+
accessibleAutocomplete.enhanceSelectElement({
|
296
|
+
selectElement: select
|
297
|
+
})
|
298
|
+
|
299
|
+
const autocompleteInstances = document.querySelectorAll('.autocomplete__wrapper')
|
300
|
+
const autocompleteInstance = autocompleteInstances[0]
|
301
|
+
const autocompleteInput = autocompleteInstance.querySelector('.autocomplete__input')
|
302
|
+
const autocompleteOption = autocompleteInstance.querySelector('.autocomplete__option')
|
303
|
+
|
304
|
+
autocompleteInput.value = 'Fran'
|
305
|
+
setTimeout(() => {
|
306
|
+
expect(autocompleteOption.textContent).to.equal('France')
|
307
|
+
const iosSuffixSpan = autocompleteOption.querySelector('#location-picker-id__option-suffix--0')
|
308
|
+
expect(iosSuffixSpan).to.equal(null)
|
309
|
+
done()
|
310
|
+
}, 250)
|
311
|
+
})
|
312
|
+
|
313
|
+
it('onConfirm updates original select', (done) => {
|
314
|
+
const select = injectSelectToEnhanceIntoDOM(scratch, { selected: 'de' })
|
315
|
+
|
316
|
+
accessibleAutocomplete.enhanceSelectElement({
|
317
|
+
selectElement: select
|
318
|
+
})
|
319
|
+
|
320
|
+
const autocompleteInstances = document.querySelectorAll('.autocomplete__wrapper')
|
321
|
+
const autocompleteInstance = autocompleteInstances[0]
|
322
|
+
const autocompleteInput = autocompleteInstance.querySelector('.autocomplete__input')
|
323
|
+
const autocompleteOption = autocompleteInstance.querySelector('.autocomplete__option')
|
324
|
+
|
325
|
+
// Check the initial value of the original selectElement
|
326
|
+
expect(select.value).to.equal('de')
|
327
|
+
// Using setTimeouts here since changes in values take a while to reflect in lists
|
328
|
+
autocompleteInput.value = 'United'
|
329
|
+
setTimeout(() => {
|
330
|
+
expect(autocompleteOption.textContent).to.equal('United Kingdom of Great Britain & Northern Ireland')
|
331
|
+
autocompleteOption.click()
|
332
|
+
expect(select.value).to.equal('gb')
|
333
|
+
setTimeout(() => {
|
334
|
+
expect(autocompleteInput.value).to.equal('United Kingdom of Great Britain & Northern Ireland')
|
335
|
+
done()
|
336
|
+
}, 250)
|
337
|
+
}, 250)
|
338
|
+
})
|
339
|
+
})
|
@@ -0,0 +1,309 @@
|
|
1
|
+
/* global afterEach, beforeEach, describe, it */
|
2
|
+
|
3
|
+
const { mkdir } = require('fs/promises')
|
4
|
+
const { dirname, join } = require('path')
|
5
|
+
const { cwd } = require('process')
|
6
|
+
const { $, browser, expect } = require('@wdio/globals')
|
7
|
+
const { Key } = require('webdriverio')
|
8
|
+
|
9
|
+
const { browserName, browserVersion } = browser.capabilities
|
10
|
+
const isChrome = browserName === 'chrome'
|
11
|
+
// const isFireFox = browserName === 'firefox'
|
12
|
+
const isIE = browserName === 'internet explorer'
|
13
|
+
const liveRegionWaitTimeMillis = 10000
|
14
|
+
|
15
|
+
const basicExample = () => {
|
16
|
+
describe('basic example', function () {
|
17
|
+
const input = 'input#autocomplete-default'
|
18
|
+
|
19
|
+
let $input
|
20
|
+
let $menu
|
21
|
+
|
22
|
+
beforeEach(async () => {
|
23
|
+
$input = await $(input)
|
24
|
+
$menu = await $(`${input} + ul`)
|
25
|
+
})
|
26
|
+
|
27
|
+
it('should show the input', async () => {
|
28
|
+
await $input.waitForExist()
|
29
|
+
expect(await $input.isDisplayed()).toEqual(true)
|
30
|
+
})
|
31
|
+
|
32
|
+
it('should allow focusing the input', async () => {
|
33
|
+
await $input.click()
|
34
|
+
expect(await $input.isFocused()).toEqual(true)
|
35
|
+
})
|
36
|
+
|
37
|
+
it('should display suggestions', async () => {
|
38
|
+
await $input.click()
|
39
|
+
await $input.setValue('ita')
|
40
|
+
await $menu.waitForDisplayed()
|
41
|
+
expect(await $menu.isDisplayed()).toEqual(true)
|
42
|
+
})
|
43
|
+
|
44
|
+
// These tests are flakey when run through Saucelabs so we only run them
|
45
|
+
// in Chrome
|
46
|
+
if (isChrome) {
|
47
|
+
it('should announce status changes using two alternately updated aria live regions', async () => {
|
48
|
+
const $regionA = await $('#autocomplete-default__status--A')
|
49
|
+
const $regionB = await $('#autocomplete-default__status--B')
|
50
|
+
|
51
|
+
expect(await $regionA.getText()).toEqual('')
|
52
|
+
expect(await $regionB.getText()).toEqual('')
|
53
|
+
|
54
|
+
await $input.click()
|
55
|
+
await $input.setValue('a')
|
56
|
+
|
57
|
+
// We can't tell which region will be used first, so we have to
|
58
|
+
// await all region status text for at least one non-empty value
|
59
|
+
const waitForText = ($elements) => async () =>
|
60
|
+
Promise.all($elements.map(($element) => $element.getText()))
|
61
|
+
.then((values) => values.some(Boolean))
|
62
|
+
|
63
|
+
await browser.waitUntil(
|
64
|
+
waitForText([$regionA, $regionB]),
|
65
|
+
liveRegionWaitTimeMillis,
|
66
|
+
`expected the first aria live region to be populated within ${liveRegionWaitTimeMillis} milliseconds`
|
67
|
+
)
|
68
|
+
|
69
|
+
if (await $regionA.getText()) {
|
70
|
+
await $input.addValue('s')
|
71
|
+
await browser.waitUntil(
|
72
|
+
waitForText([$regionA, $regionB]),
|
73
|
+
liveRegionWaitTimeMillis,
|
74
|
+
`expected the first aria live region to be cleared, and the second to be populated within ${liveRegionWaitTimeMillis} milliseconds`
|
75
|
+
)
|
76
|
+
|
77
|
+
await $input.addValue('h')
|
78
|
+
await browser.waitUntil(
|
79
|
+
waitForText([$regionA, $regionB]),
|
80
|
+
liveRegionWaitTimeMillis,
|
81
|
+
`expected the first aria live region to be populated, and the second to be cleared within ${liveRegionWaitTimeMillis} milliseconds`
|
82
|
+
)
|
83
|
+
} else {
|
84
|
+
await $input.addValue('s')
|
85
|
+
await browser.waitUntil(
|
86
|
+
waitForText([$regionA, $regionB]),
|
87
|
+
liveRegionWaitTimeMillis,
|
88
|
+
`expected the first aria live region to be populated, and the second to be cleared within ${liveRegionWaitTimeMillis} milliseconds`
|
89
|
+
)
|
90
|
+
|
91
|
+
await $input.addValue('h')
|
92
|
+
await browser.waitUntil(
|
93
|
+
waitForText([$regionA, $regionB]),
|
94
|
+
liveRegionWaitTimeMillis,
|
95
|
+
`expected the first aria live region to be cleared, and the second to be populated within ${liveRegionWaitTimeMillis} milliseconds`
|
96
|
+
)
|
97
|
+
}
|
98
|
+
})
|
99
|
+
}
|
100
|
+
|
101
|
+
it('should set aria-selected to true on selected option and false on other options', async () => {
|
102
|
+
await $input.click()
|
103
|
+
await $input.setValue('ita')
|
104
|
+
|
105
|
+
const $option1 = await $(`${input} + ul li:nth-child(1)`)
|
106
|
+
const $option2 = await $(`${input} + ul li:nth-child(2)`)
|
107
|
+
|
108
|
+
await browser.keys([Key.ArrowDown])
|
109
|
+
expect(await $option1.getAttribute('aria-selected')).toEqual('true')
|
110
|
+
expect(await $option2.getAttribute('aria-selected')).toEqual('false')
|
111
|
+
await browser.keys([Key.ArrowDown])
|
112
|
+
expect(await $option1.getAttribute('aria-selected')).toEqual('false')
|
113
|
+
expect(await $option2.getAttribute('aria-selected')).toEqual('true')
|
114
|
+
})
|
115
|
+
|
116
|
+
describe('keyboard use', () => {
|
117
|
+
it('should allow typing', async () => {
|
118
|
+
await $input.click()
|
119
|
+
await $input.addValue('ita')
|
120
|
+
expect(await $input.getValue()).toEqual('ita')
|
121
|
+
})
|
122
|
+
|
123
|
+
it('should allow selecting an option', async () => {
|
124
|
+
await $input.click()
|
125
|
+
await $input.setValue('ita')
|
126
|
+
|
127
|
+
const $option1 = await $(`${input} + ul li:nth-child(1)`)
|
128
|
+
const $option2 = await $(`${input} + ul li:nth-child(2)`)
|
129
|
+
|
130
|
+
await browser.keys([Key.ArrowDown])
|
131
|
+
expect(await $input.isFocused()).toEqual(false)
|
132
|
+
expect(await $option1.isFocused()).toEqual(true)
|
133
|
+
await browser.keys([Key.ArrowDown])
|
134
|
+
expect(await $menu.isDisplayed()).toEqual(true)
|
135
|
+
expect(await $input.getValue()).toEqual('ita')
|
136
|
+
expect(await $option1.isFocused()).toEqual(false)
|
137
|
+
expect(await $option2.isFocused()).toEqual(true)
|
138
|
+
})
|
139
|
+
|
140
|
+
it('should allow confirming an option', async () => {
|
141
|
+
await $input.click()
|
142
|
+
await $input.setValue('ita')
|
143
|
+
await browser.keys([Key.ArrowDown, Key.Enter])
|
144
|
+
await browser.waitUntil(async () => await $input.getValue() !== 'ita')
|
145
|
+
expect(await $input.isFocused()).toEqual(true)
|
146
|
+
expect(await $input.getValue()).toEqual('Italy')
|
147
|
+
})
|
148
|
+
|
149
|
+
it('should redirect keypresses on an option to input', async () => {
|
150
|
+
if (!isIE) {
|
151
|
+
await $input.click()
|
152
|
+
await $input.setValue('ita')
|
153
|
+
|
154
|
+
const $option1 = await $(`${input} + ul li:nth-child(1)`)
|
155
|
+
|
156
|
+
await browser.keys([Key.ArrowDown])
|
157
|
+
expect(await $input.isFocused()).toEqual(false)
|
158
|
+
expect(await $option1.isFocused()).toEqual(true)
|
159
|
+
await $option1.addValue('l')
|
160
|
+
expect(await $input.isFocused()).toEqual(true)
|
161
|
+
expect(await $input.getValue()).toEqual('ital')
|
162
|
+
} else {
|
163
|
+
// FIXME: This feature does not work correctly on IE 11
|
164
|
+
}
|
165
|
+
})
|
166
|
+
})
|
167
|
+
|
168
|
+
describe('mouse use', () => {
|
169
|
+
it('should allow confirming an option', async () => {
|
170
|
+
await $input.click()
|
171
|
+
await $input.setValue('ita')
|
172
|
+
|
173
|
+
const $option1 = await $(`${input} + ul li:nth-child(1)`)
|
174
|
+
await $option1.click()
|
175
|
+
|
176
|
+
expect(await $input.isFocused()).toEqual(true)
|
177
|
+
expect(await $input.getValue()).toEqual('Italy')
|
178
|
+
})
|
179
|
+
})
|
180
|
+
})
|
181
|
+
}
|
182
|
+
|
183
|
+
const customTemplatesExample = () => {
|
184
|
+
describe('custom templates example', function () {
|
185
|
+
const input = 'input#autocomplete-customTemplates'
|
186
|
+
|
187
|
+
let $input
|
188
|
+
|
189
|
+
beforeEach(async () => {
|
190
|
+
$input = await $(input)
|
191
|
+
|
192
|
+
await $input.setValue('') // Prevent autofilling, IE likes to do this.
|
193
|
+
})
|
194
|
+
|
195
|
+
describe('mouse use', () => {
|
196
|
+
it('should allow confirming an option by clicking on child elements', async () => {
|
197
|
+
await $input.setValue('uni')
|
198
|
+
|
199
|
+
const $option1InnerElement = await $(`${input} + ul li:nth-child(1) strong`)
|
200
|
+
|
201
|
+
if (isIE) {
|
202
|
+
// FIXME: This feature works correctly on IE but testing it doesn't seem to work.
|
203
|
+
return
|
204
|
+
}
|
205
|
+
|
206
|
+
try {
|
207
|
+
await $option1InnerElement.click()
|
208
|
+
} catch (error) {
|
209
|
+
// In some cases (mainly ChromeDriver) the automation protocol won't
|
210
|
+
// allow clicking span elements. In this case we just skip the test.
|
211
|
+
if (error.toString().match(/Other element would receive the click/)) {
|
212
|
+
return
|
213
|
+
} else {
|
214
|
+
throw error
|
215
|
+
}
|
216
|
+
}
|
217
|
+
|
218
|
+
expect(await $input.isFocused()).toEqual(true)
|
219
|
+
expect(await $input.getValue()).toEqual('United Kingdom')
|
220
|
+
})
|
221
|
+
})
|
222
|
+
})
|
223
|
+
}
|
224
|
+
|
225
|
+
const classesPropsExamples = () => {
|
226
|
+
describe('classes properties', () => {
|
227
|
+
it('should set `inputClasses` on both hint and input', async () => {
|
228
|
+
const $input = await $('input#autocomplete-inputClasses')
|
229
|
+
await expect($input).toHaveElementClass('app-input')
|
230
|
+
|
231
|
+
// Trigger the display of the hint
|
232
|
+
await $input.setValue('fra')
|
233
|
+
|
234
|
+
const $hint = $input.parentElement().$('.autocomplete__hint')
|
235
|
+
await expect($hint).toBeDisplayed()
|
236
|
+
await expect($hint).toHaveElementClass('app-input')
|
237
|
+
})
|
238
|
+
|
239
|
+
it('should set `hintClasses` on the hint', async () => {
|
240
|
+
const $input = await $('input#autocomplete-hintClasses')
|
241
|
+
|
242
|
+
// Trigger the display of the hint
|
243
|
+
await $input.setValue('fra')
|
244
|
+
|
245
|
+
const $hint = $input.parentElement().$('.autocomplete__hint')
|
246
|
+
await expect($hint).toBeDisplayed()
|
247
|
+
await expect($hint).toHaveElementClass('app-hint')
|
248
|
+
})
|
249
|
+
})
|
250
|
+
}
|
251
|
+
|
252
|
+
const takeScreenshotsIfFail = () => {
|
253
|
+
afterEach(async function () {
|
254
|
+
const testFailed = this.currentTest.state === 'failed'
|
255
|
+
if (testFailed) {
|
256
|
+
const timestamp = +new Date()
|
257
|
+
const browserVariant = isIE ? `ie${browserVersion}` : browserName
|
258
|
+
const testTitle = this.currentTest.title.replace(/\W/g, '-')
|
259
|
+
const filename = join(cwd(), `screenshots/${timestamp}-${browserVariant}-${testTitle}.png`)
|
260
|
+
await mkdir(dirname(filename), { recursive: true })
|
261
|
+
await browser.saveScreenshot(filename)
|
262
|
+
console.log(`Test failed, created: ${filename}`)
|
263
|
+
}
|
264
|
+
})
|
265
|
+
}
|
266
|
+
|
267
|
+
describe('Accessible Autocomplete', () => {
|
268
|
+
beforeEach(async () => {
|
269
|
+
await browser.url('/')
|
270
|
+
})
|
271
|
+
|
272
|
+
it('should have the right title', async () => {
|
273
|
+
expect(await browser.getTitle()).toEqual('Accessible Autocomplete examples')
|
274
|
+
})
|
275
|
+
|
276
|
+
basicExample()
|
277
|
+
customTemplatesExample()
|
278
|
+
classesPropsExamples()
|
279
|
+
|
280
|
+
takeScreenshotsIfFail()
|
281
|
+
})
|
282
|
+
|
283
|
+
describe('Accessible Autocomplete Preact', () => {
|
284
|
+
beforeEach(async () => {
|
285
|
+
await browser.url('/preact')
|
286
|
+
})
|
287
|
+
|
288
|
+
it('should have the right title', async () => {
|
289
|
+
expect(await browser.getTitle()).toEqual('Accessible Autocomplete Preact examples')
|
290
|
+
})
|
291
|
+
|
292
|
+
basicExample()
|
293
|
+
|
294
|
+
takeScreenshotsIfFail()
|
295
|
+
})
|
296
|
+
|
297
|
+
describe('Accessible Autocomplete React', () => {
|
298
|
+
beforeEach(async () => {
|
299
|
+
await browser.url('/react')
|
300
|
+
})
|
301
|
+
|
302
|
+
it('should have the right title', async () => {
|
303
|
+
expect(await browser.getTitle()).toEqual('Accessible Autocomplete React examples')
|
304
|
+
})
|
305
|
+
|
306
|
+
basicExample()
|
307
|
+
|
308
|
+
takeScreenshotsIfFail()
|
309
|
+
})
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require('@babel/register')({
|
2
|
+
rootMode: 'upward'
|
3
|
+
})
|
4
|
+
|
5
|
+
const puppeteer = require('puppeteer')
|
6
|
+
const webpackConfig = require('../webpack.config.mjs')
|
7
|
+
|
8
|
+
// Use Chrome headless
|
9
|
+
process.env.CHROME_BIN = puppeteer.executablePath()
|
10
|
+
|
11
|
+
module.exports = function (config) {
|
12
|
+
config.set({
|
13
|
+
basePath: '../',
|
14
|
+
frameworks: ['mocha', 'webpack'],
|
15
|
+
reporters: ['mocha'],
|
16
|
+
|
17
|
+
browsers: ['ChromeHeadless'],
|
18
|
+
|
19
|
+
files: [
|
20
|
+
'test/functional/**/*.js'
|
21
|
+
],
|
22
|
+
|
23
|
+
preprocessors: {
|
24
|
+
'test/**/*.js': ['webpack'],
|
25
|
+
'src/**/*.js': ['webpack'],
|
26
|
+
'**/*.js': ['sourcemap']
|
27
|
+
},
|
28
|
+
|
29
|
+
webpack: {
|
30
|
+
// Use standalone webpack config [0] rather
|
31
|
+
// than Preact [1] or React [2] configs
|
32
|
+
...webpackConfig.default[0],
|
33
|
+
|
34
|
+
// Use Karma managed test entry points
|
35
|
+
entry: undefined,
|
36
|
+
|
37
|
+
// Use Karma default `os.tmpdir()` output
|
38
|
+
output: undefined,
|
39
|
+
|
40
|
+
// Suppress webpack performance warnings due to
|
41
|
+
// Karma chunked output and inline source maps
|
42
|
+
performance: { hints: false },
|
43
|
+
stats: { preset: 'errors-only' }
|
44
|
+
}
|
45
|
+
})
|
46
|
+
}
|