@ons/design-system 50.0.1 → 51.0.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 +35 -13
- package/components/access-code/_macro.njk +1 -1
- package/components/access-code/_macro.spec.js +162 -0
- package/components/access-code/uac.spec.js +26 -0
- package/components/accordion/_macro.spec.js +224 -0
- package/components/accordion/accordion.spec.js +134 -0
- package/components/address-input/_macro.njk +1 -1
- package/components/address-input/_macro.spec.js +465 -0
- package/components/address-input/autosuggest.address.js +5 -4
- package/components/address-input/autosuggest.address.setter.js +3 -1
- package/components/address-input/autosuggest.address.spec.js +733 -0
- package/components/address-output/_macro.njk +6 -6
- package/components/address-output/_macro.spec.js +122 -0
- package/components/autosuggest/_macro.njk +1 -1
- package/components/autosuggest/_macro.spec.js +229 -0
- package/components/autosuggest/autosuggest.helpers.js +2 -3
- package/components/autosuggest/autosuggest.helpers.spec.js +85 -0
- package/components/autosuggest/autosuggest.js +4 -2
- package/components/autosuggest/autosuggest.spec.js +625 -0
- package/components/autosuggest/autosuggest.ui.js +6 -2
- package/components/breadcrumbs/_macro.spec.js +129 -0
- package/components/button/_macro.njk +5 -5
- package/components/button/_macro.spec.js +446 -0
- package/components/button/button.spec.js +290 -0
- package/components/call-to-action/_macro.njk +3 -1
- package/components/call-to-action/_macro.spec.js +52 -0
- package/components/card/_macro.njk +26 -19
- package/components/card/_macro.spec.js +261 -0
- package/components/char-check-limit/_macro.spec.js +73 -0
- package/components/char-check-limit/character-check.spec.js +196 -0
- package/components/char-check-limit/character-limit.js +1 -1
- package/components/checkboxes/_checkbox-macro.spec.js +419 -0
- package/components/checkboxes/_macro.njk +1 -3
- package/components/checkboxes/_macro.spec.js +306 -0
- package/components/checkboxes/checkboxes.spec.js +208 -0
- package/components/code-highlight/_macro.spec.js +56 -0
- package/components/code-highlight/code-highlight.spec.js +18 -0
- package/components/collapsible/_macro.spec.js +204 -0
- package/components/collapsible/collapsible.js +2 -1
- package/components/collapsible/collapsible.spec.js +236 -0
- package/components/content-pagination/_macro.spec.js +199 -0
- package/components/cookies-banner/_macro.njk +1 -1
- package/components/cookies-banner/_macro.spec.js +171 -0
- package/components/cookies-banner/cookies-banner.spec.js +90 -0
- package/components/date-input/_macro.njk +6 -3
- package/components/date-input/_macro.spec.js +286 -0
- package/components/document-list/_macro.njk +3 -5
- package/components/document-list/_macro.spec.js +491 -0
- package/components/download-resources/download-resources.spec.js +540 -0
- package/components/duration/_macro.njk +7 -6
- package/components/duration/_macro.spec.js +251 -0
- package/components/error/_macro.spec.js +97 -0
- package/components/external-link/_macro.spec.js +60 -0
- package/components/feedback/_macro.njk +5 -3
- package/components/feedback/_macro.spec.js +122 -0
- package/components/field/_macro.njk +2 -2
- package/components/field/_macro.spec.js +97 -0
- package/components/fieldset/_macro.njk +3 -3
- package/components/fieldset/_macro.spec.js +173 -0
- package/components/footer/_macro.njk +11 -48
- package/components/footer/_macro.spec.js +549 -0
- package/components/header/_macro.njk +2 -2
- package/components/header/_macro.spec.js +562 -0
- package/components/hero/_hero.scss +0 -3
- package/components/hero/_macro.njk +4 -4
- package/components/hero/_macro.spec.js +224 -0
- package/components/icons/_macro.njk +15 -15
- package/components/icons/_macro.spec.js +140 -0
- package/components/images/_macro.njk +1 -1
- package/components/images/_macro.spec.js +121 -0
- package/components/input/_input-type.scss +12 -5
- package/components/input/_macro.njk +4 -5
- package/components/input/_macro.spec.js +658 -0
- package/components/label/_macro.spec.js +189 -0
- package/components/language-selector/_macro.spec.js +129 -0
- package/components/lists/_list.scss +4 -0
- package/components/lists/_macro.njk +4 -7
- package/components/lists/_macro.spec.js +618 -0
- package/components/message/_macro.spec.js +137 -0
- package/components/message-list/_macro.njk +7 -7
- package/components/message-list/_macro.spec.js +159 -0
- package/components/metadata/_macro.spec.js +167 -0
- package/components/modal/_macro.njk +6 -6
- package/components/modal/_macro.spec.js +87 -0
- package/components/modal/modal.spec.js +59 -0
- package/components/mutually-exclusive/_macro.njk +1 -1
- package/components/mutually-exclusive/_macro.spec.js +182 -0
- package/components/mutually-exclusive/mutually-exclusive.checkboxes.spec.js +203 -0
- package/components/mutually-exclusive/mutually-exclusive.date.spec.js +142 -0
- package/components/mutually-exclusive/mutually-exclusive.duration.spec.js +141 -0
- package/components/mutually-exclusive/mutually-exclusive.email.spec.js +117 -0
- package/components/mutually-exclusive/mutually-exclusive.multiple-options.checkboxes.spec.js +213 -0
- package/components/mutually-exclusive/mutually-exclusive.number.spec.js +125 -0
- package/components/mutually-exclusive/mutually-exclusive.textarea.spec.js +131 -0
- package/components/navigation/_macro.njk +6 -6
- package/components/navigation/_macro.spec.js +327 -0
- package/components/navigation/navigation.dom.js +1 -1
- package/components/navigation/navigation.spec.js +232 -0
- package/components/pagination/_macro.njk +1 -1
- package/components/pagination/_macro.spec.js +411 -0
- package/components/panel/_macro.njk +6 -6
- package/components/panel/_macro.spec.js +423 -0
- package/components/password/_macro.spec.js +137 -0
- package/components/password/password.spec.js +40 -0
- package/components/phase-banner/_macro.spec.js +73 -0
- package/components/promotional-banner/_macro.spec.js +97 -0
- package/components/question/_macro.njk +25 -33
- package/components/question/_macro.spec.js +309 -0
- package/components/quote/_macro.spec.js +81 -0
- package/components/radios/_macro.njk +3 -6
- package/components/radios/_macro.spec.js +575 -0
- package/components/radios/radios.spec.js +180 -0
- package/components/related-content/_macro.njk +1 -0
- package/components/related-content/_macro.spec.js +142 -0
- package/components/relationships/_macro.spec.js +108 -0
- package/components/relationships/relationships.spec.js +84 -0
- package/components/reply/_macro.njk +2 -2
- package/components/reply/_macro.spec.js +69 -0
- package/components/reply/reply.spec.js +78 -0
- package/components/search/_macro.njk +14 -12
- package/components/search/_macro.spec.js +44 -0
- package/components/search/_search.scss +7 -7
- package/components/section-navigation/_macro.njk +7 -2
- package/components/section-navigation/_macro.spec.js +206 -0
- package/components/select/_macro.njk +3 -3
- package/components/select/_macro.spec.js +203 -0
- package/components/select/select.spec.js +56 -0
- package/components/share-page/_macro.njk +2 -2
- package/components/share-page/_macro.spec.js +110 -0
- package/components/skip-to-content/_macro.spec.js +57 -0
- package/components/skip-to-content/skip-to-content.spec.js +44 -0
- package/components/status/_macro.spec.js +77 -0
- package/components/summary/_macro.njk +5 -5
- package/components/summary/_macro.spec.js +472 -0
- package/components/table/_macro.njk +2 -2
- package/components/table/_macro.spec.js +557 -0
- package/components/table/table.spec.js +155 -0
- package/components/table-of-contents/_macro.njk +35 -35
- package/components/table-of-contents/_macro.spec.js +178 -0
- package/components/table-of-contents/toc.js +29 -25
- package/components/table-of-contents/toc.spec.js +61 -0
- package/components/tabs/_macro.njk +1 -1
- package/components/tabs/_macro.spec.js +79 -0
- package/components/tabs/tabs.spec.js +162 -0
- package/components/text-indent/_macro.spec.js +52 -0
- package/components/textarea/_macro.njk +5 -3
- package/components/textarea/_macro.spec.js +300 -0
- package/components/textarea/textarea.spec.js +98 -0
- package/components/timeline/_macro.njk +3 -3
- package/components/timeline/_macro.spec.js +81 -0
- package/components/timeout-modal/_macro.spec.js +68 -0
- package/components/timeout-modal/timeout-modal.spec.js +226 -0
- package/components/timeout-panel/_macro.njk +0 -1
- package/components/timeout-panel/_macro.spec.js +54 -0
- package/components/timeout-panel/timeout-panel.dom.js +1 -2
- package/components/timeout-panel/timeout-panel.spec.js +161 -0
- package/components/upload/_macro.spec.js +75 -0
- package/components/video/_macro.spec.js +34 -0
- package/css/census.css +1 -1
- package/css/main.css +1 -1
- package/js/cookies-settings.spec.js +154 -0
- package/package.json +10 -23
- package/scripts/main.es5.js +1 -1
- package/scripts/main.js +1 -1
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import puppeteer from 'puppeteer';
|
|
2
|
+
|
|
3
|
+
import { setViewport } from '../../tests/helpers/puppeteer';
|
|
4
|
+
import { renderComponent, setTestPage } from '../../tests/helpers/rendering';
|
|
5
|
+
|
|
6
|
+
const EXAMPLE_TABS = {
|
|
7
|
+
title: 'Example tabs',
|
|
8
|
+
tabs: [
|
|
9
|
+
{
|
|
10
|
+
title: 'Tab 1',
|
|
11
|
+
content: 'First content...',
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
title: 'Tab 2',
|
|
15
|
+
content: 'Second content...',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
title: 'Tab 3',
|
|
19
|
+
content: 'Third content...',
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
describe('script: tabs', () => {
|
|
25
|
+
afterEach(async () => {
|
|
26
|
+
// Clear viewport size and browser emulation after each test.
|
|
27
|
+
await jestPuppeteer.resetPage();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe('when the viewport is large', () => {
|
|
31
|
+
beforeEach(async () => {
|
|
32
|
+
await setViewport(page, { width: 1650, height: 1050 });
|
|
33
|
+
await setTestPage('/test', renderComponent('tabs', EXAMPLE_TABS));
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('has the "presentation" role assigned to tab list items', async () => {
|
|
37
|
+
const role = await page.$eval('.ons-tab__list-item', node => node.getAttribute('role'));
|
|
38
|
+
expect(role).toBe('presentation');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('has the "tab" role assigned to each tab', async () => {
|
|
42
|
+
const tabRoleValues = await page.$$eval('.ons-tab', nodes => nodes.map(node => node.getAttribute('role')));
|
|
43
|
+
|
|
44
|
+
expect(tabRoleValues).toEqual(['tab', 'tab', 'tab']);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('has "aria-controls" assigned to each tab with the corresponding panel id', async () => {
|
|
48
|
+
const ariaControlsValues = await page.$$eval('.ons-tab', nodes => nodes.map(node => node.getAttribute('aria-controls')));
|
|
49
|
+
|
|
50
|
+
expect(ariaControlsValues).toEqual(['tabId1', 'tabId2', 'tabId3']);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('has "aria-selected" assigned to the first tab', async () => {
|
|
54
|
+
const ariaSelectedValue = await page.$eval('.ons-tab', node => node.getAttribute('aria-selected'));
|
|
55
|
+
|
|
56
|
+
expect(ariaSelectedValue).toBe('true');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('has the "ons-tab--selected" class assigned to the first tab', async () => {
|
|
60
|
+
const hasClass = await page.$eval('.ons-tab', node => node.classList.contains('ons-tab--selected'));
|
|
61
|
+
|
|
62
|
+
expect(hasClass).toBe(true);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('has "tabindex" assigned to each tab', async () => {
|
|
66
|
+
const tabIndexValues = await page.$$eval('.ons-tab', nodes => nodes.map(node => node.getAttribute('tabindex')));
|
|
67
|
+
|
|
68
|
+
expect(tabIndexValues).toEqual(['0', '-1', '-1']);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('has only one visible tab panel', async () => {
|
|
72
|
+
const panelHiddenStates = await page.$$eval('.ons-tabs__panel', nodes =>
|
|
73
|
+
nodes.map(node => node.classList.contains('ons-tabs__panel--hidden')),
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
expect(panelHiddenStates).toEqual([false, true, true]);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe('when a tab is clicked', () => {
|
|
80
|
+
beforeEach(async () => {
|
|
81
|
+
await page.focus('#tabId2Item a');
|
|
82
|
+
await page.keyboard.press('Enter');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('is assigned a "tabindex" value', async () => {
|
|
86
|
+
const tabIndexValues = await page.$$eval('.ons-tab', nodes => nodes.map(node => node.getAttribute('tabindex')));
|
|
87
|
+
|
|
88
|
+
expect(tabIndexValues).toEqual(['-1', '0', '-1']);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('has the "aria-selected" attribute', async () => {
|
|
92
|
+
const ariaSelectedValue = await page.$eval('#tabId2Item a', node => node.getAttribute('aria-selected'));
|
|
93
|
+
|
|
94
|
+
expect(ariaSelectedValue).toBe('true');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('has the "ons-tab--selected" class assigned', async () => {
|
|
98
|
+
const hasClass = await page.$eval('#tabId2Item a', node => node.classList.contains('ons-tab--selected'));
|
|
99
|
+
|
|
100
|
+
expect(hasClass).toBe(true);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('shows the corresponding panel', async () => {
|
|
104
|
+
const panelHiddenStates = await page.$$eval('.ons-tabs__panel', nodes =>
|
|
105
|
+
nodes.map(node => node.classList.contains('ons-tabs__panel--hidden')),
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
expect(panelHiddenStates).toEqual([true, false, true]);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe('when the right arrow key is pressed', () => {
|
|
113
|
+
it('focuses the next tab', async () => {
|
|
114
|
+
await page.focus('#tabId2Item a');
|
|
115
|
+
await page.keyboard.press('ArrowRight');
|
|
116
|
+
|
|
117
|
+
const activeElementId = await page.evaluate(() => document.activeElement.parentElement.id);
|
|
118
|
+
expect(activeElementId).toBe('tabId3Item');
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe('when the left arrow key is pressed', () => {
|
|
123
|
+
it('focuses the previous tab', async () => {
|
|
124
|
+
await page.focus('#tabId2Item a');
|
|
125
|
+
await page.keyboard.press('ArrowLeft');
|
|
126
|
+
|
|
127
|
+
const activeElementId = await page.evaluate(() => document.activeElement.parentElement.id);
|
|
128
|
+
expect(activeElementId).toBe('tabId1Item');
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe('when the viewport is small', () => {
|
|
134
|
+
beforeEach(async () => {
|
|
135
|
+
await page.emulate(puppeteer.devices['iPhone X']);
|
|
136
|
+
|
|
137
|
+
await setTestPage('/test', renderComponent('tabs', EXAMPLE_TABS));
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('has no aria attributes on tabs', async () => {
|
|
141
|
+
const tabElements = await page.$$('.ons-tab');
|
|
142
|
+
for (let i = 0; i < 3; ++i) {
|
|
143
|
+
const hasRoleAttribute = await tabElements[i].evaluate(node => node.getAttribute('role') !== null);
|
|
144
|
+
expect(hasRoleAttribute).toBe(false);
|
|
145
|
+
|
|
146
|
+
const hasAriaControlsAttribute = await tabElements[i].evaluate(node => node.getAttribute('aria-controls') !== null);
|
|
147
|
+
expect(hasAriaControlsAttribute).toBe(false);
|
|
148
|
+
|
|
149
|
+
const hasAriaSelectedAttribute = await tabElements[i].evaluate(node => node.getAttribute('aria-selected') !== null);
|
|
150
|
+
expect(hasAriaSelectedAttribute).toBe(false);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('has no hidden tab panels', async () => {
|
|
155
|
+
const panelCount = await page.$$eval('.ons-tabs__panel', nodes => nodes.length);
|
|
156
|
+
expect(panelCount).toBe(3);
|
|
157
|
+
|
|
158
|
+
const hiddenPanelCount = await page.$$eval('.ons-tabs__panel--hidden', nodes => nodes.length);
|
|
159
|
+
expect(hiddenPanelCount).toBe(0);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/** @jest-environment jsdom */
|
|
2
|
+
|
|
3
|
+
import * as cheerio from 'cheerio';
|
|
4
|
+
|
|
5
|
+
import axe from '../../tests/helpers/axe';
|
|
6
|
+
import { renderComponent } from '../../tests/helpers/rendering';
|
|
7
|
+
|
|
8
|
+
describe('macro: text-indent', () => {
|
|
9
|
+
describe('mode: text parameter', () => {
|
|
10
|
+
it('passes jest-axe checks', async () => {
|
|
11
|
+
const $ = cheerio.load(
|
|
12
|
+
renderComponent('text-indent', {
|
|
13
|
+
text: 'Example text...',
|
|
14
|
+
}),
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
const results = await axe($.html());
|
|
18
|
+
expect(results).toHaveNoViolations();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('has expected text', async () => {
|
|
22
|
+
const $ = cheerio.load(
|
|
23
|
+
renderComponent('text-indent', {
|
|
24
|
+
text: 'Example text...',
|
|
25
|
+
}),
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
const content = $('.ons-text-indent')
|
|
29
|
+
.text()
|
|
30
|
+
.trim();
|
|
31
|
+
expect(content).toBe('Example text...');
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe('mode: called with content', () => {
|
|
36
|
+
it('passes jest-axe checks', async () => {
|
|
37
|
+
const $ = cheerio.load(renderComponent('text-indent', {}, 'Example content...'));
|
|
38
|
+
|
|
39
|
+
const results = await axe($.html());
|
|
40
|
+
expect(results).toHaveNoViolations();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('has expected text', async () => {
|
|
44
|
+
const $ = cheerio.load(renderComponent('text-indent', {}, 'Example content...'));
|
|
45
|
+
|
|
46
|
+
const content = $('.ons-text-indent')
|
|
47
|
+
.text()
|
|
48
|
+
.trim();
|
|
49
|
+
expect(content).toBe('Example content...');
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -19,8 +19,10 @@
|
|
|
19
19
|
|
|
20
20
|
<textarea
|
|
21
21
|
id="{{ params.id }}"
|
|
22
|
-
class="ons-input ons-input--textarea {% if params.error is defined and params.error %} ons-input--error {% endif %}{% if params.charCheckLimit is defined and params.charCheckLimit and params.charCheckLimit.limit is defined and params.charCheckLimit.limit %} ons-js-char-limit-input{% endif %}{{ textareaExclusiveClass }} {{ params.classes }} {% if params.width is defined and params.width %}ons-input--w-{{ params.width }}{% endif %}"
|
|
23
|
-
|
|
22
|
+
class="ons-input ons-input--textarea {% if params.error is defined and params.error %} ons-input--error {% endif %}{% if params.charCheckLimit is defined and params.charCheckLimit and params.charCheckLimit.limit is defined and params.charCheckLimit.limit %} ons-js-char-limit-input{% endif %}{{ textareaExclusiveClass }} {{ params.classes if params.classes is defined and params.classes }} {% if params.width is defined and params.width %}ons-input--w-{{ params.width }}{% endif %}"
|
|
23
|
+
{% if params.name is defined and params.name %}
|
|
24
|
+
name="{{ params.name }}"
|
|
25
|
+
{% endif %}
|
|
24
26
|
rows="{{ params.rows | default(8) }}"
|
|
25
27
|
{% if params.charCheckLimit is defined and params.charCheckLimit and params.charCheckLimit.limit is defined and params.charCheckLimit.limit %}
|
|
26
28
|
maxlength="{{ params.charCheckLimit.limit }}"
|
|
@@ -28,7 +30,7 @@
|
|
|
28
30
|
aria-describedby="{{ params.id }}-lim-remaining"
|
|
29
31
|
{% endif %}
|
|
30
32
|
{% if params.attributes is defined and params.attributes %}{% for attribute, value in (params.attributes.items() if params.attributes is mapping and params.attributes.items else params.attributes) %}{{ attribute }}{% if value is defined and value %}="{{ value }}"{% endif %} {% endfor %}{% endif %}
|
|
31
|
-
>{{ params.value }}</textarea>
|
|
33
|
+
>{{ params.value if params.value is defined and params.value }}</textarea>
|
|
32
34
|
|
|
33
35
|
{% if params.charCheckLimit is defined and params.charCheckLimit and params.charCheckLimit.limit is defined and params.charCheckLimit.limit %}
|
|
34
36
|
{% call onsCharLimit({
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
/** @jest-environment jsdom */
|
|
2
|
+
|
|
3
|
+
import * as cheerio from 'cheerio';
|
|
4
|
+
|
|
5
|
+
import axe from '../../tests/helpers/axe';
|
|
6
|
+
import { renderComponent, templateFaker } from '../../tests/helpers/rendering';
|
|
7
|
+
|
|
8
|
+
const EXAMPLE_TEXTAREA = {
|
|
9
|
+
id: 'example-id',
|
|
10
|
+
name: 'feedback',
|
|
11
|
+
label: {
|
|
12
|
+
classes: 'extra-label-class',
|
|
13
|
+
text: 'Please provide some feedback',
|
|
14
|
+
description: 'For example, describe any difficulties you experienced in the use of this service',
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const EXAMPLE_TEXTAREA_WITH_CHARACTER_LIMIT = {
|
|
19
|
+
...EXAMPLE_TEXTAREA,
|
|
20
|
+
width: 30,
|
|
21
|
+
charCheckLimit: {
|
|
22
|
+
limit: 200,
|
|
23
|
+
charCountSingular: 'You have {x} character remaining',
|
|
24
|
+
charCountPlural: 'You have {x} characters remaining',
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const EXAMPLE_TEXTAREA_WITH_ERROR = {
|
|
29
|
+
...EXAMPLE_TEXTAREA,
|
|
30
|
+
error: {
|
|
31
|
+
id: 'feedback-error',
|
|
32
|
+
text: 'Enter your feedback',
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const EXAMPLE_TEXTAREA_WITH_MUTUALLY_EXCLUSIVE_WITH_ERROR = {
|
|
37
|
+
...EXAMPLE_TEXTAREA_WITH_ERROR,
|
|
38
|
+
dontWrap: true,
|
|
39
|
+
mutuallyExclusive: {
|
|
40
|
+
or: 'Or',
|
|
41
|
+
deselectMessage: 'Selecting this will clear your feedback',
|
|
42
|
+
deselectGroupAdjective: 'cleared',
|
|
43
|
+
deselectExclusiveOptionAdjective: 'deselected',
|
|
44
|
+
exclusiveOptions: [
|
|
45
|
+
{
|
|
46
|
+
id: 'feedback-exclusive-option',
|
|
47
|
+
name: 'no-feedback',
|
|
48
|
+
value: 'no-feedback',
|
|
49
|
+
label: {
|
|
50
|
+
text: 'I dont want to provide feedback',
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
describe('macro: textarea', () => {
|
|
58
|
+
it('passes jest-axe checks', async () => {
|
|
59
|
+
const $ = cheerio.load(renderComponent('textarea', EXAMPLE_TEXTAREA));
|
|
60
|
+
|
|
61
|
+
const results = await axe($.html());
|
|
62
|
+
expect(results).toHaveNoViolations();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('has the provided `id` attribute', () => {
|
|
66
|
+
const $ = cheerio.load(renderComponent('textarea', EXAMPLE_TEXTAREA));
|
|
67
|
+
|
|
68
|
+
expect($('.ons-input--textarea').attr('id')).toBe('example-id');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('has additionally provided classes', () => {
|
|
72
|
+
const $ = cheerio.load(
|
|
73
|
+
renderComponent('textarea', {
|
|
74
|
+
...EXAMPLE_TEXTAREA,
|
|
75
|
+
classes: 'extra-class another-extra-class',
|
|
76
|
+
}),
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
expect($('.ons-input--textarea').hasClass('extra-class')).toBe(true);
|
|
80
|
+
expect($('.ons-input--textarea').hasClass('another-extra-class')).toBe(true);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('has additionally provided `attributes`', () => {
|
|
84
|
+
const $ = cheerio.load(
|
|
85
|
+
renderComponent('textarea', {
|
|
86
|
+
...EXAMPLE_TEXTAREA,
|
|
87
|
+
attributes: {
|
|
88
|
+
a: 123,
|
|
89
|
+
b: 456,
|
|
90
|
+
},
|
|
91
|
+
}),
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
expect($('.ons-input--textarea').attr('a')).toBe('123');
|
|
95
|
+
expect($('.ons-input--textarea').attr('b')).toBe('456');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('has the provided `name` attribute', () => {
|
|
99
|
+
const $ = cheerio.load(renderComponent('textarea', EXAMPLE_TEXTAREA));
|
|
100
|
+
|
|
101
|
+
expect($('.ons-input--textarea').attr('name')).toBe('feedback');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('defaults to having 8 rows of text', () => {
|
|
105
|
+
const $ = cheerio.load(renderComponent('textarea', EXAMPLE_TEXTAREA));
|
|
106
|
+
|
|
107
|
+
expect($('.ons-input--textarea').attr('rows')).toBe('8');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('has the provided number of rows', () => {
|
|
111
|
+
const $ = cheerio.load(
|
|
112
|
+
renderComponent('textarea', {
|
|
113
|
+
...EXAMPLE_TEXTAREA,
|
|
114
|
+
rows: 12,
|
|
115
|
+
}),
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
expect($('.ons-input--textarea').attr('rows')).toBe('12');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('has a class representing the provided `width`', () => {
|
|
122
|
+
const $ = cheerio.load(
|
|
123
|
+
renderComponent('textarea', {
|
|
124
|
+
...EXAMPLE_TEXTAREA,
|
|
125
|
+
width: 10,
|
|
126
|
+
}),
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
expect($('.ons-input--textarea').hasClass('ons-input--w-10')).toBe(true);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('has no value initially', () => {
|
|
133
|
+
const $ = cheerio.load(renderComponent('textarea', EXAMPLE_TEXTAREA));
|
|
134
|
+
|
|
135
|
+
expect($('.ons-input--textarea').text()).toBe('');
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('has the provided initial value', () => {
|
|
139
|
+
const $ = cheerio.load(
|
|
140
|
+
renderComponent('textarea', {
|
|
141
|
+
...EXAMPLE_TEXTAREA,
|
|
142
|
+
value: 'Initial value',
|
|
143
|
+
}),
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
expect($('.ons-input--textarea').text()).toBe('Initial value');
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('does not render label component when not specified', () => {
|
|
150
|
+
const faker = templateFaker();
|
|
151
|
+
const labelSpy = faker.spy('label');
|
|
152
|
+
|
|
153
|
+
faker.renderComponent('textarea', {
|
|
154
|
+
...EXAMPLE_TEXTAREA,
|
|
155
|
+
label: undefined,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
expect(labelSpy.occurrences.length).toBe(0);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('renders label using the label component', () => {
|
|
162
|
+
const faker = templateFaker();
|
|
163
|
+
const labelSpy = faker.spy('label');
|
|
164
|
+
|
|
165
|
+
faker.renderComponent('textarea', EXAMPLE_TEXTAREA);
|
|
166
|
+
|
|
167
|
+
expect(labelSpy.occurrences).toContainEqual({
|
|
168
|
+
for: 'example-id',
|
|
169
|
+
text: 'Please provide some feedback',
|
|
170
|
+
description: 'For example, describe any difficulties you experienced in the use of this service',
|
|
171
|
+
classes: 'extra-label-class',
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('does not render character limit component when not specified', () => {
|
|
176
|
+
const faker = templateFaker();
|
|
177
|
+
const charCheckLimitSpy = faker.spy('char-check-limit');
|
|
178
|
+
|
|
179
|
+
faker.renderComponent('textarea', EXAMPLE_TEXTAREA);
|
|
180
|
+
|
|
181
|
+
expect(charCheckLimitSpy.occurrences.length).toBe(0);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('renders field component', () => {
|
|
185
|
+
const faker = templateFaker();
|
|
186
|
+
const fieldSpy = faker.spy('field');
|
|
187
|
+
|
|
188
|
+
faker.renderComponent('textarea', {
|
|
189
|
+
...EXAMPLE_TEXTAREA_WITH_ERROR,
|
|
190
|
+
dontWrap: true,
|
|
191
|
+
fieldId: 'example-field-id',
|
|
192
|
+
fieldClasses: 'extra-field-class',
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
expect(fieldSpy.occurrences).toContainEqual({
|
|
196
|
+
id: 'example-field-id',
|
|
197
|
+
classes: 'extra-field-class',
|
|
198
|
+
dontWrap: true,
|
|
199
|
+
error: EXAMPLE_TEXTAREA_WITH_ERROR.error,
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
describe('with character limit', () => {
|
|
204
|
+
it('has the `ons-js-char-limit-input` class', () => {
|
|
205
|
+
const $ = cheerio.load(renderComponent('textarea', EXAMPLE_TEXTAREA_WITH_CHARACTER_LIMIT));
|
|
206
|
+
|
|
207
|
+
expect($('.ons-input--textarea').hasClass('ons-js-char-limit-input')).toBe(true);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('has the provided maximum length', () => {
|
|
211
|
+
const $ = cheerio.load(renderComponent('textarea', EXAMPLE_TEXTAREA_WITH_CHARACTER_LIMIT));
|
|
212
|
+
|
|
213
|
+
expect($('.ons-input--textarea').attr('maxlength')).toBe('200');
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('has data attribute which references the character limit component', () => {
|
|
217
|
+
const $ = cheerio.load(renderComponent('textarea', EXAMPLE_TEXTAREA_WITH_CHARACTER_LIMIT));
|
|
218
|
+
|
|
219
|
+
expect($('.ons-input--textarea').attr('data-char-limit-ref')).toBe('example-id-lim-remaining');
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('has `aria-describedby` attribute which references the character limit component', () => {
|
|
223
|
+
const $ = cheerio.load(renderComponent('textarea', EXAMPLE_TEXTAREA_WITH_CHARACTER_LIMIT));
|
|
224
|
+
|
|
225
|
+
expect($('.ons-input--textarea').attr('aria-describedby')).toBe('example-id-lim-remaining');
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('renders character limit component', () => {
|
|
229
|
+
const faker = templateFaker();
|
|
230
|
+
const charCheckLimitSpy = faker.spy('char-check-limit');
|
|
231
|
+
|
|
232
|
+
faker.renderComponent('textarea', EXAMPLE_TEXTAREA_WITH_CHARACTER_LIMIT);
|
|
233
|
+
|
|
234
|
+
expect(charCheckLimitSpy.occurrences).toContainEqual({
|
|
235
|
+
id: 'example-id-lim',
|
|
236
|
+
limit: 200,
|
|
237
|
+
charCountSingular: 'You have {x} character remaining',
|
|
238
|
+
charCountPlural: 'You have {x} characters remaining',
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
describe('with error', () => {
|
|
244
|
+
it('has the `error` modifier class', () => {
|
|
245
|
+
const $ = cheerio.load(renderComponent('textarea', EXAMPLE_TEXTAREA_WITH_ERROR));
|
|
246
|
+
|
|
247
|
+
expect($('.ons-input--textarea').hasClass('ons-input--error')).toBe(true);
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
describe('mutually exclusive', () => {
|
|
252
|
+
it('has the `ons-js-exclusive-group-item` class', () => {
|
|
253
|
+
const $ = cheerio.load(renderComponent('textarea', EXAMPLE_TEXTAREA_WITH_MUTUALLY_EXCLUSIVE_WITH_ERROR));
|
|
254
|
+
|
|
255
|
+
expect($('.ons-input--textarea').hasClass('ons-js-exclusive-group-item')).toBe(true);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('renders mutually exclusive component', () => {
|
|
259
|
+
const faker = templateFaker();
|
|
260
|
+
const mutuallyExclusiveSpy = faker.spy('mutually-exclusive');
|
|
261
|
+
|
|
262
|
+
faker.renderComponent('textarea', {
|
|
263
|
+
...EXAMPLE_TEXTAREA_WITH_MUTUALLY_EXCLUSIVE_WITH_ERROR,
|
|
264
|
+
fieldId: 'example-field-id',
|
|
265
|
+
fieldClasses: 'extra-field-class',
|
|
266
|
+
legend: 'Legend text',
|
|
267
|
+
legendClasses: 'extra-legend-class',
|
|
268
|
+
description: 'Example description text',
|
|
269
|
+
legendIsQuestionTitle: true,
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
expect(mutuallyExclusiveSpy.occurrences).toContainEqual({
|
|
273
|
+
id: 'example-field-id',
|
|
274
|
+
classes: 'extra-field-class',
|
|
275
|
+
legend: 'Legend text',
|
|
276
|
+
legendClasses: 'extra-legend-class',
|
|
277
|
+
description: 'Example description text',
|
|
278
|
+
dontWrap: true,
|
|
279
|
+
legendIsQuestionTitle: true,
|
|
280
|
+
exclusiveOptions: EXAMPLE_TEXTAREA_WITH_MUTUALLY_EXCLUSIVE_WITH_ERROR.mutuallyExclusive.exclusiveOptions,
|
|
281
|
+
or: 'Or',
|
|
282
|
+
deselectMessage: 'Selecting this will clear your feedback',
|
|
283
|
+
deselectGroupAdjective: 'cleared',
|
|
284
|
+
deselectExclusiveOptionAdjective: 'deselected',
|
|
285
|
+
error: EXAMPLE_TEXTAREA_WITH_MUTUALLY_EXCLUSIVE_WITH_ERROR.error,
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it('still renders field component', () => {
|
|
290
|
+
const faker = templateFaker();
|
|
291
|
+
const fieldSpy = faker.spy('field');
|
|
292
|
+
|
|
293
|
+
faker.renderComponent('textarea', EXAMPLE_TEXTAREA_WITH_MUTUALLY_EXCLUSIVE_WITH_ERROR);
|
|
294
|
+
|
|
295
|
+
expect(fieldSpy.occurrences).toContainEqual({
|
|
296
|
+
error: EXAMPLE_TEXTAREA_WITH_MUTUALLY_EXCLUSIVE_WITH_ERROR.error,
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
});
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { renderComponent, setTestPage } from '../../tests/helpers/rendering';
|
|
2
|
+
|
|
3
|
+
describe('script: textarea', () => {
|
|
4
|
+
describe('character limit', () => {
|
|
5
|
+
beforeEach(async () => {
|
|
6
|
+
await setTestPage(
|
|
7
|
+
'/test',
|
|
8
|
+
renderComponent('textarea', {
|
|
9
|
+
id: 'example-textarea',
|
|
10
|
+
name: 'feedback-limited',
|
|
11
|
+
width: '30',
|
|
12
|
+
label: {
|
|
13
|
+
text: 'Please provide some feedback',
|
|
14
|
+
description: 'For example describe any difficulties you experienced in the use of this service',
|
|
15
|
+
},
|
|
16
|
+
charCheckLimit: {
|
|
17
|
+
limit: 50,
|
|
18
|
+
charCountSingular: 'You have {x} character remaining',
|
|
19
|
+
charCountPlural: 'You have {x} characters remaining',
|
|
20
|
+
},
|
|
21
|
+
}),
|
|
22
|
+
);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe('Given that the char limit helper has initialised correctly', () => {
|
|
26
|
+
it('the char limit readout should be visible', async () => {
|
|
27
|
+
const hasClass = await page.$eval('#example-textarea-lim-remaining', node => node.classList.contains('ons-u-d-no'));
|
|
28
|
+
expect(hasClass).toBe(false);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('Given that the user has not typed into the textarea', () => {
|
|
33
|
+
describe('when the user types into the textarea', () => {
|
|
34
|
+
beforeEach(async () => {
|
|
35
|
+
await page.type('#example-textarea', 'Lorem ipsum dolor.\nMorbi rhoncus amet.');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('then the characters remaining readout reflect the number of characters remaining', async () => {
|
|
39
|
+
const readout = await page.$eval('#example-textarea-lim-remaining', node => node.textContent);
|
|
40
|
+
expect(readout).toBe('You have 12 characters remaining');
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe('when the user reaches/exceeds the maxlength of the textarea', () => {
|
|
45
|
+
beforeEach(async () => {
|
|
46
|
+
await page.type('#example-textarea', 'Lorem ipsum dolor sit amet, consectetur porttitor.');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('then the characters remaining readout reflect the number of characters remaining', async () => {
|
|
50
|
+
const readout = await page.$eval('#example-textarea-lim-remaining', node => node.textContent);
|
|
51
|
+
expect(readout).toBe('You have 0 characters remaining');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('then the textarea should be given limit reached classes', async () => {
|
|
55
|
+
const hasClass = await page.$eval('#example-textarea', node => node.classList.contains('ons-input--limit-reached'));
|
|
56
|
+
expect(hasClass).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('then the readout should be given limit reached classes', async () => {
|
|
60
|
+
const hasClass = await page.$eval('#example-textarea-lim-remaining', node =>
|
|
61
|
+
node.classList.contains('ons-input__limit--reached'),
|
|
62
|
+
);
|
|
63
|
+
expect(hasClass).toBe(true);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe('Given that the user has reached/exceeded the maxlength of the textarea', () => {
|
|
69
|
+
beforeEach(async () => {
|
|
70
|
+
await page.type('#example-textarea', 'Lorem ipsum dolor sit amet, consectetur porttitor.');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('when the user removes a character', () => {
|
|
74
|
+
beforeEach(async () => {
|
|
75
|
+
await page.focus('#example-textarea');
|
|
76
|
+
await page.keyboard.press('Backspace');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('then the characters remaining readout reflect the number of characters remaining', async () => {
|
|
80
|
+
const readout = await page.$eval('#example-textarea-lim-remaining', node => node.textContent);
|
|
81
|
+
expect(readout).toBe('You have 1 character remaining');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('then the textarea should not be given limit reached classes', async () => {
|
|
85
|
+
const hasClass = await page.$eval('#example-textarea', node => node.classList.contains('ons-input--limit-reached'));
|
|
86
|
+
expect(hasClass).toBe(false);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('then the readout should not be given limit reached classes', async () => {
|
|
90
|
+
const hasClass = await page.$eval('#example-textarea-lim-remaining', node =>
|
|
91
|
+
node.classList.contains('ons-input__limit--reached'),
|
|
92
|
+
);
|
|
93
|
+
expect(hasClass).toBe(false);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
});
|