@ministryofjustice/frontend 3.3.0 → 3.4.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.
- package/README.md +4 -10
- package/govuk-prototype-kit.config.json +5 -16
- package/moj/all.jquery.min.js +77 -3
- package/moj/all.js +2022 -1444
- package/moj/all.scss +2 -0
- package/moj/all.spec.js +15 -13
- package/moj/components/_all.scss +1 -0
- package/moj/components/action-bar/_action-bar.scss +4 -6
- package/moj/components/add-another/_add-another.scss +9 -7
- package/moj/components/add-another/add-another.js +90 -69
- package/moj/components/add-another/add-another.spec.js +165 -0
- package/moj/components/alert/README.md +0 -0
- package/moj/components/alert/_alert.scss +142 -0
- package/moj/components/alert/alert.js +247 -0
- package/moj/components/alert/alert.spec.helper.js +67 -0
- package/moj/components/alert/alert.spec.js +229 -0
- package/moj/components/alert/macro.njk +3 -0
- package/moj/components/alert/template.njk +83 -0
- package/moj/components/badge/_badge.scss +3 -4
- package/moj/components/banner/_banner.scss +5 -10
- package/moj/components/button-menu/_button-menu.scss +10 -9
- package/moj/components/button-menu/button-menu.js +139 -136
- package/moj/components/button-menu/button-menu.spec.js +295 -296
- package/moj/components/cookie-banner/_cookie-banner.scss +6 -5
- package/moj/components/currency-input/_currency-input.scss +4 -4
- package/moj/components/date-picker/README.md +14 -17
- package/moj/components/date-picker/_date-picker.scss +122 -106
- package/moj/components/date-picker/date-picker.js +473 -471
- package/moj/components/date-picker/date-picker.spec.js +962 -914
- package/moj/components/filter/README.md +1 -1
- package/moj/components/filter/_filter.scss +53 -75
- package/moj/components/filter-toggle-button/filter-toggle-button.js +71 -67
- package/moj/components/filter-toggle-button/filter-toggle-button.spec.js +203 -205
- package/moj/components/form-validator/form-validator.js +117 -109
- package/moj/components/header/_header.scss +17 -19
- package/moj/components/identity-bar/_identity-bar.scss +5 -5
- package/moj/components/interruption-card/_interruption-card.scss +9 -2
- package/moj/components/messages/_messages.scss +12 -19
- package/moj/components/multi-file-upload/README.md +1 -1
- package/moj/components/multi-file-upload/_multi-file-upload.scss +34 -30
- package/moj/components/multi-file-upload/multi-file-upload.js +188 -152
- package/moj/components/multi-file-upload/multi-file-upload.spec.js +510 -0
- package/moj/components/multi-select/_multi-select.scss +4 -3
- package/moj/components/multi-select/multi-select.js +55 -50
- package/moj/components/multi-select/multi-select.spec.js +128 -0
- package/moj/components/notification-badge/_notification-badge.scss +12 -12
- package/moj/components/organisation-switcher/_organisation-switcher.scss +1 -1
- package/moj/components/page-header-actions/_page-header-actions.scss +3 -2
- package/moj/components/pagination/_pagination.scss +26 -31
- package/moj/components/password-reveal/_password-reveal.scss +1 -2
- package/moj/components/password-reveal/password-reveal.js +22 -21
- package/moj/components/password-reveal/password-reveal.spec.js +39 -37
- package/moj/components/primary-navigation/_primary-navigation.scss +26 -29
- package/moj/components/progress-bar/_progress-bar.scss +21 -26
- package/moj/components/rich-text-editor/_rich-text-editor.scss +17 -16
- package/moj/components/rich-text-editor/rich-text-editor.js +117 -103
- package/moj/components/search/_search.scss +6 -4
- package/moj/components/search-toggle/search-toggle.js +29 -30
- package/moj/components/search-toggle/search-toggle.scss +21 -15
- package/moj/components/search-toggle/search-toggle.spec.js +129 -0
- package/moj/components/side-navigation/_side-navigation.scss +12 -21
- package/moj/components/sortable-table/_sortable-table.scss +25 -23
- package/moj/components/sortable-table/sortable-table.js +139 -117
- package/moj/components/sortable-table/sortable-table.spec.js +362 -0
- package/moj/components/sub-navigation/_sub-navigation.scss +24 -28
- package/moj/components/tag/_tag.scss +8 -9
- package/moj/components/task-list/_task-list.scss +8 -7
- package/moj/components/ticket-panel/_ticket-panel.scss +14 -6
- package/moj/components/timeline/_timeline.scss +18 -20
- package/moj/filters/all.js +28 -30
- package/moj/filters/prototype-kit-13-filters.js +2 -1
- package/moj/helpers/_all.scss +1 -0
- package/moj/helpers/_hidden.scss +1 -1
- package/moj/helpers/_links.scss +20 -0
- package/moj/helpers.js +160 -31
- package/moj/helpers.spec.js +235 -0
- package/moj/init.js +2 -2
- package/moj/moj-frontend.min.css +2 -2
- package/moj/moj-frontend.min.js +77 -3
- package/moj/namespace.js +2 -1
- package/moj/objects/_filter-layout.scss +11 -10
- package/moj/objects/_scrollable-pane.scss +11 -14
- package/moj/settings/_colours.scss +5 -0
- package/moj/settings/_measurements.scss +0 -2
- package/moj/utilities/_hidden.scss +3 -3
- package/moj/utilities/_width-container.scss +1 -1
- package/package.json +1 -1
|
@@ -1,361 +1,360 @@
|
|
|
1
|
-
const { queryByRole, screen } = require(
|
|
2
|
-
const { userEvent } = require(
|
|
3
|
-
const { configureAxe } = require(
|
|
1
|
+
const { queryByRole, screen } = require('@testing-library/dom')
|
|
2
|
+
const { userEvent } = require('@testing-library/user-event')
|
|
3
|
+
const { configureAxe } = require('jest-axe')
|
|
4
4
|
|
|
5
|
-
require(
|
|
5
|
+
require('./button-menu.js')
|
|
6
6
|
|
|
7
|
-
const user = userEvent.setup()
|
|
7
|
+
const user = userEvent.setup()
|
|
8
8
|
const axe = configureAxe({
|
|
9
9
|
rules: {
|
|
10
10
|
// disable landmark rules when testing isolated components.
|
|
11
|
-
region: { enabled: false }
|
|
12
|
-
}
|
|
13
|
-
})
|
|
11
|
+
region: { enabled: false }
|
|
12
|
+
}
|
|
13
|
+
})
|
|
14
14
|
|
|
15
15
|
const kebabize = (str) => {
|
|
16
16
|
return str.replace(
|
|
17
17
|
/[A-Z]+(?![a-z])|[A-Z]/g,
|
|
18
|
-
($, ofset) => (ofset ?
|
|
19
|
-
)
|
|
20
|
-
}
|
|
18
|
+
($, ofset) => (ofset ? '-' : '') + $.toLowerCase()
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
21
|
|
|
22
22
|
const configToDataAttributes = (config) => {
|
|
23
|
-
let attributes =
|
|
24
|
-
for (
|
|
25
|
-
attributes += `data-${kebabize(key)}="${value}"
|
|
23
|
+
let attributes = ''
|
|
24
|
+
for (const [key, value] of Object.entries(config)) {
|
|
25
|
+
attributes += `data-${kebabize(key)}="${value}" `
|
|
26
26
|
}
|
|
27
|
-
return attributes
|
|
28
|
-
}
|
|
27
|
+
return attributes
|
|
28
|
+
}
|
|
29
29
|
|
|
30
30
|
const createComponent = (config = {}, html) => {
|
|
31
|
-
const dataAttributes = configToDataAttributes(config)
|
|
32
|
-
if(typeof html ===
|
|
33
|
-
|
|
31
|
+
const dataAttributes = configToDataAttributes(config)
|
|
32
|
+
if (typeof html === 'undefined') {
|
|
33
|
+
html = `
|
|
34
34
|
<div class="moj-button-menu" data-module="moj-button-menu" ${dataAttributes}>
|
|
35
35
|
<a href="#one" role="button">First action</a>
|
|
36
36
|
<a href="#two" role="button" class="govuk-button--warning">Second action</a>
|
|
37
37
|
<a href="#three" role="button" class="custom-class">Third action</a>
|
|
38
|
-
</div
|
|
38
|
+
</div>`
|
|
39
39
|
}
|
|
40
|
-
document.body.insertAdjacentHTML(
|
|
41
|
-
|
|
42
|
-
component
|
|
43
|
-
|
|
44
|
-
};
|
|
40
|
+
document.body.insertAdjacentHTML('afterbegin', html)
|
|
41
|
+
const component = document.querySelector('[data-module="moj-button-menu"]')
|
|
42
|
+
return component
|
|
43
|
+
}
|
|
45
44
|
|
|
46
|
-
describe(
|
|
47
|
-
let component
|
|
48
|
-
let toggleButton
|
|
49
|
-
let menu
|
|
50
|
-
let items
|
|
45
|
+
describe('Button menu with defaults', () => {
|
|
46
|
+
let component
|
|
47
|
+
let toggleButton
|
|
48
|
+
let menu
|
|
49
|
+
let items
|
|
51
50
|
|
|
52
51
|
beforeEach(() => {
|
|
53
|
-
component = createComponent()
|
|
54
|
-
new MOJFrontend.ButtonMenu(component).init()
|
|
52
|
+
component = createComponent()
|
|
53
|
+
new MOJFrontend.ButtonMenu(component).init()
|
|
55
54
|
|
|
56
|
-
toggleButton = queryByRole(component,
|
|
57
|
-
menu = screen.queryByRole(
|
|
58
|
-
items = menu?.querySelectorAll(
|
|
59
|
-
})
|
|
55
|
+
toggleButton = queryByRole(component, 'button', { hidden: false })
|
|
56
|
+
menu = screen.queryByRole('list', { hidden: true })
|
|
57
|
+
items = menu?.querySelectorAll('a, button')
|
|
58
|
+
})
|
|
60
59
|
|
|
61
60
|
afterEach(() => {
|
|
62
|
-
document.body.innerHTML =
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
test(
|
|
66
|
-
expect(toggleButton).not.toBeNull()
|
|
67
|
-
expect(menu).not.toBeNull()
|
|
68
|
-
expect(items).not.toBeNull()
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
test(
|
|
72
|
-
expect(component).toContainElement(toggleButton)
|
|
73
|
-
expect(toggleButton).toHaveAttribute(
|
|
74
|
-
expect(toggleButton).toHaveAttribute(
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
test(
|
|
78
|
-
expect(component).toContainElement(menu)
|
|
79
|
-
expect(menu).not.toBeVisible()
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
test(
|
|
83
|
-
expect(items
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
test(
|
|
87
|
-
expect(items[1]).not.toHaveClass(
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
test(
|
|
91
|
-
expect(items[2]).toHaveClass(
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
test(
|
|
95
|
-
await user.click(toggleButton)
|
|
96
|
-
|
|
97
|
-
expect(menu).toBeVisible()
|
|
98
|
-
expect(toggleButton).toHaveAttribute(
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
test(
|
|
102
|
-
await user.click(toggleButton)
|
|
103
|
-
|
|
104
|
-
expect(menu).toBeVisible()
|
|
105
|
-
await user.click(items[0])
|
|
106
|
-
expect(global.window.location.hash).toContain(
|
|
107
|
-
await user.click(items[2])
|
|
108
|
-
expect(global.window.location.hash).toContain(
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
test(
|
|
112
|
-
await user.click(toggleButton)
|
|
113
|
-
expect(menu).toBeVisible()
|
|
114
|
-
|
|
115
|
-
await user.click(document.body)
|
|
116
|
-
expect(menu).not.toBeVisible()
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
describe(
|
|
120
|
-
test(
|
|
121
|
-
toggleButton.focus()
|
|
122
|
-
await user.keyboard(
|
|
123
|
-
|
|
124
|
-
expect(menu).toBeVisible()
|
|
125
|
-
expect(toggleButton).toHaveAttribute(
|
|
126
|
-
expect(items[0]).toHaveFocus()
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
test(
|
|
130
|
-
toggleButton.focus()
|
|
131
|
-
await user.keyboard(
|
|
132
|
-
|
|
133
|
-
expect(menu).toBeVisible()
|
|
134
|
-
expect(toggleButton).toHaveAttribute(
|
|
135
|
-
expect(items[0]).toHaveFocus()
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
test(
|
|
139
|
-
toggleButton.focus()
|
|
140
|
-
await user.keyboard(
|
|
141
|
-
expect(menu).toBeVisible()
|
|
142
|
-
await user.keyboard(
|
|
143
|
-
|
|
144
|
-
expect(menu).not.toBeVisible()
|
|
145
|
-
expect(toggleButton).toHaveFocus()
|
|
146
|
-
})
|
|
147
|
-
|
|
148
|
-
test(
|
|
149
|
-
toggleButton.focus()
|
|
150
|
-
await user.keyboard(
|
|
151
|
-
|
|
152
|
-
expect(menu).toBeVisible()
|
|
153
|
-
expect(toggleButton).toHaveAttribute(
|
|
154
|
-
expect(items[0]).toHaveFocus()
|
|
155
|
-
})
|
|
156
|
-
|
|
157
|
-
test(
|
|
158
|
-
toggleButton.focus()
|
|
159
|
-
await user.keyboard(
|
|
160
|
-
|
|
161
|
-
expect(menu).toBeVisible()
|
|
162
|
-
expect(toggleButton).toHaveAttribute(
|
|
163
|
-
expect(items[items.length - 1]).toHaveFocus()
|
|
164
|
-
})
|
|
165
|
-
|
|
166
|
-
test(
|
|
167
|
-
toggleButton.focus()
|
|
168
|
-
await user.keyboard(
|
|
169
|
-
expect(items[0]).toHaveFocus()
|
|
170
|
-
|
|
171
|
-
await user.keyboard(
|
|
172
|
-
expect(items[1]).toHaveFocus()
|
|
173
|
-
|
|
174
|
-
await user.keyboard(
|
|
175
|
-
expect(items[2]).toHaveFocus()
|
|
176
|
-
|
|
177
|
-
await user.keyboard(
|
|
178
|
-
expect(items[0]).toHaveFocus()
|
|
179
|
-
})
|
|
180
|
-
|
|
181
|
-
test(
|
|
182
|
-
toggleButton.focus()
|
|
183
|
-
await user.keyboard(
|
|
184
|
-
expect(items[items.length - 1]).toHaveFocus()
|
|
185
|
-
|
|
186
|
-
await user.keyboard(
|
|
187
|
-
expect(items[1]).toHaveFocus()
|
|
188
|
-
|
|
189
|
-
await user.keyboard(
|
|
190
|
-
expect(items[0]).toHaveFocus()
|
|
191
|
-
|
|
192
|
-
await user.keyboard(
|
|
193
|
-
expect(items[items.length - 1]).toHaveFocus()
|
|
194
|
-
})
|
|
195
|
-
|
|
196
|
-
test(
|
|
197
|
-
toggleButton.focus()
|
|
198
|
-
await user.keyboard(
|
|
199
|
-
expect(items[items.length - 1]).toHaveFocus()
|
|
200
|
-
|
|
201
|
-
await user.keyboard(
|
|
202
|
-
expect(items[0]).toHaveFocus()
|
|
203
|
-
})
|
|
204
|
-
|
|
205
|
-
test(
|
|
206
|
-
toggleButton.focus()
|
|
207
|
-
await user.keyboard(
|
|
208
|
-
expect(items[0]).toHaveFocus()
|
|
209
|
-
|
|
210
|
-
await user.keyboard(
|
|
211
|
-
expect(items[items.length - 1]).toHaveFocus()
|
|
212
|
-
})
|
|
213
|
-
|
|
214
|
-
test(
|
|
215
|
-
toggleButton.focus()
|
|
216
|
-
await user.keyboard(
|
|
217
|
-
expect(menu).toBeVisible()
|
|
218
|
-
expect(items[0]).toHaveFocus()
|
|
219
|
-
await user.tab()
|
|
220
|
-
|
|
221
|
-
expect(document.body).toHaveFocus()
|
|
222
|
-
expect(menu).not.toBeVisible()
|
|
223
|
-
})
|
|
224
|
-
})
|
|
225
|
-
|
|
226
|
-
describe(
|
|
227
|
-
test(
|
|
228
|
-
expect(await axe(document.body)).toHaveNoViolations()
|
|
229
|
-
await user.click(toggleButton)
|
|
230
|
-
expect(await axe(document.body)).toHaveNoViolations()
|
|
231
|
-
})
|
|
232
|
-
})
|
|
233
|
-
})
|
|
234
|
-
|
|
235
|
-
describe(
|
|
236
|
-
let component
|
|
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
|
|
237
236
|
|
|
238
237
|
beforeEach(() => {
|
|
239
|
-
component = createComponent()
|
|
240
|
-
})
|
|
238
|
+
component = createComponent()
|
|
239
|
+
})
|
|
241
240
|
|
|
242
241
|
afterEach(() => {
|
|
243
|
-
document.body.innerHTML =
|
|
244
|
-
})
|
|
242
|
+
document.body.innerHTML = ''
|
|
243
|
+
})
|
|
245
244
|
|
|
246
|
-
test(
|
|
247
|
-
const label =
|
|
248
|
-
new MOJFrontend.ButtonMenu(component, { buttonText: label }).init()
|
|
249
|
-
const toggleButton = queryByRole(component,
|
|
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 })
|
|
250
249
|
|
|
251
|
-
expect(toggleButton).
|
|
252
|
-
})
|
|
250
|
+
expect(toggleButton).toBeInTheDocument()
|
|
251
|
+
})
|
|
253
252
|
|
|
254
|
-
test(
|
|
255
|
-
new MOJFrontend.ButtonMenu(component, { alignMenu:
|
|
256
|
-
const menu = screen.queryByRole(
|
|
253
|
+
test('setting menu alignment', () => {
|
|
254
|
+
new MOJFrontend.ButtonMenu(component, { alignMenu: 'right' }).init()
|
|
255
|
+
const menu = screen.queryByRole('list', { hidden: true })
|
|
257
256
|
|
|
258
|
-
expect(menu).toHaveClass(
|
|
259
|
-
})
|
|
257
|
+
expect(menu).toHaveClass('moj-button-menu__wrapper--right')
|
|
258
|
+
})
|
|
260
259
|
|
|
261
|
-
test(
|
|
262
|
-
const defaultClassNames =
|
|
263
|
-
const classNames =
|
|
260
|
+
test('setting button classes', () => {
|
|
261
|
+
const defaultClassNames = 'govuk-button moj-button-menu__toggle-button'
|
|
262
|
+
const classNames = 'classOne classTwo'
|
|
264
263
|
|
|
265
|
-
new MOJFrontend.ButtonMenu(component, { buttonClasses: classNames }).init()
|
|
266
|
-
const toggleButton = queryByRole(component,
|
|
264
|
+
new MOJFrontend.ButtonMenu(component, { buttonClasses: classNames }).init()
|
|
265
|
+
const toggleButton = queryByRole(component, 'button', { hidden: false })
|
|
267
266
|
|
|
268
|
-
expect(toggleButton).toHaveClass(defaultClassNames)
|
|
269
|
-
expect(toggleButton).toHaveClass(classNames)
|
|
270
|
-
})
|
|
271
|
-
})
|
|
267
|
+
expect(toggleButton).toHaveClass(defaultClassNames)
|
|
268
|
+
expect(toggleButton).toHaveClass(classNames)
|
|
269
|
+
})
|
|
270
|
+
})
|
|
272
271
|
|
|
273
|
-
describe(
|
|
274
|
-
let component
|
|
272
|
+
describe('Button menu data-attributes API', () => {
|
|
273
|
+
let component
|
|
275
274
|
|
|
276
|
-
beforeEach(() => {})
|
|
275
|
+
beforeEach(() => {})
|
|
277
276
|
|
|
278
277
|
afterEach(() => {
|
|
279
|
-
document.body.innerHTML =
|
|
280
|
-
})
|
|
278
|
+
document.body.innerHTML = ''
|
|
279
|
+
})
|
|
281
280
|
|
|
282
|
-
test(
|
|
283
|
-
const label =
|
|
281
|
+
test('setting toggle button text', () => {
|
|
282
|
+
const label = 'click me'
|
|
284
283
|
|
|
285
|
-
component = createComponent({ buttonText: label })
|
|
286
|
-
new MOJFrontend.ButtonMenu(component).init()
|
|
287
|
-
const toggleButton = queryByRole(component,
|
|
284
|
+
component = createComponent({ buttonText: label })
|
|
285
|
+
new MOJFrontend.ButtonMenu(component).init()
|
|
286
|
+
const toggleButton = queryByRole(component, 'button', { name: label })
|
|
288
287
|
|
|
289
|
-
expect(toggleButton).
|
|
290
|
-
})
|
|
288
|
+
expect(toggleButton).toBeInTheDocument()
|
|
289
|
+
})
|
|
291
290
|
|
|
292
|
-
test(
|
|
293
|
-
component = createComponent({ alignMenu:
|
|
294
|
-
new MOJFrontend.ButtonMenu(component).init()
|
|
295
|
-
const menu = screen.queryByRole(
|
|
291
|
+
test('setting menu alignment', () => {
|
|
292
|
+
component = createComponent({ alignMenu: 'right' })
|
|
293
|
+
new MOJFrontend.ButtonMenu(component).init()
|
|
294
|
+
const menu = screen.queryByRole('list', { hidden: true })
|
|
296
295
|
|
|
297
|
-
expect(menu).toHaveClass(
|
|
298
|
-
})
|
|
296
|
+
expect(menu).toHaveClass('moj-button-menu__wrapper--right')
|
|
297
|
+
})
|
|
299
298
|
|
|
300
|
-
test(
|
|
301
|
-
const defaultClassNames =
|
|
302
|
-
const classNames =
|
|
299
|
+
test('setting button classes', () => {
|
|
300
|
+
const defaultClassNames = 'govuk-button moj-button-menu__toggle-button'
|
|
301
|
+
const classNames = 'classOne classTwo'
|
|
303
302
|
|
|
304
|
-
component = createComponent({ buttonClasses: classNames })
|
|
305
|
-
new MOJFrontend.ButtonMenu(component).init()
|
|
306
|
-
const toggleButton = queryByRole(component,
|
|
303
|
+
component = createComponent({ buttonClasses: classNames })
|
|
304
|
+
new MOJFrontend.ButtonMenu(component).init()
|
|
305
|
+
const toggleButton = queryByRole(component, 'button', { hidden: false })
|
|
307
306
|
|
|
308
|
-
expect(toggleButton).toHaveClass(defaultClassNames)
|
|
309
|
-
expect(toggleButton).toHaveClass(classNames)
|
|
310
|
-
})
|
|
311
|
-
})
|
|
307
|
+
expect(toggleButton).toHaveClass(defaultClassNames)
|
|
308
|
+
expect(toggleButton).toHaveClass(classNames)
|
|
309
|
+
})
|
|
310
|
+
})
|
|
312
311
|
|
|
313
|
-
describe(
|
|
314
|
-
let component
|
|
315
|
-
let toggleButton
|
|
316
|
-
let menu
|
|
317
|
-
let items
|
|
312
|
+
describe('menu button with a single item', () => {
|
|
313
|
+
let component
|
|
314
|
+
let toggleButton
|
|
315
|
+
let menu
|
|
316
|
+
let items
|
|
318
317
|
|
|
319
318
|
beforeEach(() => {
|
|
320
319
|
const html = `
|
|
321
320
|
<div class="moj-button-menu" data-module="moj-button-menu" data-button-classes="govuk-button--warning custom-class">
|
|
322
321
|
<a href="#one" role="button" class="govuk-button--inverse">First action</a>
|
|
323
|
-
</div
|
|
322
|
+
</div>`
|
|
324
323
|
|
|
325
|
-
component = createComponent({}, html)
|
|
326
|
-
new MOJFrontend.ButtonMenu(component).init()
|
|
324
|
+
component = createComponent({}, html)
|
|
325
|
+
new MOJFrontend.ButtonMenu(component).init()
|
|
327
326
|
|
|
328
|
-
toggleButton = queryByRole(component,
|
|
329
|
-
menu = screen.queryByRole(
|
|
330
|
-
items = menu?.queryByRole(
|
|
331
|
-
})
|
|
327
|
+
toggleButton = queryByRole(component, 'button', { name: 'Actions' })
|
|
328
|
+
menu = screen.queryByRole('list', { hidden: true })
|
|
329
|
+
items = menu?.queryByRole('button', { hidden: true })
|
|
330
|
+
})
|
|
332
331
|
|
|
333
332
|
afterEach(() => {
|
|
334
|
-
document.body.innerHTML =
|
|
335
|
-
})
|
|
333
|
+
document.body.innerHTML = ''
|
|
334
|
+
})
|
|
336
335
|
|
|
337
|
-
test(
|
|
338
|
-
expect(menu).toBeNull()
|
|
339
|
-
})
|
|
336
|
+
test('menu is not created', () => {
|
|
337
|
+
expect(menu).toBeNull()
|
|
338
|
+
})
|
|
340
339
|
|
|
341
|
-
test(
|
|
342
|
-
expect(items).toBeUndefined()
|
|
343
|
-
})
|
|
340
|
+
test('there are no items', () => {
|
|
341
|
+
expect(items).toBeUndefined()
|
|
342
|
+
})
|
|
344
343
|
|
|
345
|
-
test(
|
|
346
|
-
expect(toggleButton).toBeNull()
|
|
347
|
-
})
|
|
344
|
+
test('there is no toggle button', () => {
|
|
345
|
+
expect(toggleButton).toBeNull()
|
|
346
|
+
})
|
|
348
347
|
|
|
349
|
-
test(
|
|
350
|
-
const button = screen.queryByRole(
|
|
348
|
+
test('first item has become button', () => {
|
|
349
|
+
const button = screen.queryByRole('button', { name: 'First action' })
|
|
351
350
|
|
|
352
|
-
expect(button).
|
|
353
|
-
})
|
|
351
|
+
expect(button).toBeInTheDocument()
|
|
352
|
+
})
|
|
354
353
|
|
|
355
|
-
test(
|
|
356
|
-
const button = screen.queryByRole(
|
|
354
|
+
test('first item has buttonClasses config applied', () => {
|
|
355
|
+
const button = screen.queryByRole('button', { name: 'First action' })
|
|
357
356
|
|
|
358
|
-
expect(button).toHaveClass(
|
|
359
|
-
expect(button).not.toHaveClass(
|
|
360
|
-
})
|
|
361
|
-
})
|
|
357
|
+
expect(button).toHaveClass('govuk-button--warning', 'custom-class')
|
|
358
|
+
expect(button).not.toHaveClass('govuk-button--inverse')
|
|
359
|
+
})
|
|
360
|
+
})
|