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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/app/views/govuk_publishing_components/components/_radio.html.erb +2 -0
  3. data/app/views/govuk_publishing_components/components/docs/inverse_header.yml +2 -30
  4. data/app/views/govuk_publishing_components/components/docs/radio.yml +15 -0
  5. data/lib/govuk_publishing_components/version.rb +1 -1
  6. data/node_modules/accessible-autocomplete/CHANGELOG.md +398 -0
  7. data/node_modules/accessible-autocomplete/CODEOWNERS +2 -0
  8. data/node_modules/accessible-autocomplete/CONTRIBUTING.md +161 -0
  9. data/node_modules/accessible-autocomplete/LICENSE.txt +20 -0
  10. data/node_modules/accessible-autocomplete/Procfile +1 -0
  11. data/node_modules/accessible-autocomplete/README.md +490 -0
  12. data/node_modules/accessible-autocomplete/accessibility-criteria.md +43 -0
  13. data/node_modules/accessible-autocomplete/app.json +15 -0
  14. data/node_modules/accessible-autocomplete/babel.config.js +29 -0
  15. data/node_modules/accessible-autocomplete/dist/accessible-autocomplete.min.css +3 -0
  16. data/node_modules/accessible-autocomplete/dist/accessible-autocomplete.min.css.map +1 -0
  17. data/node_modules/accessible-autocomplete/dist/accessible-autocomplete.min.js +2 -0
  18. data/node_modules/accessible-autocomplete/dist/accessible-autocomplete.min.js.map +1 -0
  19. data/node_modules/accessible-autocomplete/dist/lib/accessible-autocomplete.preact.min.js +2 -0
  20. data/node_modules/accessible-autocomplete/dist/lib/accessible-autocomplete.preact.min.js.map +1 -0
  21. data/node_modules/accessible-autocomplete/dist/lib/accessible-autocomplete.react.min.js +2 -0
  22. data/node_modules/accessible-autocomplete/dist/lib/accessible-autocomplete.react.min.js.map +1 -0
  23. data/node_modules/accessible-autocomplete/examples/ajax-source.html +300 -0
  24. data/node_modules/accessible-autocomplete/examples/form-single.html +381 -0
  25. data/node_modules/accessible-autocomplete/examples/form.html +673 -0
  26. data/node_modules/accessible-autocomplete/examples/index.html +693 -0
  27. data/node_modules/accessible-autocomplete/examples/preact/index.html +346 -0
  28. data/node_modules/accessible-autocomplete/examples/react/index.html +347 -0
  29. data/node_modules/accessible-autocomplete/examples/suggestions.json +258 -0
  30. data/node_modules/accessible-autocomplete/package.json +93 -0
  31. data/node_modules/accessible-autocomplete/postcss.config.js +16 -0
  32. data/node_modules/accessible-autocomplete/preact.js +1 -0
  33. data/node_modules/accessible-autocomplete/react.js +1 -0
  34. data/node_modules/accessible-autocomplete/scripts/check-staged.mjs +16 -0
  35. data/node_modules/accessible-autocomplete/src/autocomplete.css +167 -0
  36. data/node_modules/accessible-autocomplete/src/autocomplete.js +610 -0
  37. data/node_modules/accessible-autocomplete/src/dropdown-arrow-down.js +11 -0
  38. data/node_modules/accessible-autocomplete/src/status.js +125 -0
  39. data/node_modules/accessible-autocomplete/src/wrapper.js +60 -0
  40. data/node_modules/accessible-autocomplete/test/functional/dropdown-arrow-down.js +46 -0
  41. data/node_modules/accessible-autocomplete/test/functional/index.js +809 -0
  42. data/node_modules/accessible-autocomplete/test/functional/wrapper.js +339 -0
  43. data/node_modules/accessible-autocomplete/test/integration/index.js +309 -0
  44. data/node_modules/accessible-autocomplete/test/karma.config.js +46 -0
  45. data/node_modules/accessible-autocomplete/test/wdio.config.js +123 -0
  46. data/node_modules/accessible-autocomplete/webpack.config.mjs +244 -0
  47. 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
+ }