@ons/design-system 73.3.0 → 73.4.1
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/components/button/_button.scss +3 -6
- package/components/chart/annotations-options.js +15 -3
- package/components/chart/chart.js +27 -2
- package/components/chart/common-chart-options.js +10 -0
- package/components/chart/range-annotations-options.js +19 -3
- package/components/chart/reference-line-annotations-options.js +19 -0
- package/components/description-list/_description-list.scss +26 -0
- package/components/header/_header.scss +44 -1
- package/components/header/_macro.njk +27 -21
- package/components/header/_macro.spec.js +116 -14
- package/components/header/example-header-basic-with-search-no-heading.njk +22 -0
- package/components/header/header.spec.js +18 -1
- package/components/hero/example-hero-grey-with-last-updated.njk +27 -0
- package/components/navigation/navigation.js +20 -2
- package/components/navigation/navigation.spec.js +22 -0
- package/css/main.css +1 -1
- package/layout/_template.njk +1 -1
- package/package.json +1 -1
- package/scripts/main.es5.js +1 -1
- package/scripts/main.js +2 -2
- package/scss/utilities/_highlight.scss +6 -1
- package/scss/utilities/_typography.scss +4 -0
|
@@ -35,6 +35,100 @@ describe('FOR: Macro: Header', () => {
|
|
|
35
35
|
});
|
|
36
36
|
});
|
|
37
37
|
});
|
|
38
|
+
|
|
39
|
+
describe('Accessibility: search heading', () => {
|
|
40
|
+
test('THEN: axe passes when search heading is present (params.search.links.heading)', async () => {
|
|
41
|
+
const params = {
|
|
42
|
+
...EXAMPLE_HEADER_BASIC,
|
|
43
|
+
variants: 'basic',
|
|
44
|
+
search: {
|
|
45
|
+
links: {
|
|
46
|
+
heading: 'Popular searches',
|
|
47
|
+
itemsList: [{ text: 'Census', url: '/census' }],
|
|
48
|
+
},
|
|
49
|
+
form: { action: '/search' },
|
|
50
|
+
},
|
|
51
|
+
searchLinks: undefined,
|
|
52
|
+
};
|
|
53
|
+
const $ = cheerio.load(renderComponent('header', params));
|
|
54
|
+
const results = await axe($.html());
|
|
55
|
+
expect(results).toHaveNoViolations();
|
|
56
|
+
expect($('.ons-header-nav-search__heading').text()).toBe('Popular searches');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('THEN: axe passes when search heading is present (params.searchLinks.heading)', async () => {
|
|
60
|
+
const params = {
|
|
61
|
+
...EXAMPLE_HEADER_BASIC,
|
|
62
|
+
variants: 'basic',
|
|
63
|
+
search: undefined,
|
|
64
|
+
searchLinks: {
|
|
65
|
+
heading: 'Other searches',
|
|
66
|
+
itemsList: [{ text: 'Population', url: '/population' }],
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
const $ = cheerio.load(renderComponent('header', params));
|
|
70
|
+
const results = await axe($.html());
|
|
71
|
+
expect(results).toHaveNoViolations();
|
|
72
|
+
expect($('.ons-header-nav-search__heading').text()).toBe('Other searches');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('THEN: axe passes and no h2 is rendered when no heading present', async () => {
|
|
76
|
+
const params = {
|
|
77
|
+
...EXAMPLE_HEADER_BASIC,
|
|
78
|
+
variants: 'basic',
|
|
79
|
+
search: { links: { itemsList: [] }, form: { action: '/search' } },
|
|
80
|
+
searchLinks: undefined,
|
|
81
|
+
};
|
|
82
|
+
const $ = cheerio.load(renderComponent('header', params));
|
|
83
|
+
const results = await axe($.html());
|
|
84
|
+
expect(results).toHaveNoViolations();
|
|
85
|
+
expect($('.ons-header-nav-search__heading').length).toBe(0);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe('Snapshot: search heading', () => {
|
|
90
|
+
test('matches snapshot when search heading is present (params.search.links.heading)', () => {
|
|
91
|
+
const params = {
|
|
92
|
+
...EXAMPLE_HEADER_BASIC,
|
|
93
|
+
variants: 'basic',
|
|
94
|
+
search: {
|
|
95
|
+
links: {
|
|
96
|
+
heading: 'Popular searches',
|
|
97
|
+
itemsList: [{ text: 'Census', url: '/census' }],
|
|
98
|
+
},
|
|
99
|
+
form: { action: '/search' },
|
|
100
|
+
},
|
|
101
|
+
searchLinks: undefined,
|
|
102
|
+
};
|
|
103
|
+
const $ = cheerio.load(renderComponent('header', params));
|
|
104
|
+
expect($.html()).toMatchSnapshot();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('matches snapshot when search heading is present (params.searchLinks.heading)', () => {
|
|
108
|
+
const params = {
|
|
109
|
+
...EXAMPLE_HEADER_BASIC,
|
|
110
|
+
variants: 'basic',
|
|
111
|
+
search: undefined,
|
|
112
|
+
searchLinks: {
|
|
113
|
+
heading: 'Other searches',
|
|
114
|
+
itemsList: [{ text: 'Population', url: '/population' }],
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
const $ = cheerio.load(renderComponent('header', params));
|
|
118
|
+
expect($.html()).toMatchSnapshot();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('matches snapshot when no search heading is present', () => {
|
|
122
|
+
const params = {
|
|
123
|
+
...EXAMPLE_HEADER_BASIC,
|
|
124
|
+
variants: 'basic',
|
|
125
|
+
search: { links: { itemsList: [] }, form: { action: '/search' } },
|
|
126
|
+
searchLinks: undefined,
|
|
127
|
+
};
|
|
128
|
+
const $ = cheerio.load(renderComponent('header', params));
|
|
129
|
+
expect($.html()).toMatchSnapshot();
|
|
130
|
+
});
|
|
131
|
+
});
|
|
38
132
|
describe('GIVEN: Params: variants', () => {
|
|
39
133
|
describe('WHEN: variants are provided', () => {
|
|
40
134
|
const $ = cheerio.load(
|
|
@@ -823,12 +917,12 @@ describe('FOR: Macro: Header', () => {
|
|
|
823
917
|
test('THEN: renders search icon button', () => {
|
|
824
918
|
expect(buttonSpy.occurrences[0]).toEqual({
|
|
825
919
|
iconType: 'search',
|
|
826
|
-
classes: 'ons-u-fs-s--b ons-js-toggle-header-search ons-
|
|
920
|
+
classes: 'ons-u-fs-s--b ons-js-toggle-header-search ons-u-db-no-js_disabled ons-btn--search-icon disabled',
|
|
827
921
|
type: 'button',
|
|
828
922
|
variants: 'search',
|
|
829
923
|
attributes: {
|
|
830
924
|
'aria-controls': 'search-id',
|
|
831
|
-
'aria-expanded': '
|
|
925
|
+
'aria-expanded': 'false',
|
|
832
926
|
'aria-label': 'Custom search button aria label',
|
|
833
927
|
'aria-disabled': 'true',
|
|
834
928
|
},
|
|
@@ -911,12 +1005,12 @@ describe('FOR: Macro: Header', () => {
|
|
|
911
1005
|
});
|
|
912
1006
|
});
|
|
913
1007
|
|
|
914
|
-
describe('WHEN: using basic header variant and search is
|
|
1008
|
+
describe('WHEN: using basic header variant and search is disabled by default before JS loads', () => {
|
|
915
1009
|
const $ = cheerio.load(renderComponent('header', { ...EXAMPLE_HEADER_SEARCH, variants: 'basic' }));
|
|
916
1010
|
const $searchBtn = $('.ons-js-toggle-header-search');
|
|
917
1011
|
|
|
918
|
-
test('THEN:
|
|
919
|
-
expect($searchBtn.hasClass('active')).toBe(
|
|
1012
|
+
test('THEN: does not add the "active" class to the search toggle button', () => {
|
|
1013
|
+
expect($searchBtn.hasClass('active')).toBe(false);
|
|
920
1014
|
});
|
|
921
1015
|
|
|
922
1016
|
test('THEN: adds the "disabled" class to the search toggle button', () => {
|
|
@@ -926,6 +1020,10 @@ describe('FOR: Macro: Header', () => {
|
|
|
926
1020
|
test('THEN: sets aria-disabled="true" on the search toggle button', () => {
|
|
927
1021
|
expect($searchBtn.attr('aria-disabled')).toBe('true');
|
|
928
1022
|
});
|
|
1023
|
+
|
|
1024
|
+
test('THEN: sets aria-expanded="false" on the search toggle button', () => {
|
|
1025
|
+
expect($searchBtn.attr('aria-expanded')).toBe('false');
|
|
1026
|
+
});
|
|
929
1027
|
});
|
|
930
1028
|
|
|
931
1029
|
describe('WHEN: search is provided with all custom properties', () => {
|
|
@@ -1044,12 +1142,12 @@ describe('FOR: Macro: Header', () => {
|
|
|
1044
1142
|
test('THEN: renders search icon button', () => {
|
|
1045
1143
|
expect(buttonSpy.occurrences[0]).toEqual({
|
|
1046
1144
|
iconType: 'search',
|
|
1047
|
-
classes: 'ons-u-fs-s--b ons-js-toggle-header-search ons-
|
|
1145
|
+
classes: 'ons-u-fs-s--b ons-js-toggle-header-search ons-u-db-no-js_disabled ons-btn--search-icon disabled',
|
|
1048
1146
|
type: 'button',
|
|
1049
1147
|
variants: 'search',
|
|
1050
1148
|
attributes: {
|
|
1051
1149
|
'aria-controls': 'search-links-id',
|
|
1052
|
-
'aria-expanded': '
|
|
1150
|
+
'aria-expanded': 'false',
|
|
1053
1151
|
'aria-label': 'Custom search button aria label',
|
|
1054
1152
|
'aria-disabled': 'true',
|
|
1055
1153
|
},
|
|
@@ -1132,12 +1230,12 @@ describe('FOR: Macro: Header', () => {
|
|
|
1132
1230
|
});
|
|
1133
1231
|
});
|
|
1134
1232
|
|
|
1135
|
-
describe('WHEN: using basic header variant and search is
|
|
1233
|
+
describe('WHEN: using basic header variant and search is disabled by default before JS loads', () => {
|
|
1136
1234
|
const $ = cheerio.load(renderComponent('header', { ...EXAMPLE_HEADER_SEARCH_LINKS, variants: 'basic' }));
|
|
1137
1235
|
const $searchBtn = $('.ons-js-toggle-header-search');
|
|
1138
1236
|
|
|
1139
|
-
test('THEN:
|
|
1140
|
-
expect($searchBtn.hasClass('active')).toBe(
|
|
1237
|
+
test('THEN: does not add the "active" class to the search toggle button', () => {
|
|
1238
|
+
expect($searchBtn.hasClass('active')).toBe(false);
|
|
1141
1239
|
});
|
|
1142
1240
|
|
|
1143
1241
|
test('THEN: adds the "disabled" class to the search toggle button', () => {
|
|
@@ -1147,6 +1245,10 @@ describe('FOR: Macro: Header', () => {
|
|
|
1147
1245
|
test('THEN: sets aria-disabled="true" on the search toggle button', () => {
|
|
1148
1246
|
expect($searchBtn.attr('aria-disabled')).toBe('true');
|
|
1149
1247
|
});
|
|
1248
|
+
|
|
1249
|
+
test('THEN: sets aria-expanded="false" on the search toggle button', () => {
|
|
1250
|
+
expect($searchBtn.attr('aria-expanded')).toBe('false');
|
|
1251
|
+
});
|
|
1150
1252
|
});
|
|
1151
1253
|
|
|
1152
1254
|
describe('WHEN: searchLinks are provided with all custom properties', () => {
|
|
@@ -1259,8 +1361,8 @@ describe('FOR: Macro: Header', () => {
|
|
|
1259
1361
|
const $ = cheerio.load(renderComponent('header', EXAMPLE_HEADER_MENU_LINKS));
|
|
1260
1362
|
const $menuBtn = $('.ons-js-toggle-nav-menu');
|
|
1261
1363
|
|
|
1262
|
-
test('THEN:
|
|
1263
|
-
expect($menuBtn.hasClass('active')).toBe(
|
|
1364
|
+
test('THEN: does not add the "active" class to the menu toggle button', () => {
|
|
1365
|
+
expect($menuBtn.hasClass('active')).toBe(false);
|
|
1264
1366
|
});
|
|
1265
1367
|
|
|
1266
1368
|
test('THEN: adds the "disabled" class to the menu toggle button', () => {
|
|
@@ -1271,8 +1373,8 @@ describe('FOR: Macro: Header', () => {
|
|
|
1271
1373
|
expect($menuBtn.attr('aria-disabled')).toBe('true');
|
|
1272
1374
|
});
|
|
1273
1375
|
|
|
1274
|
-
test('THEN: sets aria-expanded="
|
|
1275
|
-
expect($menuBtn.attr('aria-expanded')).toBe('
|
|
1376
|
+
test('THEN: sets aria-expanded="false" on the menu toggle button', () => {
|
|
1377
|
+
expect($menuBtn.attr('aria-expanded')).toBe('false');
|
|
1276
1378
|
});
|
|
1277
1379
|
|
|
1278
1380
|
test('THEN: sets aria-controls to the correct menu ID', () => {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
'fullWidth': true
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
{% from "components/header/_macro.njk" import onsHeader %}
|
|
6
|
+
|
|
7
|
+
{{
|
|
8
|
+
onsHeader({
|
|
9
|
+
"variants": 'basic',
|
|
10
|
+
"search": {
|
|
11
|
+
"id": "search",
|
|
12
|
+
"navAriaLabel": 'Nav Search',
|
|
13
|
+
"toggleAriaLabel": 'Toggle search',
|
|
14
|
+
"form": {
|
|
15
|
+
"action": "https://www.ons.gov.uk/search" ,
|
|
16
|
+
"inputName": "q",
|
|
17
|
+
"inputLabel": "Search the ONS",
|
|
18
|
+
"buttonText": "Search"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
}}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { renderComponent, setTestPage } from '../../tests/helpers/rendering';
|
|
2
2
|
import { EXAMPLE_HEADER_SEARCH_AND_MENU_LINKS } from './_test-examples';
|
|
3
|
-
import { getNodeAttributes } from '../../tests/helpers/puppeteer';
|
|
3
|
+
import { getNodeAttributes, setViewport } from '../../tests/helpers/puppeteer';
|
|
4
4
|
|
|
5
5
|
describe('script: header', () => {
|
|
6
6
|
beforeEach(async () => {
|
|
@@ -107,6 +107,23 @@ describe('script: header', () => {
|
|
|
107
107
|
const isAriaHiddenFalse = await page.$eval('.ons-header-nav-menu', (el) => el.getAttribute('aria-hidden') === 'false');
|
|
108
108
|
expect(isAriaHiddenFalse).toBe(true);
|
|
109
109
|
});
|
|
110
|
+
|
|
111
|
+
describe('and only the viewport height changes', () => {
|
|
112
|
+
beforeEach(async () => {
|
|
113
|
+
const viewport = page.viewport();
|
|
114
|
+
await setViewport(page, { width: viewport.width, height: viewport.height - 150 });
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('then the navigation menu stays displayed', async () => {
|
|
118
|
+
const isMenuNavVisible = await page.$eval('.ons-header-nav-menu', (el) => !el.classList.contains('ons-u-d-no'));
|
|
119
|
+
expect(isMenuNavVisible).toBe(true);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('then the menu button stays marked as expanded', async () => {
|
|
123
|
+
const menuButtonAriaExpanded = await page.$eval('.ons-btn--menu', (el) => el.getAttribute('aria-expanded'));
|
|
124
|
+
expect(menuButtonAriaExpanded).toBe('true');
|
|
125
|
+
});
|
|
126
|
+
});
|
|
110
127
|
});
|
|
111
128
|
|
|
112
129
|
describe('when the menu button is not clicked', () => {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
'fullWidth': true
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
{% from "components/hero/_macro.njk" import onsHero %}
|
|
6
|
+
{{
|
|
7
|
+
onsHero({
|
|
8
|
+
"variants": 'grey',
|
|
9
|
+
"detailsColumns": '12',
|
|
10
|
+
"title": 'Lorem Ipsum',
|
|
11
|
+
"text": 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.',
|
|
12
|
+
"descriptionList": {
|
|
13
|
+
"termCol": "5",
|
|
14
|
+
"descriptionCol": "7",
|
|
15
|
+
"itemsList": [
|
|
16
|
+
{
|
|
17
|
+
"term": "Last updated:",
|
|
18
|
+
"descriptions": [
|
|
19
|
+
{
|
|
20
|
+
"description": "23 February 2024"
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
}}
|
|
@@ -90,15 +90,33 @@ export default class NavigationToggle {
|
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
isHidden(el) {
|
|
93
|
-
|
|
93
|
+
if (!el) {
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const style = window.getComputedStyle(el);
|
|
98
|
+
if (style.display === 'none') {
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// `offsetParent` can be `null` for reasons other than `display: none` (e.g. fixed positioning),
|
|
103
|
+
// so use layout rects as a more reliable proxy for visibility.
|
|
104
|
+
return el.getClientRects().length === 0;
|
|
94
105
|
}
|
|
95
106
|
|
|
96
|
-
setAria() {
|
|
107
|
+
setAria(viewportDetails = null) {
|
|
97
108
|
const isToggleHidden = this.isHidden(this.toggle);
|
|
98
109
|
this.toggle.setAttribute(attrDisabled, 'false');
|
|
99
110
|
|
|
100
111
|
this.searchToggleBtn?.setAttribute(attrDisabled, 'false');
|
|
101
112
|
|
|
113
|
+
// iOS Safari/Chrome can change viewport height during scroll when the browser chrome
|
|
114
|
+
// hides/shows, which can trigger resize events. If the user has the navigation open
|
|
115
|
+
// on a small screen, don't force-close it in response to these resizes.
|
|
116
|
+
if (viewportDetails && !isToggleHidden && this.navigation.getAttribute(attrHidden) === 'false') {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
102
120
|
if (!isToggleHidden) {
|
|
103
121
|
// close nav by default if toggle button is visible
|
|
104
122
|
this.closeNav();
|
|
@@ -259,6 +259,28 @@ describe('script: navigation', () => {
|
|
|
259
259
|
expect(hasClass).toBe(false);
|
|
260
260
|
});
|
|
261
261
|
});
|
|
262
|
+
|
|
263
|
+
describe('when the navigation is open and only the viewport height changes', () => {
|
|
264
|
+
beforeEach(async () => {
|
|
265
|
+
await page.focus(buttonEl);
|
|
266
|
+
await page.keyboard.press('Enter');
|
|
267
|
+
// Simulate iOS address bar show/hide which changes height during scroll
|
|
268
|
+
// without any meaningful layout/breakpoint change.
|
|
269
|
+
await setViewport(page, { width: 600, height: 900 });
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it('keeps aria-hidden set as `false` on the navigation list', async () => {
|
|
273
|
+
const nav = await page.$(navEl);
|
|
274
|
+
const hasAriaAttribute = await nav.evaluate((node) => node.getAttribute('aria-hidden') === 'false');
|
|
275
|
+
expect(hasAriaAttribute).toBe(true);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it('keeps aria-expanded set as `true` on the navigation toggle button', async () => {
|
|
279
|
+
const button = await page.$(buttonEl);
|
|
280
|
+
const ariaExpandedIsTrue = await button.evaluate((node) => node.getAttribute('aria-expanded') === 'true');
|
|
281
|
+
expect(ariaExpandedIsTrue).toBe(true);
|
|
282
|
+
});
|
|
283
|
+
});
|
|
262
284
|
});
|
|
263
285
|
});
|
|
264
286
|
|