@ons/design-system 73.10.0-beta.2 → 73.11.2

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.
@@ -411,6 +411,29 @@ describe('script: address-input', () => {
411
411
  });
412
412
  });
413
413
  });
414
+
415
+ describe('when a grouped suggestion is selected with the mouse', () => {
416
+ beforeEach(async () => {
417
+ const options = await page.$$('.ons-autosuggest__option');
418
+ await options[0].click();
419
+ await setTimeout(400);
420
+ });
421
+
422
+ it('makes expected bucket request', async () => {
423
+ expect(
424
+ await apiFaker.getRequestCount(
425
+ '/addresses/eq/bucket?postcode=CF14%202AA&streetname=Penlline%20Road&townname=Whitchurch&groupfullpostcodes=combo',
426
+ ),
427
+ ).toBe(1);
428
+ });
429
+
430
+ it('keeps nested suggestion entries after blur timeout', async () => {
431
+ const suggestions = await page.$$eval('.ons-autosuggest__option', (nodes) =>
432
+ nodes.map((node) => node.textContent.trim()),
433
+ );
434
+ expect(suggestions).toEqual(['197 College Road, Whitchurch, Cardiff, CF14 2AB']);
435
+ });
436
+ });
414
437
  });
415
438
 
416
439
  describe('when there is an error retrieving the address', () => {
@@ -201,6 +201,8 @@ export default class AutosuggestUI {
201
201
  }
202
202
 
203
203
  handleFocus() {
204
+ this.blurring = false;
205
+
204
206
  if (this.allowMultiple === 'true' && this.allSelections.length && this.input.value.slice(-1) !== ' ' && this.input.value !== '') {
205
207
  this.input.value = `${this.input.value}, `;
206
208
  }
@@ -513,6 +515,7 @@ export default class AutosuggestUI {
513
515
 
514
516
  selectResult(index) {
515
517
  if (this.results.length) {
518
+ clearTimeout(this.blurTimeout);
516
519
  this.settingResult = true;
517
520
  const result = this.results[index || this.highlightedResultIndex || 0];
518
521
  const resultItem = result.item ?? result;
@@ -42,7 +42,7 @@
42
42
  document.metadata.file.fileSize if document.metadata.file.fileSize,
43
43
  document.metadata.file.filePages if document.metadata.file.filePages ]
44
44
  %}
45
- , {{ fileMetadataItems | join(', ') }}
45
+ , {{ fileMetadataItems | select | join(', ') }}
46
46
  </span>
47
47
  {% endif %}
48
48
  </a>
@@ -89,7 +89,7 @@
89
89
  document.metadata.file.fileSize if document.metadata.file.fileSize,
90
90
  document.metadata.file.filePages if document.metadata.file.filePages ]
91
91
  %}
92
- {{ fileMetadataItems | join(', ') }}
92
+ {{ fileMetadataItems | select | join(', ') }}
93
93
  </li>
94
94
  {%- endif -%}
95
95
  </ul>
@@ -277,6 +277,61 @@ describe('FOR: Macro: Document list', () => {
277
277
  expect(fileInfo).toBe('PDF, 499KB, 1 page');
278
278
  });
279
279
  });
280
+
281
+ describe('WHEN: file configuration is missing optional fileSize and filePages', () => {
282
+ const $ = cheerio.load(
283
+ renderComponent('document-list', {
284
+ documents: [
285
+ {
286
+ ...EXAMPLE_DOCUMENT_LIST_BASIC,
287
+ metadata: {
288
+ file: {
289
+ fileType: 'PDF',
290
+ },
291
+ },
292
+ },
293
+ ],
294
+ }),
295
+ );
296
+
297
+ test('THEN: has visually hidden file information without stray separators', () => {
298
+ const hiddenText = $('.ons-document-list__item-title a .ons-u-vh').text().trim();
299
+ expect(hiddenText).toBe(', PDF document download');
300
+ });
301
+
302
+ test('THEN: has file information without stray separators', () => {
303
+ const fileInfo = $('.ons-document-list__item-attribute').text().trim();
304
+ expect(fileInfo).toBe('PDF');
305
+ });
306
+ });
307
+
308
+ describe('WHEN: file configuration is missing optional filePages', () => {
309
+ const $ = cheerio.load(
310
+ renderComponent('document-list', {
311
+ documents: [
312
+ {
313
+ ...EXAMPLE_DOCUMENT_LIST_BASIC,
314
+ metadata: {
315
+ file: {
316
+ fileType: 'PDF',
317
+ fileSize: '499KB',
318
+ },
319
+ },
320
+ },
321
+ ],
322
+ }),
323
+ );
324
+
325
+ test('THEN: has visually hidden file information without stray separators', () => {
326
+ const hiddenText = $('.ons-document-list__item-title a .ons-u-vh').text().trim();
327
+ expect(hiddenText).toBe(', PDF document download, 499KB');
328
+ });
329
+
330
+ test('THEN: has file information without stray separators', () => {
331
+ const fileInfo = $('.ons-document-list__item-attribute').text().trim();
332
+ expect(fileInfo).toBe('PDF, 499KB');
333
+ });
334
+ });
280
335
  });
281
336
 
282
337
  describe('GIVEN: Params: object', () => {
@@ -10,8 +10,13 @@
10
10
  {% set closingTitleTag = "</h" ~ headingLevel ~ ">" %}
11
11
  {% set openingSubtitleTag = '<h' ~ (headingLevel + 1) ~ ' class="ons-figure__subtitle">' %}
12
12
  {% set closingSubtitleTag = "</h" ~ (headingLevel + 1) ~ ">" %}
13
- {% set openingDownloadTitleTag = '<h' ~ (headingLevel + 2) ~ '>' %}
14
- {% set closingDownloadTitleTag = "</h" ~ (headingLevel + 2) ~ ">" %}
13
+ {% if params.title and params.subtitle %}
14
+ {% set childHeadingLevel = headingLevel + 2 %}
15
+ {% elif params.title %}
16
+ {% set childHeadingLevel = headingLevel + 1 %}
17
+ {% else %}
18
+ {% set childHeadingLevel = headingLevel %}
19
+ {% endif %}
15
20
  {% set audioDescriptionId = "figure-audio-description-" ~ params.id %}
16
21
  {% set rootElement = 'div' if params.useDivForRootElement else 'figure' %}
17
22
  {% set openingRootTag = '<' ~ rootElement %}
@@ -34,7 +39,7 @@
34
39
  {% if params.title %}
35
40
  {{ openingTitleTag | safe }}{{ params.title }}{{ closingTitleTag | safe }}
36
41
  {% endif %}
37
- {% if params.subtitle %}
42
+ {% if params.subtitle and params.title %}
38
43
  {{ openingSubtitleTag | safe }}{{ params.subtitle }}{{ closingSubtitleTag | safe }}
39
44
  {% endif %}
40
45
 
@@ -70,7 +75,7 @@
70
75
  "id": footnotesId,
71
76
  "title": params.footnotes.title,
72
77
  "content": params.footnotes.content,
73
- "headingLevel": headingLevel + 2,
78
+ "headingLevel": childHeadingLevel,
74
79
  "classes": "ons-u-mb-l"
75
80
  })
76
81
  }}
@@ -86,7 +91,7 @@
86
91
  "itemsList": params.download.itemsList,
87
92
  "variants": "bare"
88
93
  }),
89
- "headingLevel": headingLevel + 2
94
+ "headingLevel": childHeadingLevel
90
95
  })
91
96
  }}
92
97
  {% endif %}
@@ -173,7 +173,7 @@ describe('macro: figure', () => {
173
173
  });
174
174
 
175
175
  describe('subtitle', () => {
176
- it('renders the subtitle', () => {
176
+ it('renders the subtitle when title is also provided', () => {
177
177
  const $ = cheerio.load(
178
178
  renderComponent(
179
179
  'figure',
@@ -193,6 +193,21 @@ describe('macro: figure', () => {
193
193
 
194
194
  expect($('.ons-figure__subtitle').length).toBe(0);
195
195
  });
196
+
197
+ it('does not render the subtitle when title is not provided', () => {
198
+ const $ = cheerio.load(
199
+ renderComponent(
200
+ 'figure',
201
+ {
202
+ id: 'example-figure',
203
+ subtitle: 'A subtitle',
204
+ },
205
+ CALLER_CONTENT,
206
+ ),
207
+ );
208
+
209
+ expect($('.ons-figure__subtitle').length).toBe(0);
210
+ });
196
211
  });
197
212
 
198
213
  describe('heading levels', () => {
@@ -244,7 +259,7 @@ describe('macro: figure', () => {
244
259
  expect($('.ons-figure__subtitle')[0].tagName).toBe('h5');
245
260
  });
246
261
 
247
- it('renders footnotes heading two levels below the title', () => {
262
+ it('renders footnotes heading two levels below when title and subtitle are provided', () => {
248
263
  const faker = templateFaker();
249
264
  const detailsSpy = faker.spy('details');
250
265
 
@@ -253,6 +268,7 @@ describe('macro: figure', () => {
253
268
  {
254
269
  ...EXAMPLE_FIGURE_BASIC,
255
270
  headingLevel: 3,
271
+ subtitle: 'A subtitle',
256
272
  footnotes: {
257
273
  title: 'Footnotes',
258
274
  content: 'Some notes.',
@@ -264,7 +280,47 @@ describe('macro: figure', () => {
264
280
  expect(detailsSpy.occurrences[0].headingLevel).toBe(5);
265
281
  });
266
282
 
267
- it('renders download heading two levels below the title', () => {
283
+ it('renders footnotes heading one level below when only title is provided', () => {
284
+ const faker = templateFaker();
285
+ const detailsSpy = faker.spy('details');
286
+
287
+ faker.renderComponent(
288
+ 'figure',
289
+ {
290
+ ...EXAMPLE_FIGURE_BASIC,
291
+ headingLevel: 3,
292
+ footnotes: {
293
+ title: 'Footnotes',
294
+ content: 'Some notes.',
295
+ },
296
+ },
297
+ CALLER_CONTENT,
298
+ );
299
+
300
+ expect(detailsSpy.occurrences[0].headingLevel).toBe(4);
301
+ });
302
+
303
+ it('renders footnotes at the base heading level when no title or subtitle is provided', () => {
304
+ const faker = templateFaker();
305
+ const detailsSpy = faker.spy('details');
306
+
307
+ faker.renderComponent(
308
+ 'figure',
309
+ {
310
+ id: 'example-figure',
311
+ headingLevel: 3,
312
+ footnotes: {
313
+ title: 'Footnotes',
314
+ content: 'Some notes.',
315
+ },
316
+ },
317
+ CALLER_CONTENT,
318
+ );
319
+
320
+ expect(detailsSpy.occurrences[0].headingLevel).toBe(3);
321
+ });
322
+
323
+ it('renders download heading two levels below when title and subtitle are provided', () => {
268
324
  const faker = templateFaker();
269
325
  const detailsSpy = faker.spy('details');
270
326
 
@@ -273,6 +329,7 @@ describe('macro: figure', () => {
273
329
  {
274
330
  ...EXAMPLE_FIGURE_BASIC,
275
331
  headingLevel: 3,
332
+ subtitle: 'A subtitle',
276
333
  download: {
277
334
  title: 'Downloads',
278
335
  itemsList: [{ text: 'A file', url: '/file.csv', download: 'file' }],
@@ -283,6 +340,46 @@ describe('macro: figure', () => {
283
340
 
284
341
  expect(detailsSpy.occurrences[0].headingLevel).toBe(5);
285
342
  });
343
+
344
+ it('renders download heading one level below when only title is provided', () => {
345
+ const faker = templateFaker();
346
+ const detailsSpy = faker.spy('details');
347
+
348
+ faker.renderComponent(
349
+ 'figure',
350
+ {
351
+ ...EXAMPLE_FIGURE_BASIC,
352
+ headingLevel: 3,
353
+ download: {
354
+ title: 'Downloads',
355
+ itemsList: [{ text: 'A file', url: '/file.csv', download: 'file' }],
356
+ },
357
+ },
358
+ CALLER_CONTENT,
359
+ );
360
+
361
+ expect(detailsSpy.occurrences[0].headingLevel).toBe(4);
362
+ });
363
+
364
+ it('renders download at the base heading level when no title or subtitle is provided', () => {
365
+ const faker = templateFaker();
366
+ const detailsSpy = faker.spy('details');
367
+
368
+ faker.renderComponent(
369
+ 'figure',
370
+ {
371
+ id: 'example-figure',
372
+ headingLevel: 3,
373
+ download: {
374
+ title: 'Downloads',
375
+ itemsList: [{ text: 'A file', url: '/file.csv', download: 'file' }],
376
+ },
377
+ },
378
+ CALLER_CONTENT,
379
+ );
380
+
381
+ expect(detailsSpy.occurrences[0].headingLevel).toBe(3);
382
+ });
286
383
  });
287
384
 
288
385
  describe('audio description', () => {
@@ -368,7 +368,7 @@
368
368
  "name": params.search.form.inputName | default('q'),
369
369
  "width": 'full',
370
370
  "label": {
371
- "text": (params.search.form.inputLabel if params.search.form else params.searchLinks.searchNavigationInputLabel) | default('Chwilio’r SYG' if params.lang == 'cy' else 'Search the ONS'),
371
+ "text": (params.search.form.inputLabel if params.search.form else params.searchLinks.searchNavigationInputLabel) | default('Chwilio’r SYG' if currentLanguageIsoCode == 'cy' else 'Search the ONS'),
372
372
  "id": "header-search-input-label"
373
373
  },
374
374
  "searchButton": {
@@ -1104,6 +1104,27 @@ describe('FOR: Macro: Header', () => {
1104
1104
  expect($('#header-search-input-label').text().trim()).toBe('Search the ONS');
1105
1105
  });
1106
1106
  });
1107
+ describe('WHEN: inputLabel is not provided and Welsh is the current language', () => {
1108
+ const $ = cheerio.load(
1109
+ renderComponent('header', {
1110
+ ...EXAMPLE_HEADER_SEARCH,
1111
+ variants: 'basic',
1112
+ language: {
1113
+ languages: [
1114
+ { url: '#0', isoCode: 'en', text: 'English', current: false },
1115
+ { url: '#0', isoCode: 'cy', text: 'Cymraeg', current: true },
1116
+ ],
1117
+ },
1118
+ search: {
1119
+ ...EXAMPLE_HEADER_SEARCH.search,
1120
+ form: { ...EXAMPLE_HEADER_SEARCH.search.form, inputLabel: undefined },
1121
+ },
1122
+ }),
1123
+ );
1124
+ test('THEN: renders search input with Welsh default label', () => {
1125
+ expect($('#header-search-input-label').text().trim()).toBe('Chwilio’r SYG');
1126
+ });
1127
+ });
1107
1128
  describe('WHEN: buttonText is provided', () => {
1108
1129
  const $ = cheerio.load(renderComponent('header', { ...EXAMPLE_HEADER_SEARCH, variants: 'basic' }));
1109
1130
 
@@ -13,9 +13,22 @@
13
13
  "title": params.title,
14
14
  "subtitle": params.subtitle,
15
15
  "footnotes": params.footnotes,
16
- "download": params.download
16
+ "download": params.download,
17
+ "caption": params.sourceNote
17
18
  })
18
19
  %}
20
+ {% set tableClasses = "ons-table" %}
21
+
22
+ {# If there is no source note, footnotes or downloads after the table, add a bottom margin to the table to match the previous spacing #}
23
+ {# If there source note, footnotes or downloads after the table, the figure component handles the extra spacing #}
24
+ {% if not params.sourceNote and not params.footnotes and not params.download %}
25
+ {% set tableClasses = tableClasses + " ons-table--bottom-space" %}
26
+ {% endif %}
27
+
28
+ {% if params.tableClasses %}
29
+ {% set tableClasses = tableClasses + " " + params.tableClasses %}
30
+ {% endif %}
31
+
19
32
  <div class="ons-table-scrollable ons-table-scrollable--on">
20
33
  <div
21
34
  class="ons-table-scrollable__content"
@@ -25,7 +38,7 @@
25
38
  >
26
39
  {# Uses multiple loops to set the rowspan class so that it can work in both nunjucks and jinja2 environments due to the scoping rules of each language #}
27
40
  <table
28
- class="ons-table{{ ' ' + params.tableClasses if params.tableClasses else '' }}{% for row in params.thList %}{% for th in row.ths %}{% if th.rowspan %}{{ ' ' }}ons-table--has-rowspan{% endif %}{% endfor %}{% endfor %}{% for row in params.trs %}{% for td in row.tds %}{% if td.rowspan %}{{ ' ' }}ons-table--has-rowspan{% endif %}{% endfor %}{% endfor %}{% if variants %}{% if variants is not string %}{% for variant in variants %}{{ ' ' }}ons-table--{{ variant }}{% endfor %}{% else %}{{ ' ' }}ons-table--{{ variants }}{% endif %}{% endif %}"
41
+ class="{{ tableClasses }}{% for row in params.thList %}{% for th in row.ths %}{% if th.rowspan %}{{ ' ' }}ons-table--has-rowspan{% endif %}{% endfor %}{% endfor %}{% for row in params.trs %}{% for td in row.tds %}{% if td.rowspan %}{{ ' ' }}ons-table--has-rowspan{% endif %}{% endfor %}{% endfor %}{% if variants %}{% if variants is not string %}{% for variant in variants %}{{ ' ' }}ons-table--{{ variant }}{% endfor %}{% else %}{{ ' ' }}ons-table--{{ variants }}{% endif %}{% endif %}"
29
42
  {% if params.sortBy and 'sortable' in variants %}
30
43
  data-aria-sort="{{ params.sortBy }}" data-aria-asc="{{ params.ariaAsc }}" data-aria-desc="{{ params.ariaDesc }}"
31
44
  {% endif %}
@@ -670,6 +670,59 @@ describe('macro: table', () => {
670
670
  });
671
671
  });
672
672
 
673
+ describe('bottom spacing', () => {
674
+ it('adds "ons-table--bottom-space" class when there are no footnotes, download or sourceNote', () => {
675
+ const $ = cheerio.load(renderComponent('table', EXAMPLE_TABLE));
676
+
677
+ expect($('.ons-table').hasClass('ons-table--bottom-space')).toBe(true);
678
+ });
679
+
680
+ it('does not add "ons-table--bottom-space" class when `sourceNote` is provided', () => {
681
+ const $ = cheerio.load(
682
+ renderComponent('table', {
683
+ ...EXAMPLE_TABLE,
684
+ sourceNote: 'Source: Office for National Statistics',
685
+ }),
686
+ );
687
+
688
+ expect($('.ons-table').hasClass('ons-table--bottom-space')).toBe(false);
689
+ });
690
+
691
+ it('does not add "ons-table--bottom-space" class when `footnotes` is provided', () => {
692
+ const $ = cheerio.load(
693
+ renderComponent('table', {
694
+ ...EXAMPLE_TABLE,
695
+ footnotes: {
696
+ title: 'Footnotes',
697
+ content: 'Data is seasonally adjusted.',
698
+ },
699
+ }),
700
+ );
701
+
702
+ expect($('.ons-table').hasClass('ons-table--bottom-space')).toBe(false);
703
+ });
704
+
705
+ it('does not add "ons-table--bottom-space" class when `download` is provided', () => {
706
+ const $ = cheerio.load(
707
+ renderComponent('table', {
708
+ ...EXAMPLE_TABLE,
709
+ download: {
710
+ title: 'Download this table',
711
+ itemsList: [
712
+ {
713
+ text: 'Table data (CSV, 2KB)',
714
+ url: '/data/table.csv',
715
+ download: 'file',
716
+ },
717
+ ],
718
+ },
719
+ }),
720
+ );
721
+
722
+ expect($('.ons-table').hasClass('ons-table--bottom-space')).toBe(false);
723
+ });
724
+ });
725
+
673
726
  describe('figure wrapper', () => {
674
727
  it('renders with a <div> root element instead of <figure>', () => {
675
728
  const $ = cheerio.load(renderComponent('table', EXAMPLE_TABLE));
@@ -765,6 +818,18 @@ describe('macro: table', () => {
765
818
  expect(figureSpy.occurrences[0].download).toEqual(download);
766
819
  });
767
820
 
821
+ it('passes `sourceNote` to the figure component', () => {
822
+ const faker = templateFaker();
823
+ const figureSpy = faker.spy('figure');
824
+
825
+ faker.renderComponent('table', {
826
+ ...EXAMPLE_TABLE,
827
+ sourceNote: 'Source: Office for National Statistics',
828
+ });
829
+
830
+ expect(figureSpy.occurrences[0].caption).toBe('Source: Office for National Statistics');
831
+ });
832
+
768
833
  it('sets `useDivForRootElement` on the figure component', () => {
769
834
  const faker = templateFaker();
770
835
  const figureSpy = faker.spy('figure');
@@ -1,7 +1,6 @@
1
1
  .ons-table {
2
2
  border-collapse: collapse;
3
3
  border-spacing: 0;
4
- margin-bottom: 1rem;
5
4
  width: 100%;
6
5
 
7
6
  &__head {
@@ -241,4 +240,10 @@
241
240
  }
242
241
  }
243
242
  }
243
+
244
+ // Used when there is no source note, footnotes or downloads after the table, to match the previous bottom spacing
245
+ // If there source note, footnotes or downloads after the table, the figure component handles the extra spacing
246
+ &--bottom-space {
247
+ margin-bottom: 1rem;
248
+ }
244
249
  }
@@ -7,6 +7,7 @@
7
7
  "title": "Population by region, mid-2023",
8
8
  "subtitle": "Estimates for England and Wales",
9
9
  "caption": "Population estimates by region",
10
+ "sourceNote": "Source: Office for National Statistics – Population estimates for the UK, England and Wales, Scotland and Northern Ireland",
10
11
  "ths": [
11
12
  {
12
13
  "value": "Region"