@ons/design-system 50.0.0 → 52.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 (166) hide show
  1. package/README.md +35 -15
  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 +9 -3
  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/checkbox-with-autoselect.js +2 -1
  36. package/components/checkboxes/checkboxes.spec.js +208 -0
  37. package/components/code-highlight/_macro.spec.js +56 -0
  38. package/components/code-highlight/code-highlight.spec.js +18 -0
  39. package/components/collapsible/_macro.spec.js +204 -0
  40. package/components/collapsible/collapsible.js +2 -1
  41. package/components/collapsible/collapsible.spec.js +236 -0
  42. package/components/content-pagination/_macro.spec.js +199 -0
  43. package/components/cookies-banner/_macro.njk +1 -1
  44. package/components/cookies-banner/_macro.spec.js +171 -0
  45. package/components/cookies-banner/cookies-banner.spec.js +90 -0
  46. package/components/date-input/_macro.njk +6 -3
  47. package/components/date-input/_macro.spec.js +286 -0
  48. package/components/document-list/_macro.njk +3 -5
  49. package/components/document-list/_macro.spec.js +491 -0
  50. package/components/download-resources/download-resources.spec.js +540 -0
  51. package/components/duration/_macro.njk +7 -6
  52. package/components/duration/_macro.spec.js +251 -0
  53. package/components/error/_macro.spec.js +97 -0
  54. package/components/external-link/_macro.spec.js +60 -0
  55. package/components/feedback/_macro.njk +5 -3
  56. package/components/feedback/_macro.spec.js +122 -0
  57. package/components/field/_macro.njk +2 -2
  58. package/components/field/_macro.spec.js +97 -0
  59. package/components/fieldset/_macro.njk +3 -3
  60. package/components/fieldset/_macro.spec.js +173 -0
  61. package/components/footer/_macro.njk +12 -49
  62. package/components/footer/_macro.spec.js +549 -0
  63. package/components/header/_macro.njk +3 -3
  64. package/components/header/_macro.spec.js +562 -0
  65. package/components/hero/_hero.scss +0 -3
  66. package/components/hero/_macro.njk +4 -4
  67. package/components/hero/_macro.spec.js +224 -0
  68. package/components/icons/_macro.njk +15 -15
  69. package/components/icons/_macro.spec.js +140 -0
  70. package/components/images/_macro.njk +1 -1
  71. package/components/images/_macro.spec.js +121 -0
  72. package/components/input/_input-type.scss +12 -5
  73. package/components/input/_macro.njk +4 -5
  74. package/components/input/_macro.spec.js +658 -0
  75. package/components/label/_macro.spec.js +189 -0
  76. package/components/language-selector/_macro.spec.js +129 -0
  77. package/components/lists/_list.scss +4 -0
  78. package/components/lists/_macro.njk +4 -7
  79. package/components/lists/_macro.spec.js +618 -0
  80. package/components/message/_macro.spec.js +137 -0
  81. package/components/message-list/_macro.njk +7 -7
  82. package/components/message-list/_macro.spec.js +159 -0
  83. package/components/metadata/_macro.spec.js +167 -0
  84. package/components/modal/_macro.njk +6 -6
  85. package/components/modal/_macro.spec.js +87 -0
  86. package/components/modal/modal.js +0 -16
  87. package/components/modal/modal.spec.js +59 -0
  88. package/components/mutually-exclusive/_macro.njk +2 -2
  89. package/components/mutually-exclusive/_macro.spec.js +184 -0
  90. package/components/mutually-exclusive/mutually-exclusive.checkboxes.spec.js +203 -0
  91. package/components/mutually-exclusive/mutually-exclusive.date.spec.js +142 -0
  92. package/components/mutually-exclusive/mutually-exclusive.duration.spec.js +141 -0
  93. package/components/mutually-exclusive/mutually-exclusive.email.spec.js +117 -0
  94. package/components/mutually-exclusive/mutually-exclusive.multiple-options.checkboxes.spec.js +213 -0
  95. package/components/mutually-exclusive/mutually-exclusive.number.spec.js +125 -0
  96. package/components/mutually-exclusive/mutually-exclusive.textarea.spec.js +131 -0
  97. package/components/navigation/_macro.njk +6 -6
  98. package/components/navigation/_macro.spec.js +327 -0
  99. package/components/navigation/navigation.dom.js +1 -1
  100. package/components/navigation/navigation.spec.js +232 -0
  101. package/components/pagination/_macro.njk +1 -1
  102. package/components/pagination/_macro.spec.js +411 -0
  103. package/components/panel/_macro.njk +6 -6
  104. package/components/panel/_macro.spec.js +423 -0
  105. package/components/password/_macro.spec.js +137 -0
  106. package/components/password/password.spec.js +40 -0
  107. package/components/phase-banner/_macro.spec.js +73 -0
  108. package/components/promotional-banner/_macro.spec.js +97 -0
  109. package/components/question/_macro.njk +16 -22
  110. package/components/question/_macro.spec.js +309 -0
  111. package/components/quote/_macro.spec.js +81 -0
  112. package/components/radios/_macro.njk +3 -6
  113. package/components/radios/_macro.spec.js +575 -0
  114. package/components/radios/radios.spec.js +180 -0
  115. package/components/related-content/_macro.njk +1 -0
  116. package/components/related-content/_macro.spec.js +142 -0
  117. package/components/relationships/_macro.spec.js +108 -0
  118. package/components/relationships/relationships.spec.js +84 -0
  119. package/components/reply/_macro.njk +2 -2
  120. package/components/reply/_macro.spec.js +69 -0
  121. package/components/reply/reply.spec.js +78 -0
  122. package/components/search/_macro.njk +14 -12
  123. package/components/search/_macro.spec.js +44 -0
  124. package/components/search/_search.scss +7 -7
  125. package/components/section-navigation/_macro.njk +7 -2
  126. package/components/section-navigation/_macro.spec.js +206 -0
  127. package/components/select/_macro.njk +3 -3
  128. package/components/select/_macro.spec.js +203 -0
  129. package/components/select/select.spec.js +56 -0
  130. package/components/share-page/_macro.njk +2 -2
  131. package/components/share-page/_macro.spec.js +110 -0
  132. package/components/skip-to-content/_macro.spec.js +57 -0
  133. package/components/skip-to-content/skip-to-content.spec.js +44 -0
  134. package/components/status/_macro.spec.js +77 -0
  135. package/components/summary/_macro.njk +5 -5
  136. package/components/summary/_macro.spec.js +472 -0
  137. package/components/table/_macro.njk +2 -2
  138. package/components/table/_macro.spec.js +557 -0
  139. package/components/table/table.spec.js +155 -0
  140. package/components/table-of-contents/_macro.njk +35 -35
  141. package/components/table-of-contents/_macro.spec.js +178 -0
  142. package/components/table-of-contents/toc.js +29 -25
  143. package/components/table-of-contents/toc.spec.js +61 -0
  144. package/components/tabs/_macro.njk +1 -1
  145. package/components/tabs/_macro.spec.js +79 -0
  146. package/components/tabs/tabs.spec.js +162 -0
  147. package/components/text-indent/_macro.spec.js +52 -0
  148. package/components/textarea/_macro.njk +5 -3
  149. package/components/textarea/_macro.spec.js +300 -0
  150. package/components/textarea/textarea.spec.js +98 -0
  151. package/components/timeline/_macro.njk +3 -3
  152. package/components/timeline/_macro.spec.js +81 -0
  153. package/components/timeout-modal/_macro.spec.js +68 -0
  154. package/components/timeout-modal/timeout-modal.spec.js +226 -0
  155. package/components/timeout-panel/_macro.njk +0 -1
  156. package/components/timeout-panel/_macro.spec.js +54 -0
  157. package/components/timeout-panel/timeout-panel.dom.js +1 -2
  158. package/components/timeout-panel/timeout-panel.spec.js +161 -0
  159. package/components/upload/_macro.spec.js +75 -0
  160. package/components/video/_macro.spec.js +34 -0
  161. package/css/census.css +1 -1
  162. package/css/main.css +1 -1
  163. package/js/cookies-settings.spec.js +154 -0
  164. package/package.json +10 -23
  165. package/scripts/main.es5.js +1 -1
  166. package/scripts/main.js +2 -2
@@ -0,0 +1,81 @@
1
+ /** @jest-environment jsdom */
2
+
3
+ import * as cheerio from 'cheerio';
4
+
5
+ import axe from '../../tests/helpers/axe';
6
+ import { renderComponent, templateFaker } from '../../tests/helpers/rendering';
7
+
8
+ const EXAMPLE_TIMELINE = {
9
+ items: [
10
+ {
11
+ heading: 'January 2020',
12
+ content: 'Timeline entry 1',
13
+ },
14
+ {
15
+ heading: 'March 2020',
16
+ content: 'Timeline entry 2',
17
+ },
18
+ {
19
+ heading: 'December 2020',
20
+ itemsList: [
21
+ {
22
+ text: 'Timeline entry 3 item 1',
23
+ },
24
+ {
25
+ text: 'Timeline entry 3 item 2',
26
+ },
27
+ ],
28
+ },
29
+ ],
30
+ };
31
+
32
+ describe('macro: timeline', () => {
33
+ it('passes jest-axe checks', async () => {
34
+ const $ = cheerio.load(renderComponent('timeline', EXAMPLE_TIMELINE));
35
+
36
+ const results = await axe($.html());
37
+ expect(results).toHaveNoViolations();
38
+ });
39
+
40
+ it('has additionally provided style classes', () => {
41
+ const $ = cheerio.load(
42
+ renderComponent('timeline', {
43
+ ...EXAMPLE_TIMELINE,
44
+ classes: 'extra-class another-extra-class',
45
+ }),
46
+ );
47
+
48
+ expect($('.ons-timeline').hasClass('extra-class')).toBe(true);
49
+ expect($('.ons-timeline').hasClass('another-extra-class')).toBe(true);
50
+ });
51
+
52
+ it('has the provided timeline items', () => {
53
+ const $ = cheerio.load(renderComponent('timeline', EXAMPLE_TIMELINE));
54
+
55
+ const $firstItem = $('.ons-timeline__item:nth-child(1)');
56
+ const $firstItemHeading = $firstItem.find('.ons-timeline__heading');
57
+ const $secondItem = $('.ons-timeline__item:nth-child(2)');
58
+ const $secondItemHeading = $secondItem.find('.ons-timeline__heading');
59
+
60
+ expect($firstItemHeading.text().trim()).toBe('January 2020');
61
+ expect($firstItem.text()).toContain('Timeline entry 1');
62
+ expect($secondItemHeading.text().trim()).toBe('March 2020');
63
+ expect($secondItem.text()).toContain('Timeline entry 2');
64
+ });
65
+
66
+ it('has the provided inner item list', () => {
67
+ const faker = templateFaker();
68
+ const listSpy = faker.spy('lists');
69
+
70
+ faker.renderComponent('timeline', EXAMPLE_TIMELINE);
71
+
72
+ expect(listSpy.occurrences[0].itemsList).toEqual([
73
+ {
74
+ text: 'Timeline entry 3 item 1',
75
+ },
76
+ {
77
+ text: 'Timeline entry 3 item 2',
78
+ },
79
+ ]);
80
+ });
81
+ });
@@ -0,0 +1,68 @@
1
+ /** @jest-environment jsdom */
2
+
3
+ import * as cheerio from 'cheerio';
4
+
5
+ import axe from '../../tests/helpers/axe';
6
+ import { renderComponent, templateFaker } from '../../tests/helpers/rendering';
7
+
8
+ const EXAMPLE_TIMEOUT_MODAL_BASIC = {
9
+ showModalTimeInSeconds: 60,
10
+ redirectUrl: '#!',
11
+ title: 'You will be signed out soon',
12
+ serverSessionExpiryEndpoint: '/some-endpoint',
13
+ sessionExpiresAt: '000-000-000',
14
+ textFirstLine: 'It appears you have been inactive for a while.',
15
+ countdownText: 'To protect your information, your progress will be saved and you will be signed out in',
16
+ countdownExpiredText: 'You are being signed out.',
17
+ btnText: 'Continue survey',
18
+ minutesTextSingular: 'minute',
19
+ minutesTextPlural: 'minutes',
20
+ secondsTextSingular: 'second',
21
+ secondsTextPlural: 'seconds',
22
+ endWithFullStop: true,
23
+ };
24
+
25
+ describe('macro: timeout modal', () => {
26
+ it('passes jest-axe checks with', async () => {
27
+ const $ = cheerio.load(renderComponent('timeout-modal', EXAMPLE_TIMEOUT_MODAL_BASIC));
28
+
29
+ const results = await axe($.html());
30
+ expect(results).toHaveNoViolations();
31
+ });
32
+
33
+ it('provides expected parameters to the inner `modal` component', () => {
34
+ const faker = templateFaker();
35
+ const modalSpy = faker.spy('modal');
36
+
37
+ cheerio.load(faker.renderComponent('timeout-modal', EXAMPLE_TIMEOUT_MODAL_BASIC));
38
+
39
+ expect(modalSpy.occurrences[0]).toEqual({
40
+ title: 'You will be signed out soon',
41
+ btnText: 'Continue survey',
42
+ classes: 'ons-js-timeout-modal',
43
+ attributes: {
44
+ 'data-redirect-url': '#!',
45
+ 'data-server-session-expires-at': '000-000-000',
46
+ 'data-show-modal-time': 60,
47
+ 'data-server-session-expiry-endpoint': '/some-endpoint',
48
+ 'data-countdown-text': 'To protect your information, your progress will be saved and you will be signed out in',
49
+ 'data-countdown-expired-text': 'You are being signed out.',
50
+ 'data-minutes-text-singular': 'minute',
51
+ 'data-minutes-text-plural': 'minutes',
52
+ 'data-seconds-text-singular': 'second',
53
+ 'data-seconds-text-plural': 'seconds',
54
+ 'data-full-stop': true,
55
+ 'aria-describedby': 'timeout-time-remaining',
56
+ },
57
+ });
58
+ });
59
+
60
+ it('has expected `textFirstLine`', () => {
61
+ const $ = cheerio.load(renderComponent('timeout-modal', EXAMPLE_TIMEOUT_MODAL_BASIC));
62
+
63
+ const title = $('.ons-modal__body > p')
64
+ .html()
65
+ .trim();
66
+ expect(title).toBe('It appears you have been inactive for a while.');
67
+ });
68
+ });
@@ -0,0 +1,226 @@
1
+ import { renderComponent, setTestPage } from '../../tests/helpers/rendering';
2
+
3
+ const EXAMPLE_TIMEOUT_MODAL_BASIC = {
4
+ redirectUrl: '#!',
5
+ title: 'You will be signed out soon',
6
+ textFirstLine: 'It appears you have been inactive for a while.',
7
+ countdownText: 'To protect your information, your progress will be saved and you will be signed out in',
8
+ countdownExpiredText: 'You are being signed out.',
9
+ btnText: 'Continue survey',
10
+ minutesTextSingular: 'minute',
11
+ minutesTextPlural: 'minutes',
12
+ secondsTextSingular: 'second',
13
+ secondsTextPlural: 'seconds',
14
+ endWithFullStop: true,
15
+ };
16
+
17
+ describe('script: timeout modal', () => {
18
+ describe('when the page loads', () => {
19
+ beforeEach(async () => {
20
+ const component = renderComponent('timeout-modal', { ...EXAMPLE_TIMEOUT_MODAL_BASIC, showModalTimeInSeconds: 58 });
21
+ const template = `
22
+ <div class="ons-page">
23
+ ${component}
24
+ </div>
25
+ `;
26
+ await setTestPage('/test', template);
27
+ });
28
+
29
+ it('displays the modal after the correct number of seconds', async () => {
30
+ await page.waitForTimeout(2000);
31
+ const modalIsVisible = await page.$eval('.ons-modal', node => node.classList.contains('ons-u-db'));
32
+ expect(modalIsVisible).toBe(true);
33
+ });
34
+ });
35
+
36
+ describe('when the modal first opens', () => {
37
+ describe('when the countdown starts', () => {
38
+ beforeEach(async () => {
39
+ const component = renderComponent('timeout-modal', {
40
+ ...EXAMPLE_TIMEOUT_MODAL_BASIC,
41
+ showModalTimeInSeconds: 60,
42
+ });
43
+
44
+ const template = `
45
+ <div class="ons-page">
46
+ ${component}
47
+ </div>
48
+ `;
49
+
50
+ await setTestPage('/test', template);
51
+ });
52
+
53
+ it('shows the time counting down', async () => {
54
+ const timeAtStart = await page.$eval('.ons-js-timeout-timer span', element => element.innerHTML);
55
+ await page.waitForTimeout(1000);
56
+ const timeAfterOneSecond = await page.$eval('.ons-js-timeout-timer span', element => element.innerHTML);
57
+ expect(timeAfterOneSecond).not.toEqual(timeAtStart);
58
+ });
59
+ });
60
+
61
+ describe('when there are two minutes or more remaining', () => {
62
+ beforeEach(async () => {
63
+ const expiryTime = new Date(Date.now() + 60 * 3000);
64
+ const expiryTimeInISOFormat = new Date(expiryTime).toISOString();
65
+
66
+ const component = renderComponent('timeout-modal', {
67
+ ...EXAMPLE_TIMEOUT_MODAL_BASIC,
68
+ showModalTimeInSeconds: 180,
69
+ sessionExpiresAt: expiryTimeInISOFormat,
70
+ });
71
+
72
+ const template = `
73
+ <div class="ons-page">
74
+ ${component}
75
+ </div>
76
+ `;
77
+ await setTestPage('/test', template);
78
+ });
79
+
80
+ it('displays the `minutes` (plural) string', async () => {
81
+ const timeString = await page.$eval('.ons-js-timeout-timer span', element => element.innerHTML);
82
+ expect(timeString).toEqual(expect.stringContaining('minutes'));
83
+ });
84
+ });
85
+
86
+ describe('when there are two seconds or more remaining', () => {
87
+ beforeEach(async () => {
88
+ const expiryTime = new Date(Date.now() + 60 * 1000);
89
+ const expiryTimeInISOFormat = new Date(expiryTime).toISOString();
90
+
91
+ const component = renderComponent('timeout-modal', {
92
+ ...EXAMPLE_TIMEOUT_MODAL_BASIC,
93
+ showModalTimeInSeconds: 60,
94
+ sessionExpiresAt: expiryTimeInISOFormat,
95
+ });
96
+
97
+ const template = `
98
+ <div class="ons-page">
99
+ ${component}
100
+ </div>
101
+ `;
102
+ await setTestPage('/test', template);
103
+ });
104
+
105
+ it('displays the `seconds` (plural) string', async () => {
106
+ const timeString = await page.$eval('.ons-js-timeout-timer span', element => element.innerHTML);
107
+ expect(timeString).toEqual(expect.stringContaining('seconds'));
108
+ });
109
+ });
110
+
111
+ describe('when there is one minute remaining', () => {
112
+ beforeEach(async () => {
113
+ const expiryTime = new Date(Date.now() + 60 * 1500);
114
+ const expiryTimeInISOFormat = new Date(expiryTime).toISOString();
115
+
116
+ const component = renderComponent('timeout-modal', {
117
+ ...EXAMPLE_TIMEOUT_MODAL_BASIC,
118
+ showModalTimeInSeconds: 90,
119
+ sessionExpiresAt: expiryTimeInISOFormat,
120
+ });
121
+
122
+ const template = `
123
+ <div class="ons-page">
124
+ ${component}
125
+ </div>
126
+ `;
127
+ await setTestPage('/test', template);
128
+ });
129
+
130
+ it('displays the `minute` (singular) string', async () => {
131
+ const timeString = await page.$eval('.ons-js-timeout-timer span', element => element.innerHTML);
132
+ expect(timeString).toEqual(expect.stringContaining('minute'));
133
+ });
134
+ });
135
+
136
+ describe('when there is one second remaining', () => {
137
+ beforeEach(async () => {
138
+ const expiryTime = new Date(Date.now() + 3 * 1000);
139
+ const expiryTimeInISOFormat = new Date(expiryTime).toISOString();
140
+
141
+ const component = renderComponent('timeout-modal', {
142
+ ...EXAMPLE_TIMEOUT_MODAL_BASIC,
143
+ showModalTimeInSeconds: 3,
144
+ sessionExpiresAt: expiryTimeInISOFormat,
145
+ });
146
+
147
+ const template = `
148
+ <div class="ons-page">
149
+ ${component}
150
+ </div>
151
+ `;
152
+ await setTestPage('/test', template);
153
+ });
154
+
155
+ it('displays the `second` (singular) string', async () => {
156
+ const timeString = await page.$eval('.ons-js-timeout-timer span', element => element.innerHTML);
157
+ expect(timeString).toEqual(expect.stringContaining('second'));
158
+ });
159
+ });
160
+
161
+ describe('when the timer runs to zero', () => {
162
+ beforeEach(async () => {
163
+ const expiryTime = new Date(Date.now() + 1 * 1000);
164
+ const expiryTimeInISOFormat = new Date(expiryTime).toISOString();
165
+
166
+ const component = renderComponent('timeout-modal', {
167
+ ...EXAMPLE_TIMEOUT_MODAL_BASIC,
168
+ showModalTimeInSeconds: 1,
169
+ sessionExpiresAt: expiryTimeInISOFormat,
170
+ });
171
+
172
+ const template = `
173
+ <div class="ons-page">
174
+ ${component}
175
+ </div>
176
+ `;
177
+ await setTestPage('/test', template);
178
+ });
179
+
180
+ it('displays the `countdownExpiredText` text', async () => {
181
+ const timeString = await page.$eval('.ons-js-timeout-timer span', element => element.innerHTML);
182
+ expect(timeString).toEqual(expect.stringContaining('You are being signed out'));
183
+ });
184
+
185
+ it('then redirects to the provided `redirectUrl`', async () => {
186
+ await page.waitForTimeout(2000);
187
+ expect(page.url()).toContain('#!');
188
+ });
189
+ });
190
+ });
191
+
192
+ describe('when the modal is open', () => {
193
+ beforeEach(async () => {
194
+ const component = renderComponent('timeout-modal', {
195
+ ...EXAMPLE_TIMEOUT_MODAL_BASIC,
196
+ showModalTimeInSeconds: 59,
197
+ });
198
+
199
+ const template = `
200
+ <div class="ons-page">
201
+ ${component}
202
+ </div>
203
+ `;
204
+
205
+ await setTestPage('/test', template);
206
+ });
207
+
208
+ describe('when the `esc` key is pressed', () => {
209
+ beforeEach(async () => {
210
+ await page.waitForSelector('.ons-modal');
211
+ await page.keyboard.press('Escape');
212
+ });
213
+
214
+ it('closes the modal', async () => {
215
+ const modalIsVisible = await page.$eval('.ons-modal', node => node.classList.contains('ons-u-db'));
216
+ expect(modalIsVisible).toBe(false);
217
+ });
218
+
219
+ it('restarts the timer and displays the modal after the correct number of seconds', async () => {
220
+ await page.waitForTimeout(2000);
221
+ const modalIsVisible = await page.$eval('.ons-modal', node => node.classList.contains('ons-u-db'));
222
+ expect(modalIsVisible).toBe(true);
223
+ });
224
+ });
225
+ });
226
+ });
@@ -7,7 +7,6 @@
7
7
  "attributes": {
8
8
  "data-redirect-url": params.redirectUrl,
9
9
  "data-server-session-expires-at": params.sessionExpiresAt,
10
- "data-server-session-expiry-endpoint": params.serverSessionExpiryEndpoint,
11
10
  "data-countdown-text": params.countdownText,
12
11
  "data-countdown-expired-text": params.countdownExpiredText,
13
12
  "data-minutes-text-singular": params.minutesTextSingular,
@@ -0,0 +1,54 @@
1
+ /** @jest-environment jsdom */
2
+
3
+ import * as cheerio from 'cheerio';
4
+
5
+ import axe from '../../tests/helpers/axe';
6
+ import { renderComponent, templateFaker } from '../../tests/helpers/rendering';
7
+
8
+ const EXAMPLE_TIMEOUT_PANEL_BASIC = {
9
+ id: 'countdown',
10
+ sessionExpiresAt: '000-000-000',
11
+ redirectUrl: '#!',
12
+ minutesTextSingular: 'minute',
13
+ minutesTextPlural: 'minutes',
14
+ secondsTextSingular: 'second',
15
+ secondsTextPlural: 'seconds',
16
+ countdownText: 'For security, your answers will only be available to view for another',
17
+ nojsText: 'For security, your answers will only be available to view for another 1 minute',
18
+ countdownExpiredText: 'You are being signed out',
19
+ endWithFullStop: true,
20
+ };
21
+
22
+ describe('macro: timeout panel', () => {
23
+ it('passes jest-axe checks with', async () => {
24
+ const $ = cheerio.load(renderComponent('timeout-panel', EXAMPLE_TIMEOUT_PANEL_BASIC));
25
+
26
+ const results = await axe($.html());
27
+ expect(results).toHaveNoViolations();
28
+ });
29
+
30
+ it('provides expected parameters to the inner `panel` component', () => {
31
+ const faker = templateFaker();
32
+ const panelSpy = faker.spy('panel');
33
+
34
+ cheerio.load(faker.renderComponent('timeout-panel', EXAMPLE_TIMEOUT_PANEL_BASIC));
35
+
36
+ expect(panelSpy.occurrences[0]).toEqual({
37
+ id: 'countdown',
38
+ classes: 'ons-js-panel-with-countdown',
39
+ type: 'warn',
40
+ attributes: {
41
+ 'data-redirect-url': '#!',
42
+ 'data-server-session-expires-at': '000-000-000',
43
+ 'data-countdown-text': 'For security, your answers will only be available to view for another',
44
+ 'data-countdown-expired-text': 'You are being signed out',
45
+ 'data-minutes-text-singular': 'minute',
46
+ 'data-minutes-text-plural': 'minutes',
47
+ 'data-seconds-text-singular': 'second',
48
+ 'data-seconds-text-plural': 'seconds',
49
+ 'aria-describedby': 'timeout-time-remaining',
50
+ 'data-full-stop': true,
51
+ },
52
+ });
53
+ });
54
+ });
@@ -7,9 +7,8 @@ async function timeoutPanels() {
7
7
  const Timeout = (await import('../../js/timeout')).default;
8
8
 
9
9
  panels.forEach(context => {
10
- let url = context.getAttribute('data-server-session-expiry-endpoint');
11
10
  let time = context.getAttribute('data-server-session-expires-at');
12
- new Timeout(context, url, time, false, true);
11
+ new Timeout(context, null, time, false, true);
13
12
  });
14
13
  }
15
14
  }
@@ -0,0 +1,161 @@
1
+ import { renderComponent, setTestPage } from '../../tests/helpers/rendering';
2
+
3
+ const EXAMPLE_TIMEOUT_PANEL_BASIC = {
4
+ id: 'countdown',
5
+ redirectUrl: '#!',
6
+ minutesTextSingular: 'minute',
7
+ minutesTextPlural: 'minutes',
8
+ secondsTextSingular: 'second',
9
+ secondsTextPlural: 'seconds',
10
+ countdownText: 'For security, your answers will only be available to view for another',
11
+ nojsText: 'For security, your answers will only be available to view for another 1 minute',
12
+ countdownExpiredText: 'You are being signed out',
13
+ endWithFullStop: true,
14
+ };
15
+
16
+ describe('script: timeout panel', () => {
17
+ describe('when the page loads', () => {
18
+ beforeEach(async () => {
19
+ const expiryTime = new Date(Date.now() + 60 * 3000);
20
+ const expiryTimeInISOFormat = new Date(expiryTime).toISOString();
21
+
22
+ const component = renderComponent('timeout-panel', {
23
+ ...EXAMPLE_TIMEOUT_PANEL_BASIC,
24
+ sessionExpiresAt: expiryTimeInISOFormat,
25
+ });
26
+
27
+ await setTestPage('/test', component);
28
+ });
29
+
30
+ it('shows the time counting down', async () => {
31
+ const timeAtStart = await page.$eval('.ons-js-timeout-timer', element => element.innerHTML);
32
+ await page.waitForTimeout(1000);
33
+ const timeAfterOneSecond = await page.$eval('.ons-js-timeout-timer', element => element.innerHTML);
34
+ expect(timeAfterOneSecond).not.toEqual(timeAtStart);
35
+ });
36
+ });
37
+
38
+ describe('when there are two minutes or more remaining', () => {
39
+ beforeEach(async () => {
40
+ const expiryTime = new Date(Date.now() + 60 * 3000);
41
+ const expiryTimeInISOFormat = new Date(expiryTime).toISOString();
42
+
43
+ const component = renderComponent('timeout-panel', {
44
+ ...EXAMPLE_TIMEOUT_PANEL_BASIC,
45
+ sessionExpiresAt: expiryTimeInISOFormat,
46
+ });
47
+
48
+ await setTestPage('/test', component);
49
+ });
50
+
51
+ it('displays the `minutes` (plural) string', async () => {
52
+ const timeString = await page.$eval('.ons-js-timeout-timer', element => element.innerHTML);
53
+ expect(timeString).toEqual(expect.stringContaining('minutes'));
54
+ });
55
+ });
56
+
57
+ describe('when there are two seconds or more remaining', () => {
58
+ beforeEach(async () => {
59
+ const expiryTime = new Date(Date.now() + 60 * 1000);
60
+ const expiryTimeInISOFormat = new Date(expiryTime).toISOString();
61
+
62
+ const component = renderComponent('timeout-panel', {
63
+ ...EXAMPLE_TIMEOUT_PANEL_BASIC,
64
+ sessionExpiresAt: expiryTimeInISOFormat,
65
+ });
66
+
67
+ await setTestPage('/test', component);
68
+ });
69
+
70
+ it('displays the `seconds` (plural) string', async () => {
71
+ const timeString = await page.$eval('.ons-js-timeout-timer span', element => element.innerHTML);
72
+ expect(timeString).toEqual(expect.stringContaining('seconds'));
73
+ });
74
+ });
75
+
76
+ describe('when there is one minute remaining', () => {
77
+ beforeEach(async () => {
78
+ const expiryTime = new Date(Date.now() + 60 * 1500);
79
+ const expiryTimeInISOFormat = new Date(expiryTime).toISOString();
80
+
81
+ const component = renderComponent('timeout-panel', {
82
+ ...EXAMPLE_TIMEOUT_PANEL_BASIC,
83
+ sessionExpiresAt: expiryTimeInISOFormat,
84
+ });
85
+
86
+ await setTestPage('/test', component);
87
+ });
88
+
89
+ it('displays the `minute` (singular) string', async () => {
90
+ const timeString = await page.$eval('.ons-js-timeout-timer', element => element.innerHTML);
91
+ expect(timeString).toEqual(expect.stringContaining('minute'));
92
+ });
93
+ });
94
+
95
+ describe('when there is one second remaining', () => {
96
+ beforeEach(async () => {
97
+ const expiryTime = new Date(Date.now() + 3 * 1000);
98
+ const expiryTimeInISOFormat = new Date(expiryTime).toISOString();
99
+
100
+ const component = renderComponent('timeout-panel', {
101
+ ...EXAMPLE_TIMEOUT_PANEL_BASIC,
102
+ sessionExpiresAt: expiryTimeInISOFormat,
103
+ });
104
+
105
+ await setTestPage('/test', component);
106
+ });
107
+
108
+ it('displays the `second` (singular) string', async () => {
109
+ const timeString = await page.$eval('.ons-js-timeout-timer span', element => element.innerHTML);
110
+ expect(timeString).toEqual(expect.stringContaining('second'));
111
+ });
112
+ });
113
+
114
+ describe('when the timer runs to zero', () => {
115
+ beforeEach(async () => {
116
+ const expiryTime = new Date(Date.now() + 1 * 1000);
117
+ const expiryTimeInISOFormat = new Date(expiryTime).toISOString();
118
+
119
+ const component = renderComponent('timeout-panel', {
120
+ ...EXAMPLE_TIMEOUT_PANEL_BASIC,
121
+ sessionExpiresAt: expiryTimeInISOFormat,
122
+ });
123
+
124
+ await setTestPage('/test', component);
125
+ });
126
+
127
+ it('displays the `countdownExpiredText` text', async () => {
128
+ const timeString = await page.$eval('.ons-js-timeout-timer', element => element.innerHTML);
129
+ expect(timeString).toEqual(expect.stringContaining('You are being signed out'));
130
+ });
131
+
132
+ it('then redirects to the provided `redirectUrl`', async () => {
133
+ await page.waitForTimeout(2000);
134
+ expect(page.url()).toContain('#!');
135
+ });
136
+ });
137
+
138
+ describe('when Javascript is disabled', () => {
139
+ beforeEach(async () => {
140
+ const expiryTime = new Date(Date.now() + 1 * 1000);
141
+ const expiryTimeInISOFormat = new Date(expiryTime).toISOString();
142
+
143
+ const component = renderComponent('timeout-panel', {
144
+ ...EXAMPLE_TIMEOUT_PANEL_BASIC,
145
+ sessionExpiresAt: expiryTimeInISOFormat,
146
+ });
147
+
148
+ await page.setJavaScriptEnabled(false);
149
+ await setTestPage('/test', component);
150
+ });
151
+
152
+ afterEach(async () => {
153
+ await page.setJavaScriptEnabled(true);
154
+ });
155
+
156
+ it('displays the `nojsText` text', async () => {
157
+ const nojsText = await page.$eval('.ons-js-nojs-text', element => element.innerHTML);
158
+ expect(nojsText.trim()).toBe('For security, your answers will only be available to view for another 1 minute');
159
+ });
160
+ });
161
+ });
@@ -0,0 +1,75 @@
1
+ /** @jest-environment jsdom */
2
+
3
+ import * as cheerio from 'cheerio';
4
+
5
+ import axe from '../../tests/helpers/axe';
6
+ import { renderComponent, templateFaker } from '../../tests/helpers/rendering';
7
+
8
+ const EXAMPLE_UPLOAD = {
9
+ id: 'example-upload',
10
+ accept: '.xls,.xlsx,.pdf',
11
+ name: 'example-upload-name',
12
+ label: {
13
+ description: 'File types accepted are XLS and XLSX or PDF',
14
+ text: 'Upload a file',
15
+ },
16
+ dontWrap: true,
17
+ fieldId: 'example-upload-field',
18
+ fieldClasses: 'extra-field-class',
19
+ attributes: { a: 42 },
20
+ listeners: { test: 'console.log(42)' },
21
+ };
22
+
23
+ const EXAMPLE_UPLOAD_WITH_ERROR = {
24
+ ...EXAMPLE_UPLOAD,
25
+ error: {
26
+ id: 'file-error',
27
+ text: 'Select a file that is an XLS, XLSX or PDF',
28
+ },
29
+ };
30
+
31
+ describe('macro: upload', () => {
32
+ describe.each([
33
+ ['without error', EXAMPLE_UPLOAD, 'ons-input--upload'],
34
+ ['with error', EXAMPLE_UPLOAD_WITH_ERROR, 'ons-input--upload ons-input--error'],
35
+ ])('%s', (_, params, expectedInputClasses) => {
36
+ it('passes jest-axe checks', async () => {
37
+ const $ = cheerio.load(renderComponent('upload', params));
38
+
39
+ const results = await axe($.html());
40
+ expect(results).toHaveNoViolations();
41
+ });
42
+
43
+ it('renders `field` component with the expected parameters', () => {
44
+ const faker = templateFaker();
45
+ const fieldSpy = faker.spy('field');
46
+
47
+ faker.renderComponent('upload', params);
48
+
49
+ expect(fieldSpy.occurrences).toContainEqual({
50
+ id: 'example-upload-field',
51
+ classes: 'extra-field-class',
52
+ dontWrap: true,
53
+ error: params.error,
54
+ });
55
+ });
56
+
57
+ it('renders `input` component with the expected parameters', () => {
58
+ const faker = templateFaker();
59
+ const inputSpy = faker.spy('input');
60
+
61
+ faker.renderComponent('upload', params);
62
+
63
+ expect(inputSpy.occurrences).toContainEqual({
64
+ id: 'example-upload',
65
+ type: 'file',
66
+ label: params.label,
67
+ classes: expectedInputClasses,
68
+ accept: '.xls,.xlsx,.pdf',
69
+ name: 'example-upload-name',
70
+ attributes: { a: 42 },
71
+ listeners: params.listeners,
72
+ });
73
+ });
74
+ });
75
+ });