@ministryofjustice/frontend 3.4.0 → 3.6.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 (50) hide show
  1. package/moj/all.jquery.js +13378 -0
  2. package/moj/all.jquery.min.js +1 -144
  3. package/moj/all.js +2266 -2551
  4. package/moj/all.mjs +126 -0
  5. package/moj/components/add-another/add-another.js +110 -100
  6. package/moj/components/add-another/add-another.mjs +106 -0
  7. package/moj/components/alert/alert.js +319 -211
  8. package/moj/components/alert/alert.mjs +251 -0
  9. package/moj/components/alert/alert.spec.helper.js +12 -5
  10. package/moj/components/alert/alert.spec.helper.mjs +66 -0
  11. package/moj/components/button-menu/button-menu.js +302 -292
  12. package/moj/components/button-menu/button-menu.mjs +329 -0
  13. package/moj/components/date-picker/date-picker.js +850 -842
  14. package/moj/components/date-picker/date-picker.mjs +961 -0
  15. package/moj/components/filter-toggle-button/filter-toggle-button.js +98 -88
  16. package/moj/components/filter-toggle-button/filter-toggle-button.mjs +93 -0
  17. package/moj/components/form-validator/form-validator.js +195 -155
  18. package/moj/components/form-validator/form-validator.mjs +168 -0
  19. package/moj/components/multi-file-upload/multi-file-upload.js +158 -137
  20. package/moj/components/multi-file-upload/multi-file-upload.mjs +219 -0
  21. package/moj/components/multi-select/multi-select.js +75 -65
  22. package/moj/components/multi-select/multi-select.mjs +77 -0
  23. package/moj/components/password-reveal/password-reveal.js +40 -30
  24. package/moj/components/password-reveal/password-reveal.mjs +35 -0
  25. package/moj/components/rich-text-editor/rich-text-editor.js +92 -80
  26. package/moj/components/rich-text-editor/rich-text-editor.mjs +157 -0
  27. package/moj/components/search-toggle/search-toggle.js +55 -45
  28. package/moj/components/search-toggle/search-toggle.mjs +54 -0
  29. package/moj/components/sortable-table/sortable-table.js +141 -141
  30. package/moj/components/sortable-table/sortable-table.mjs +138 -0
  31. package/moj/helpers/_links.scss +1 -1
  32. package/moj/helpers.js +171 -152
  33. package/moj/helpers.mjs +123 -0
  34. package/moj/moj-frontend.min.js +1 -144
  35. package/moj/version.js +11 -1
  36. package/moj/version.mjs +3 -0
  37. package/package.json +13 -1
  38. package/moj/all.spec.js +0 -24
  39. package/moj/components/add-another/add-another.spec.js +0 -165
  40. package/moj/components/alert/alert.spec.js +0 -229
  41. package/moj/components/button-menu/button-menu.spec.js +0 -360
  42. package/moj/components/date-picker/date-picker.spec.js +0 -1178
  43. package/moj/components/filter-toggle-button/filter-toggle-button.spec.js +0 -302
  44. package/moj/components/multi-file-upload/multi-file-upload.spec.js +0 -510
  45. package/moj/components/multi-select/multi-select.spec.js +0 -128
  46. package/moj/components/password-reveal/password-reveal.spec.js +0 -57
  47. package/moj/components/search-toggle/search-toggle.spec.js +0 -129
  48. package/moj/components/sortable-table/sortable-table.spec.js +0 -362
  49. package/moj/helpers.spec.js +0 -235
  50. package/moj/namespace.js +0 -2
@@ -1,360 +0,0 @@
1
- const { queryByRole, screen } = require('@testing-library/dom')
2
- const { userEvent } = require('@testing-library/user-event')
3
- const { configureAxe } = require('jest-axe')
4
-
5
- require('./button-menu.js')
6
-
7
- const user = userEvent.setup()
8
- const axe = configureAxe({
9
- rules: {
10
- // disable landmark rules when testing isolated components.
11
- region: { enabled: false }
12
- }
13
- })
14
-
15
- const kebabize = (str) => {
16
- return str.replace(
17
- /[A-Z]+(?![a-z])|[A-Z]/g,
18
- ($, ofset) => (ofset ? '-' : '') + $.toLowerCase()
19
- )
20
- }
21
-
22
- const configToDataAttributes = (config) => {
23
- let attributes = ''
24
- for (const [key, value] of Object.entries(config)) {
25
- attributes += `data-${kebabize(key)}="${value}" `
26
- }
27
- return attributes
28
- }
29
-
30
- const createComponent = (config = {}, html) => {
31
- const dataAttributes = configToDataAttributes(config)
32
- if (typeof html === 'undefined') {
33
- html = `
34
- <div class="moj-button-menu" data-module="moj-button-menu" ${dataAttributes}>
35
- <a href="#one" role="button">First action</a>
36
- <a href="#two" role="button" class="govuk-button--warning">Second action</a>
37
- <a href="#three" role="button" class="custom-class">Third action</a>
38
- </div>`
39
- }
40
- document.body.insertAdjacentHTML('afterbegin', html)
41
- const component = document.querySelector('[data-module="moj-button-menu"]')
42
- return component
43
- }
44
-
45
- describe('Button menu with defaults', () => {
46
- let component
47
- let toggleButton
48
- let menu
49
- let items
50
-
51
- beforeEach(() => {
52
- component = createComponent()
53
- new MOJFrontend.ButtonMenu(component).init()
54
-
55
- toggleButton = queryByRole(component, 'button', { hidden: false })
56
- menu = screen.queryByRole('list', { hidden: true })
57
- items = menu?.querySelectorAll('a, button')
58
- })
59
-
60
- afterEach(() => {
61
- document.body.innerHTML = ''
62
- })
63
-
64
- test('initialises component elements', () => {
65
- expect(toggleButton).not.toBeNull()
66
- expect(menu).not.toBeNull()
67
- expect(items).not.toBeNull()
68
- })
69
-
70
- test('intialises toggle button', () => {
71
- expect(component).toContainElement(toggleButton)
72
- expect(toggleButton).toHaveAttribute('aria-expanded', 'false')
73
- expect(toggleButton).toHaveAttribute('aria-haspopup', 'true')
74
- })
75
-
76
- test('intialises menu', () => {
77
- expect(component).toContainElement(menu)
78
- expect(menu).not.toBeVisible()
79
- })
80
-
81
- test('creates menuitems', () => {
82
- expect(items).toHaveLength(3)
83
- })
84
-
85
- test('removes other govuk-button classes from menuitems', () => {
86
- expect(items[1]).not.toHaveClass('govuk-button--warning')
87
- })
88
-
89
- test('keeps custom classes on items', () => {
90
- expect(items[2]).toHaveClass('custom-class')
91
- })
92
-
93
- test('clicking toggle button shows menu', async () => {
94
- await user.click(toggleButton)
95
-
96
- expect(menu).toBeVisible()
97
- expect(toggleButton).toHaveAttribute('aria-expanded', 'true')
98
- })
99
-
100
- test('clicking a link in the menu', async () => {
101
- await user.click(toggleButton)
102
-
103
- expect(menu).toBeVisible()
104
- await user.click(items[0])
105
- expect(global.window.location.hash).toContain('#one')
106
- await user.click(items[2])
107
- expect(global.window.location.hash).toContain('#three')
108
- })
109
-
110
- test('clicking outside closes menu', async () => {
111
- await user.click(toggleButton)
112
- expect(menu).toBeVisible()
113
-
114
- await user.click(document.body)
115
- expect(menu).not.toBeVisible()
116
- })
117
-
118
- describe('keyboard interactions', () => {
119
- test('enter on toggle button opens menu', async () => {
120
- toggleButton.focus()
121
- await user.keyboard('[Enter]')
122
-
123
- expect(menu).toBeVisible()
124
- expect(toggleButton).toHaveAttribute('aria-expanded', 'true')
125
- expect(items[0]).toHaveFocus()
126
- })
127
-
128
- test('space on toggle button opens menu', async () => {
129
- toggleButton.focus()
130
- await user.keyboard('[Space]')
131
-
132
- expect(menu).toBeVisible()
133
- expect(toggleButton).toHaveAttribute('aria-expanded', 'true')
134
- expect(items[0]).toHaveFocus()
135
- })
136
-
137
- test('esc closes menu', async () => {
138
- toggleButton.focus()
139
- await user.keyboard('[Space]')
140
- expect(menu).toBeVisible()
141
- await user.keyboard('[Escape]')
142
-
143
- expect(menu).not.toBeVisible()
144
- expect(toggleButton).toHaveFocus()
145
- })
146
-
147
- test('down arrow on toggle button opens menu with focus on first item', async () => {
148
- toggleButton.focus()
149
- await user.keyboard('[ArrowDown]')
150
-
151
- expect(menu).toBeVisible()
152
- expect(toggleButton).toHaveAttribute('aria-expanded', 'true')
153
- expect(items[0]).toHaveFocus()
154
- })
155
-
156
- test('up arrow on toggle button opens menu with focus on last item', async () => {
157
- toggleButton.focus()
158
- await user.keyboard('[ArrowUp]')
159
-
160
- expect(menu).toBeVisible()
161
- expect(toggleButton).toHaveAttribute('aria-expanded', 'true')
162
- expect(items[items.length - 1]).toHaveFocus()
163
- })
164
-
165
- test('down arrow on menu item navigates to next item with looping', async () => {
166
- toggleButton.focus()
167
- await user.keyboard('[Enter]')
168
- expect(items[0]).toHaveFocus()
169
-
170
- await user.keyboard('[ArrowDown]')
171
- expect(items[1]).toHaveFocus()
172
-
173
- await user.keyboard('[ArrowDown]')
174
- expect(items[2]).toHaveFocus()
175
-
176
- await user.keyboard('[ArrowDown]')
177
- expect(items[0]).toHaveFocus()
178
- })
179
-
180
- test('up arrow on menu item navigates to previous item with looping', async () => {
181
- toggleButton.focus()
182
- await user.keyboard('[ArrowUp]')
183
- expect(items[items.length - 1]).toHaveFocus()
184
-
185
- await user.keyboard('[ArrowUp]')
186
- expect(items[1]).toHaveFocus()
187
-
188
- await user.keyboard('[ArrowUp]')
189
- expect(items[0]).toHaveFocus()
190
-
191
- await user.keyboard('[ArrowUp]')
192
- expect(items[items.length - 1]).toHaveFocus()
193
- })
194
-
195
- test('home navigates to first item', async () => {
196
- toggleButton.focus()
197
- await user.keyboard('[ArrowUp]')
198
- expect(items[items.length - 1]).toHaveFocus()
199
-
200
- await user.keyboard('[Home]')
201
- expect(items[0]).toHaveFocus()
202
- })
203
-
204
- test('end navigates to last item', async () => {
205
- toggleButton.focus()
206
- await user.keyboard('[Enter]')
207
- expect(items[0]).toHaveFocus()
208
-
209
- await user.keyboard('[End]')
210
- expect(items[items.length - 1]).toHaveFocus()
211
- })
212
-
213
- test('tab moves focus out of the menu', async () => {
214
- toggleButton.focus()
215
- await user.keyboard('[Enter]')
216
- expect(menu).toBeVisible()
217
- expect(items[0]).toHaveFocus()
218
- await user.tab()
219
-
220
- expect(document.body).toHaveFocus()
221
- expect(menu).not.toBeVisible()
222
- })
223
- })
224
-
225
- describe('accessibility', () => {
226
- test('component has no wcag violations', async () => {
227
- expect(await axe(document.body)).toHaveNoViolations()
228
- await user.click(toggleButton)
229
- expect(await axe(document.body)).toHaveNoViolations()
230
- })
231
- })
232
- })
233
-
234
- describe('Button menu javascript API', () => {
235
- let component
236
-
237
- beforeEach(() => {
238
- component = createComponent()
239
- })
240
-
241
- afterEach(() => {
242
- document.body.innerHTML = ''
243
- })
244
-
245
- test('setting toggle button text', () => {
246
- const label = 'click me'
247
- new MOJFrontend.ButtonMenu(component, { buttonText: label }).init()
248
- const toggleButton = queryByRole(component, 'button', { name: label })
249
-
250
- expect(toggleButton).toBeInTheDocument()
251
- })
252
-
253
- test('setting menu alignment', () => {
254
- new MOJFrontend.ButtonMenu(component, { alignMenu: 'right' }).init()
255
- const menu = screen.queryByRole('list', { hidden: true })
256
-
257
- expect(menu).toHaveClass('moj-button-menu__wrapper--right')
258
- })
259
-
260
- test('setting button classes', () => {
261
- const defaultClassNames = 'govuk-button moj-button-menu__toggle-button'
262
- const classNames = 'classOne classTwo'
263
-
264
- new MOJFrontend.ButtonMenu(component, { buttonClasses: classNames }).init()
265
- const toggleButton = queryByRole(component, 'button', { hidden: false })
266
-
267
- expect(toggleButton).toHaveClass(defaultClassNames)
268
- expect(toggleButton).toHaveClass(classNames)
269
- })
270
- })
271
-
272
- describe('Button menu data-attributes API', () => {
273
- let component
274
-
275
- beforeEach(() => {})
276
-
277
- afterEach(() => {
278
- document.body.innerHTML = ''
279
- })
280
-
281
- test('setting toggle button text', () => {
282
- const label = 'click me'
283
-
284
- component = createComponent({ buttonText: label })
285
- new MOJFrontend.ButtonMenu(component).init()
286
- const toggleButton = queryByRole(component, 'button', { name: label })
287
-
288
- expect(toggleButton).toBeInTheDocument()
289
- })
290
-
291
- test('setting menu alignment', () => {
292
- component = createComponent({ alignMenu: 'right' })
293
- new MOJFrontend.ButtonMenu(component).init()
294
- const menu = screen.queryByRole('list', { hidden: true })
295
-
296
- expect(menu).toHaveClass('moj-button-menu__wrapper--right')
297
- })
298
-
299
- test('setting button classes', () => {
300
- const defaultClassNames = 'govuk-button moj-button-menu__toggle-button'
301
- const classNames = 'classOne classTwo'
302
-
303
- component = createComponent({ buttonClasses: classNames })
304
- new MOJFrontend.ButtonMenu(component).init()
305
- const toggleButton = queryByRole(component, 'button', { hidden: false })
306
-
307
- expect(toggleButton).toHaveClass(defaultClassNames)
308
- expect(toggleButton).toHaveClass(classNames)
309
- })
310
- })
311
-
312
- describe('menu button with a single item', () => {
313
- let component
314
- let toggleButton
315
- let menu
316
- let items
317
-
318
- beforeEach(() => {
319
- const html = `
320
- <div class="moj-button-menu" data-module="moj-button-menu" data-button-classes="govuk-button--warning custom-class">
321
- <a href="#one" role="button" class="govuk-button--inverse">First action</a>
322
- </div>`
323
-
324
- component = createComponent({}, html)
325
- new MOJFrontend.ButtonMenu(component).init()
326
-
327
- toggleButton = queryByRole(component, 'button', { name: 'Actions' })
328
- menu = screen.queryByRole('list', { hidden: true })
329
- items = menu?.queryByRole('button', { hidden: true })
330
- })
331
-
332
- afterEach(() => {
333
- document.body.innerHTML = ''
334
- })
335
-
336
- test('menu is not created', () => {
337
- expect(menu).toBeNull()
338
- })
339
-
340
- test('there are no items', () => {
341
- expect(items).toBeUndefined()
342
- })
343
-
344
- test('there is no toggle button', () => {
345
- expect(toggleButton).toBeNull()
346
- })
347
-
348
- test('first item has become button', () => {
349
- const button = screen.queryByRole('button', { name: 'First action' })
350
-
351
- expect(button).toBeInTheDocument()
352
- })
353
-
354
- test('first item has buttonClasses config applied', () => {
355
- const button = screen.queryByRole('button', { name: 'First action' })
356
-
357
- expect(button).toHaveClass('govuk-button--warning', 'custom-class')
358
- expect(button).not.toHaveClass('govuk-button--inverse')
359
- })
360
- })