@ons/design-system 72.10.7 → 72.10.9

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 (45) hide show
  1. package/components/accordion/accordion.js +3 -2
  2. package/components/autosuggest/autosuggest.spec.js +2 -0
  3. package/components/autosuggest/autosuggest.ui.js +12 -7
  4. package/components/breadcrumbs/_breadcrumbs.scss +53 -0
  5. package/components/breadcrumbs/_macro.njk +33 -24
  6. package/components/breadcrumbs/_macro.spec.js +25 -0
  7. package/components/chart/_chart.scss +91 -0
  8. package/components/chart/_macro.njk +38 -7
  9. package/components/chart/_macro.spec.js +102 -13
  10. package/components/chart/bar-chart.js +9 -1
  11. package/components/chart/chart-iframe-resize.js +1 -1
  12. package/components/chart/chart.dom.js +5 -3
  13. package/components/chart/chart.js +8 -2
  14. package/components/chart/common-chart-options.js +9 -0
  15. package/components/chart/example-area-chart.njk +2 -1
  16. package/components/chart/example-bar-chart-with-axis-min-and-max-values.njk +0 -1
  17. package/components/chart/example-bar-chart-with-point-range-and-reference-line-annotations.njk +4 -4
  18. package/components/chart/example-bar-chart.njk +0 -1
  19. package/components/chart/example-iframe-chart.njk +2 -1
  20. package/components/chart/example-line-chart.njk +2 -1
  21. package/components/chart/range-annotations-options.js +1 -1
  22. package/components/download-resources/download-resources.spec.js +2 -0
  23. package/components/hero/_hero.scss +17 -22
  24. package/components/hero/_macro.njk +1 -1
  25. package/components/hero/_macro.spec.js +1 -1
  26. package/components/hero/example-hero-dark-with-external-breadcrumbs.njk +194 -0
  27. package/components/hero/example-hero-default-with-external-breadcrumbs.njk +201 -0
  28. package/components/hero/example-hero-grey-with-external-breadcrumbs.njk +243 -0
  29. package/components/hero/example-hero-navy-blue-with-external-breadcrumbs.njk +200 -0
  30. package/components/hero/example-hero-pale-blue-with-external-breadcrumbs.njk +201 -0
  31. package/components/icon/_macro.njk +1 -1
  32. package/components/mutually-exclusive/mutually-exclusive.js +3 -1
  33. package/components/radios/clear-radios.js +4 -2
  34. package/components/relationships/relationships.js +4 -2
  35. package/components/tabs/tabs.js +2 -2
  36. package/components/video/video.js +2 -0
  37. package/css/main.css +1 -1
  38. package/img/small/area-chart-screenshot.png +0 -0
  39. package/img/small/line-chart-screenshot.png +0 -0
  40. package/js/timeout.js +9 -6
  41. package/layout/_template.njk +4 -1
  42. package/package.json +7 -5
  43. package/scripts/main.es5.js +4 -2
  44. package/scripts/main.js +4 -2
  45. package/scss/objects/_page.scss +1 -1
@@ -1,3 +1,4 @@
1
+ import DOMPurify from 'dompurify';
1
2
  export default class Accordion {
2
3
  constructor(button, detailsEls) {
3
4
  this.openDetailsEls = 0;
@@ -52,11 +53,11 @@ export default class Accordion {
52
53
 
53
54
  setButton() {
54
55
  if (this.canClose()) {
55
- this.buttonInner.innerHTML = this.closeButton;
56
+ this.buttonInner.innerHTML = DOMPurify.sanitize(this.closeButton);
56
57
  this.button.setAttribute('data-ga-label', this.buttonOpen);
57
58
  this.button.setAttribute('aria-expanded', 'true');
58
59
  } else {
59
- this.buttonInner.innerHTML = this.buttonOpen;
60
+ this.buttonInner.innerHTML = DOMPurify.sanitize(this.buttonOpen);
60
61
  this.button.setAttribute('data-ga-label', this.closeButton);
61
62
  this.button.setAttribute('aria-expanded', 'false');
62
63
  }
@@ -386,9 +386,11 @@ describe('script: autosuggest', () => {
386
386
  describe('when the mouse moves over a result and a suggestion is focused', () => {
387
387
  it('removes the focused class', async () => {
388
388
  await setTestPage('/test', renderComponent('autosuggest', EXAMPLE_AUTOSUGGEST));
389
+ await page.mouse.move(0, 0); // move out of the component
389
390
 
390
391
  await page.type('.ons-js-autosuggest-input', 'state', { delay: 20 });
391
392
  await page.keyboard.press('ArrowDown');
393
+
392
394
  await page.hover('.ons-autosuggest__option:nth-child(2)');
393
395
 
394
396
  const focusedClassCount = await page.$$eval('.ons-autosuggest__option--focused', (nodes) => nodes.length);
@@ -1,6 +1,7 @@
1
1
  import abortableFetch from '../../js/abortable-fetch';
2
2
  import { sanitiseAutosuggestText } from './autosuggest.helpers';
3
3
  import runFuse from './fuse-config';
4
+ import DOMPurify from 'dompurify';
4
5
 
5
6
  export const baseClass = 'ons-js-autosuggest';
6
7
 
@@ -398,7 +399,7 @@ export default class AutosuggestUI {
398
399
  const listElement = document.createElement('li');
399
400
  listElement.className = `${classAutosuggestOption} ${classAutosuggestOptionMoreResults}`;
400
401
  listElement.setAttribute('aria-hidden', 'true');
401
- listElement.innerHTML = this.moreResults;
402
+ listElement.innerHTML = DOMPurify.sanitize(this.moreResults);
402
403
  this.listbox.appendChild(listElement);
403
404
  }
404
405
 
@@ -436,10 +437,12 @@ export default class AutosuggestUI {
436
437
  if (status === 400 || status === false) {
437
438
  message = this.typeMore;
438
439
  this.setAriaStatus(message);
439
- this.listbox.innerHTML = `<li class="${classAutosuggestOption} ${classAutosuggestOptionNoResults}">${message}</li>`;
440
+ this.listbox.innerHTML = DOMPurify.sanitize(
441
+ `<li class="${classAutosuggestOption} ${classAutosuggestOptionNoResults}">${message}</li>`,
442
+ );
440
443
  } else if (status > 400 || status === '') {
441
- message =
442
- this.errorAPI + (this.errorAPILinkText ? ' <a href="' + window.location.href + '">' + this.errorAPILinkText + '</a>.' : '');
444
+ const sanitizedHref = DOMPurify.sanitize(window.location.href);
445
+ message = this.errorAPI + (this.errorAPILinkText ? ' <a href="' + sanitizedHref + '">' + this.errorAPILinkText + '</a>.' : '');
443
446
  let ariaMessage = this.errorAPI + (this.errorAPILinkText ? ' ' + this.errorAPILinkText : '');
444
447
 
445
448
  this.input.setAttribute('disabled', true);
@@ -453,7 +456,9 @@ export default class AutosuggestUI {
453
456
  this.resultsTitleContainer.remove();
454
457
  } else {
455
458
  message = this.noResults;
456
- this.listbox.innerHTML = `<li class="${classAutosuggestOption} ${classAutosuggestOptionNoResults}">${message}</li>`;
459
+ this.listbox.innerHTML = DOMPurify.sanitize(
460
+ `<li class="${classAutosuggestOption} ${classAutosuggestOptionNoResults}">${message}</li>`,
461
+ );
457
462
  }
458
463
  }
459
464
 
@@ -503,7 +508,7 @@ export default class AutosuggestUI {
503
508
  }
504
509
  }
505
510
  }
506
- this.ariaStatus.innerHTML = content;
511
+ this.ariaStatus.innerHTML = DOMPurify.sanitize(content);
507
512
  }
508
513
 
509
514
  selectResult(index) {
@@ -548,7 +553,7 @@ export default class AutosuggestUI {
548
553
  warningSpanElement.innerHTML = '!';
549
554
 
550
555
  warningBodyElement.className = 'ons-panel__body';
551
- warningBodyElement.innerHTML = content;
556
+ warningBodyElement.innerHTML = DOMPurify.sanitize(content);
552
557
 
553
558
  warningElement.appendChild(warningSpanElement);
554
559
  warningElement.appendChild(warningBodyElement);
@@ -1,5 +1,58 @@
1
1
  $breadcrumb-chevron-height: 0.65rem;
2
2
 
3
+ .ons-breadcrumbs-wrapper {
4
+ position: relative;
5
+ z-index: 10;
6
+ margin-bottom: -2rem;
7
+ &--grey {
8
+ background-color: var(--ons-color-banner-bg);
9
+ }
10
+
11
+ &--dark {
12
+ background-color: var(--ons-color-hero-bg-dark);
13
+ }
14
+
15
+ &--navy-blue {
16
+ background-color: var(--ons-color-navy-blue-light);
17
+ margin-bottom: -1.75rem;
18
+ @include mq(l) {
19
+ margin-bottom: -3.25rem;
20
+ }
21
+ }
22
+
23
+ &--dark,
24
+ &--navy-blue {
25
+ .ons-icon {
26
+ color: var(--ons-color-text-inverse);
27
+ }
28
+
29
+ .ons-breadcrumbs__link {
30
+ color: var(--ons-color-text-inverse);
31
+ text-decoration: underline;
32
+
33
+ &:hover {
34
+ color: var(--ons-color-text-inverse);
35
+ text-decoration: underline solid var(--ons-color-text-inverse) 2px;
36
+ }
37
+
38
+ &:focus {
39
+ color: var(--ons-color-text);
40
+ }
41
+
42
+ &:focus:hover {
43
+ text-decoration: none;
44
+ }
45
+ }
46
+ }
47
+
48
+ &--pale-blue {
49
+ background-color: var(--ons-color-pale-blue);
50
+ }
51
+ .ons-breadcrumbs {
52
+ padding: 1rem;
53
+ }
54
+ }
55
+
3
56
  .ons-breadcrumbs {
4
57
  align-items: center;
5
58
  display: flex;
@@ -1,28 +1,37 @@
1
1
  {% from "components/icon/_macro.njk" import onsIcon %}
2
-
3
2
  {% macro onsBreadcrumbs(params) %}
4
- <nav
5
- class="ons-breadcrumbs{{ ' ' + params.classes if params.classes else '' }}"
6
- aria-label="{{ params.ariaLabel | default('Breadcrumbs') }}"
7
- {% if params.id %}id="{{ params.id }}"{% endif %}
8
- >
9
- <ol class="ons-breadcrumbs__items ons-u-fs-s">
10
- {%- for item in params.itemsList -%}
11
- <li class="ons-breadcrumbs__item{{ ' ' + item.itemClasses if item.itemClasses else '' }}" id="breadcrumb-{{ loop.index }}">
12
- <a
13
- class="ons-breadcrumbs__link{{ ' ' + item.linkClasses if item.linkClasses else '' }}"
14
- href="{% if not isDesignSystemExample %}{{ item.url }}{% else %}#0{% endif %}"
15
- {% if item.id %}{{ ' ' }}id='{{ item.id }}'{% endif %}
16
- {% if item.attributes %}{% for attribute, value in (item.attributes.items() if item.attributes is mapping and item.attributes.items else item.attributes) %}{{ ' ' }}{{ attribute }}="{{ value }}"{% endfor %}{% endif %}
17
- >{{ item.text }}</a
3
+ {% set breadcrumbs %}
4
+ <nav
5
+ class="ons-breadcrumbs{{ ' ons-container' if params.variant }}{{ ' ' + params.classes if params.classes else '' }}"
6
+ aria-label="{{ params.ariaLabel | default('Breadcrumbs') }}"
7
+ {% if params.id %}id="{{ params.id }}"{% endif %}
8
+ >
9
+ <ol class="ons-breadcrumbs__items ons-u-fs-s">
10
+ {%- for item in params.itemsList -%}
11
+ <li
12
+ class="ons-breadcrumbs__item{{ ' ' + item.itemClasses if item.itemClasses else '' }}"
13
+ id="breadcrumb-{{ loop.index }}"
18
14
  >
19
- {{-
20
- onsIcon({
21
- "iconType": "chevron"
22
- })
23
- -}}
24
- </li>
25
- {%- endfor -%}
26
- </ol>
27
- </nav>
15
+ <a
16
+ class="ons-breadcrumbs__link{{ ' ' + item.linkClasses if item.linkClasses else '' }}"
17
+ href="{% if not isDesignSystemExample %}{{ item.url }}{% else %}#0{% endif %}"
18
+ {% if item.id %}{{ ' ' }}id='{{ item.id }}'{% endif %}
19
+ {% if item.attributes %}{% for attribute, value in (item.attributes.items() if item.attributes is mapping and item.attributes.items else item.attributes) %}{{ ' ' }}{{ attribute }}="{{ value }}"{% endfor %}{% endif %}
20
+ >{{ item.text }}</a
21
+ >
22
+ {{-
23
+ onsIcon({
24
+ "iconType": "chevron"
25
+ })
26
+ -}}
27
+ </li>
28
+ {%- endfor -%}
29
+ </ol>
30
+ </nav>
31
+ {% endset %}
32
+ {% if params.variant %}
33
+ <div class="ons-breadcrumbs-wrapper ons-breadcrumbs-wrapper--{{ params.variant }}">{{ breadcrumbs | safe }}</div>
34
+ {% else %}
35
+ {{ breadcrumbs | safe }}
36
+ {% endif %}
28
37
  {% endmacro %}
@@ -132,4 +132,29 @@ describe('FOR: Macro: Breadcrumbs', () => {
132
132
  });
133
133
  });
134
134
  });
135
+
136
+ describe('GIVEN: Params: variant', () => {
137
+ describe('WHEN: variant is provided', () => {
138
+ const $ = cheerio.load(
139
+ renderComponent('breadcrumbs', {
140
+ ...EXAMPLE_BREADCRUMBS_REQUIRED_PARAMS,
141
+ variant: 'grey',
142
+ }),
143
+ );
144
+ test('THEN: renders breadcrumbs in a breadcrumbs-wrapper with correct modifier class', () => {
145
+ expect($('.ons-breadcrumbs-wrapper').hasClass('ons-breadcrumbs-wrapper--grey')).toBe(true);
146
+ });
147
+ });
148
+
149
+ describe('WHEN: variant is not provided', () => {
150
+ const $ = cheerio.load(
151
+ renderComponent('breadcrumbs', {
152
+ ...EXAMPLE_BREADCRUMBS_REQUIRED_PARAMS,
153
+ }),
154
+ );
155
+ test('THEN: does not render breadcrumbs-wrapper', () => {
156
+ expect($('.ons-breadcrumbs-wrapper').length).toBe(0);
157
+ });
158
+ });
159
+ });
135
160
  });
@@ -7,6 +7,28 @@
7
7
  overflow: visible !important;
8
8
  }
9
9
 
10
+ @media (forced-colors: active) {
11
+ .highcharts-plot-band {
12
+ fill: var(--ons-color-grey-75); /* Forces the color used for text in high contrast mode */
13
+ }
14
+ .highcharts-annotation-label text,
15
+ .highcharts-plot-line-label,
16
+ .highcharts-plot-band-label {
17
+ fill: CanvasText !important; /* Forces the color used for text in high contrast mode */
18
+ }
19
+
20
+ .highcharts-annotation-label-box {
21
+ fill: Canvas; /* Sets the background to match system background */
22
+ stroke: CanvasText; /* Ensures border is visible in contrast mode */
23
+ }
24
+ }
25
+
26
+ &__container:focus {
27
+ box-shadow: 0 0 0 3px var(--ons-color-black);
28
+ outline: 4px solid var(--ons-color-focus);
29
+ outline-offset: 2px;
30
+ }
31
+
10
32
  &__download-title {
11
33
  @extend .ons-u-pt-l;
12
34
  @extend .ons-u-fs-r--b;
@@ -110,6 +132,12 @@
110
132
  aspect-ratio: 16 / 9;
111
133
  }
112
134
  }
135
+
136
+ &__non-js-hide {
137
+ @media (scripting: none) {
138
+ display: none;
139
+ }
140
+ }
113
141
  }
114
142
 
115
143
  // This is a workaround to position the axis title to the left
@@ -164,6 +192,69 @@
164
192
  border-left: 1px solid var(--ons-color-grey-100);
165
193
  }
166
194
 
195
+ @media (forced-colors: active) {
196
+ .ons-chart__boxplot-legend-item--uncertainty,
197
+ .highcharts-boxplot-box {
198
+ background-color: SelectedItem;
199
+ fill: SelectedItem;
200
+ }
201
+
202
+ .ons-chart__boxplot-legend-item--estimate,
203
+ .highcharts-boxplot-median {
204
+ background-color: WindowText;
205
+ stroke: WindowText;
206
+ }
207
+ .highcharts-color-0,
208
+ .highcharts-color-0 .highcharts-area,
209
+ .highcharts-legend-item.highcharts-color-0 rect {
210
+ fill: #007faa;
211
+ }
212
+ .highcharts-color-1,
213
+ .highcharts-color-1 .highcharts-area,
214
+ .highcharts-legend-item.highcharts-color-1 rect {
215
+ fill: #a6f0ff;
216
+ }
217
+ .highcharts-color-2,
218
+ .highcharts-color-2 .highcharts-area,
219
+ .highcharts-legend-item.highcharts-color-2 rect {
220
+ fill: #70d49e;
221
+ }
222
+ .highcharts-color-3,
223
+ .highcharts-color-3 .highcharts-area,
224
+ .highcharts-legend-item.highcharts-color-3 rect {
225
+ fill: #e898a5;
226
+ }
227
+ .highcharts-color-4,
228
+ .highcharts-color-4 .highcharts-area,
229
+ .highcharts-legend-item.highcharts-color-4 rect {
230
+ fill: #dadfe1;
231
+ }
232
+ .highcharts-color-5,
233
+ .highcharts-color-5 .highcharts-area,
234
+ .highcharts-legend-item.highcharts-color-5 rect {
235
+ fill: #f9db72;
236
+ }
237
+ .highcharts-color-6,
238
+ .highcharts-color-6 .highcharts-area,
239
+ .highcharts-legend-item.highcharts-color-6 rect {
240
+ fill: #f45b5b;
241
+ }
242
+ .highcharts-color-7,
243
+ .highcharts-color-7 .highcharts-area,
244
+ .highcharts-legend-item.highcharts-color-7 rect {
245
+ fill: #1e824c;
246
+ }
247
+ .highcharts-color-8,
248
+ .highcharts-color-8 .highcharts-area,
249
+ .highcharts-legend-item.highcharts-color-8 rect {
250
+ fill: #e7934c;
251
+ }
252
+ .highcharts-color-9,
253
+ .highcharts-color-9 .highcharts-area,
254
+ .highcharts-legend-item.highcharts-color-9 rect {
255
+ fill: #a0618b;
256
+ }
257
+ }
167
258
  // Allow last label to overflow
168
259
  .highcharts-container {
169
260
  overflow: visible !important;
@@ -14,6 +14,10 @@
14
14
  {% set closingSubtitleTag = "</h" ~ (headingLevel + 1) ~ ">" %}
15
15
  {% set openingDownloadTitleTag = "<h" ~ (headingLevel + 2) %}
16
16
  {% set closingDownloadTitleTag = "</h" ~ (headingLevel + 2) ~ ">" %}
17
+ {% set audioDescriptionId = "chart-audio-description-" ~ params.id %}
18
+ {% set instructionsId = "chart-instructions-" ~ params.id %}
19
+ {% set instructions = params.instructions | default('Use the Tab key to move focus into the chart. Once inside, use the arrow keys to navigate between data points. As you move, tooltips will be announced to describe each point. Touch device users, explore by touch or with swipe gestures.') %}
20
+
17
21
  <div
18
22
  {% if not params.iframeUrl %}
19
23
  data-highcharts-base-chart data-highcharts-type="{{ params.chartType }}" data-highcharts-theme="{{ params.theme }}"
@@ -48,18 +52,23 @@
48
52
  {% endif %}
49
53
  {% endif %}
50
54
  >
51
- <figure class="ons-chart">
55
+ <figure class="ons-chart" aria-describedby="{{ audioDescriptionId }}">
52
56
  {{ openingTitleTag | safe }} class="ons-chart__title">{{ params.title }}{{ closingTitleTag | safe }}
53
57
  {% if params.subtitle %}
54
58
  {{ openingSubtitleTag | safe }}
55
59
  class="ons-chart__subtitle">{{ params.subtitle }}{{ closingSubtitleTag | safe }}
56
60
  {% endif %}
57
- {% if params.description %}
58
- <p class="ons-u-vh">{{ params.description }}</p>
59
- {% endif %}
61
+
62
+ <p class="ons-u-vh" id="{{ audioDescriptionId }}">{{ params.description }}</p>
63
+
60
64
  {% if params.iframeUrl %}
61
- <div data-chart-iframe id="{{ params.id }}" data-url="{{ params.iframeUrl }}" data-title="{{ params.title }}">
62
- <iframe src="{{ params.iframeUrl }}" title="{{ params.title }}" class="ons-chart__iframe"> </iframe>
65
+ <div
66
+ class="ons-chart__iframe-wrapper{{ ' ons-chart__non-js-hide' if params.fallbackImageUrl else '' }}"
67
+ id="{{ params.id }}"
68
+ data-url="{{ params.iframeUrl }}"
69
+ data-title="{{ params.title }}"
70
+ >
71
+ <iframe src="{{ params.iframeUrl }}" title="{{ params.title }}" class="ons-chart__iframe"></iframe>
63
72
  </div>
64
73
  {% else %}
65
74
  {% if params.chartType == 'boxplot' and (params.estimateLineLabel or params.uncertaintyRangeLabel) and params.legend == true %}
@@ -79,13 +88,35 @@
79
88
  </div>
80
89
  {% endif %}
81
90
  {% if params.chartType in supportedChartTypes %}
82
- <div data-highcharts-chart class="ons-chart__chart"></div>
91
+ <div
92
+ data-highcharts-chart-container
93
+ class="ons-chart__container"
94
+ tabindex="0"
95
+ role="region"
96
+ aria-label="chart container"
97
+ aria-describedby="{{ instructionsId }}"
98
+ >
99
+ <div id="{{ instructionsId }}" class="ons-u-vh">{{ instructions }}</div>
100
+ <div data-highcharts-chart class="ons-chart__chart"></div>
101
+ </div>
83
102
  {% else %}
84
103
  <p data-invalid-chart-type data-unsupported-chart-text>
85
104
  <b>{{ '"' + params.chartType + '" - ' + (params.unsupportedChartText or 'chart type is not supported') }}</b>
86
105
  </p>
87
106
  {% endif %}
88
107
  {% endif %}
108
+
109
+ {% if params.fallbackImageUrl %}
110
+ {% set fallbackImageId = ["fallback-image--", params.id] | join %}
111
+ <noscript id="{{ fallbackImageId }}">
112
+ {# Note that a more detailed description of the chart is provided in the audio description above #}
113
+ <img
114
+ src="{{ params.fallbackImageUrl }}"
115
+ alt="{{ params.fallbackImageAlt or 'Fallback image for the chart as JavaScript is disabled' }}"
116
+ class="ons-chart__fallback-image"
117
+ />
118
+ </noscript>
119
+ {% endif %}
89
120
  {#
90
121
  Footnotes for the annotations at mobile
91
122
  Hidden from screen readers because the full text will be read out where they appear in the chart
@@ -63,6 +63,36 @@ describe('Macro: Chart', () => {
63
63
  expect($('figcaption').length).toBe(0);
64
64
  expect($('.ons-chart__download-title').length).toBe(0);
65
65
  });
66
+
67
+ test('THEN: it renders the chart container with the correct aria attributes', () => {
68
+ expect($('.ons-chart__container').attr('aria-label')).toBe('chart container');
69
+ });
70
+ });
71
+ });
72
+
73
+ describe('GIVEN: Params: Instructions', () => {
74
+ describe('WHEN: instructions is provided', () => {
75
+ const $ = cheerio.load(
76
+ renderComponent('chart', {
77
+ ...EXAMPLE_LINE_CHART_REQUIRED_PARAMS,
78
+ instructions: 'Some custom instructions for the chart.',
79
+ }),
80
+ );
81
+
82
+ test('THEN: it renders the chart with the correct instructions text', () => {
83
+ const expectedText = 'Some custom instructions for the chart.';
84
+ expect($('#chart-instructions-chart-123').text().trim()).toBe(expectedText);
85
+ });
86
+ });
87
+
88
+ describe('WHEN: instructions is not provided', () => {
89
+ const $ = cheerio.load(renderComponent('chart', EXAMPLE_LINE_CHART_REQUIRED_PARAMS));
90
+
91
+ test('THEN: it renders the chart with the default instructions text', () => {
92
+ const expectedText =
93
+ 'Use the Tab key to move focus into the chart. Once inside, use the arrow keys to navigate between data points. As you move, tooltips will be announced to describe each point. Touch device users, explore by touch or with swipe gestures.';
94
+ expect($('#chart-instructions-chart-123').text().trim()).toBe(expectedText);
95
+ });
66
96
  });
67
97
  });
68
98
 
@@ -193,6 +223,52 @@ describe('Macro: Chart', () => {
193
223
  });
194
224
  });
195
225
 
226
+ describe('GIVEN: Params: fallbackImageUrl', () => {
227
+ describe('WHEN: fallbackImageUrl is provided', () => {
228
+ const $ = cheerio.load(
229
+ renderComponent('chart', {
230
+ ...EXAMPLE_LINE_CHART_WITH_CONFIG_PARAMS,
231
+ fallbackImageUrl: '/img/small/line-chart-screenshot.png',
232
+ }),
233
+ );
234
+ const noScriptFallbackImage = $(`#fallback-image--chart-456`).html();
235
+ test('THEN: it renders the fallback image', () => {
236
+ expect(noScriptFallbackImage).toContain('/img/small/line-chart-screenshot.png');
237
+ });
238
+ });
239
+ });
240
+
241
+ describe('GIVEN: Params: fallbackImageAlt', () => {
242
+ describe('WHEN: fallbackImageAlt is provided', () => {
243
+ const $ = cheerio.load(
244
+ renderComponent('chart', {
245
+ ...EXAMPLE_LINE_CHART_WITH_CONFIG_PARAMS,
246
+ fallbackImageUrl: '/img/small/line-chart-screenshot.png',
247
+ fallbackImageAlt: 'A description of the fallback image for screen readers',
248
+ }),
249
+ );
250
+ const noScriptFallbackImage = $(`#fallback-image--chart-456`).html();
251
+ test('THEN: it renders the customised fallback image alt text', () => {
252
+ expect(noScriptFallbackImage).toContain('alt="A description of the fallback image for screen readers"');
253
+ });
254
+ });
255
+ });
256
+
257
+ describe('GIVEN: Params: no fallbackImageAlt', () => {
258
+ describe('WHEN: fallbackImageAlt is not provided', () => {
259
+ const $ = cheerio.load(
260
+ renderComponent('chart', {
261
+ ...EXAMPLE_LINE_CHART_WITH_CONFIG_PARAMS,
262
+ fallbackImageUrl: '/img/small/line-chart-screenshot.png',
263
+ }),
264
+ );
265
+ const noScriptFallbackImage = $(`#fallback-image--chart-456`).html();
266
+ test('THEN: it renders the default fallback image alt text', () => {
267
+ expect(noScriptFallbackImage).toContain('alt="Fallback image for the chart as JavaScript is disabled"');
268
+ });
269
+ });
270
+ });
271
+
196
272
  describe('GIVEN: Params: Caption', () => {
197
273
  describe('WHEN: caption is provided', () => {
198
274
  const $ = cheerio.load(
@@ -242,7 +318,7 @@ describe('Macro: Chart', () => {
242
318
  );
243
319
 
244
320
  test('THEN: it renders the description for accessibility', () => {
245
- expect($('.ons-u-vh').text()).toBe('An accessible description for screen readers.');
321
+ expect($('#chart-audio-description-chart-123').text()).toBe('An accessible description for screen readers.');
246
322
  });
247
323
  });
248
324
  });
@@ -419,7 +495,7 @@ describe('Macro: Chart', () => {
419
495
  );
420
496
 
421
497
  test('THEN: it renders the description for accessibility', () => {
422
- expect($('.ons-u-vh').text()).toBe('An accessible description for screen readers.');
498
+ expect($('#chart-audio-description-bar-chart-123').text()).toBe('An accessible description for screen readers.');
423
499
  });
424
500
  });
425
501
  });
@@ -566,7 +642,7 @@ describe('Macro: Chart', () => {
566
642
  );
567
643
 
568
644
  test('THEN: it renders the description for accessibility', () => {
569
- expect($('.ons-u-vh').text()).toBe('An accessible description for screen readers.');
645
+ expect($('#chart-audio-description-column-chart-123').text()).toBe('An accessible description for screen readers.');
570
646
  });
571
647
  });
572
648
  });
@@ -791,7 +867,7 @@ describe('Macro: Chart', () => {
791
867
  );
792
868
 
793
869
  test('THEN: it renders the description for accessibility', () => {
794
- expect($('.ons-u-vh').text()).toBe('An accessible description for screen readers.');
870
+ expect($('#chart-audio-description-column-chart-123').text()).toBe('An accessible description for screen readers.');
795
871
  });
796
872
  });
797
873
  });
@@ -947,7 +1023,7 @@ describe('Macro: Chart', () => {
947
1023
  );
948
1024
 
949
1025
  test('THEN: it renders the description for accessibility', () => {
950
- expect($('.ons-u-vh').text()).toBe('An accessible description for screen readers.');
1026
+ expect($('#chart-audio-description-area-chart-123').text()).toBe('An accessible description for screen readers.');
951
1027
  });
952
1028
  });
953
1029
  });
@@ -1004,7 +1080,7 @@ describe('Macro: Chart', () => {
1004
1080
  describe('GIVEN: Params: required', () => {
1005
1081
  describe('WHEN: required params are provided', () => {
1006
1082
  const $ = cheerio.load(renderComponent('chart', EXAMPLE_BOXPLOT_CHART_PARAMS));
1007
- const configScript = $(`script[data-highcharts-config--uuid]`).html();
1083
+ const configScript = $(`script[data-highcharts-config--boxplot-chart-123]`).html();
1008
1084
 
1009
1085
  test('THEN: it passes jest-axe checks', async () => {
1010
1086
  const results = await axe($.html());
@@ -1020,7 +1096,7 @@ describe('Macro: Chart', () => {
1020
1096
  expect(chartContainer.attr('data-highcharts-type')).toBe('boxplot');
1021
1097
  expect(chartContainer.attr('data-highcharts-theme')).toBe('primary');
1022
1098
  expect(chartContainer.attr('data-highcharts-title')).toBe('Example Boxplot Chart');
1023
- expect(chartContainer.attr('data-highcharts-id')).toBe('uuid');
1099
+ expect(chartContainer.attr('data-highcharts-id')).toBe('boxplot-chart-123');
1024
1100
  });
1025
1101
 
1026
1102
  test('THEN: it includes the Highcharts JSON config', () => {
@@ -1124,7 +1200,7 @@ describe('Macro: Chart', () => {
1124
1200
  );
1125
1201
 
1126
1202
  test('THEN: it renders the description for accessibility', () => {
1127
- expect($('.ons-u-vh').text()).toBe(accessibleDescription);
1203
+ expect($('#chart-audio-description-boxplot-chart-123').text()).toBe(accessibleDescription);
1128
1204
  });
1129
1205
  });
1130
1206
  });
@@ -1156,7 +1232,7 @@ describe('Macro: Chart', () => {
1156
1232
  describe('GIVEN: Params: Required', () => {
1157
1233
  describe('WHEN: required params are provided', () => {
1158
1234
  const $ = cheerio.load(renderComponent('chart', EXAMPLE_COLUMN_RANGE_CHART_PARAMS));
1159
- const configScript = $(`script[data-highcharts-config--uuid]`).html();
1235
+ const configScript = $(`script[data-highcharts-config--column-range-chart-123]`).html();
1160
1236
 
1161
1237
  test('THEN: it passes jest-axe accessibility checks', async () => {
1162
1238
  const results = await axe($.html());
@@ -1170,7 +1246,7 @@ describe('Macro: Chart', () => {
1170
1246
  expect(baseChart.attr('data-highcharts-title')).toBe(
1171
1247
  'Food stores showed a strong rise on the month, while non-food stores fell',
1172
1248
  );
1173
- expect(baseChart.attr('data-highcharts-id')).toBe('uuid');
1249
+ expect(baseChart.attr('data-highcharts-id')).toBe('column-range-chart-123');
1174
1250
  });
1175
1251
 
1176
1252
  test('THEN: it includes columnrange and scatter series types', () => {
@@ -1208,7 +1284,7 @@ describe('Macro: Chart', () => {
1208
1284
  });
1209
1285
 
1210
1286
  test('THEN: it still renders the description', () => {
1211
- expect($('.ons-u-vh').text()).toBe('A detailed description');
1287
+ expect($('#chart-audio-description-invalid-chart-123').text()).toBe('A detailed description');
1212
1288
  });
1213
1289
 
1214
1290
  test('THEN: it still renders the caption', () => {
@@ -1489,11 +1565,11 @@ describe('Macro: Chart', () => {
1489
1565
  });
1490
1566
 
1491
1567
  test('THEN: it renders the iframe', () => {
1492
- expect($('[data-chart-iframe]').length).toBe(1);
1568
+ expect($('.ons-chart__iframe-wrapper').length).toBe(1);
1493
1569
  });
1494
1570
 
1495
1571
  test('THEN: it includes the iframe title as a data attribute', () => {
1496
- const iframe = $('[data-chart-iframe]');
1572
+ const iframe = $('.ons-chart__iframe-wrapper');
1497
1573
  expect(iframe.attr('data-title')).toBe(EXAMPLE_IFRAME_CHART_PARAMS.title);
1498
1574
  });
1499
1575
 
@@ -1648,6 +1724,19 @@ describe('Macro: Chart', () => {
1648
1724
  expect($('[data-invalid-chart-type]').length).toBe(0);
1649
1725
  });
1650
1726
  });
1727
+
1728
+ describe('WHEN: Params: fallbackImageUrl is set', () => {
1729
+ const $ = cheerio.load(
1730
+ renderComponent('chart', {
1731
+ ...EXAMPLE_IFRAME_CHART_PARAMS,
1732
+ fallbackImageUrl: '/img/small/line-chart-screenshot.png',
1733
+ }),
1734
+ );
1735
+ const iframe = $(`.ons-chart__iframe-wrapper`);
1736
+ test('THEN: the iframe is hidden when JavaScript is disabled', () => {
1737
+ expect(iframe.attr('class')).toContain('ons-chart__non-js-hide');
1738
+ });
1739
+ });
1651
1740
  });
1652
1741
  });
1653
1742
  });