@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,165 +0,0 @@
1
- /* eslint-disable no-new */
2
-
3
- const {
4
- getByLabelText,
5
- getByRole,
6
- queryByRole
7
- } = require('@testing-library/dom')
8
- const { userEvent } = require('@testing-library/user-event')
9
-
10
- require('./add-another.js')
11
-
12
- const user = userEvent.setup()
13
-
14
- const createComponent = () => {
15
- const html = `
16
- <div data-module="moj-add-another">
17
- <h2 class="govuk-heading-l moj-add-another__heading" tabindex="-1">Add a person</h2>
18
- <form>
19
- <fieldset class="govuk-fieldset moj-add-another__item">
20
- <legend>Person</legend>
21
- <div class="govuk-form-group">
22
- <label for="person[0][first_name]">First name</label>
23
- <input class="govuk-input" id="person[0][first_name]" name="person[0][first_name]" type="text" data-name="person[%index%][first_name]" data-id="person[%index%][first_name]">
24
- </div>
25
- <div class="govuk-form-group">
26
- <label for="person[0][last_name]">Last name</label>
27
- <input class="govuk-input" id="person[0][last_name]" name="person[0][last_name]" type="text" data-name="person[%index%][last_name]" data-id="person[%index%][last_name]">
28
- </div>
29
- </fieldset>
30
- <button type="button" class="govuk-button moj-add-another__add-button">Add another person</button>
31
- </form>
32
- </div>`
33
- document.body.innerHTML = html
34
- const component = document.querySelector('[data-module="moj-add-another"]')
35
- return component
36
- }
37
-
38
- describe('Add Another component', () => {
39
- let component
40
- let addButton
41
-
42
- beforeEach(() => {
43
- component = createComponent()
44
- new MOJFrontend.AddAnother(component)
45
- addButton = getByRole(component, 'button', { name: 'Add another person' })
46
- })
47
-
48
- afterEach(() => {
49
- document.body.innerHTML = ''
50
- })
51
-
52
- test('adds a new item when "Add another person" is clicked', async () => {
53
- const initialItems = component.querySelectorAll('.moj-add-another__item')
54
- expect(initialItems).toHaveLength(1)
55
-
56
- await user.click(addButton)
57
-
58
- const updatedItems = component.querySelectorAll('.moj-add-another__item')
59
- expect(updatedItems).toHaveLength(2)
60
-
61
- const secondItemFirstName = updatedItems[1].querySelector(
62
- '[name="person[1][first_name]"]'
63
- )
64
- expect(secondItemFirstName).toBeInTheDocument()
65
- expect(secondItemFirstName.value).toBe('')
66
- })
67
-
68
- test('adds a remove button to new items', async () => {
69
- await user.click(addButton)
70
-
71
- const firstItem = component.querySelectorAll('.moj-add-another__item')[0]
72
- const secondItem = component.querySelectorAll('.moj-add-another__item')[1]
73
- const firstItemRemoveButton = getByRole(firstItem, 'button', {
74
- name: 'Remove'
75
- })
76
- const secondItemRemoveButton = getByRole(secondItem, 'button', {
77
- name: 'Remove'
78
- })
79
-
80
- expect(firstItemRemoveButton).toBeInTheDocument()
81
- expect(secondItemRemoveButton).toBeInTheDocument()
82
- })
83
-
84
- test('removes an item when the "Remove" button is clicked', async () => {
85
- await user.click(addButton)
86
-
87
- const secondItem = component.querySelectorAll('.moj-add-another__item')[1]
88
- const removeButton = getByRole(secondItem, 'button', { name: 'Remove' })
89
-
90
- await user.click(removeButton)
91
-
92
- const remainingItems = component.querySelectorAll('.moj-add-another__item')
93
-
94
- expect(remainingItems).toHaveLength(1)
95
- expect(
96
- queryByRole(component, 'button', { name: 'Remove' })
97
- ).not.toBeInTheDocument()
98
- })
99
-
100
- test('new item inputs have no value', async () => {
101
- await user.click(addButton)
102
-
103
- const secondItem = component.querySelectorAll('.moj-add-another__item')[1]
104
- const firstNameInput = getByLabelText(secondItem, 'First name')
105
- const lastNameInput = getByLabelText(secondItem, 'Last name')
106
-
107
- expect(firstNameInput.value).toBe('')
108
- expect(lastNameInput.value).toBe('')
109
- })
110
-
111
- test('resets form values in a new item', async () => {
112
- await user.click(addButton)
113
-
114
- const firstItem = component.querySelectorAll('.moj-add-another__item')[0]
115
- const firstItemFirstNameInput = getByLabelText(firstItem, 'First name')
116
- const firstItemLastNameInput = getByLabelText(firstItem, 'Last name')
117
-
118
- await user.type(firstItemFirstNameInput, 'Steve')
119
- await user.type(firstItemLastNameInput, 'Jobs')
120
-
121
- expect(firstItemFirstNameInput.value).toBe('Steve')
122
- expect(firstItemLastNameInput.value).toBe('Jobs')
123
-
124
- const secondItem = component.querySelectorAll('.moj-add-another__item')[1]
125
- const secondItemFirstNameInput = getByLabelText(secondItem, 'First name')
126
- const secondItemLastNameInput = getByLabelText(secondItem, 'Last name')
127
-
128
- expect(secondItemFirstNameInput.value).toBe('')
129
- expect(secondItemLastNameInput.value).toBe('')
130
- })
131
-
132
- test('focuses the heading after removing an item', async () => {
133
- await user.click(addButton)
134
-
135
- const heading = queryByRole(component, 'heading', { level: 2 })
136
- const secondItem = component.querySelectorAll('.moj-add-another__item')[1]
137
- const removeButton = getByRole(secondItem, 'button', { name: 'Remove' })
138
-
139
- await user.click(removeButton)
140
-
141
- expect(heading).toHaveFocus()
142
- })
143
-
144
- test('updates attributes correctly after removing an item', async () => {
145
- await user.click(addButton)
146
- await user.click(addButton)
147
-
148
- const secondItem = component.querySelectorAll('.moj-add-another__item')[1]
149
- const removeButton = getByRole(secondItem, 'button', { name: 'Remove' })
150
-
151
- await user.click(removeButton)
152
-
153
- const remainingItems = component.querySelectorAll('.moj-add-another__item')
154
- remainingItems.forEach((item, index) => {
155
- const firstNameInput = item.querySelector(
156
- `[name="person[${index}][first_name]"]`
157
- )
158
- const lastNameInput = item.querySelector(
159
- `[name="person[${index}][last_name]"]`
160
- )
161
- expect(firstNameInput).toBeInTheDocument()
162
- expect(lastNameInput).toBeInTheDocument()
163
- })
164
- })
165
- })
@@ -1,229 +0,0 @@
1
- /* eslint-disable no-new */
2
- const { getByRole, queryByRole, screen } = require('@testing-library/dom')
3
- const { userEvent } = require('@testing-library/user-event')
4
-
5
- const { pageTemplate } = require('./alert.spec.helper.js')
6
- require('../../helpers.js')
7
- require('./alert.js')
8
-
9
- const user = userEvent.setup()
10
-
11
- const kebabize = (str) => {
12
- return str.replace(
13
- /[A-Z]+(?![a-z])|[A-Z]/g,
14
- ($, ofset) => (ofset ? '-' : '') + $.toLowerCase()
15
- )
16
- }
17
-
18
- const configToDataAttributes = (config) => {
19
- let attributes = ''
20
- for (const [key, value] of Object.entries(config)) {
21
- attributes += `data-${kebabize(key)}="${value}" `
22
- }
23
- return attributes
24
- }
25
-
26
- const createComponent = (options = {}, role = 'region') => {
27
- const dataAttributes = configToDataAttributes(options)
28
- const html = `<div role="${role}" class="moj-alert moj-alert--information moj-alert--with-heading" aria-label="information: The finance section has moved" data-module="moj-alert" ${dataAttributes}>
29
- <div>
30
- <svg class="moj-alert__icon" role="presentation" focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30" height="30" width="30">
31
- <path fill-rule="evenodd" clip-rule="evenodd" d="M10.2165 3.45151C11.733 2.82332 13.3585 2.5 15 2.5C16.6415 2.5 18.267 2.82332 19.7835 3.45151C21.3001 4.07969 22.6781 5.00043 23.8388 6.16117C24.9996 7.3219 25.9203 8.69989 26.5485 10.2165C27.1767 11.733 27.5 13.3585 27.5 15C27.5 18.3152 26.183 21.4946 23.8388 23.8388C21.4946 26.183 18.3152 27.5 15 27.5C13.3585 27.5 11.733 27.1767 10.2165 26.5485C8.69989 25.9203 7.3219 24.9996 6.16117 23.8388C3.81696 21.4946 2.5 18.3152 2.5 15C2.5 11.6848 3.81696 8.50537 6.16117 6.16117C7.3219 5.00043 8.69989 4.07969 10.2165 3.45151ZM16.3574 22.4121H13.6621V12.95H16.3574V22.4121ZM13.3789 9.20898C13.3789 8.98763 13.4212 8.7793 13.5059 8.58398C13.5905 8.38216 13.7044 8.20964 13.8477 8.06641C13.9974 7.91667 14.1699 7.79948 14.3652 7.71484C14.5605 7.63021 14.7721 7.58789 15 7.58789C15.2214 7.58789 15.4297 7.63021 15.625 7.71484C15.8268 7.79948 15.9993 7.91667 16.1426 8.06641C16.2923 8.20964 16.4095 8.38216 16.4941 8.58398C16.5788 8.7793 16.6211 8.98763 16.6211 9.20898C16.6211 9.43685 16.5788 9.64844 16.4941 9.84375C16.4095 10.0391 16.2923 10.2116 16.1426 10.3613C15.9993 10.5046 15.8268 10.6185 15.625 10.7031C15.4297 10.7878 15.2214 10.8301 15 10.8301C14.7721 10.8301 14.5605 10.7878 14.3652 10.7031C14.1699 10.6185 13.9974 10.5046 13.8477 10.3613C13.7044 10.2116 13.5905 10.0391 13.5059 9.84375C13.4212 9.64844 13.3789 9.43685 13.3789 9.20898Z" fill="currentColor" />
32
- </svg>
33
- </div>
34
- <div class="moj-alert__content">
35
- <h2 class="govuk-heading-m">The finance section has moved
36
- </h2>You can now find it in the <a href="#">dashboard</a>.
37
- </div>
38
- <div class="moj-alert__action">
39
- <button class="moj-alert__dismiss" hidden>Dismiss</button>
40
- </div>
41
- </div>
42
- `
43
-
44
- document.body.insertAdjacentHTML('afterbegin', html)
45
- const component = document.querySelector('[data-module="moj-alert"]')
46
- return component
47
- }
48
-
49
- describe('alert', () => {
50
- let component
51
- let dismissButton
52
-
53
- afterEach(() => {
54
- document.body.innerHTML = ''
55
- })
56
-
57
- test('non-dismissible', () => {
58
- component = createComponent()
59
- new MOJFrontend.Alert(component).init()
60
- dismissButton = queryByRole(component, 'button', { hidden: true })
61
-
62
- expect(dismissButton).toHaveAttribute('hidden')
63
- })
64
-
65
- test('dismissible', () => {
66
- component = createComponent({ dismissible: 'true' })
67
- new MOJFrontend.Alert(component).init()
68
- dismissButton = queryByRole(component, 'button', { hidden: true })
69
-
70
- expect(dismissButton).not.toHaveAttribute('hidden')
71
- })
72
-
73
- test('non-dismissible "false" string', () => {
74
- component = createComponent({ dismissible: 'false' })
75
- new MOJFrontend.Alert(component).init()
76
- dismissButton = queryByRole(component, 'button', { hidden: true })
77
-
78
- expect(dismissButton).toHaveAttribute('hidden')
79
- })
80
-
81
- test('applies custom dismiss text', () => {
82
- component = createComponent({
83
- dismissible: true,
84
- dismissText: 'Close'
85
- })
86
- new MOJFrontend.Alert(component).init()
87
- dismissButton = queryByRole(component, 'button', { name: 'Close' })
88
-
89
- expect(dismissButton).toBeInTheDocument()
90
- })
91
-
92
- test('region role does not receive focus', () => {
93
- component = createComponent({}, 'region')
94
- new MOJFrontend.Alert(component).init()
95
-
96
- expect(component).not.toHaveFocus()
97
- })
98
-
99
- test('alert role autofocuses', () => {
100
- component = createComponent({}, 'alert')
101
- new MOJFrontend.Alert(component).init()
102
-
103
- expect(component).toHaveFocus()
104
- })
105
-
106
- test('disableAutoFocus prevents autofocus', () => {
107
- component = createComponent(
108
- {
109
- disableAutoFocus: true
110
- },
111
- 'alert'
112
- )
113
- new MOJFrontend.Alert(component).init()
114
-
115
- expect(component).not.toHaveFocus()
116
- })
117
-
118
- test('dismiss button removes component', async () => {
119
- component = createComponent({
120
- dismissible: true
121
- })
122
- new MOJFrontend.Alert(component).init()
123
- dismissButton = queryByRole(component, 'button')
124
-
125
- await user.click(dismissButton)
126
-
127
- expect(component).not.toBeInTheDocument()
128
- })
129
-
130
- describe('focus on dismiss', () => {
131
- let firstAlert
132
- let secondAlert
133
- let thirdAlert
134
- let fourthAlert
135
- let fifthAlert
136
- let heading1
137
- let heading2
138
-
139
- beforeEach(() => {
140
- document.body.insertAdjacentHTML('afterbegin', pageTemplate)
141
- const alerts = document.querySelectorAll('[data-module="moj-alert"]')
142
- alerts.forEach((alert) => {
143
- new MOJFrontend.Alert(alert).init()
144
- })
145
-
146
- firstAlert = document.querySelector('#alert-1')
147
- secondAlert = document.querySelector('#alert-2')
148
- thirdAlert = document.querySelector('#alert-3')
149
- fourthAlert = document.querySelector('#alert-4')
150
- fifthAlert = document.querySelector('#alert-5')
151
-
152
- heading1 = document.querySelector('#h1')
153
- heading2 = document.querySelector('#h2')
154
- })
155
-
156
- afterEach(() => {
157
- document.body.innerHTML = ''
158
- })
159
-
160
- test('it moves focus to the element provide by focusOnDismiss', async () => {
161
- const alert = fifthAlert
162
- const focusedElement = document.querySelector('#focusOnMe')
163
- const dismissButton = getByRole(alert, 'button')
164
-
165
- await user.click(dismissButton)
166
-
167
- expect(focusedElement).toHaveFocus()
168
- })
169
-
170
- test('it moves focus to the next sibling alert if present', async () => {
171
- const alert = thirdAlert
172
- const dismissButton = getByRole(alert, 'button')
173
-
174
- await user.click(dismissButton)
175
-
176
- expect(fourthAlert).toHaveFocus()
177
- })
178
-
179
- test('it moves focus to the previous sibling alert if present', async () => {
180
- fourthAlert.remove()
181
- fifthAlert.remove()
182
- const alert = thirdAlert
183
- const dismissButton = getByRole(alert, 'button')
184
-
185
- await user.click(dismissButton)
186
-
187
- expect(secondAlert).toHaveFocus()
188
- })
189
-
190
- test('it moves focus to the previous heading if present', async () => {
191
- thirdAlert.remove()
192
- fourthAlert.remove()
193
- fifthAlert.remove()
194
- const alert = secondAlert
195
- const dismissButton = getByRole(alert, 'button')
196
-
197
- await user.click(dismissButton)
198
-
199
- expect(heading2).toHaveFocus()
200
- })
201
-
202
- test('it moves focus to the parent heading if present', async () => {
203
- heading2.remove()
204
- thirdAlert.remove()
205
- fourthAlert.remove()
206
- fifthAlert.remove()
207
- const alert = secondAlert
208
- const dismissButton = getByRole(alert, 'button')
209
-
210
- await user.click(dismissButton)
211
-
212
- expect(heading1).toHaveFocus()
213
- })
214
-
215
- test('it moves focus to main if no other element matches', async () => {
216
- heading2.remove()
217
- thirdAlert.remove()
218
- fourthAlert.remove()
219
- fifthAlert.remove()
220
- const alert = firstAlert
221
- const main = screen.getByRole('main')
222
- const dismissButton = getByRole(alert, 'button')
223
-
224
- await user.click(dismissButton)
225
-
226
- expect(main).toHaveFocus()
227
- })
228
- })
229
- })