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