govuk_publishing_components 43.3.0 → 43.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/assets/images/govuk_publishing_components/icon-autocomplete-search-suggestion.svg +4 -0
- data/app/assets/javascripts/govuk_publishing_components/components/search-with-autocomplete.js +123 -0
- data/app/assets/stylesheets/govuk_publishing_components/components/_layout-super-navigation-header.scss +5 -4
- data/app/assets/stylesheets/govuk_publishing_components/components/_phase-banner.scss +0 -2
- data/app/assets/stylesheets/govuk_publishing_components/components/_search-with-autocomplete.scss +200 -0
- data/app/views/govuk_publishing_components/components/_layout_header.html.erb +16 -40
- data/app/views/govuk_publishing_components/components/_search.html.erb +16 -14
- data/app/views/govuk_publishing_components/components/_search_with_autocomplete.html.erb +27 -0
- data/app/views/govuk_publishing_components/components/docs/layout_header.yml +0 -41
- data/app/views/govuk_publishing_components/components/docs/phase_banner.yml +4 -0
- data/app/views/govuk_publishing_components/components/docs/search_with_autocomplete.yml +61 -0
- data/lib/govuk_publishing_components/version.rb +1 -1
- data/node_modules/accessible-autocomplete/CHANGELOG.md +390 -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 +42 -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/form-single.html +379 -0
- data/node_modules/accessible-autocomplete/examples/form.html +673 -0
- data/node_modules/accessible-autocomplete/examples/index.html +738 -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/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 +608 -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 +793 -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 +46 -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
|
+
}
|