@ons/design-system 50.0.1 → 51.0.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 (164) hide show
  1. package/README.md +35 -13
  2. package/components/access-code/_macro.njk +1 -1
  3. package/components/access-code/_macro.spec.js +162 -0
  4. package/components/access-code/uac.spec.js +26 -0
  5. package/components/accordion/_macro.spec.js +224 -0
  6. package/components/accordion/accordion.spec.js +134 -0
  7. package/components/address-input/_macro.njk +1 -1
  8. package/components/address-input/_macro.spec.js +465 -0
  9. package/components/address-input/autosuggest.address.js +5 -4
  10. package/components/address-input/autosuggest.address.setter.js +3 -1
  11. package/components/address-input/autosuggest.address.spec.js +733 -0
  12. package/components/address-output/_macro.njk +6 -6
  13. package/components/address-output/_macro.spec.js +122 -0
  14. package/components/autosuggest/_macro.njk +1 -1
  15. package/components/autosuggest/_macro.spec.js +229 -0
  16. package/components/autosuggest/autosuggest.helpers.js +2 -3
  17. package/components/autosuggest/autosuggest.helpers.spec.js +85 -0
  18. package/components/autosuggest/autosuggest.js +4 -2
  19. package/components/autosuggest/autosuggest.spec.js +625 -0
  20. package/components/autosuggest/autosuggest.ui.js +6 -2
  21. package/components/breadcrumbs/_macro.spec.js +129 -0
  22. package/components/button/_macro.njk +5 -5
  23. package/components/button/_macro.spec.js +446 -0
  24. package/components/button/button.spec.js +290 -0
  25. package/components/call-to-action/_macro.njk +3 -1
  26. package/components/call-to-action/_macro.spec.js +52 -0
  27. package/components/card/_macro.njk +26 -19
  28. package/components/card/_macro.spec.js +261 -0
  29. package/components/char-check-limit/_macro.spec.js +73 -0
  30. package/components/char-check-limit/character-check.spec.js +196 -0
  31. package/components/char-check-limit/character-limit.js +1 -1
  32. package/components/checkboxes/_checkbox-macro.spec.js +419 -0
  33. package/components/checkboxes/_macro.njk +1 -3
  34. package/components/checkboxes/_macro.spec.js +306 -0
  35. package/components/checkboxes/checkboxes.spec.js +208 -0
  36. package/components/code-highlight/_macro.spec.js +56 -0
  37. package/components/code-highlight/code-highlight.spec.js +18 -0
  38. package/components/collapsible/_macro.spec.js +204 -0
  39. package/components/collapsible/collapsible.js +2 -1
  40. package/components/collapsible/collapsible.spec.js +236 -0
  41. package/components/content-pagination/_macro.spec.js +199 -0
  42. package/components/cookies-banner/_macro.njk +1 -1
  43. package/components/cookies-banner/_macro.spec.js +171 -0
  44. package/components/cookies-banner/cookies-banner.spec.js +90 -0
  45. package/components/date-input/_macro.njk +6 -3
  46. package/components/date-input/_macro.spec.js +286 -0
  47. package/components/document-list/_macro.njk +3 -5
  48. package/components/document-list/_macro.spec.js +491 -0
  49. package/components/download-resources/download-resources.spec.js +540 -0
  50. package/components/duration/_macro.njk +7 -6
  51. package/components/duration/_macro.spec.js +251 -0
  52. package/components/error/_macro.spec.js +97 -0
  53. package/components/external-link/_macro.spec.js +60 -0
  54. package/components/feedback/_macro.njk +5 -3
  55. package/components/feedback/_macro.spec.js +122 -0
  56. package/components/field/_macro.njk +2 -2
  57. package/components/field/_macro.spec.js +97 -0
  58. package/components/fieldset/_macro.njk +3 -3
  59. package/components/fieldset/_macro.spec.js +173 -0
  60. package/components/footer/_macro.njk +11 -48
  61. package/components/footer/_macro.spec.js +549 -0
  62. package/components/header/_macro.njk +2 -2
  63. package/components/header/_macro.spec.js +562 -0
  64. package/components/hero/_hero.scss +0 -3
  65. package/components/hero/_macro.njk +4 -4
  66. package/components/hero/_macro.spec.js +224 -0
  67. package/components/icons/_macro.njk +15 -15
  68. package/components/icons/_macro.spec.js +140 -0
  69. package/components/images/_macro.njk +1 -1
  70. package/components/images/_macro.spec.js +121 -0
  71. package/components/input/_input-type.scss +12 -5
  72. package/components/input/_macro.njk +4 -5
  73. package/components/input/_macro.spec.js +658 -0
  74. package/components/label/_macro.spec.js +189 -0
  75. package/components/language-selector/_macro.spec.js +129 -0
  76. package/components/lists/_list.scss +4 -0
  77. package/components/lists/_macro.njk +4 -7
  78. package/components/lists/_macro.spec.js +618 -0
  79. package/components/message/_macro.spec.js +137 -0
  80. package/components/message-list/_macro.njk +7 -7
  81. package/components/message-list/_macro.spec.js +159 -0
  82. package/components/metadata/_macro.spec.js +167 -0
  83. package/components/modal/_macro.njk +6 -6
  84. package/components/modal/_macro.spec.js +87 -0
  85. package/components/modal/modal.spec.js +59 -0
  86. package/components/mutually-exclusive/_macro.njk +1 -1
  87. package/components/mutually-exclusive/_macro.spec.js +182 -0
  88. package/components/mutually-exclusive/mutually-exclusive.checkboxes.spec.js +203 -0
  89. package/components/mutually-exclusive/mutually-exclusive.date.spec.js +142 -0
  90. package/components/mutually-exclusive/mutually-exclusive.duration.spec.js +141 -0
  91. package/components/mutually-exclusive/mutually-exclusive.email.spec.js +117 -0
  92. package/components/mutually-exclusive/mutually-exclusive.multiple-options.checkboxes.spec.js +213 -0
  93. package/components/mutually-exclusive/mutually-exclusive.number.spec.js +125 -0
  94. package/components/mutually-exclusive/mutually-exclusive.textarea.spec.js +131 -0
  95. package/components/navigation/_macro.njk +6 -6
  96. package/components/navigation/_macro.spec.js +327 -0
  97. package/components/navigation/navigation.dom.js +1 -1
  98. package/components/navigation/navigation.spec.js +232 -0
  99. package/components/pagination/_macro.njk +1 -1
  100. package/components/pagination/_macro.spec.js +411 -0
  101. package/components/panel/_macro.njk +6 -6
  102. package/components/panel/_macro.spec.js +423 -0
  103. package/components/password/_macro.spec.js +137 -0
  104. package/components/password/password.spec.js +40 -0
  105. package/components/phase-banner/_macro.spec.js +73 -0
  106. package/components/promotional-banner/_macro.spec.js +97 -0
  107. package/components/question/_macro.njk +25 -33
  108. package/components/question/_macro.spec.js +309 -0
  109. package/components/quote/_macro.spec.js +81 -0
  110. package/components/radios/_macro.njk +3 -6
  111. package/components/radios/_macro.spec.js +575 -0
  112. package/components/radios/radios.spec.js +180 -0
  113. package/components/related-content/_macro.njk +1 -0
  114. package/components/related-content/_macro.spec.js +142 -0
  115. package/components/relationships/_macro.spec.js +108 -0
  116. package/components/relationships/relationships.spec.js +84 -0
  117. package/components/reply/_macro.njk +2 -2
  118. package/components/reply/_macro.spec.js +69 -0
  119. package/components/reply/reply.spec.js +78 -0
  120. package/components/search/_macro.njk +14 -12
  121. package/components/search/_macro.spec.js +44 -0
  122. package/components/search/_search.scss +7 -7
  123. package/components/section-navigation/_macro.njk +7 -2
  124. package/components/section-navigation/_macro.spec.js +206 -0
  125. package/components/select/_macro.njk +3 -3
  126. package/components/select/_macro.spec.js +203 -0
  127. package/components/select/select.spec.js +56 -0
  128. package/components/share-page/_macro.njk +2 -2
  129. package/components/share-page/_macro.spec.js +110 -0
  130. package/components/skip-to-content/_macro.spec.js +57 -0
  131. package/components/skip-to-content/skip-to-content.spec.js +44 -0
  132. package/components/status/_macro.spec.js +77 -0
  133. package/components/summary/_macro.njk +5 -5
  134. package/components/summary/_macro.spec.js +472 -0
  135. package/components/table/_macro.njk +2 -2
  136. package/components/table/_macro.spec.js +557 -0
  137. package/components/table/table.spec.js +155 -0
  138. package/components/table-of-contents/_macro.njk +35 -35
  139. package/components/table-of-contents/_macro.spec.js +178 -0
  140. package/components/table-of-contents/toc.js +29 -25
  141. package/components/table-of-contents/toc.spec.js +61 -0
  142. package/components/tabs/_macro.njk +1 -1
  143. package/components/tabs/_macro.spec.js +79 -0
  144. package/components/tabs/tabs.spec.js +162 -0
  145. package/components/text-indent/_macro.spec.js +52 -0
  146. package/components/textarea/_macro.njk +5 -3
  147. package/components/textarea/_macro.spec.js +300 -0
  148. package/components/textarea/textarea.spec.js +98 -0
  149. package/components/timeline/_macro.njk +3 -3
  150. package/components/timeline/_macro.spec.js +81 -0
  151. package/components/timeout-modal/_macro.spec.js +68 -0
  152. package/components/timeout-modal/timeout-modal.spec.js +226 -0
  153. package/components/timeout-panel/_macro.njk +0 -1
  154. package/components/timeout-panel/_macro.spec.js +54 -0
  155. package/components/timeout-panel/timeout-panel.dom.js +1 -2
  156. package/components/timeout-panel/timeout-panel.spec.js +161 -0
  157. package/components/upload/_macro.spec.js +75 -0
  158. package/components/video/_macro.spec.js +34 -0
  159. package/css/census.css +1 -1
  160. package/css/main.css +1 -1
  161. package/js/cookies-settings.spec.js +154 -0
  162. package/package.json +10 -23
  163. package/scripts/main.es5.js +1 -1
  164. package/scripts/main.js +1 -1
@@ -0,0 +1,625 @@
1
+ import { getNodeAttributes, PuppeteerEndpointFaker } from '../../tests/helpers/puppeteer';
2
+ import { renderComponent, setTestPage } from '../../tests/helpers/rendering';
3
+
4
+ const EXAMPLE_AUTOSUGGEST = {
5
+ id: 'country-of-birth',
6
+ label: {
7
+ text: 'Current name of country',
8
+ description: 'Enter your own answer or select from suggestions',
9
+ id: 'country-of-birth-label',
10
+ classes: 'extra-label-class',
11
+ },
12
+ autocomplete: 'off',
13
+ instructions: 'Use up and down keys to navigate.',
14
+ ariaYouHaveSelected: 'You have selected',
15
+ ariaMinChars: 'Enter 3 or more characters for suggestions.',
16
+ minChars: 3,
17
+ ariaResultsLabel: 'Country suggestions',
18
+ ariaOneResult: 'There is one suggestion available.',
19
+ ariaNResults: 'There are {n} suggestions available.',
20
+ ariaLimitedResults: 'Type more characters to improve your search',
21
+ moreResults: 'Continue entering to improve suggestions',
22
+ resultsTitle: 'Suggestions',
23
+ resultsTitleId: 'country-of-birth-suggestions',
24
+ noResults: 'No suggestions found.',
25
+ typeMore: 'Continue entering to get suggestions',
26
+ autosuggestData: '/test/fake/api/countries',
27
+ };
28
+
29
+ describe('script: autosuggest', () => {
30
+ const apiFaker = new PuppeteerEndpointFaker('/test/fake/api');
31
+
32
+ apiFaker.setOverride('/countries', {
33
+ data: [
34
+ { en: 'England' },
35
+ { en: 'Wales' },
36
+ { en: 'Scotland' },
37
+ { en: 'United States of America' },
38
+ { en: 'United States Virgin Islands' },
39
+ { en: 'Åland Islands' },
40
+ ],
41
+ });
42
+
43
+ beforeAll(async () => {
44
+ await apiFaker.setup(page);
45
+ });
46
+
47
+ beforeEach(async () => {
48
+ await apiFaker.reset();
49
+ });
50
+
51
+ describe('when the component initialises', () => {
52
+ it('the input should be given the correct aria attributes', async () => {
53
+ await setTestPage('/test', renderComponent('autosuggest', EXAMPLE_AUTOSUGGEST));
54
+
55
+ const attributes = await getNodeAttributes(page, '.ons-js-autosuggest-input');
56
+ expect(attributes['aria-autocomplete']).toBe('list');
57
+ expect(attributes['aria-controls']).toBe('country-of-birth-listbox');
58
+ expect(attributes['aria-describedby']).toBe('country-of-birth-instructions');
59
+ expect(attributes['aria-haspopup']).toBe('true');
60
+ expect(attributes['aria-owns']).toBe('country-of-birth-listbox');
61
+ expect(attributes['aria-expanded']).toBe('false');
62
+ expect(attributes['role']).toBe('combobox');
63
+ });
64
+
65
+ it('the autocomplete attribute be set to be not set to on', async () => {
66
+ await setTestPage('/test', renderComponent('autosuggest', EXAMPLE_AUTOSUGGEST));
67
+
68
+ const attributes = await getNodeAttributes(page, '.ons-js-autosuggest-input');
69
+ expect(attributes['autocomplete']).not.toBe('on');
70
+ });
71
+
72
+ it('the instructions, listbox, and status should become visible', async () => {
73
+ await setTestPage('/test', renderComponent('autosuggest', EXAMPLE_AUTOSUGGEST));
74
+
75
+ const instructionsDisplayStyle = await page.$eval('.ons-js-autosuggest-instructions', node => getComputedStyle(node).display);
76
+ expect(instructionsDisplayStyle).toBe('block');
77
+ const listboxDisplayStyle = await page.$eval('.ons-js-autosuggest-listbox', node => getComputedStyle(node).display);
78
+ expect(listboxDisplayStyle).toBe('block');
79
+ const statusDisplayStyle = await page.$eval('.ons-js-autosuggest-aria-status', node => getComputedStyle(node).display);
80
+ expect(statusDisplayStyle).toBe('block');
81
+ });
82
+ });
83
+
84
+ describe('when the user presses the "Down" arrow key', () => {
85
+ it('navigates to the first suggestion initially', async () => {
86
+ await setTestPage('/test', renderComponent('autosuggest', EXAMPLE_AUTOSUGGEST));
87
+
88
+ await page.type('.ons-js-autosuggest-input', 'Unite', { delay: 20 });
89
+ await page.keyboard.press('ArrowDown');
90
+
91
+ const selectedOption = await page.$eval('.ons-autosuggest-input__option--focused', node => node.textContent);
92
+ expect(selectedOption.trim()).toBe('United States of America');
93
+ });
94
+
95
+ it('navigates to the suggestion below the current suggestion', async () => {
96
+ await setTestPage('/test', renderComponent('autosuggest', EXAMPLE_AUTOSUGGEST));
97
+
98
+ await page.type('.ons-js-autosuggest-input', 'Unite', { delay: 20 });
99
+ await page.keyboard.press('ArrowDown');
100
+ await page.keyboard.press('ArrowDown');
101
+
102
+ const selectedOption = await page.$eval('.ons-autosuggest-input__option--focused', node => node.textContent);
103
+ expect(selectedOption.trim()).toBe('United States Virgin Islands');
104
+ });
105
+
106
+ it('marks suggestion as being selected', async () => {
107
+ await setTestPage('/test', renderComponent('autosuggest', EXAMPLE_AUTOSUGGEST));
108
+
109
+ await page.type('.ons-js-autosuggest-input', 'Unite', { delay: 20 });
110
+ await page.keyboard.press('ArrowDown');
111
+ await page.keyboard.press('ArrowDown');
112
+
113
+ const ariaSelectedValue = await page.$eval('.ons-autosuggest-input__option--focused', node => node.getAttribute('aria-selected'));
114
+ expect(ariaSelectedValue).toBe('true');
115
+
116
+ const selectedOptionId = await page.$eval('.ons-autosuggest-input__option--focused', node => node.getAttribute('id'));
117
+ const ariaActiveDescendant = await page.$eval('.ons-js-autosuggest-input', node => node.getAttribute('aria-activedescendant'));
118
+ expect(ariaActiveDescendant).toBe(selectedOptionId);
119
+ });
120
+
121
+ it('does not mark other suggestions as being selected', async () => {
122
+ await setTestPage('/test', renderComponent('autosuggest', EXAMPLE_AUTOSUGGEST));
123
+
124
+ await page.type('.ons-js-autosuggest-input', 'Unite', { delay: 20 });
125
+ await page.keyboard.press('ArrowDown');
126
+ await page.keyboard.press('ArrowDown');
127
+
128
+ const selectedSuggestionCount = await page.$$eval('.ons-autosuggest-input__option[aria-selected=true]', nodes => nodes.length);
129
+ expect(selectedSuggestionCount).toBe(1);
130
+ });
131
+
132
+ it('does not affect suggestion selection when already on last item', async () => {
133
+ await setTestPage('/test', renderComponent('autosuggest', EXAMPLE_AUTOSUGGEST));
134
+
135
+ await page.type('.ons-js-autosuggest-input', 'Unite', { delay: 20 });
136
+ await page.keyboard.press('ArrowDown');
137
+ await page.keyboard.press('ArrowDown');
138
+ await page.keyboard.press('ArrowDown');
139
+
140
+ const selectedOption = await page.$eval('.ons-autosuggest-input__option--focused', node => node.textContent);
141
+ expect(selectedOption.trim()).toBe('United States Virgin Islands');
142
+ });
143
+
144
+ // The down arrow will typically move caret to the end of an input field. This should
145
+ // not occur when suggestions are presented.
146
+ it('does not interfere with text input', async () => {
147
+ await setTestPage('/test', renderComponent('autosuggest', EXAMPLE_AUTOSUGGEST));
148
+
149
+ await page.type('.ons-js-autosuggest-input', 'nited', { delay: 20 });
150
+ // Move to start of input to verify if the down arrow moves the caret to the end.
151
+ await page.keyboard.press('Home');
152
+ await page.keyboard.press('ArrowDown');
153
+ await page.keyboard.press('U');
154
+
155
+ const inputValue = await page.$eval('.ons-js-autosuggest-input', node => node.value);
156
+ expect(inputValue).toBe('United');
157
+ });
158
+ });
159
+
160
+ describe('when the user presses the "Up" arrow key', () => {
161
+ it('navigates to the first suggestion initially', async () => {
162
+ await setTestPage('/test', renderComponent('autosuggest', EXAMPLE_AUTOSUGGEST));
163
+
164
+ await page.type('.ons-js-autosuggest-input', 'Unite', { delay: 20 });
165
+ await page.keyboard.press('ArrowUp');
166
+
167
+ const selectedOption = await page.$eval('.ons-autosuggest-input__option--focused', node => node.textContent);
168
+ expect(selectedOption.trim()).toBe('United States of America');
169
+ });
170
+
171
+ it('navigates to the suggestion above the current suggestion', async () => {
172
+ await setTestPage('/test', renderComponent('autosuggest', EXAMPLE_AUTOSUGGEST));
173
+
174
+ await page.type('.ons-js-autosuggest-input', 'Unite', { delay: 20 });
175
+ await page.keyboard.press('ArrowDown');
176
+ await page.keyboard.press('ArrowDown');
177
+ await page.keyboard.press('ArrowUp');
178
+
179
+ const selectedOption = await page.$eval('.ons-autosuggest-input__option--focused', node => node.textContent);
180
+ expect(selectedOption.trim()).toBe('United States of America');
181
+ });
182
+
183
+ it('marks suggestion as being selected', async () => {
184
+ await setTestPage('/test', renderComponent('autosuggest', EXAMPLE_AUTOSUGGEST));
185
+
186
+ await page.type('.ons-js-autosuggest-input', 'Unite', { delay: 20 });
187
+ await page.keyboard.press('ArrowDown');
188
+ await page.keyboard.press('ArrowDown');
189
+ await page.keyboard.press('ArrowUp');
190
+
191
+ const ariaSelectedValue = await page.$eval('.ons-autosuggest-input__option--focused', node => node.getAttribute('aria-selected'));
192
+ expect(ariaSelectedValue).toBe('true');
193
+
194
+ const selectedOptionId = await page.$eval('.ons-autosuggest-input__option--focused', node => node.getAttribute('id'));
195
+ const ariaActiveDescendant = await page.$eval('.ons-js-autosuggest-input', node => node.getAttribute('aria-activedescendant'));
196
+ expect(ariaActiveDescendant).toBe(selectedOptionId);
197
+ });
198
+
199
+ it('does not mark other suggestions as being selected', async () => {
200
+ await setTestPage('/test', renderComponent('autosuggest', EXAMPLE_AUTOSUGGEST));
201
+
202
+ await page.type('.ons-js-autosuggest-input', 'Unite', { delay: 20 });
203
+ await page.keyboard.press('ArrowDown');
204
+ await page.keyboard.press('ArrowDown');
205
+ await page.keyboard.press('ArrowUp');
206
+
207
+ const selectedSuggestionCount = await page.$$eval('.ons-autosuggest-input__option[aria-selected=true]', nodes => nodes.length);
208
+ expect(selectedSuggestionCount).toBe(1);
209
+ });
210
+
211
+ it('does not affect suggestion selection when already on first item', async () => {
212
+ await setTestPage('/test', renderComponent('autosuggest', EXAMPLE_AUTOSUGGEST));
213
+
214
+ await page.type('.ons-js-autosuggest-input', 'Unite', { delay: 20 });
215
+ await page.keyboard.press('ArrowUp');
216
+ await page.keyboard.press('ArrowUp');
217
+
218
+ const selectedOption = await page.$eval('.ons-autosuggest-input__option--focused', node => node.textContent);
219
+ expect(selectedOption.trim()).toBe('United States of America');
220
+ });
221
+
222
+ // The down arrow will typically move caret to the start of an input field. This
223
+ // should not occur when suggestions are presented.
224
+ it('does not interfere with text input', async () => {
225
+ await setTestPage('/test', renderComponent('autosuggest', EXAMPLE_AUTOSUGGEST));
226
+
227
+ await page.type('.ons-js-autosuggest-input', 'Unite', { delay: 20 });
228
+ await page.keyboard.press('ArrowUp');
229
+ await page.keyboard.press('d');
230
+
231
+ const inputValue = await page.$eval('.ons-js-autosuggest-input', node => node.value);
232
+ expect(inputValue).toBe('United');
233
+ });
234
+ });
235
+
236
+ describe('when the user presses the "Enter" key', () => {
237
+ it('accepts the selected suggestion', async () => {
238
+ await setTestPage('/test', renderComponent('autosuggest', EXAMPLE_AUTOSUGGEST));
239
+
240
+ await page.type('.ons-js-autosuggest-input', 'Unite', { delay: 20 });
241
+ await page.keyboard.press('ArrowDown');
242
+ await page.keyboard.press('ArrowDown');
243
+ await page.keyboard.press('Enter');
244
+
245
+ const inputValue = await page.$eval('.ons-js-autosuggest-input', node => node.value);
246
+ expect(inputValue).toBe('United States Virgin Islands');
247
+ });
248
+ });
249
+
250
+ describe('when the user blurs the input', () => {
251
+ it('suggestions should remain visible for 300ms', async () => {
252
+ await setTestPage('/test', renderComponent('autosuggest', EXAMPLE_AUTOSUGGEST));
253
+
254
+ await page.type('.ons-js-autosuggest-input', 'Unite', { delay: 20 });
255
+ await page.keyboard.press('Tab');
256
+
257
+ const suggestionCountSample1 = await page.$$eval('.ons-autosuggest-input__option', nodes => nodes.length);
258
+ expect(suggestionCountSample1).toBe(2);
259
+
260
+ await page.waitForTimeout(200);
261
+ const suggestionCountSample2 = await page.$$eval('.ons-autosuggest-input__option', nodes => nodes.length);
262
+ expect(suggestionCountSample2).toBe(2);
263
+
264
+ await page.waitForTimeout(320);
265
+ const suggestionCountSample3 = await page.$$eval('.ons-autosuggest-input__option', nodes => nodes.length);
266
+ expect(suggestionCountSample3).toBe(0);
267
+ });
268
+
269
+ it('clears innerHTML of listbox', async () => {
270
+ await setTestPage('/test', renderComponent('autosuggest', EXAMPLE_AUTOSUGGEST));
271
+
272
+ await page.type('.ons-js-autosuggest-input', 'Unite', { delay: 20 });
273
+ await page.keyboard.press('Tab');
274
+ await page.waitForTimeout(320);
275
+
276
+ const listboxInnerHTML = await page.$eval('.ons-js-autosuggest-listbox', node => node.innerHTML);
277
+ expect(listboxInnerHTML).toBe('');
278
+ });
279
+
280
+ it('clears `has-results` modifier of autosuggest component', async () => {
281
+ await setTestPage('/test', renderComponent('autosuggest', EXAMPLE_AUTOSUGGEST));
282
+
283
+ await page.type('.ons-js-autosuggest-input', 'Unite', { delay: 20 });
284
+ await page.keyboard.press('Tab');
285
+ await page.waitForTimeout(320);
286
+
287
+ const hasClass = await page.$eval('.ons-autosuggest-input', node => node.classList.contains('ons-autosuggest-input--has-results'));
288
+ expect(hasClass).toBe(false);
289
+ });
290
+
291
+ it('clears aria attributes of input element', async () => {
292
+ await setTestPage('/test', renderComponent('autosuggest', EXAMPLE_AUTOSUGGEST));
293
+
294
+ await page.type('.ons-js-autosuggest-input', 'Unite', { delay: 20 });
295
+ await page.keyboard.press('Tab');
296
+ await page.waitForTimeout(320);
297
+
298
+ const attributes = await getNodeAttributes(page, '.ons-js-autosuggest-input');
299
+ expect(attributes['aria-activedescendant']).toBeUndefined();
300
+ expect(attributes['aria-expanded']).toBeUndefined();
301
+ });
302
+ });
303
+
304
+ describe('when the user clicks on a result', () => {
305
+ it('accepts the suggestion', async () => {
306
+ await setTestPage('/test', renderComponent('autosuggest', EXAMPLE_AUTOSUGGEST));
307
+
308
+ await page.type('.ons-js-autosuggest-input', 'Unite', { delay: 20 });
309
+ await page.click('.ons-autosuggest-input__option:nth-child(2)');
310
+
311
+ const inputValue = await page.$eval('.ons-js-autosuggest-input', node => node.value);
312
+ expect(inputValue).toBe('United States Virgin Islands');
313
+ });
314
+ });
315
+
316
+ describe('when the user inputs text', () => {
317
+ it('does not show suggestions when input length < minimum characters', async () => {
318
+ await setTestPage('/test', renderComponent('autosuggest', EXAMPLE_AUTOSUGGEST));
319
+
320
+ await page.type('.ons-js-autosuggest-input', 'En', { delay: 20 });
321
+
322
+ const suggestionCount = await page.$$eval('.ons-autosuggest-input__option', nodes => nodes.length);
323
+ expect(suggestionCount).toBe(0);
324
+ });
325
+
326
+ it('shows suggestions when input length >= minimum characters', async () => {
327
+ await setTestPage('/test', renderComponent('autosuggest', EXAMPLE_AUTOSUGGEST));
328
+
329
+ await page.type('.ons-js-autosuggest-input', 'Eng', { delay: 20 });
330
+
331
+ const suggestionCount = await page.$$eval('.ons-autosuggest-input__option', nodes => nodes.length);
332
+ expect(suggestionCount).toBe(1);
333
+ });
334
+ });
335
+
336
+ describe('when the mouse moves over a result and a suggestion is focused', () => {
337
+ it('removes the focused class', async () => {
338
+ await setTestPage('/test', renderComponent('autosuggest', EXAMPLE_AUTOSUGGEST));
339
+
340
+ await page.type('.ons-js-autosuggest-input', 'state', { delay: 20 });
341
+ await page.keyboard.press('ArrowDown');
342
+ await page.hover('.ons-autosuggest-input__option:nth-child(2)');
343
+
344
+ const focusedClassCount = await page.$$eval('.ons-autosuggest-input__option--focused', nodes => nodes.length);
345
+ expect(focusedClassCount).toBe(0);
346
+ });
347
+ });
348
+
349
+ describe('when the mouse moves off a result and a suggestion was focused', () => {
350
+ it('restores the focused class', async () => {
351
+ await setTestPage(
352
+ '/test',
353
+ `
354
+ ${renderComponent('autosuggest', EXAMPLE_AUTOSUGGEST)}
355
+ <a id="dummy">Dummy</a>
356
+ `,
357
+ );
358
+
359
+ await page.type('.ons-js-autosuggest-input', 'state', { delay: 20 });
360
+ await page.keyboard.press('ArrowDown');
361
+ await page.hover('.ons-autosuggest-input__option:nth-child(2)');
362
+ await page.hover('#dummy');
363
+
364
+ const focusedClassCount = await page.$$eval('.ons-autosuggest-input__option--focused', nodes => nodes.length);
365
+ expect(focusedClassCount).toBe(1);
366
+ });
367
+ });
368
+
369
+ describe('when there are results', () => {
370
+ it('un-highlights a previously highlighted suggestion', async () => {
371
+ await setTestPage('/test', renderComponent('autosuggest', EXAMPLE_AUTOSUGGEST));
372
+
373
+ await page.type('.ons-js-autosuggest-input', 'Unite', { delay: 20 });
374
+ await page.keyboard.press('ArrowDown');
375
+ await page.type('.ons-js-autosuggest-input', 'd', { delay: 20 });
376
+
377
+ const focusedNodeCount = await page.$$eval('.ons-autosuggest-input__option--focused', nodes => nodes.length);
378
+ expect(focusedNodeCount).toBe(0);
379
+ });
380
+
381
+ it('decorates input element with `aria-expanded` attribute', async () => {
382
+ await setTestPage('/test', renderComponent('autosuggest', EXAMPLE_AUTOSUGGEST));
383
+
384
+ await page.type('.ons-js-autosuggest-input', 'Unite', { delay: 20 });
385
+
386
+ const ariaExpandedValue = await page.$eval('.ons-js-autosuggest-input', node => node.getAttribute('aria-expanded'));
387
+ expect(ariaExpandedValue).toBe('true');
388
+ });
389
+
390
+ it('emboldens matched suggestion text', async () => {
391
+ await setTestPage('/test', renderComponent('autosuggest', EXAMPLE_AUTOSUGGEST));
392
+
393
+ await page.type('.ons-js-autosuggest-input', 'Unite', { delay: 20 });
394
+
395
+ const emboldened = await page.$$eval('.ons-autosuggest-input__option strong', nodes => nodes.map(node => node.textContent));
396
+ expect(emboldened).toEqual(['Unite', 'Unite']);
397
+ });
398
+
399
+ it('does not embolden anything when suggestion does not contain query text', async () => {
400
+ await setTestPage('/test', renderComponent('autosuggest', EXAMPLE_AUTOSUGGEST));
401
+
402
+ await page.type('.ons-js-autosuggest-input', 'tland', { delay: 20 });
403
+
404
+ const matchCount = await page.$$eval('.ons-autosuggest-input__option', nodes => nodes.length);
405
+ expect(matchCount).toBe(2);
406
+ const emboldened = await page.$$eval('.ons-autosuggest-input__option strong', nodes => nodes.map(node => node.textContent));
407
+ expect(emboldened).toEqual(['tland']);
408
+ });
409
+
410
+ it('sets aria status to a message asking for more characters to be input', async () => {
411
+ await setTestPage('/test', renderComponent('autosuggest', EXAMPLE_AUTOSUGGEST));
412
+
413
+ await page.type('.ons-js-autosuggest-input', 'st', { delay: 20 });
414
+
415
+ const statusMessage = await page.$eval('.ons-js-autosuggest-aria-status', node => node.textContent);
416
+ expect(statusMessage.trim()).toBe('Enter 3 or more characters for suggestions.');
417
+ });
418
+
419
+ it('sets aria status to a message indicating a count of one suggestion', async () => {
420
+ await setTestPage('/test', renderComponent('autosuggest', EXAMPLE_AUTOSUGGEST));
421
+
422
+ await page.type('.ons-js-autosuggest-input', 'Engla', { delay: 20 });
423
+
424
+ const statusMessage = await page.$eval('.ons-js-autosuggest-aria-status', node => node.textContent);
425
+ expect(statusMessage.trim()).toBe('There is one suggestion available.');
426
+ });
427
+
428
+ it('sets aria status to a message indicating the count of suggestions', async () => {
429
+ await setTestPage('/test', renderComponent('autosuggest', EXAMPLE_AUTOSUGGEST));
430
+
431
+ await page.type('.ons-js-autosuggest-input', 'sta', { delay: 20 });
432
+
433
+ const statusMessage = await page.$eval('.ons-js-autosuggest-aria-status', node => node.textContent);
434
+ expect(statusMessage.trim()).toBe('There are 2 suggestions available.');
435
+ });
436
+ });
437
+
438
+ describe('when there are no results', () => {
439
+ describe('where `noResults` content is provided', () => {
440
+ it('outputs `noResults` content', async () => {
441
+ await setTestPage('/test', renderComponent('autosuggest', EXAMPLE_AUTOSUGGEST));
442
+
443
+ await page.type('.ons-js-autosuggest-input', 'abc', { delay: 20 });
444
+
445
+ const noResultsContent = await page.$eval('.ons-autosuggest-input__option--no-results', node => node.textContent);
446
+ expect(noResultsContent).toBe('No suggestions found.');
447
+ });
448
+
449
+ it('decorates input element with `aria-expanded` attribute', async () => {
450
+ await setTestPage('/test', renderComponent('autosuggest', EXAMPLE_AUTOSUGGEST));
451
+
452
+ await page.type('.ons-js-autosuggest-input', 'abc', { delay: 20 });
453
+
454
+ const ariaExpandedValue = await page.$eval('.ons-js-autosuggest-input', node => node.getAttribute('aria-expanded'));
455
+ expect(ariaExpandedValue).toBe('true');
456
+ });
457
+
458
+ it('sets aria status to a message indicating that query is too short for suggestions', async () => {
459
+ await setTestPage('/test', renderComponent('autosuggest', EXAMPLE_AUTOSUGGEST));
460
+
461
+ await page.type('.ons-js-autosuggest-input', 'ab', { delay: 20 });
462
+
463
+ const statusMessage = await page.$eval('.ons-js-autosuggest-aria-status', node => node.textContent);
464
+ expect(statusMessage.trim()).toBe('Enter 3 or more characters for suggestions.');
465
+ });
466
+
467
+ it('sets aria status to a message indicating the zero count of suggestions', async () => {
468
+ await setTestPage('/test', renderComponent('autosuggest', EXAMPLE_AUTOSUGGEST));
469
+
470
+ await page.type('.ons-js-autosuggest-input', 'abc', { delay: 20 });
471
+
472
+ const statusMessage = await page.$eval('.ons-js-autosuggest-aria-status', node => node.textContent);
473
+ expect(statusMessage.trim()).toBe('No suggestions found.: "abc"');
474
+ });
475
+ });
476
+
477
+ describe('where `noResults` content is not provided', () => {
478
+ it('has an empty listbox', async () => {
479
+ await setTestPage(
480
+ '/test',
481
+ renderComponent('autosuggest', {
482
+ ...EXAMPLE_AUTOSUGGEST,
483
+ noResults: undefined,
484
+ }),
485
+ );
486
+
487
+ await page.type('.ons-js-autosuggest-input', 'abc', { delay: 20 });
488
+
489
+ const matchCount = await page.$$eval('.ons-autosuggest-input__option', nodes => nodes.length);
490
+ expect(matchCount).toBe(0);
491
+ });
492
+
493
+ it('decorates input element with `aria-expanded` attribute', async () => {
494
+ await setTestPage(
495
+ '/test',
496
+ renderComponent('autosuggest', {
497
+ ...EXAMPLE_AUTOSUGGEST,
498
+ noResults: undefined,
499
+ }),
500
+ );
501
+
502
+ await page.type('.ons-js-autosuggest-input', 'abc', { delay: 20 });
503
+
504
+ const ariaExpandedValue = await page.$eval('.ons-js-autosuggest-input', node => node.getAttribute('aria-expanded'));
505
+ expect(ariaExpandedValue).toBe(null);
506
+ });
507
+ });
508
+ });
509
+
510
+ describe('when there are no results due to an error', () => {
511
+ describe('when the status code is 400', () => {
512
+ beforeEach(async () => {
513
+ apiFaker.setTemporaryOverride('/countries', {
514
+ status: 400,
515
+ data: {},
516
+ });
517
+
518
+ await setTestPage('/test', renderComponent('autosuggest', EXAMPLE_AUTOSUGGEST));
519
+
520
+ await page.focus('.ons-js-autosuggest-input');
521
+ await page.type('.ons-js-autosuggest-input', 'tes', { delay: 20 });
522
+ });
523
+
524
+ it('shows the type more message', async () => {
525
+ const listItemCount = await page.$$eval('.ons-js-autosuggest-listbox > *', nodes => nodes.length);
526
+ expect(listItemCount).toBe(1);
527
+ const noResultsText = await page.$eval('.ons-autosuggest-input__option--no-results', node => node.innerText);
528
+ expect(noResultsText.trim()).toBe('Continue entering to get suggestions');
529
+ });
530
+ });
531
+
532
+ describe.each([
533
+ ['when the status code is greater than 400', {}, 401],
534
+ ['when there is no status code', null, undefined],
535
+ ])('%s', (_, fakeAutosuggestData, fakeStatusCode) => {
536
+ beforeEach(async () => {
537
+ apiFaker.setTemporaryOverride('/countries', {
538
+ status: fakeStatusCode,
539
+ data: fakeAutosuggestData,
540
+ });
541
+
542
+ await setTestPage(
543
+ '/test',
544
+ renderComponent('autosuggest', {
545
+ ...EXAMPLE_AUTOSUGGEST,
546
+ errorTitle: 'There is a problem with your answer',
547
+ errorMessage: 'Enter an address ',
548
+ errorMessageAPI: 'Sorry, there is a problem.',
549
+ }),
550
+ );
551
+
552
+ await page.type('.ons-js-autosuggest-input', 'tes', { delay: 20 });
553
+ });
554
+
555
+ it('shows the API error message', async () => {
556
+ const listItemCount = await page.$$eval('.ons-js-autosuggest-listbox > *', nodes => nodes.length);
557
+ expect(listItemCount).toBe(1);
558
+ const warningText = await page.$eval('.ons-autosuggest-input__warning', node => node.textContent);
559
+ expect(warningText.trim()).toBe('!Sorry, there is a problem.');
560
+ });
561
+
562
+ it('the input should be disabled', async () => {
563
+ const hasDisabledAttribute = await page.$eval('.ons-js-autosuggest-input', node => node.hasAttribute('disabled'));
564
+ expect(hasDisabledAttribute).toBe(true);
565
+ });
566
+
567
+ it('the input value should be empty', async () => {
568
+ const inputValue = await page.$eval('.ons-js-autosuggest-input', node => node.value);
569
+ expect(inputValue).toBe('');
570
+ });
571
+
572
+ it('the label class should be added', async () => {
573
+ const hasClass = await page.$eval('.ons-label', node => node.classList.contains('ons-u-lighter'));
574
+ expect(hasClass).toBe(true);
575
+ });
576
+
577
+ it('the aria status should be set', async () => {
578
+ const statusMessage = await page.$eval('.ons-js-autosuggest-aria-status', node => node.textContent);
579
+ expect(statusMessage.trim()).toBe('Sorry, there is a problem.');
580
+ });
581
+ });
582
+ });
583
+
584
+ describe('when the component initialises with the allowMultiple parameter', () => {
585
+ describe('when a result is selected', () => {
586
+ it('the input value should contain a comma when focused', async () => {
587
+ await setTestPage(
588
+ '/test',
589
+ renderComponent('autosuggest', {
590
+ ...EXAMPLE_AUTOSUGGEST,
591
+ allowMultiple: true,
592
+ }),
593
+ );
594
+
595
+ await page.type('.ons-js-autosuggest-input', 'England', { delay: 20 });
596
+ await page.keyboard.press('ArrowUp');
597
+ await page.keyboard.press('Enter');
598
+ // Defocus the autosuggest input.
599
+ await page.keyboard.press('Tab');
600
+ await page.focus('.ons-js-autosuggest-input');
601
+
602
+ const inputValue = await page.$eval('.ons-js-autosuggest-input', node => node.value);
603
+ expect(inputValue).toBe('England, ');
604
+ });
605
+ });
606
+
607
+ describe('when the user blurs the input', () => {
608
+ it('the input value should not contain a comma', async () => {
609
+ await setTestPage(
610
+ '/test',
611
+ renderComponent('autosuggest', {
612
+ ...EXAMPLE_AUTOSUGGEST,
613
+ allowMultiple: true,
614
+ }),
615
+ );
616
+
617
+ await page.type('.ons-js-autosuggest-input', 'England, ', { delay: 20 });
618
+ await page.keyboard.press('Tab');
619
+
620
+ const inputValue = await page.$eval('.ons-js-autosuggest-input', node => node.value);
621
+ expect(inputValue).toBe('England');
622
+ });
623
+ });
624
+ });
625
+ });
@@ -24,7 +24,6 @@ export default class AutosuggestUI {
24
24
  onUnsetResult,
25
25
  suggestionFunction,
26
26
  handleUpdate,
27
- lang,
28
27
  ariaYouHaveSelected,
29
28
  ariaMinChars,
30
29
  ariaOneResult,
@@ -69,7 +68,6 @@ export default class AutosuggestUI {
69
68
  this.listboxId = this.listbox.getAttribute('id');
70
69
  this.resultLimit = resultLimit || 10;
71
70
  this.suggestOnBoot = suggestOnBoot;
72
- this.lang = lang || 'en';
73
71
 
74
72
  // Callbacks
75
73
  this.onSelect = onSelect;
@@ -105,6 +103,10 @@ export default class AutosuggestUI {
105
103
  this.initialiseUI();
106
104
  }
107
105
 
106
+ get lang() {
107
+ return document.documentElement.getAttribute('lang').toLowerCase();
108
+ }
109
+
108
110
  initialiseUI() {
109
111
  this.input.setAttribute('aria-autocomplete', 'list');
110
112
  this.input.setAttribute('aria-controls', this.listbox.getAttribute('id'));
@@ -124,6 +126,7 @@ export default class AutosuggestUI {
124
126
  this.fetch = abortableFetch(this.autosuggestData);
125
127
  const response = await this.fetch.send();
126
128
  this.data = await response.json();
129
+ this.responseStatus = response.status;
127
130
  }
128
131
 
129
132
  bindEventListeners() {
@@ -293,6 +296,7 @@ export default class AutosuggestUI {
293
296
  result.sanitisedText = sanitiseAutosuggestText(result[this.lang], this.sanitisedQueryReplaceChars);
294
297
  });
295
298
  return {
299
+ status: this.responseStatus,
296
300
  results,
297
301
  totalResults: results.length,
298
302
  };