-
diff --git a/src/components/message/_macro.spec.js b/src/components/message/_macro.spec.js
index 4e40ff003b..335cfabdcf 100644
--- a/src/components/message/_macro.spec.js
+++ b/src/components/message/_macro.spec.js
@@ -6,107 +6,107 @@ import axe from '../../tests/helpers/axe';
import { renderComponent } from '../../tests/helpers/rendering';
const EXAMPLE_MESSAGE_MINIMAL = {
- variant: 'sent',
- fromLabel: 'From',
- fromValue: 'Example Sender',
- sentLabel: 'Date sent',
- sentValue: 'Tue 4 Jul 2020 at 7:47',
+ variant: 'sent',
+ fromLabel: 'From',
+ fromValue: 'Example Sender',
+ sentLabel: 'Date sent',
+ sentValue: 'Tue 4 Jul 2020 at 7:47',
};
const EXAMPLE_MESSAGE = {
- ...EXAMPLE_MESSAGE_MINIMAL,
- unreadLink: 'https://example.com/message/1',
- unreadLinkText: 'Unread message',
- id: 'message1',
- fromId: 'from1',
- sentId: 'sent1',
- unreadLinkId: 'unreadLink1',
- messageID: 'messageBody1',
+ ...EXAMPLE_MESSAGE_MINIMAL,
+ unreadLink: 'https://example.com/message/1',
+ unreadLinkText: 'Unread message',
+ id: 'message1',
+ fromId: 'from1',
+ sentId: 'sent1',
+ unreadLinkId: 'unreadLink1',
+ messageID: 'messageBody1',
};
describe('macro: message', () => {
- it('passes jest-axe checks when all parameters are provided', async () => {
- const $ = cheerio.load(renderComponent('message', EXAMPLE_MESSAGE, ['Message content...']));
+ it('passes jest-axe checks when all parameters are provided', async () => {
+ const $ = cheerio.load(renderComponent('message', EXAMPLE_MESSAGE, ['Message content...']));
- const results = await axe($.html());
- expect(results).toHaveNoViolations();
- });
+ const results = await axe($.html());
+ expect(results).toHaveNoViolations();
+ });
- it.each([
- ['sent', 'ons-message--sent'],
- ['received', 'ons-message--received'],
- ])('has appropriate class for provided `variant` (%s -> %s)', (variant, expectedClass) => {
- const $ = cheerio.load(
- renderComponent(
- 'message',
- {
- ...EXAMPLE_MESSAGE_MINIMAL,
- variant,
- },
- ['Message content...'],
- ),
- );
+ it.each([
+ ['sent', 'ons-message--sent'],
+ ['received', 'ons-message--received'],
+ ])('has appropriate class for provided `variant` (%s -> %s)', (variant, expectedClass) => {
+ const $ = cheerio.load(
+ renderComponent(
+ 'message',
+ {
+ ...EXAMPLE_MESSAGE_MINIMAL,
+ variant,
+ },
+ ['Message content...'],
+ ),
+ );
- expect($('.ons-message').hasClass(expectedClass)).toBe(true);
- });
+ expect($('.ons-message').hasClass(expectedClass)).toBe(true);
+ });
- it('has `id` attribute on `.ons-message__metadata` using the provided value', () => {
- const $ = cheerio.load(renderComponent('message', EXAMPLE_MESSAGE, ['Message content...']));
+ it('has `id` attribute on `.ons-message__metadata` using the provided value', () => {
+ const $ = cheerio.load(renderComponent('message', EXAMPLE_MESSAGE, ['Message content...']));
- expect($('.ons-message__metadata').attr('id')).toBe('message1');
- });
+ expect($('.ons-message__metadata').attr('id')).toBe('message1');
+ });
- it('has the provided `fromLabel`', () => {
- const $ = cheerio.load(renderComponent('message', EXAMPLE_MESSAGE_MINIMAL, ['Message content...']));
+ it('has the provided `fromLabel`', () => {
+ const $ = cheerio.load(renderComponent('message', EXAMPLE_MESSAGE_MINIMAL, ['Message content...']));
- expect($('.ons-message__sender .ons-message__term').text().trim()).toBe('From:');
- });
+ expect($('.ons-message__sender .ons-message__term').text().trim()).toBe('From:');
+ });
- it('has the provided `fromValue`', () => {
- const $ = cheerio.load(renderComponent('message', EXAMPLE_MESSAGE_MINIMAL, ['Message content...']));
+ it('has the provided `fromValue`', () => {
+ const $ = cheerio.load(renderComponent('message', EXAMPLE_MESSAGE_MINIMAL, ['Message content...']));
- expect($('.ons-message__sender .ons-message__value').text().trim()).toBe('Example Sender');
- });
+ expect($('.ons-message__sender .ons-message__value').text().trim()).toBe('Example Sender');
+ });
- it('has the provided `fromId`', () => {
- const $ = cheerio.load(renderComponent('message', EXAMPLE_MESSAGE, ['Message content...']));
+ it('has the provided `fromId`', () => {
+ const $ = cheerio.load(renderComponent('message', EXAMPLE_MESSAGE, ['Message content...']));
- expect($('.ons-message__sender .ons-message__value').attr('id')).toBe('from1');
- });
+ expect($('.ons-message__sender .ons-message__value').attr('id')).toBe('from1');
+ });
- it('has the provided `sentLabel`', () => {
- const $ = cheerio.load(renderComponent('message', EXAMPLE_MESSAGE_MINIMAL, ['Message content...']));
+ it('has the provided `sentLabel`', () => {
+ const $ = cheerio.load(renderComponent('message', EXAMPLE_MESSAGE_MINIMAL, ['Message content...']));
- expect($('.ons-message__timestamp .ons-message__term').text().trim()).toBe('Date sent:');
- });
+ expect($('.ons-message__timestamp .ons-message__term').text().trim()).toBe('Date sent:');
+ });
- it('has the provided `sentValue`', () => {
- const $ = cheerio.load(renderComponent('message', EXAMPLE_MESSAGE_MINIMAL, ['Message content...']));
+ it('has the provided `sentValue`', () => {
+ const $ = cheerio.load(renderComponent('message', EXAMPLE_MESSAGE_MINIMAL, ['Message content...']));
- expect($('.ons-message__timestamp .ons-message__value').text().trim()).toBe('Tue 4 Jul 2020 at 7:47');
- });
+ expect($('.ons-message__timestamp .ons-message__value').text().trim()).toBe('Tue 4 Jul 2020 at 7:47');
+ });
- it('has the provided `sentId`', () => {
- const $ = cheerio.load(renderComponent('message', EXAMPLE_MESSAGE, ['Message content...']));
+ it('has the provided `sentId`', () => {
+ const $ = cheerio.load(renderComponent('message', EXAMPLE_MESSAGE, ['Message content...']));
- expect($('.ons-message__timestamp .ons-message__value').attr('id')).toBe('sent1');
- });
+ expect($('.ons-message__timestamp .ons-message__value').attr('id')).toBe('sent1');
+ });
- it('has the provided `unreadLink`', () => {
- const $ = cheerio.load(renderComponent('message', EXAMPLE_MESSAGE, ['Message content...']));
+ it('has the provided `unreadLink`', () => {
+ const $ = cheerio.load(renderComponent('message', EXAMPLE_MESSAGE, ['Message content...']));
- expect($('.ons-message__unread-link').attr('href')).toBe('https://example.com/message/1');
- });
+ expect($('.ons-message__unread-link').attr('href')).toBe('https://example.com/message/1');
+ });
- it('has the provided `unreadLinkText`', () => {
- const $ = cheerio.load(renderComponent('message', EXAMPLE_MESSAGE, ['Message content...']));
+ it('has the provided `unreadLinkText`', () => {
+ const $ = cheerio.load(renderComponent('message', EXAMPLE_MESSAGE, ['Message content...']));
- expect($('.ons-message__unread-link').text().trim()).toBe('Unread message');
- });
+ expect($('.ons-message__unread-link').text().trim()).toBe('Unread message');
+ });
- it('has the message content', () => {
- const $ = cheerio.load(renderComponent('message', EXAMPLE_MESSAGE_MINIMAL, ['Message content...']));
+ it('has the message content', () => {
+ const $ = cheerio.load(renderComponent('message', EXAMPLE_MESSAGE_MINIMAL, ['Message content...']));
- expect($('.ons-message__body').text().trim()).toBe('Message content...');
- });
+ expect($('.ons-message__body').text().trim()).toBe('Message content...');
+ });
});
diff --git a/src/components/message/_message.scss b/src/components/message/_message.scss
index 57a12af330..55720fbbb6 100644
--- a/src/components/message/_message.scss
+++ b/src/components/message/_message.scss
@@ -1,55 +1,55 @@
@use 'sass:math';
.ons-message {
- border-radius: math.div($grid-gutters, 2);
- margin-bottom: 2rem;
- outline: 1px solid transparent; // Add transparent outline because Windows High Contrast Mode doesn't show background
+ border-radius: math.div($grid-gutters, 2);
+ margin-bottom: 2rem;
+ outline: 1px solid transparent; // Add transparent outline because Windows High Contrast Mode doesn't show background
- &--sent {
- background: var(--ons-color-info-tint);
+ &--sent {
+ background: var(--ons-color-info-tint);
- @include mq(m) {
- margin-left: 4rem;
+ @include mq(m) {
+ margin-left: 4rem;
+ }
}
- }
- &--received {
- background: var(--ons-color-success-tint);
+ &--received {
+ background: var(--ons-color-success-tint);
- @include mq(m) {
- margin-right: 4rem;
+ @include mq(m) {
+ margin-right: 4rem;
+ }
}
- }
- &__head {
- border-bottom: 1px solid var(--ons-color-borders);
- padding: $grid-gutters;
- position: relative;
- }
+ &__head {
+ border-bottom: 1px solid var(--ons-color-borders);
+ padding: $grid-gutters;
+ position: relative;
+ }
- &__metadata {
- margin: 0;
+ &__metadata {
+ margin: 0;
- @include mq(m) {
- width: 65%;
+ @include mq(m) {
+ width: 65%;
+ }
}
- }
-
- &__term,
- &__value {
- display: inline-block;
- margin: 0;
- }
-
- &__unread-link {
- @include mq(m) {
- position: absolute;
- right: 1rem;
- top: 1rem;
+
+ &__term,
+ &__value {
+ display: inline-block;
+ margin: 0;
}
- }
- &__body {
- padding: $grid-gutters;
- }
+ &__unread-link {
+ @include mq(m) {
+ position: absolute;
+ right: 1rem;
+ top: 1rem;
+ }
+ }
+
+ &__body {
+ padding: $grid-gutters;
+ }
}
diff --git a/src/components/modal/_macro.spec.js b/src/components/modal/_macro.spec.js
index bc7ac57657..44f66596ce 100644
--- a/src/components/modal/_macro.spec.js
+++ b/src/components/modal/_macro.spec.js
@@ -6,76 +6,76 @@ import axe from '../../tests/helpers/axe';
import { renderComponent, templateFaker } from '../../tests/helpers/rendering';
const EXAMPLE_MODAL_BASIC = {
- title: 'Modal title',
- body: 'Modal body text',
- btnText: 'Modal button',
+ title: 'Modal title',
+ body: 'Modal body text',
+ btnText: 'Modal button',
};
describe('macro: modal', () => {
- it('passes jest-axe checks with', async () => {
- const $ = cheerio.load(renderComponent('modal', EXAMPLE_MODAL_BASIC));
-
- const results = await axe($.html());
- expect(results).toHaveNoViolations();
- });
-
- it('has expected `title`', () => {
- const $ = cheerio.load(renderComponent('modal', EXAMPLE_MODAL_BASIC));
-
- const title = $('.ons-modal__title').html().trim();
- expect(title).toBe('Modal title');
- });
-
- it('has expected `body`', () => {
- const $ = cheerio.load(renderComponent('modal', EXAMPLE_MODAL_BASIC));
-
- const title = $('.ons-modal__body').html().trim();
- expect(title).toBe('Modal body text');
- });
-
- it('has the default ID', () => {
- const $ = cheerio.load(renderComponent('modal', EXAMPLE_MODAL_BASIC));
- expect($('#dialog').length).toBe(1);
- });
-
- it('has the provided `id`', () => {
- const $ = cheerio.load(renderComponent('modal', { ...EXAMPLE_MODAL_BASIC, id: 'modal-id' }));
- expect($('#modal-id').length).toBe(1);
- });
-
- it('has the provided `classes`', () => {
- const $ = cheerio.load(renderComponent('modal', { ...EXAMPLE_MODAL_BASIC, classes: 'modal-class' }));
- expect($('.modal-class').length).toBe(1);
- });
-
- it('outputs the expected button', () => {
- const faker = templateFaker();
- const buttonSpy = faker.spy('button');
-
- faker.renderComponent('modal', EXAMPLE_MODAL_BASIC);
-
- expect(buttonSpy.occurrences[0]).toHaveProperty('text', 'Modal button');
- });
-
- it('calls with content', () => {
- const $ = cheerio.load(renderComponent('modal', { EXAMPLE_MODAL_BASIC }, 'Example content...'));
-
- const content = $('.ons-modal__body').text().trim();
- expect(content).toBe('Example content...');
- });
-
- it('has additionally provided `attributes`', () => {
- const $ = cheerio.load(
- renderComponent('modal', {
- ...EXAMPLE_MODAL_BASIC,
- attributes: {
- a: 123,
- b: 456,
- },
- }),
- );
-
- expect($('.ons-modal').attr('a')).toBe('123');
- expect($('.ons-modal').attr('b')).toBe('456');
- });
+ it('passes jest-axe checks with', async () => {
+ const $ = cheerio.load(renderComponent('modal', EXAMPLE_MODAL_BASIC));
+
+ const results = await axe($.html());
+ expect(results).toHaveNoViolations();
+ });
+
+ it('has expected `title`', () => {
+ const $ = cheerio.load(renderComponent('modal', EXAMPLE_MODAL_BASIC));
+
+ const title = $('.ons-modal__title').html().trim();
+ expect(title).toBe('Modal title');
+ });
+
+ it('has expected `body`', () => {
+ const $ = cheerio.load(renderComponent('modal', EXAMPLE_MODAL_BASIC));
+
+ const title = $('.ons-modal__body').html().trim();
+ expect(title).toBe('Modal body text');
+ });
+
+ it('has the default ID', () => {
+ const $ = cheerio.load(renderComponent('modal', EXAMPLE_MODAL_BASIC));
+ expect($('#dialog').length).toBe(1);
+ });
+
+ it('has the provided `id`', () => {
+ const $ = cheerio.load(renderComponent('modal', { ...EXAMPLE_MODAL_BASIC, id: 'modal-id' }));
+ expect($('#modal-id').length).toBe(1);
+ });
+
+ it('has the provided `classes`', () => {
+ const $ = cheerio.load(renderComponent('modal', { ...EXAMPLE_MODAL_BASIC, classes: 'modal-class' }));
+ expect($('.modal-class').length).toBe(1);
+ });
+
+ it('outputs the expected button', () => {
+ const faker = templateFaker();
+ const buttonSpy = faker.spy('button');
+
+ faker.renderComponent('modal', EXAMPLE_MODAL_BASIC);
+
+ expect(buttonSpy.occurrences[0]).toHaveProperty('text', 'Modal button');
+ });
+
+ it('calls with content', () => {
+ const $ = cheerio.load(renderComponent('modal', { EXAMPLE_MODAL_BASIC }, 'Example content...'));
+
+ const content = $('.ons-modal__body').text().trim();
+ expect(content).toBe('Example content...');
+ });
+
+ it('has additionally provided `attributes`', () => {
+ const $ = cheerio.load(
+ renderComponent('modal', {
+ ...EXAMPLE_MODAL_BASIC,
+ attributes: {
+ a: 123,
+ b: 456,
+ },
+ }),
+ );
+
+ expect($('.ons-modal').attr('a')).toBe('123');
+ expect($('.ons-modal').attr('b')).toBe('456');
+ });
});
diff --git a/src/components/modal/_modal.scss b/src/components/modal/_modal.scss
index 4c1568dfa9..2d2219245c 100644
--- a/src/components/modal/_modal.scss
+++ b/src/components/modal/_modal.scss
@@ -1,47 +1,47 @@
$backdrop-colour: rgb(0 0 0 / 80%);
.ons-modal {
- border: none;
- border-radius: 0.4rem;
- box-shadow: 0 0 7px 0 #000;
- display: none;
- left: 0;
- margin-left: 2rem;
- margin-right: 2rem;
- outline: 2px solid transparent; // Add transparent outline because Windows High Contrast Mode doesn't show box-shadows
- padding: 2rem;
- position: fixed;
- top: 0;
+ border: 0;
+ border-radius: 0.4rem;
+ box-shadow: 0 0 7px 0 #000;
+ display: none;
+ left: 0;
+ margin-left: 2rem;
+ margin-right: 2rem;
+ outline: 2px solid transparent; // Add transparent outline because Windows High Contrast Mode doesn't show box-shadows
+ padding: 2rem;
+ position: fixed;
+ top: 0;
- @media screen and (width >= 600px) {
- margin-left: auto;
- margin-right: auto;
- max-width: 500px;
- }
+ @media screen and (width >= 600px) {
+ margin-left: auto;
+ margin-right: auto;
+ max-width: 500px;
+ }
- &-ie11 & {
- background: var(--ons-color-white);
- inset: 50% 0 0;
- height: 350px;
- position: fixed;
- transform: translate(0, -50%);
- }
+ &-ie11 & {
+ background: var(--ons-color-white);
+ inset: 50% 0 0;
+ height: 350px;
+ position: fixed;
+ transform: translate(0, -50%);
+ }
- &::backdrop {
- backdrop-filter: blur(3px);
- background: $backdrop-colour;
- }
+ &::backdrop {
+ backdrop-filter: blur(3px);
+ background: $backdrop-colour;
+ }
- & + .backdrop {
- background: $backdrop-colour;
- height: 100%;
- left: 0;
- position: absolute;
- top: 0;
- width: 100%;
- }
+ + .backdrop {
+ background: $backdrop-colour;
+ height: 100%;
+ left: 0;
+ position: absolute;
+ top: 0;
+ width: 100%;
+ }
}
.ons-modal-overlay {
- overflow: hidden;
+ overflow: hidden;
}
diff --git a/src/components/modal/modal.dom.js b/src/components/modal/modal.dom.js
index 872ec18267..9709cd972f 100644
--- a/src/components/modal/modal.dom.js
+++ b/src/components/modal/modal.dom.js
@@ -1,14 +1,14 @@
import domready from '../../js/domready';
async function modals() {
- const modals = [...document.querySelectorAll('.ons-js-modal')];
- const timeouts = [...document.querySelectorAll('.ons-js-timeout-modal')];
+ const modals = [...document.querySelectorAll('.ons-js-modal')];
+ const timeouts = [...document.querySelectorAll('.ons-js-timeout-modal')];
- if (modals.length && !timeouts.length) {
- const Modal = (await import('./modal')).default;
+ if (modals.length && !timeouts.length) {
+ const Modal = (await import('./modal')).default;
- modals.forEach((component) => new Modal(component));
- }
+ modals.forEach((component) => new Modal(component));
+ }
}
domready(modals);
diff --git a/src/components/modal/modal.js b/src/components/modal/modal.js
index 97328b9eda..2eeb7aa1c7 100644
--- a/src/components/modal/modal.js
+++ b/src/components/modal/modal.js
@@ -4,110 +4,110 @@ const overLayClass = 'ons-modal-overlay';
const ie11Class = 'ons-modal-ie11';
export default class Modal {
- constructor(component) {
- this.component = component;
- this.launcher = document.querySelector(`[data-modal-id=${component.id}]`);
- this.closeButton = component.querySelector('.ons-js-modal-btn');
- this.lastFocusedEl = null;
- this.dialogCSSSupported = true;
- this.modalType = this.component.classList.contains('ons-js-timeout-modal') ? 'Timeout' : 'Generic';
-
- this.initialise();
- }
-
- initialise() {
- if (!this.dialogSupported()) {
- /* istanbul ignore next */
- return;
+ constructor(component) {
+ this.component = component;
+ this.launcher = document.querySelector(`[data-modal-id=${component.id}]`);
+ this.closeButton = component.querySelector('.ons-js-modal-btn');
+ this.lastFocusedEl = null;
+ this.dialogCSSSupported = true;
+ this.modalType = this.component.classList.contains('ons-js-timeout-modal') ? 'Timeout' : 'Generic';
+
+ this.initialise();
}
- if (this.launcher) {
- this.launcher.addEventListener('click', this.openDialog.bind(this));
- }
+ initialise() {
+ if (!this.dialogSupported()) {
+ /* istanbul ignore next */
+ return;
+ }
- if (this.closeButton) {
- this.closeButton.addEventListener('click', this.closeDialog.bind(this));
- }
+ if (this.launcher) {
+ this.launcher.addEventListener('click', this.openDialog.bind(this));
+ }
- if (this.modalType !== 'Timeout') {
- window.addEventListener('keydown', this.escToClose.bind(this));
+ if (this.closeButton) {
+ this.closeButton.addEventListener('click', this.closeDialog.bind(this));
+ }
+
+ if (this.modalType !== 'Timeout') {
+ window.addEventListener('keydown', this.escToClose.bind(this));
+ }
}
- }
-
- dialogSupported() {
- if (typeof HTMLDialogElement === 'function') {
- return true;
- } else {
- try {
- dialogPolyfill.registerDialog(this.component);
- this.dialogCSSSupported = false;
- return true;
- } catch (error) {
- /* istanbul ignore next */
- return false;
- }
+
+ dialogSupported() {
+ if (typeof HTMLDialogElement === 'function') {
+ return true;
+ } else {
+ try {
+ dialogPolyfill.registerDialog(this.component);
+ this.dialogCSSSupported = false;
+ return true;
+ } catch (error) {
+ /* istanbul ignore next */
+ return false;
+ }
+ }
}
- }
- openDialog(event) {
- if (!this.isDialogOpen()) {
- this.component.classList.add('ons-u-db');
- document.querySelector('body').classList.add(overLayClass);
+ openDialog(event) {
+ if (!this.isDialogOpen()) {
+ this.component.classList.add('ons-u-db');
+ document.querySelector('body').classList.add(overLayClass);
- if (!this.dialogCSSSupported) {
- document.querySelector('body').classList.add(ie11Class);
- }
+ if (!this.dialogCSSSupported) {
+ document.querySelector('body').classList.add(ie11Class);
+ }
- this.saveLastFocusedEl();
+ this.saveLastFocusedEl();
- if (event) {
- const modal = document.getElementById(event.target.getAttribute('data-modal-id'));
- modal.showModal();
- } else {
- this.component.showModal();
- }
+ if (event) {
+ const modal = document.getElementById(event.target.getAttribute('data-modal-id'));
+ modal.showModal();
+ } else {
+ this.component.showModal();
+ }
+ }
}
- }
-
- saveLastFocusedEl() {
- this.lastFocusedEl = document.activeElement;
- if (!this.lastFocusedEl || this.lastFocusedEl === document.body) {
- this.lastFocusedEl = null;
- } else if (document.querySelector) {
- this.lastFocusedEl = document.querySelector(':focus');
+
+ saveLastFocusedEl() {
+ this.lastFocusedEl = document.activeElement;
+ if (!this.lastFocusedEl || this.lastFocusedEl === document.body) {
+ this.lastFocusedEl = null;
+ } else if (document.querySelector) {
+ this.lastFocusedEl = document.querySelector(':focus');
+ }
+ }
+
+ setFocusOnLastFocusedEl(lastFocusedEl) {
+ if (lastFocusedEl) {
+ lastFocusedEl.focus();
+ }
}
- }
- setFocusOnLastFocusedEl(lastFocusedEl) {
- if (lastFocusedEl) {
- lastFocusedEl.focus();
+ isDialogOpen() {
+ return this.component['open'];
}
- }
-
- isDialogOpen() {
- return this.component['open'];
- }
-
- closeDialog(event) {
- if (this.isDialogOpen()) {
- if (event) {
- event.preventDefault();
- }
- this.component.classList.remove('ons-u-db');
- document.querySelector('body').classList.remove(overLayClass);
-
- if (!this.dialogCSSSupported) {
- document.querySelector('body').classList.remove(ie11Class);
- }
-
- this.component.close();
- this.setFocusOnLastFocusedEl(this.lastFocusedEl);
+
+ closeDialog(event) {
+ if (this.isDialogOpen()) {
+ if (event) {
+ event.preventDefault();
+ }
+ this.component.classList.remove('ons-u-db');
+ document.querySelector('body').classList.remove(overLayClass);
+
+ if (!this.dialogCSSSupported) {
+ document.querySelector('body').classList.remove(ie11Class);
+ }
+
+ this.component.close();
+ this.setFocusOnLastFocusedEl(this.lastFocusedEl);
+ }
}
- }
- escToClose(event) {
- if (this.isDialogOpen() && event.keyCode === 27) {
- this.closeDialog(event);
+ escToClose(event) {
+ if (this.isDialogOpen() && event.keyCode === 27) {
+ this.closeDialog(event);
+ }
}
- }
}
diff --git a/src/components/modal/modal.spec.js b/src/components/modal/modal.spec.js
index 4c2a75e1c6..6a99232be0 100644
--- a/src/components/modal/modal.spec.js
+++ b/src/components/modal/modal.spec.js
@@ -1,72 +1,72 @@
import { renderComponent, setTestPage } from '../../tests/helpers/rendering';
const EXAMPLE_MODAL = {
- title: 'Modal title',
- body: 'Modal body text',
- btnText: 'Modal button',
+ title: 'Modal title',
+ body: 'Modal body text',
+ btnText: 'Modal button',
};
describe('script: modal', () => {
- beforeEach(async () => {
- const component = renderComponent('modal', EXAMPLE_MODAL);
- const template = `
+ beforeEach(async () => {
+ const component = renderComponent('modal', EXAMPLE_MODAL);
+ const template = `
Launcher
${component}
`;
- await setTestPage('/test', template);
- });
-
- describe('when the modal launcher is clicked', () => {
- beforeEach(async () => {
- await page.focus('#launcher');
- await page.keyboard.press('Enter');
+ await setTestPage('/test', template);
});
- it('displays the modal', async () => {
- const modalIsVisible = await page.$eval('.ons-modal', (node) => node.classList.contains('ons-u-db'));
- expect(modalIsVisible).toBe(true);
- });
+ describe('when the modal launcher is clicked', () => {
+ beforeEach(async () => {
+ await page.focus('#launcher');
+ await page.keyboard.press('Enter');
+ });
- it('has the correct body class added', async () => {
- const bodyClassAddition = await page.$eval('body', (node) => node.classList.contains('ons-modal-overlay'));
- expect(bodyClassAddition).toBe(true);
- });
+ it('displays the modal', async () => {
+ const modalIsVisible = await page.$eval('.ons-modal', (node) => node.classList.contains('ons-u-db'));
+ expect(modalIsVisible).toBe(true);
+ });
- describe('when the modal close button is clicked', () => {
- beforeEach(async () => {
- await page.focus('.ons-js-modal-btn');
- await page.keyboard.press('Enter');
- });
+ it('has the correct body class added', async () => {
+ const bodyClassAddition = await page.$eval('body', (node) => node.classList.contains('ons-modal-overlay'));
+ expect(bodyClassAddition).toBe(true);
+ });
- it('hides the modal', async () => {
- const modalIsVisible = await page.$eval('.ons-modal', (node) => node.classList.contains('ons-u-db'));
- expect(modalIsVisible).toBe(false);
- });
+ describe('when the modal close button is clicked', () => {
+ beforeEach(async () => {
+ await page.focus('.ons-js-modal-btn');
+ await page.keyboard.press('Enter');
+ });
- it('has the body class removed', async () => {
- const bodyClassAddition = await page.$eval('body', (node) => node.classList.contains('ons-modal-overlay'));
- expect(bodyClassAddition).toBe(false);
- });
+ it('hides the modal', async () => {
+ const modalIsVisible = await page.$eval('.ons-modal', (node) => node.classList.contains('ons-u-db'));
+ expect(modalIsVisible).toBe(false);
+ });
- it('focuses the last active element', async () => {
- const activeElementId = await page.evaluate(() => document.activeElement.id);
- expect(activeElementId).toBe('launcher');
- });
- });
+ it('has the body class removed', async () => {
+ const bodyClassAddition = await page.$eval('body', (node) => node.classList.contains('ons-modal-overlay'));
+ expect(bodyClassAddition).toBe(false);
+ });
+
+ it('focuses the last active element', async () => {
+ const activeElementId = await page.evaluate(() => document.activeElement.id);
+ expect(activeElementId).toBe('launcher');
+ });
+ });
- describe('when the `esc` key is pressed', () => {
- beforeEach(async () => {
- await page.focus('.ons-js-modal-btn');
- await page.keyboard.press('Enter');
- await page.keyboard.press('Escape');
- });
+ describe('when the `esc` key is pressed', () => {
+ beforeEach(async () => {
+ await page.focus('.ons-js-modal-btn');
+ await page.keyboard.press('Enter');
+ await page.keyboard.press('Escape');
+ });
- it('closes the modal', async () => {
- const modalIsVisible = await page.$eval('.ons-modal', (node) => node.classList.contains('ons-u-db'));
- expect(modalIsVisible).toBe(false);
- });
+ it('closes the modal', async () => {
+ const modalIsVisible = await page.$eval('.ons-modal', (node) => node.classList.contains('ons-u-db'));
+ expect(modalIsVisible).toBe(false);
+ });
+ });
});
- });
});
diff --git a/src/components/mutually-exclusive/_macro.spec.js b/src/components/mutually-exclusive/_macro.spec.js
index 48f676117d..0604f3d18d 100644
--- a/src/components/mutually-exclusive/_macro.spec.js
+++ b/src/components/mutually-exclusive/_macro.spec.js
@@ -6,171 +6,171 @@ import axe from '../../tests/helpers/axe';
import { renderComponent, templateFaker } from '../../tests/helpers/rendering';
const EXAMPLE_CHECKBOX_OPTION = {
- id: 'example-option-id',
- name: 'example-option-name',
- value: 'example-option-value',
- checked: false,
- classes: 'extra-option-class',
- attributes: {
- 'data-attribute': 'Example attribute',
- },
- label: {
- text: 'Example option text',
- description: 'Example option description',
- },
+ id: 'example-option-id',
+ name: 'example-option-name',
+ value: 'example-option-value',
+ checked: false,
+ classes: 'extra-option-class',
+ attributes: {
+ 'data-attribute': 'Example attribute',
+ },
+ label: {
+ text: 'Example option text',
+ description: 'Example option description',
+ },
};
const EXAMPLE_MUTUALLY_EXCLUSIVE = {
- id: 'example-mutually-exclusive',
- classes: 'extra-class',
- exclusiveOptions: [EXAMPLE_CHECKBOX_OPTION],
- or: 'Or',
- deselectMessage: 'Selecting this will uncheck all other checkboxes',
- deselectGroupAdjective: 'Group was unselected',
- deselectExclusiveOptionAdjective: 'Exclusive option was unselected',
- legend: 'Legend text',
- legendClasses: 'extra-legend-class',
- description: 'An example description.',
- attributes: { a: 42 },
- dontWrap: true,
- legendIsQuestionTitle: true,
- error: {
- id: 'file-error',
- text: 'Select a file that is an XLS, XLSX or PDF',
- },
+ id: 'example-mutually-exclusive',
+ classes: 'extra-class',
+ exclusiveOptions: [EXAMPLE_CHECKBOX_OPTION],
+ or: 'Or',
+ deselectMessage: 'Selecting this will uncheck all other checkboxes',
+ deselectGroupAdjective: 'Group was unselected',
+ deselectExclusiveOptionAdjective: 'Exclusive option was unselected',
+ legend: 'Legend text',
+ legendClasses: 'extra-legend-class',
+ description: 'An example description.',
+ attributes: { a: 42 },
+ dontWrap: true,
+ legendIsQuestionTitle: true,
+ error: {
+ id: 'file-error',
+ text: 'Select a file that is an XLS, XLSX or PDF',
+ },
};
const EXAMPLE_MUTUALLY_EXCLUSIVE_RADIOS = {
- ...EXAMPLE_MUTUALLY_EXCLUSIVE,
- exclusiveOptions: [
- {
- id: 'house',
- name: 'mutuallyExclusiveRadio',
- label: {
- text: 'House or bungalow',
- },
- value: 'house',
- },
- {
- id: 'flat',
- name: 'mutuallyExclusiveRadio',
- label: {
- text: 'Flat, maisonette or apartment',
- },
- value: 'flat',
- },
- ],
+ ...EXAMPLE_MUTUALLY_EXCLUSIVE,
+ exclusiveOptions: [
+ {
+ id: 'house',
+ name: 'mutuallyExclusiveRadio',
+ label: {
+ text: 'House or bungalow',
+ },
+ value: 'house',
+ },
+ {
+ id: 'flat',
+ name: 'mutuallyExclusiveRadio',
+ label: {
+ text: 'Flat, maisonette or apartment',
+ },
+ value: 'flat',
+ },
+ ],
};
const FAKE_FIELD = '
Fake field ';
describe('macro: mutually-exclusive', () => {
- it('passes jest-axe checks', async () => {
- const $ = cheerio.load(renderComponent('mutually-exclusive', EXAMPLE_MUTUALLY_EXCLUSIVE, FAKE_FIELD));
-
- const results = await axe($.html());
- expect(results).toHaveNoViolations();
- });
-
- it('renders `fieldset` component with the expected parameters', () => {
- const faker = templateFaker();
- const fieldsetSpy = faker.spy('fieldset');
-
- faker.renderComponent('mutually-exclusive', EXAMPLE_MUTUALLY_EXCLUSIVE, FAKE_FIELD);
-
- expect(fieldsetSpy.occurrences).toContainEqual({
- id: 'example-mutually-exclusive',
- classes: 'extra-class',
- legend: 'Legend text',
- legendClasses: 'extra-legend-class',
- description: 'An example description.',
- attributes: EXAMPLE_MUTUALLY_EXCLUSIVE.attributes,
- dontWrap: true,
- legendIsQuestionTitle: true,
- error: EXAMPLE_MUTUALLY_EXCLUSIVE.error,
- });
- });
-
- it('renders inner field via `caller` mechanism', () => {
- const $ = cheerio.load(renderComponent('mutually-exclusive', EXAMPLE_MUTUALLY_EXCLUSIVE, FAKE_FIELD));
+ it('passes jest-axe checks', async () => {
+ const $ = cheerio.load(renderComponent('mutually-exclusive', EXAMPLE_MUTUALLY_EXCLUSIVE, FAKE_FIELD));
- expect($('.ons-mutually-exclusive > #fake-field').length).toBe(1);
- });
-
- it('renders `autosuggestResults` after the field', () => {
- const $ = cheerio.load(
- renderComponent(
- 'mutually-exclusive',
- {
- ...EXAMPLE_MUTUALLY_EXCLUSIVE,
- autosuggestResults: '
',
- },
- FAKE_FIELD,
- ),
- );
-
- expect($('#fake-field + #autosuggest-results').length).toBe(1);
- });
+ const results = await axe($.html());
+ expect(results).toHaveNoViolations();
+ });
- it('renders `deselectGroupAdjective` and `deselectExclusiveOptionAdjective` for the alert', () => {
- const $ = cheerio.load(renderComponent('mutually-exclusive', EXAMPLE_MUTUALLY_EXCLUSIVE, FAKE_FIELD));
+ it('renders `fieldset` component with the expected parameters', () => {
+ const faker = templateFaker();
+ const fieldsetSpy = faker.spy('fieldset');
+
+ faker.renderComponent('mutually-exclusive', EXAMPLE_MUTUALLY_EXCLUSIVE, FAKE_FIELD);
+
+ expect(fieldsetSpy.occurrences).toContainEqual({
+ id: 'example-mutually-exclusive',
+ classes: 'extra-class',
+ legend: 'Legend text',
+ legendClasses: 'extra-legend-class',
+ description: 'An example description.',
+ attributes: EXAMPLE_MUTUALLY_EXCLUSIVE.attributes,
+ dontWrap: true,
+ legendIsQuestionTitle: true,
+ error: EXAMPLE_MUTUALLY_EXCLUSIVE.error,
+ });
+ });
- expect($('.ons-js-exclusive-alert').attr('data-group-adjective')).toBe('Group was unselected');
- expect($('.ons-js-exclusive-alert').attr('data-option-adjective')).toBe('Exclusive option was unselected');
- });
+ it('renders inner field via `caller` mechanism', () => {
+ const $ = cheerio.load(renderComponent('mutually-exclusive', EXAMPLE_MUTUALLY_EXCLUSIVE, FAKE_FIELD));
- describe('mode: with single checkbox', () => {
- it('renders visually hidden "Or" label', () => {
- const $ = cheerio.load(renderComponent('mutually-exclusive', EXAMPLE_MUTUALLY_EXCLUSIVE, FAKE_FIELD));
+ expect($('.ons-mutually-exclusive > #fake-field').length).toBe(1);
+ });
- expect($('.ons-checkboxes__label').text().trim()).toBe('Or');
+ it('renders `autosuggestResults` after the field', () => {
+ const $ = cheerio.load(
+ renderComponent(
+ 'mutually-exclusive',
+ {
+ ...EXAMPLE_MUTUALLY_EXCLUSIVE,
+ autosuggestResults: '
',
+ },
+ FAKE_FIELD,
+ ),
+ );
+
+ expect($('#fake-field + #autosuggest-results').length).toBe(1);
});
- it('renders a checkbox component with the expected parameters', () => {
- const faker = templateFaker();
- const checkboxSpy = faker.spy('checkboxes/checkbox');
+ it('renders `deselectGroupAdjective` and `deselectExclusiveOptionAdjective` for the alert', () => {
+ const $ = cheerio.load(renderComponent('mutually-exclusive', EXAMPLE_MUTUALLY_EXCLUSIVE, FAKE_FIELD));
- faker.renderComponent('mutually-exclusive', EXAMPLE_MUTUALLY_EXCLUSIVE, FAKE_FIELD);
+ expect($('.ons-js-exclusive-alert').attr('data-group-adjective')).toBe('Group was unselected');
+ expect($('.ons-js-exclusive-alert').attr('data-option-adjective')).toBe('Exclusive option was unselected');
+ });
- expect(checkboxSpy.occurrences).toContainEqual({
- id: 'example-option-id',
- name: 'example-option-name',
- value: 'example-option-value',
- checked: false,
- classes: 'extra-option-class',
- attributes: {
- 'data-attribute': 'Example attribute',
- },
- deselectMessage: 'Selecting this will uncheck all other checkboxes',
- inputClasses: 'ons-js-exclusive-option',
- label: {
- text: '
Or, Example option text',
- description: 'Example option description',
- },
- });
+ describe('mode: with single checkbox', () => {
+ it('renders visually hidden "Or" label', () => {
+ const $ = cheerio.load(renderComponent('mutually-exclusive', EXAMPLE_MUTUALLY_EXCLUSIVE, FAKE_FIELD));
+
+ expect($('.ons-checkboxes__label').text().trim()).toBe('Or');
+ });
+
+ it('renders a checkbox component with the expected parameters', () => {
+ const faker = templateFaker();
+ const checkboxSpy = faker.spy('checkboxes/checkbox');
+
+ faker.renderComponent('mutually-exclusive', EXAMPLE_MUTUALLY_EXCLUSIVE, FAKE_FIELD);
+
+ expect(checkboxSpy.occurrences).toContainEqual({
+ id: 'example-option-id',
+ name: 'example-option-name',
+ value: 'example-option-value',
+ checked: false,
+ classes: 'extra-option-class',
+ attributes: {
+ 'data-attribute': 'Example attribute',
+ },
+ deselectMessage: 'Selecting this will uncheck all other checkboxes',
+ inputClasses: 'ons-js-exclusive-option',
+ label: {
+ text: '
Or, Example option text',
+ description: 'Example option description',
+ },
+ });
+ });
});
- });
- describe('mode: with multiple radio options', () => {
- it('renders visually hidden "Or" label', () => {
- const $ = cheerio.load(renderComponent('mutually-exclusive', EXAMPLE_MUTUALLY_EXCLUSIVE_RADIOS, FAKE_FIELD));
+ describe('mode: with multiple radio options', () => {
+ it('renders visually hidden "Or" label', () => {
+ const $ = cheerio.load(renderComponent('mutually-exclusive', EXAMPLE_MUTUALLY_EXCLUSIVE_RADIOS, FAKE_FIELD));
- expect($('.ons-checkboxes__label').text().trim()).toBe('Or');
- });
+ expect($('.ons-checkboxes__label').text().trim()).toBe('Or');
+ });
- it('renders a radios component with the expected parameters', () => {
- const faker = templateFaker();
- const radiosSpy = faker.spy('radios');
+ it('renders a radios component with the expected parameters', () => {
+ const faker = templateFaker();
+ const radiosSpy = faker.spy('radios');
- faker.renderComponent('mutually-exclusive', EXAMPLE_MUTUALLY_EXCLUSIVE_RADIOS, FAKE_FIELD);
+ faker.renderComponent('mutually-exclusive', EXAMPLE_MUTUALLY_EXCLUSIVE_RADIOS, FAKE_FIELD);
- expect(radiosSpy.occurrences).toContainEqual({
- dontWrap: true,
- radios: EXAMPLE_MUTUALLY_EXCLUSIVE_RADIOS.exclusiveOptions,
- inputClasses: 'ons-js-exclusive-option',
- name: 'mutuallyExclusiveRadio',
- deselectMessage: 'Selecting this will uncheck all other checkboxes',
- });
+ expect(radiosSpy.occurrences).toContainEqual({
+ dontWrap: true,
+ radios: EXAMPLE_MUTUALLY_EXCLUSIVE_RADIOS.exclusiveOptions,
+ inputClasses: 'ons-js-exclusive-option',
+ name: 'mutuallyExclusiveRadio',
+ deselectMessage: 'Selecting this will uncheck all other checkboxes',
+ });
+ });
});
- });
});
diff --git a/src/components/mutually-exclusive/mutually-exclusive.checkboxes.spec.js b/src/components/mutually-exclusive/mutually-exclusive.checkboxes.spec.js
index d795e092ca..091ad1cb12 100644
--- a/src/components/mutually-exclusive/mutually-exclusive.checkboxes.spec.js
+++ b/src/components/mutually-exclusive/mutually-exclusive.checkboxes.spec.js
@@ -3,201 +3,201 @@ import { renderComponent, setTestPage } from '../../tests/helpers/rendering';
const SCREEN_READER_TIMEOUT_DELAY = 300;
const EXAMPLE_MUTUALLY_EXCLUSIVE_CHECKBOXES_PARAMS = {
- legend: 'What type of central heating do you have?',
- checkboxesLabel: 'Select all that apply',
- name: 'mutually-exclusive',
- checkboxes: [
- {
- id: 'gas',
- label: {
- text: 'Gas',
- },
- value: 'gas',
- },
- {
- id: 'electric',
- label: {
- text: 'Electric',
- },
- value: 'electric',
- },
- {
- id: 'solid-fuel',
- label: {
- text: 'Solid fuel',
- },
- value: 'solid-fuel',
- },
- {
- id: 'other-fuel',
- label: {
- text: 'Other',
- },
- value: 'other',
- other: {
- id: 'other-fuel-textbox',
- name: 'other-fuel-answer',
- label: {
- text: 'Please specify',
+ legend: 'What type of central heating do you have?',
+ checkboxesLabel: 'Select all that apply',
+ name: 'mutually-exclusive',
+ checkboxes: [
+ {
+ id: 'gas',
+ label: {
+ text: 'Gas',
+ },
+ value: 'gas',
},
- },
- },
- ],
- mutuallyExclusive: {
- or: 'or',
- deselectMessage: 'Selecting this will uncheck all other checkboxes',
- deselectGroupAdjective: 'deselected',
- deselectExclusiveOptionAdjective: 'deselected',
- exclusiveOptions: [
- {
- id: 'no-central-heating',
- label: {
- text: 'No central heating',
+ {
+ id: 'electric',
+ label: {
+ text: 'Electric',
+ },
+ value: 'electric',
+ },
+ {
+ id: 'solid-fuel',
+ label: {
+ text: 'Solid fuel',
+ },
+ value: 'solid-fuel',
+ },
+ {
+ id: 'other-fuel',
+ label: {
+ text: 'Other',
+ },
+ value: 'other',
+ other: {
+ id: 'other-fuel-textbox',
+ name: 'other-fuel-answer',
+ label: {
+ text: 'Please specify',
+ },
+ },
},
- value: 'no-central-heating',
- },
],
- },
+ mutuallyExclusive: {
+ or: 'or',
+ deselectMessage: 'Selecting this will uncheck all other checkboxes',
+ deselectGroupAdjective: 'deselected',
+ deselectExclusiveOptionAdjective: 'deselected',
+ exclusiveOptions: [
+ {
+ id: 'no-central-heating',
+ label: {
+ text: 'No central heating',
+ },
+ value: 'no-central-heating',
+ },
+ ],
+ },
};
describe('script: mutually-exclusive', () => {
- describe('checkboxes', () => {
- beforeEach(async () => {
- await setTestPage('/test', renderComponent('checkboxes', EXAMPLE_MUTUALLY_EXCLUSIVE_CHECKBOXES_PARAMS));
- });
-
- describe('Given the user has clicked multiple non-exclusive options', () => {
- beforeEach(async () => {
- await page.click('#gas');
- await page.click('#electric');
- await page.click('#other-fuel');
- await page.type('#other-fuel-textbox', 'Biofuel');
- });
-
- describe('when the user clicks the mutually exclusive option', () => {
- beforeEach(async () => {
- await page.click('#no-central-heating');
- });
-
- it('then the mutually exclusive option is checked', async () => {
- const isChecked = await page.$eval('#no-central-heating', (node) => node.checked);
- expect(isChecked).toBe(true);
- });
-
- it('then the checkboxes are not checked', async () => {
- expect(await page.$eval('#gas', (node) => node.checked)).toBe(false);
- expect(await page.$eval('#electric', (node) => node.checked)).toBe(false);
- expect(await page.$eval('#solid-fuel', (node) => node.checked)).toBe(false);
- expect(await page.$eval('#other-fuel', (node) => node.checked)).toBe(false);
- expect(await page.$eval('#other-fuel-textbox', (node) => node.value)).toBe('');
- });
-
- it('then the aria-live message should reflect the removed non exclusive options', async () => {
- await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
-
- const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
- expect(alertText).toBe('Gas deselected. Electric deselected. Other deselected. Please specify deselected.');
- });
- });
- });
-
- describe('Given the user has clicked the mutually exclusive option', () => {
- beforeEach(async () => {
- await page.click('#no-central-heating');
- });
-
- describe('when the user clicks the non-exclusive options', () => {
+ describe('checkboxes', () => {
beforeEach(async () => {
- await page.click('#gas');
- await page.click('#electric');
- await page.click('#other-fuel');
- });
-
- it('then the expected checkboxes are checked', async () => {
- expect(await page.$eval('#gas', (node) => node.checked)).toBe(true);
- expect(await page.$eval('#electric', (node) => node.checked)).toBe(true);
- expect(await page.$eval('#solid-fuel', (node) => node.checked)).toBe(false);
- expect(await page.$eval('#other-fuel', (node) => node.checked)).toBe(true);
- });
-
- it('then the exclusive option should not be checked', async () => {
- expect(await page.$eval('#no-central-heating', (node) => node.checked)).toBe(false);
- });
-
- it('then the aria-live message should reflect the removed exclusive option', async () => {
- await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
-
- const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
- expect(alertText).toBe('No central heating deselected.');
- });
-
- describe('and the user deselects an non-exclusive option', () => {
- beforeEach(async () => {
- await page.click('#electric');
- });
-
- it('the aria-live message should not be updated', async () => {
- await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
-
- const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
- expect(alertText).toBe('No central heating deselected.');
- });
- });
- });
- });
-
- describe('Given the user has not clicked the mutually exclusive option', () => {
- describe('when the user clicks multiple non-exclusive options', () => {
- beforeEach(async () => {
- await page.click('#gas');
- await page.click('#electric');
- await page.click('#other-fuel');
- });
-
- it('then the expected checkboxes are checked', async () => {
- expect(await page.$eval('#gas', (node) => node.checked)).toBe(true);
- expect(await page.$eval('#electric', (node) => node.checked)).toBe(true);
- expect(await page.$eval('#solid-fuel', (node) => node.checked)).toBe(false);
- expect(await page.$eval('#other-fuel', (node) => node.checked)).toBe(true);
- });
-
- it('then the exclusive option should not be checked', async () => {
- expect(await page.$eval('#no-central-heating', (node) => node.checked)).toBe(false);
- });
-
- it('then the aria-live message should say nothing', async () => {
- await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
-
- const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
- expect(alertText).toBe('');
- });
- });
- });
-
- describe('Given the user has not clicked any of the non-exclusive options', () => {
- describe('when the user clicks the mutually exclusive option', () => {
- beforeEach(async () => {
- await page.click('#no-central-heating');
- });
-
- it('then the expected checkboxes are not checked', async () => {
- expect(await page.$eval('#gas', (node) => node.checked)).toBe(false);
- expect(await page.$eval('#electric', (node) => node.checked)).toBe(false);
- expect(await page.$eval('#solid-fuel', (node) => node.checked)).toBe(false);
- expect(await page.$eval('#other-fuel', (node) => node.checked)).toBe(false);
- });
-
- it('then the exclusive option should be checked', async () => {
- expect(await page.$eval('#no-central-heating', (node) => node.checked)).toBe(true);
- });
-
- it('then the aria-live message should say nothing', async () => {
- await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
-
- const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
- expect(alertText).toBe('');
+ await setTestPage('/test', renderComponent('checkboxes', EXAMPLE_MUTUALLY_EXCLUSIVE_CHECKBOXES_PARAMS));
+ });
+
+ describe('Given the user has clicked multiple non-exclusive options', () => {
+ beforeEach(async () => {
+ await page.click('#gas');
+ await page.click('#electric');
+ await page.click('#other-fuel');
+ await page.type('#other-fuel-textbox', 'Biofuel');
+ });
+
+ describe('when the user clicks the mutually exclusive option', () => {
+ beforeEach(async () => {
+ await page.click('#no-central-heating');
+ });
+
+ it('then the mutually exclusive option is checked', async () => {
+ const isChecked = await page.$eval('#no-central-heating', (node) => node.checked);
+ expect(isChecked).toBe(true);
+ });
+
+ it('then the checkboxes are not checked', async () => {
+ expect(await page.$eval('#gas', (node) => node.checked)).toBe(false);
+ expect(await page.$eval('#electric', (node) => node.checked)).toBe(false);
+ expect(await page.$eval('#solid-fuel', (node) => node.checked)).toBe(false);
+ expect(await page.$eval('#other-fuel', (node) => node.checked)).toBe(false);
+ expect(await page.$eval('#other-fuel-textbox', (node) => node.value)).toBe('');
+ });
+
+ it('then the aria-live message should reflect the removed non exclusive options', async () => {
+ await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
+
+ const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
+ expect(alertText).toBe('Gas deselected. Electric deselected. Other deselected. Please specify deselected.');
+ });
+ });
+ });
+
+ describe('Given the user has clicked the mutually exclusive option', () => {
+ beforeEach(async () => {
+ await page.click('#no-central-heating');
+ });
+
+ describe('when the user clicks the non-exclusive options', () => {
+ beforeEach(async () => {
+ await page.click('#gas');
+ await page.click('#electric');
+ await page.click('#other-fuel');
+ });
+
+ it('then the expected checkboxes are checked', async () => {
+ expect(await page.$eval('#gas', (node) => node.checked)).toBe(true);
+ expect(await page.$eval('#electric', (node) => node.checked)).toBe(true);
+ expect(await page.$eval('#solid-fuel', (node) => node.checked)).toBe(false);
+ expect(await page.$eval('#other-fuel', (node) => node.checked)).toBe(true);
+ });
+
+ it('then the exclusive option should not be checked', async () => {
+ expect(await page.$eval('#no-central-heating', (node) => node.checked)).toBe(false);
+ });
+
+ it('then the aria-live message should reflect the removed exclusive option', async () => {
+ await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
+
+ const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
+ expect(alertText).toBe('No central heating deselected.');
+ });
+
+ describe('and the user deselects an non-exclusive option', () => {
+ beforeEach(async () => {
+ await page.click('#electric');
+ });
+
+ it('the aria-live message should not be updated', async () => {
+ await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
+
+ const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
+ expect(alertText).toBe('No central heating deselected.');
+ });
+ });
+ });
+ });
+
+ describe('Given the user has not clicked the mutually exclusive option', () => {
+ describe('when the user clicks multiple non-exclusive options', () => {
+ beforeEach(async () => {
+ await page.click('#gas');
+ await page.click('#electric');
+ await page.click('#other-fuel');
+ });
+
+ it('then the expected checkboxes are checked', async () => {
+ expect(await page.$eval('#gas', (node) => node.checked)).toBe(true);
+ expect(await page.$eval('#electric', (node) => node.checked)).toBe(true);
+ expect(await page.$eval('#solid-fuel', (node) => node.checked)).toBe(false);
+ expect(await page.$eval('#other-fuel', (node) => node.checked)).toBe(true);
+ });
+
+ it('then the exclusive option should not be checked', async () => {
+ expect(await page.$eval('#no-central-heating', (node) => node.checked)).toBe(false);
+ });
+
+ it('then the aria-live message should say nothing', async () => {
+ await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
+
+ const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
+ expect(alertText).toBe('');
+ });
+ });
+ });
+
+ describe('Given the user has not clicked any of the non-exclusive options', () => {
+ describe('when the user clicks the mutually exclusive option', () => {
+ beforeEach(async () => {
+ await page.click('#no-central-heating');
+ });
+
+ it('then the expected checkboxes are not checked', async () => {
+ expect(await page.$eval('#gas', (node) => node.checked)).toBe(false);
+ expect(await page.$eval('#electric', (node) => node.checked)).toBe(false);
+ expect(await page.$eval('#solid-fuel', (node) => node.checked)).toBe(false);
+ expect(await page.$eval('#other-fuel', (node) => node.checked)).toBe(false);
+ });
+
+ it('then the exclusive option should be checked', async () => {
+ expect(await page.$eval('#no-central-heating', (node) => node.checked)).toBe(true);
+ });
+
+ it('then the aria-live message should say nothing', async () => {
+ await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
+
+ const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
+ expect(alertText).toBe('');
+ });
+ });
});
- });
});
- });
});
diff --git a/src/components/mutually-exclusive/mutually-exclusive.date.spec.js b/src/components/mutually-exclusive/mutually-exclusive.date.spec.js
index 7646133196..6a44ede9fc 100644
--- a/src/components/mutually-exclusive/mutually-exclusive.date.spec.js
+++ b/src/components/mutually-exclusive/mutually-exclusive.date.spec.js
@@ -3,254 +3,254 @@ import { renderComponent, setTestPage } from '../../tests/helpers/rendering';
const SCREEN_READER_TIMEOUT_DELAY = 300;
const EXAMPLE_MUTUALLY_EXCLUSIVE_DATE_PARAMS = {
- id: 'date-mutually-exclusive',
- legendOrLabel: 'When did you leave your last paid job?',
- description: 'For example, 31 3 2018',
- day: {
- label: {
- text: 'Day',
- },
- name: 'day-exclusive',
- },
- month: {
- label: {
- text: 'Month',
+ id: 'date-mutually-exclusive',
+ legendOrLabel: 'When did you leave your last paid job?',
+ description: 'For example, 31 3 2018',
+ day: {
+ label: {
+ text: 'Day',
+ },
+ name: 'day-exclusive',
},
- name: 'month-exclusive',
- },
- year: {
- label: {
- text: 'Year',
+ month: {
+ label: {
+ text: 'Month',
+ },
+ name: 'month-exclusive',
},
- name: 'year-exclusive',
- },
- mutuallyExclusive: {
- or: 'Or',
- deselectMessage: 'Selecting this will clear the date if one has been inputted',
- deselectGroupAdjective: 'cleared',
- deselectExclusiveOptionAdjective: 'deselected',
- exclusiveOptions: [
- {
- id: 'date-exclusive-option',
- name: 'no-paid-job',
- value: 'no-paid-job',
+ year: {
label: {
- text: 'I have never had a paid job',
+ text: 'Year',
},
- },
- ],
- },
+ name: 'year-exclusive',
+ },
+ mutuallyExclusive: {
+ or: 'Or',
+ deselectMessage: 'Selecting this will clear the date if one has been inputted',
+ deselectGroupAdjective: 'cleared',
+ deselectExclusiveOptionAdjective: 'deselected',
+ exclusiveOptions: [
+ {
+ id: 'date-exclusive-option',
+ name: 'no-paid-job',
+ value: 'no-paid-job',
+ label: {
+ text: 'I have never had a paid job',
+ },
+ },
+ ],
+ },
};
const EXAMPLE_MUTUALLY_EXCLUSIVE_DATE_SINGLE_PARAMS = {
- id: 'date-mutually-exclusive',
- legendOrLabel: 'What year was your last MOT?',
- description: 'For example, 2018',
- year: {
- label: {
- text: 'Year',
- },
- name: 'year-exclusive',
- },
- mutuallyExclusive: {
- or: 'Or',
- deselectMessage: 'Selecting this will clear the date if one has been inputted',
- deselectGroupAdjective: 'cleared',
- deselectExclusiveOptionAdjective: 'deselected',
- exclusiveOptions: [
- {
- id: 'date-exclusive-option',
- name: 'no-paid-job',
- value: 'no-paid-job',
+ id: 'date-mutually-exclusive',
+ legendOrLabel: 'What year was your last MOT?',
+ description: 'For example, 2018',
+ year: {
label: {
- text: 'I have never had a paid job',
+ text: 'Year',
},
- },
- ],
- },
+ name: 'year-exclusive',
+ },
+ mutuallyExclusive: {
+ or: 'Or',
+ deselectMessage: 'Selecting this will clear the date if one has been inputted',
+ deselectGroupAdjective: 'cleared',
+ deselectExclusiveOptionAdjective: 'deselected',
+ exclusiveOptions: [
+ {
+ id: 'date-exclusive-option',
+ name: 'no-paid-job',
+ value: 'no-paid-job',
+ label: {
+ text: 'I have never had a paid job',
+ },
+ },
+ ],
+ },
};
describe('script: mutually-exclusive', () => {
- describe('date', () => {
- beforeEach(async () => {
- await setTestPage('/test', renderComponent('date-input', EXAMPLE_MUTUALLY_EXCLUSIVE_DATE_PARAMS));
- });
-
- describe('Given the user populated the date input', () => {
- beforeEach(async () => {
- await page.type('#date-mutually-exclusive-day', '14');
- await page.type('#date-mutually-exclusive-month', '12');
- await page.type('#date-mutually-exclusive-year', '2018');
- });
-
- describe('when the user clicks the mutually exclusive option', () => {
+ describe('date', () => {
beforeEach(async () => {
- await page.click('#date-exclusive-option');
+ await setTestPage('/test', renderComponent('date-input', EXAMPLE_MUTUALLY_EXCLUSIVE_DATE_PARAMS));
});
- it('then the mutually exclusive option should be checked', async () => {
- const isChecked = await page.$eval('#date-exclusive-option', (node) => node.checked);
- expect(isChecked).toBe(true);
+ describe('Given the user populated the date input', () => {
+ beforeEach(async () => {
+ await page.type('#date-mutually-exclusive-day', '14');
+ await page.type('#date-mutually-exclusive-month', '12');
+ await page.type('#date-mutually-exclusive-year', '2018');
+ });
+
+ describe('when the user clicks the mutually exclusive option', () => {
+ beforeEach(async () => {
+ await page.click('#date-exclusive-option');
+ });
+
+ it('then the mutually exclusive option should be checked', async () => {
+ const isChecked = await page.$eval('#date-exclusive-option', (node) => node.checked);
+ expect(isChecked).toBe(true);
+ });
+
+ it('then the date input should be cleared', async () => {
+ const dayValue = await page.$eval('#date-mutually-exclusive-day', (node) => node.value);
+ expect(dayValue).toBe('');
+ const monthValue = await page.$eval('#date-mutually-exclusive-month', (node) => node.value);
+ expect(monthValue).toBe('');
+ const yearValue = await page.$eval('#date-mutually-exclusive-year', (node) => node.value);
+ expect(yearValue).toBe('');
+ });
+
+ it('then the aria alert should tell the user that the date input has been cleared', async () => {
+ await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
+
+ const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
+ expect(alertText).toBe('Day cleared. Month cleared. Year cleared.');
+ });
+ });
});
- it('then the date input should be cleared', async () => {
- const dayValue = await page.$eval('#date-mutually-exclusive-day', (node) => node.value);
- expect(dayValue).toBe('');
- const monthValue = await page.$eval('#date-mutually-exclusive-month', (node) => node.value);
- expect(monthValue).toBe('');
- const yearValue = await page.$eval('#date-mutually-exclusive-year', (node) => node.value);
- expect(yearValue).toBe('');
+ describe('Given the user has checked the mutually exclusive exclusive option', () => {
+ beforeEach(async () => {
+ await page.click('#date-exclusive-option');
+ });
+
+ describe('when the user populates the dateInput', () => {
+ beforeEach(async () => {
+ await page.type('#date-mutually-exclusive-day', '14');
+ await page.type('#date-mutually-exclusive-month', '12');
+ await page.type('#date-mutually-exclusive-year', '2018');
+ });
+
+ it('then the mutually exclusive option should be unchecked', async () => {
+ const isChecked = await page.$eval('#date-exclusive-option', (node) => node.checked);
+ expect(isChecked).toBe(false);
+ });
+
+ it('then the aria alert should tell the user that the exclusive option has been unchecked', async () => {
+ await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
+
+ const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
+ expect(alertText).toBe('I have never had a paid job deselected.');
+ });
+ });
});
- it('then the aria alert should tell the user that the date input has been cleared', async () => {
- await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
-
- const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
- expect(alertText).toBe('Day cleared. Month cleared. Year cleared.');
+ describe('Given the user has not populated the date input or checked the exclusive option', () => {
+ describe('when the user populates the date input', () => {
+ beforeEach(async () => {
+ await page.type('#date-mutually-exclusive-day', '14');
+ await page.type('#date-mutually-exclusive-month', '12');
+ await page.type('#date-mutually-exclusive-year', '2018');
+ });
+
+ it('then the aria alert shouldnt say anything', async () => {
+ await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
+
+ const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
+ expect(alertText).toBe('');
+ });
+ });
+
+ describe('when the user clicks the mutually exclusive option', () => {
+ beforeEach(async () => {
+ await page.click('#date-exclusive-option');
+ });
+
+ it('then the aria alert shouldnt say anything', async () => {
+ await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
+
+ const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
+ expect(alertText).toBe('');
+ });
+ });
});
- });
});
-
- describe('Given the user has checked the mutually exclusive exclusive option', () => {
- beforeEach(async () => {
- await page.click('#date-exclusive-option');
- });
-
- describe('when the user populates the dateInput', () => {
+ describe('date-year', () => {
beforeEach(async () => {
- await page.type('#date-mutually-exclusive-day', '14');
- await page.type('#date-mutually-exclusive-month', '12');
- await page.type('#date-mutually-exclusive-year', '2018');
+ await setTestPage('/test', renderComponent('date-input', EXAMPLE_MUTUALLY_EXCLUSIVE_DATE_SINGLE_PARAMS));
});
- it('then the mutually exclusive option should be unchecked', async () => {
- const isChecked = await page.$eval('#date-exclusive-option', (node) => node.checked);
- expect(isChecked).toBe(false);
+ describe('Given the user populated the date input', () => {
+ beforeEach(async () => {
+ await page.type('#date-mutually-exclusive-year', '2018');
+ });
+
+ describe('when the user clicks the mutually exclusive option', () => {
+ beforeEach(async () => {
+ await page.click('#date-exclusive-option');
+ });
+
+ it('then the mutually exclusive option should be checked', async () => {
+ const isChecked = await page.$eval('#date-exclusive-option', (node) => node.checked);
+ expect(isChecked).toBe(true);
+ });
+
+ it('then the date input should be cleared', async () => {
+ const yearValue = await page.$eval('#date-mutually-exclusive-year', (node) => node.value);
+ expect(yearValue).toBe('');
+ });
+
+ it('then the aria alert should tell the user that the date input has been cleared', async () => {
+ await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
+
+ const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
+ expect(alertText).toBe('What year was your last MOT? cleared.');
+ });
+ });
});
- it('then the aria alert should tell the user that the exclusive option has been unchecked', async () => {
- await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
+ describe('Given the user has checked the mutually exclusive exclusive option', () => {
+ beforeEach(async () => {
+ await page.click('#date-exclusive-option');
+ });
- const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
- expect(alertText).toBe('I have never had a paid job deselected.');
- });
- });
- });
-
- describe('Given the user has not populated the date input or checked the exclusive option', () => {
- describe('when the user populates the date input', () => {
- beforeEach(async () => {
- await page.type('#date-mutually-exclusive-day', '14');
- await page.type('#date-mutually-exclusive-month', '12');
- await page.type('#date-mutually-exclusive-year', '2018');
- });
+ describe('when the user populates the dateInput', () => {
+ beforeEach(async () => {
+ await page.type('#date-mutually-exclusive-year', '2018');
+ });
- it('then the aria alert shouldnt say anything', async () => {
- await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
+ it('then the mutually exclusive option should be unchecked', async () => {
+ const isChecked = await page.$eval('#date-exclusive-option', (node) => node.checked);
+ expect(isChecked).toBe(false);
+ });
- const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
- expect(alertText).toBe('');
- });
- });
+ it('then the aria alert should tell the user that the exclusive option has been unchecked', async () => {
+ await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
- describe('when the user clicks the mutually exclusive option', () => {
- beforeEach(async () => {
- await page.click('#date-exclusive-option');
+ const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
+ expect(alertText).toBe('I have never had a paid job deselected.');
+ });
+ });
});
- it('then the aria alert shouldnt say anything', async () => {
- await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
+ describe('Given the user has not populated the date input or checked the exclusive option', () => {
+ describe('when the user populates the date input', () => {
+ beforeEach(async () => {
+ await page.type('#date-mutually-exclusive-year', '2018');
+ });
- const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
- expect(alertText).toBe('');
- });
- });
- });
- });
- describe('date-year', () => {
- beforeEach(async () => {
- await setTestPage('/test', renderComponent('date-input', EXAMPLE_MUTUALLY_EXCLUSIVE_DATE_SINGLE_PARAMS));
- });
+ it('then the aria alert shouldnt say anything', async () => {
+ await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
- describe('Given the user populated the date input', () => {
- beforeEach(async () => {
- await page.type('#date-mutually-exclusive-year', '2018');
- });
+ const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
+ expect(alertText).toBe('');
+ });
+ });
- describe('when the user clicks the mutually exclusive option', () => {
- beforeEach(async () => {
- await page.click('#date-exclusive-option');
- });
-
- it('then the mutually exclusive option should be checked', async () => {
- const isChecked = await page.$eval('#date-exclusive-option', (node) => node.checked);
- expect(isChecked).toBe(true);
- });
-
- it('then the date input should be cleared', async () => {
- const yearValue = await page.$eval('#date-mutually-exclusive-year', (node) => node.value);
- expect(yearValue).toBe('');
- });
-
- it('then the aria alert should tell the user that the date input has been cleared', async () => {
- await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
-
- const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
- expect(alertText).toBe('What year was your last MOT? cleared.');
- });
- });
- });
-
- describe('Given the user has checked the mutually exclusive exclusive option', () => {
- beforeEach(async () => {
- await page.click('#date-exclusive-option');
- });
-
- describe('when the user populates the dateInput', () => {
- beforeEach(async () => {
- await page.type('#date-mutually-exclusive-year', '2018');
- });
-
- it('then the mutually exclusive option should be unchecked', async () => {
- const isChecked = await page.$eval('#date-exclusive-option', (node) => node.checked);
- expect(isChecked).toBe(false);
- });
-
- it('then the aria alert should tell the user that the exclusive option has been unchecked', async () => {
- await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
-
- const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
- expect(alertText).toBe('I have never had a paid job deselected.');
- });
- });
- });
-
- describe('Given the user has not populated the date input or checked the exclusive option', () => {
- describe('when the user populates the date input', () => {
- beforeEach(async () => {
- await page.type('#date-mutually-exclusive-year', '2018');
- });
-
- it('then the aria alert shouldnt say anything', async () => {
- await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
-
- const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
- expect(alertText).toBe('');
- });
- });
-
- describe('when the user clicks the mutually exclusive option', () => {
- beforeEach(async () => {
- await page.click('#date-exclusive-option');
- });
+ describe('when the user clicks the mutually exclusive option', () => {
+ beforeEach(async () => {
+ await page.click('#date-exclusive-option');
+ });
- it('then the aria alert shouldnt say anything', async () => {
- await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
+ it('then the aria alert shouldnt say anything', async () => {
+ await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
- const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
- expect(alertText).toBe('');
+ const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
+ expect(alertText).toBe('');
+ });
+ });
});
- });
});
- });
});
diff --git a/src/components/mutually-exclusive/mutually-exclusive.dom.js b/src/components/mutually-exclusive/mutually-exclusive.dom.js
index 9b1d2abd69..fdbb5bb7f7 100644
--- a/src/components/mutually-exclusive/mutually-exclusive.dom.js
+++ b/src/components/mutually-exclusive/mutually-exclusive.dom.js
@@ -3,13 +3,13 @@ import domready from '../../js/domready';
const exclusiveWrapperClass = 'ons-js-mutually-exclusive';
async function mutuallyExclusiveInputs() {
- const exclusiveWrapperElements = [...document.getElementsByClassName(exclusiveWrapperClass)];
+ const exclusiveWrapperElements = [...document.getElementsByClassName(exclusiveWrapperClass)];
- if (exclusiveWrapperElements.length) {
- const MutuallyExclusive = (await import('./mutually-exclusive')).default;
+ if (exclusiveWrapperElements.length) {
+ const MutuallyExclusive = (await import('./mutually-exclusive')).default;
- exclusiveWrapperElements.forEach((element) => new MutuallyExclusive(element));
- }
+ exclusiveWrapperElements.forEach((element) => new MutuallyExclusive(element));
+ }
}
domready(mutuallyExclusiveInputs);
diff --git a/src/components/mutually-exclusive/mutually-exclusive.duration.spec.js b/src/components/mutually-exclusive/mutually-exclusive.duration.spec.js
index 3a3129614a..d8161383b3 100644
--- a/src/components/mutually-exclusive/mutually-exclusive.duration.spec.js
+++ b/src/components/mutually-exclusive/mutually-exclusive.duration.spec.js
@@ -3,250 +3,250 @@ import { renderComponent, setTestPage } from '../../tests/helpers/rendering';
const SCREEN_READER_TIMEOUT_DELAY = 300;
const EXAMPLE_MUTUALLY_EXCLUSIVE_DURATION_PARAMS = {
- id: 'address-duration',
- legendOrLabel: 'How long have you lived at this address?',
- description: 'If you have lived at this address for less than a year then enter 0 into the year input.',
- field1: {
- id: 'address-duration-years',
- name: 'address-duration-years',
- suffix: {
- text: 'Years',
- id: 'address-duration-years-suffix',
- },
- },
- field2: {
- id: 'address-duration-months',
- name: 'address-duration-months',
- suffix: {
- text: 'Months',
- id: 'address-duration-months-suffix',
+ id: 'address-duration',
+ legendOrLabel: 'How long have you lived at this address?',
+ description: 'If you have lived at this address for less than a year then enter 0 into the year input.',
+ field1: {
+ id: 'address-duration-years',
+ name: 'address-duration-years',
+ suffix: {
+ text: 'Years',
+ id: 'address-duration-years-suffix',
+ },
},
- },
- mutuallyExclusive: {
- or: 'Or',
- deselectMessage: 'Selecting this will clear the date if one has been inputted',
- deselectGroupAdjective: 'cleared',
- deselectExclusiveOptionAdjective: 'deselected',
- exclusiveOptions: [
- {
- id: 'duration-exclusive-option',
- name: 'no-duration',
- value: 'no-duration',
- label: {
- text: 'I have not moved in to this address yet',
+ field2: {
+ id: 'address-duration-months',
+ name: 'address-duration-months',
+ suffix: {
+ text: 'Months',
+ id: 'address-duration-months-suffix',
},
- },
- ],
- },
+ },
+ mutuallyExclusive: {
+ or: 'Or',
+ deselectMessage: 'Selecting this will clear the date if one has been inputted',
+ deselectGroupAdjective: 'cleared',
+ deselectExclusiveOptionAdjective: 'deselected',
+ exclusiveOptions: [
+ {
+ id: 'duration-exclusive-option',
+ name: 'no-duration',
+ value: 'no-duration',
+ label: {
+ text: 'I have not moved in to this address yet',
+ },
+ },
+ ],
+ },
};
const EXAMPLE_MUTUALLY_EXCLUSIVE_DURATION_SINGLE_PARAMS = {
- id: 'address-duration',
- legendOrLabel: 'How long have you lived at this address?',
- description: 'If you have lived at this address for less than a year then enter 0 into the year input.',
- field1: {
- id: 'address-duration-years',
- name: 'address-duration-years',
- suffix: {
- text: 'Years',
- id: 'address-duration-years-suffix',
- },
- },
- mutuallyExclusive: {
- or: 'Or',
- deselectMessage: 'Selecting this will clear the date if one has been inputted',
- deselectGroupAdjective: 'cleared',
- deselectExclusiveOptionAdjective: 'deselected',
- exclusiveOptions: [
- {
- id: 'duration-exclusive-option',
- name: 'no-duration',
- value: 'no-duration',
- label: {
- text: 'I have not moved in to this address yet',
+ id: 'address-duration',
+ legendOrLabel: 'How long have you lived at this address?',
+ description: 'If you have lived at this address for less than a year then enter 0 into the year input.',
+ field1: {
+ id: 'address-duration-years',
+ name: 'address-duration-years',
+ suffix: {
+ text: 'Years',
+ id: 'address-duration-years-suffix',
},
- },
- ],
- },
+ },
+ mutuallyExclusive: {
+ or: 'Or',
+ deselectMessage: 'Selecting this will clear the date if one has been inputted',
+ deselectGroupAdjective: 'cleared',
+ deselectExclusiveOptionAdjective: 'deselected',
+ exclusiveOptions: [
+ {
+ id: 'duration-exclusive-option',
+ name: 'no-duration',
+ value: 'no-duration',
+ label: {
+ text: 'I have not moved in to this address yet',
+ },
+ },
+ ],
+ },
};
describe('script: mutually-exclusive', () => {
- describe('duration', () => {
- beforeEach(async () => {
- await setTestPage('/test', renderComponent('duration', EXAMPLE_MUTUALLY_EXCLUSIVE_DURATION_PARAMS));
- });
-
- describe('Given the user populated the duration', () => {
- beforeEach(async () => {
- await page.type('#address-duration-years', '2');
- await page.type('#address-duration-months', '4');
- });
-
- describe('when the user clicks the mutually exclusive option', () => {
+ describe('duration', () => {
beforeEach(async () => {
- await page.click('#duration-exclusive-option');
- });
-
- it('then the mutually exclusive option should be checked', async () => {
- const isChecked = await page.$eval('#duration-exclusive-option', (node) => node.checked);
- expect(isChecked).toBe(true);
- });
-
- it('then the inputs should be cleared', async () => {
- const yearsValue = await page.$eval('#address-duration-years', (node) => node.value);
- expect(yearsValue).toBe('');
- const monthsValue = await page.$eval('#address-duration-months', (node) => node.value);
- expect(monthsValue).toBe('');
+ await setTestPage('/test', renderComponent('duration', EXAMPLE_MUTUALLY_EXCLUSIVE_DURATION_PARAMS));
+ });
+
+ describe('Given the user populated the duration', () => {
+ beforeEach(async () => {
+ await page.type('#address-duration-years', '2');
+ await page.type('#address-duration-months', '4');
+ });
+
+ describe('when the user clicks the mutually exclusive option', () => {
+ beforeEach(async () => {
+ await page.click('#duration-exclusive-option');
+ });
+
+ it('then the mutually exclusive option should be checked', async () => {
+ const isChecked = await page.$eval('#duration-exclusive-option', (node) => node.checked);
+ expect(isChecked).toBe(true);
+ });
+
+ it('then the inputs should be cleared', async () => {
+ const yearsValue = await page.$eval('#address-duration-years', (node) => node.value);
+ expect(yearsValue).toBe('');
+ const monthsValue = await page.$eval('#address-duration-months', (node) => node.value);
+ expect(monthsValue).toBe('');
+ });
+
+ it('then the aria alert should tell the user that the inputs have been cleared', async () => {
+ await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
+
+ const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
+ expect(alertText).toBe('Years cleared. Months cleared.');
+ });
+ });
+ });
+
+ describe('Given the user has checked the mutually exclusive exclusive option', () => {
+ beforeEach(async () => {
+ await page.click('#duration-exclusive-option');
+ });
+
+ describe('when the user populates the duration fields', () => {
+ beforeEach(async () => {
+ await page.type('#address-duration-years', '2');
+ await page.type('#address-duration-months', '4');
+ });
+
+ it('then the exclusive option should be unchecked', async () => {
+ const isChecked = await page.$eval('#duration-exclusive-option', (node) => node.checked);
+ expect(isChecked).toBe(false);
+ });
+
+ it('then the aria alert should tell the user that the exclusive option has been unchecked', async () => {
+ await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
+
+ const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
+ expect(alertText).toBe('I have not moved in to this address yet deselected.');
+ });
+ });
+ });
+
+ describe('Given the user has not populated the duration inputs or checked the exclusive option', () => {
+ describe('when the user populates the duration inputs', () => {
+ beforeEach(async () => {
+ await page.type('#address-duration-years', '2');
+ await page.type('#address-duration-months', '4');
+ });
+
+ it('then the aria alert shouldnt say anything', async () => {
+ await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
+
+ const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
+ expect(alertText).toBe('');
+ });
+ });
+
+ describe('when the user clicks the mutually exclusive option', () => {
+ beforeEach(async () => {
+ await page.click('#duration-exclusive-option');
+ });
+
+ it('then the aria alert shouldnt say anything', async () => {
+ await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
+
+ const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
+ expect(alertText).toBe('');
+ });
+ });
});
-
- it('then the aria alert should tell the user that the inputs have been cleared', async () => {
- await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
-
- const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
- expect(alertText).toBe('Years cleared. Months cleared.');
- });
- });
});
- describe('Given the user has checked the mutually exclusive exclusive option', () => {
- beforeEach(async () => {
- await page.click('#duration-exclusive-option');
- });
-
- describe('when the user populates the duration fields', () => {
+ describe('duration-single', () => {
beforeEach(async () => {
- await page.type('#address-duration-years', '2');
- await page.type('#address-duration-months', '4');
+ await setTestPage('/test', renderComponent('duration', EXAMPLE_MUTUALLY_EXCLUSIVE_DURATION_SINGLE_PARAMS));
});
- it('then the exclusive option should be unchecked', async () => {
- const isChecked = await page.$eval('#duration-exclusive-option', (node) => node.checked);
- expect(isChecked).toBe(false);
- });
+ describe('Given the user populated the duration', () => {
+ beforeEach(async () => {
+ await page.type('#address-duration-years', '2');
+ });
- it('then the aria alert should tell the user that the exclusive option has been unchecked', async () => {
- await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
+ describe('when the user clicks the mutually exclusive option', () => {
+ beforeEach(async () => {
+ await page.click('#duration-exclusive-option');
+ });
- const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
- expect(alertText).toBe('I have not moved in to this address yet deselected.');
- });
- });
- });
+ it('then the mutually exclusive option should be checked', async () => {
+ const isChecked = await page.$eval('#duration-exclusive-option', (node) => node.checked);
+ expect(isChecked).toBe(true);
+ });
- describe('Given the user has not populated the duration inputs or checked the exclusive option', () => {
- describe('when the user populates the duration inputs', () => {
- beforeEach(async () => {
- await page.type('#address-duration-years', '2');
- await page.type('#address-duration-months', '4');
- });
+ it('then the inputs should be cleared', async () => {
+ const yearsValue = await page.$eval('#address-duration-years', (node) => node.value);
+ expect(yearsValue).toBe('');
+ });
- it('then the aria alert shouldnt say anything', async () => {
- await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
+ it('then the aria alert should tell the user that the inputs have been cleared', async () => {
+ await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
- const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
- expect(alertText).toBe('');
+ const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
+ expect(alertText).toBe('How long have you lived at this address? cleared.');
+ });
+ });
});
- });
- describe('when the user clicks the mutually exclusive option', () => {
- beforeEach(async () => {
- await page.click('#duration-exclusive-option');
- });
+ describe('Given the user has checked the mutually exclusive exclusive option', () => {
+ beforeEach(async () => {
+ await page.click('#duration-exclusive-option');
+ });
- it('then the aria alert shouldnt say anything', async () => {
- await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
+ describe('when the user populates the duration fields', () => {
+ beforeEach(async () => {
+ await page.type('#address-duration-years', '2');
+ });
- const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
- expect(alertText).toBe('');
- });
- });
- });
- });
+ it('then the exclusive option should be unchecked', async () => {
+ const isChecked = await page.$eval('#duration-exclusive-option', (node) => node.checked);
+ expect(isChecked).toBe(false);
+ });
- describe('duration-single', () => {
- beforeEach(async () => {
- await setTestPage('/test', renderComponent('duration', EXAMPLE_MUTUALLY_EXCLUSIVE_DURATION_SINGLE_PARAMS));
- });
-
- describe('Given the user populated the duration', () => {
- beforeEach(async () => {
- await page.type('#address-duration-years', '2');
- });
-
- describe('when the user clicks the mutually exclusive option', () => {
- beforeEach(async () => {
- await page.click('#duration-exclusive-option');
- });
-
- it('then the mutually exclusive option should be checked', async () => {
- const isChecked = await page.$eval('#duration-exclusive-option', (node) => node.checked);
- expect(isChecked).toBe(true);
- });
-
- it('then the inputs should be cleared', async () => {
- const yearsValue = await page.$eval('#address-duration-years', (node) => node.value);
- expect(yearsValue).toBe('');
- });
-
- it('then the aria alert should tell the user that the inputs have been cleared', async () => {
- await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
-
- const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
- expect(alertText).toBe('How long have you lived at this address? cleared.');
- });
- });
- });
+ it('then the aria alert should tell the user that the exclusive option has been unchecked', async () => {
+ await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
- describe('Given the user has checked the mutually exclusive exclusive option', () => {
- beforeEach(async () => {
- await page.click('#duration-exclusive-option');
- });
-
- describe('when the user populates the duration fields', () => {
- beforeEach(async () => {
- await page.type('#address-duration-years', '2');
+ const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
+ expect(alertText).toBe('I have not moved in to this address yet deselected.');
+ });
+ });
});
- it('then the exclusive option should be unchecked', async () => {
- const isChecked = await page.$eval('#duration-exclusive-option', (node) => node.checked);
- expect(isChecked).toBe(false);
- });
+ describe('Given the user has not populated the duration inputs or checked the exclusive option', () => {
+ describe('when the user populates the duration inputs', () => {
+ beforeEach(async () => {
+ await page.type('#address-duration-years', '2');
+ });
- it('then the aria alert should tell the user that the exclusive option has been unchecked', async () => {
- await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
+ it('then the aria alert shouldnt say anything', async () => {
+ await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
- const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
- expect(alertText).toBe('I have not moved in to this address yet deselected.');
- });
- });
- });
+ const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
+ expect(alertText).toBe('');
+ });
+ });
- describe('Given the user has not populated the duration inputs or checked the exclusive option', () => {
- describe('when the user populates the duration inputs', () => {
- beforeEach(async () => {
- await page.type('#address-duration-years', '2');
- });
-
- it('then the aria alert shouldnt say anything', async () => {
- await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
-
- const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
- expect(alertText).toBe('');
- });
- });
-
- describe('when the user clicks the mutually exclusive option', () => {
- beforeEach(async () => {
- await page.click('#duration-exclusive-option');
- });
+ describe('when the user clicks the mutually exclusive option', () => {
+ beforeEach(async () => {
+ await page.click('#duration-exclusive-option');
+ });
- it('then the aria alert shouldnt say anything', async () => {
- await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
+ it('then the aria alert shouldnt say anything', async () => {
+ await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
- const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
- expect(alertText).toBe('');
+ const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
+ expect(alertText).toBe('');
+ });
+ });
});
- });
});
- });
});
diff --git a/src/components/mutually-exclusive/mutually-exclusive.email.spec.js b/src/components/mutually-exclusive/mutually-exclusive.email.spec.js
index 4dbacc3a45..24794ac21d 100644
--- a/src/components/mutually-exclusive/mutually-exclusive.email.spec.js
+++ b/src/components/mutually-exclusive/mutually-exclusive.email.spec.js
@@ -3,115 +3,115 @@ import { renderComponent, setTestPage } from '../../tests/helpers/rendering';
const SCREEN_READER_TIMEOUT_DELAY = 300;
const EXAMPLE_MUTUALLY_EXCLUSIVE_EMAIL_INPUT_PARAMS = {
- id: 'email',
- type: 'email',
- legend: 'Get a confirmation email',
- label: {
- text: 'Enter an email',
- },
- mutuallyExclusive: {
- or: 'Or',
- deselectMessage: 'Selecting this will clear your email',
- deselectGroupAdjective: 'cleared',
- deselectExclusiveOptionAdjective: 'deselected',
- exclusiveOptions: [
- {
- id: 'email-exclusive-option',
- name: 'no-email',
- value: 'no-email',
- label: {
- text: 'I dont want to receive a confirmation email',
- },
- },
- ],
- },
+ id: 'email',
+ type: 'email',
+ legend: 'Get a confirmation email',
+ label: {
+ text: 'Enter an email',
+ },
+ mutuallyExclusive: {
+ or: 'Or',
+ deselectMessage: 'Selecting this will clear your email',
+ deselectGroupAdjective: 'cleared',
+ deselectExclusiveOptionAdjective: 'deselected',
+ exclusiveOptions: [
+ {
+ id: 'email-exclusive-option',
+ name: 'no-email',
+ value: 'no-email',
+ label: {
+ text: 'I dont want to receive a confirmation email',
+ },
+ },
+ ],
+ },
};
describe('script: mutually-exclusive', () => {
- describe('email input', () => {
- beforeEach(async () => {
- await setTestPage('/test', renderComponent('input', EXAMPLE_MUTUALLY_EXCLUSIVE_EMAIL_INPUT_PARAMS));
- });
-
- describe('Given the user populated the email input', () => {
- beforeEach(async () => {
- await page.type('#email', 'email@email.com');
- });
-
- describe('when the user clicks the mutually exclusive option', () => {
+ describe('email input', () => {
beforeEach(async () => {
- await page.click('#email-exclusive-option');
- });
-
- it('then the mutually exclusive option should be checked', async () => {
- const isChecked = await page.$eval('#email-exclusive-option', (node) => node.checked);
- expect(isChecked).toBe(true);
+ await setTestPage('/test', renderComponent('input', EXAMPLE_MUTUALLY_EXCLUSIVE_EMAIL_INPUT_PARAMS));
});
- it('then the email input should be cleared', async () => {
- const inputValue = await page.$eval('#email', (node) => node.value);
- expect(inputValue).toBe('');
+ describe('Given the user populated the email input', () => {
+ beforeEach(async () => {
+ await page.type('#email', 'email@email.com');
+ });
+
+ describe('when the user clicks the mutually exclusive option', () => {
+ beforeEach(async () => {
+ await page.click('#email-exclusive-option');
+ });
+
+ it('then the mutually exclusive option should be checked', async () => {
+ const isChecked = await page.$eval('#email-exclusive-option', (node) => node.checked);
+ expect(isChecked).toBe(true);
+ });
+
+ it('then the email input should be cleared', async () => {
+ const inputValue = await page.$eval('#email', (node) => node.value);
+ expect(inputValue).toBe('');
+ });
+
+ it('then the aria alert should tell the user that the email input has been cleared', async () => {
+ await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
+
+ const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
+ expect(alertText).toBe('Enter an email cleared.');
+ });
+ });
});
- it('then the aria alert should tell the user that the email input has been cleared', async () => {
- await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
-
- const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
- expect(alertText).toBe('Enter an email cleared.');
- });
- });
- });
-
- describe('Given the user has checked the mutually exclusive exclusive option', () => {
- beforeEach(async () => {
- await page.click('#email-exclusive-option');
- });
+ describe('Given the user has checked the mutually exclusive exclusive option', () => {
+ beforeEach(async () => {
+ await page.click('#email-exclusive-option');
+ });
- describe('when the user populates the email input', () => {
- beforeEach(async () => {
- await page.type('#email', 'email@email.com');
- });
+ describe('when the user populates the email input', () => {
+ beforeEach(async () => {
+ await page.type('#email', 'email@email.com');
+ });
- it('then the exclusive option should be unchecked', async () => {
- const isChecked = await page.$eval('#email-exclusive-option', (node) => node.checked);
- expect(isChecked).toBe(false);
- });
+ it('then the exclusive option should be unchecked', async () => {
+ const isChecked = await page.$eval('#email-exclusive-option', (node) => node.checked);
+ expect(isChecked).toBe(false);
+ });
- it('then the aria alert should tell the user that the exclusive option has been unchecked', async () => {
- await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
+ it('then the aria alert should tell the user that the exclusive option has been unchecked', async () => {
+ await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
- const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
- expect(alertText).toBe('I dont want to receive a confirmation email deselected.');
+ const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
+ expect(alertText).toBe('I dont want to receive a confirmation email deselected.');
+ });
+ });
});
- });
- });
- describe('Given the user has not populated the email input or checked the exclusive option', () => {
- describe('when the user populates the email input', () => {
- beforeEach(async () => {
- await page.type('#email', 'email@email.com');
- });
+ describe('Given the user has not populated the email input or checked the exclusive option', () => {
+ describe('when the user populates the email input', () => {
+ beforeEach(async () => {
+ await page.type('#email', 'email@email.com');
+ });
- it('then the aria alert shouldnt say anything', async () => {
- await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
+ it('then the aria alert shouldnt say anything', async () => {
+ await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
- const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
- expect(alertText).toBe('');
- });
- });
+ const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
+ expect(alertText).toBe('');
+ });
+ });
- describe('when the user clicks the mutually exclusive option', () => {
- beforeEach(async () => {
- await page.click('#email-exclusive-option');
- });
+ describe('when the user clicks the mutually exclusive option', () => {
+ beforeEach(async () => {
+ await page.click('#email-exclusive-option');
+ });
- it('then the aria alert shouldnt say anything', async () => {
- await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
+ it('then the aria alert shouldnt say anything', async () => {
+ await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
- const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
- expect(alertText).toBe('');
+ const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
+ expect(alertText).toBe('');
+ });
+ });
});
- });
});
- });
});
diff --git a/src/components/mutually-exclusive/mutually-exclusive.js b/src/components/mutually-exclusive/mutually-exclusive.js
index 4c9b4bd6d8..bad0e176df 100644
--- a/src/components/mutually-exclusive/mutually-exclusive.js
+++ b/src/components/mutually-exclusive/mutually-exclusive.js
@@ -7,162 +7,162 @@ const inputAbbrClass = 'ons-js-input-abbr';
const inputLegendClass = 'ons-js-input-legend';
export default class MutuallyExclusive {
- constructor(context) {
- this.context = context;
-
- const groupInputs = [...context.getElementsByClassName(exclusiveGroupItemClass)];
- this.numberOfGroupInputs = groupInputs.length;
- this.groupInputs = groupInputs.map((element) => ({
- element,
- labelText: this.getElementLabelText(element),
- hasValue: this.inputHasValue(element),
- exclusive: false,
- }));
-
- const exclusiveElements = [...context.getElementsByClassName(optionClass)];
- this.numberOfExclusiveElements = exclusiveElements.length;
- this.exclusiveElements = exclusiveElements.map((element) => ({
- element,
- label: context.querySelector(`label[for=${element.id}]`),
- labelText: this.getElementLabelText(element),
- hasValue: this.inputHasValue(element),
- exclusive: true,
- }));
-
- this.allInputs = [...this.groupInputs, ...this.exclusiveElements];
- this.voiceOverAlertElement = context.querySelector(`.${voiceOverAlertClass}`);
- this.groupAdjective = this.voiceOverAlertElement.getAttribute(groupAttrAdjective);
- this.optionAttrAdjective = this.voiceOverAlertElement.getAttribute(optionAttrAdjective);
- this.deselectMessage = this.exclusiveElements[0].element.getAttribute('data-deselect-message');
-
- this.bindEventListeners();
- }
-
- bindEventListeners() {
- this.allInputs.forEach((input) => {
- const event = ['checkbox', 'radio'].includes(input.element.type) ? 'click' : 'input';
- input.element.addEventListener(event, () => this.handleValueChange(input));
- });
- }
-
- handleValueChange(input) {
- const previousSelectedValues = this.allInputs.filter((input) => input.hasValue).map((input) => input.labelText);
- let adjective;
-
- input.hasValue = this.inputHasValue(input.element);
-
- if (input.hasValue) {
- // if not self deselect
- if (input.exclusive) {
- adjective = this.groupAdjective;
-
- this.allInputs
- .filter((input) => !input.exclusive)
- .forEach((input) => {
- input.hasValue = false;
-
- if (['checkbox', 'radio'].includes(input.element.type)) {
- input.element.checked = false;
- this.triggerEvent(input.element, 'change');
- } else {
- input.element.value = '';
- this.triggerEvent(input.element, 'input');
- }
- });
- } else if (!input.exclusive) {
- const inputs = this.allInputs.filter((input) => input.exclusive);
- adjective = this.optionAttrAdjective;
-
- inputs.forEach((input) => {
- input.hasValue = false;
- input.element.checked = false;
+ constructor(context) {
+ this.context = context;
+
+ const groupInputs = [...context.getElementsByClassName(exclusiveGroupItemClass)];
+ this.numberOfGroupInputs = groupInputs.length;
+ this.groupInputs = groupInputs.map((element) => ({
+ element,
+ labelText: this.getElementLabelText(element),
+ hasValue: this.inputHasValue(element),
+ exclusive: false,
+ }));
+
+ const exclusiveElements = [...context.getElementsByClassName(optionClass)];
+ this.numberOfExclusiveElements = exclusiveElements.length;
+ this.exclusiveElements = exclusiveElements.map((element) => ({
+ element,
+ label: context.querySelector(`label[for=${element.id}]`),
+ labelText: this.getElementLabelText(element),
+ hasValue: this.inputHasValue(element),
+ exclusive: true,
+ }));
+
+ this.allInputs = [...this.groupInputs, ...this.exclusiveElements];
+ this.voiceOverAlertElement = context.querySelector(`.${voiceOverAlertClass}`);
+ this.groupAdjective = this.voiceOverAlertElement.getAttribute(groupAttrAdjective);
+ this.optionAttrAdjective = this.voiceOverAlertElement.getAttribute(optionAttrAdjective);
+ this.deselectMessage = this.exclusiveElements[0].element.getAttribute('data-deselect-message');
+
+ this.bindEventListeners();
+ }
+
+ bindEventListeners() {
+ this.allInputs.forEach((input) => {
+ const event = ['checkbox', 'radio'].includes(input.element.type) ? 'click' : 'input';
+ input.element.addEventListener(event, () => this.handleValueChange(input));
});
+ }
- this.triggerEvent(input.element, 'change');
- }
+ handleValueChange(input) {
+ const previousSelectedValues = this.allInputs.filter((input) => input.hasValue).map((input) => input.labelText);
+ let adjective;
+
+ input.hasValue = this.inputHasValue(input.element);
+
+ if (input.hasValue) {
+ // If not self deselect
+ if (input.exclusive) {
+ adjective = this.groupAdjective;
+
+ this.allInputs
+ .filter((input) => !input.exclusive)
+ .forEach((input) => {
+ input.hasValue = false;
+
+ if (['checkbox', 'radio'].includes(input.element.type)) {
+ input.element.checked = false;
+ this.triggerEvent(input.element, 'change');
+ } else {
+ input.element.value = '';
+ this.triggerEvent(input.element, 'input');
+ }
+ });
+ } else if (!input.exclusive) {
+ const inputs = this.allInputs.filter((input) => input.exclusive);
+ adjective = this.optionAttrAdjective;
+
+ inputs.forEach((input) => {
+ input.hasValue = false;
+ input.element.checked = false;
+ });
+
+ this.triggerEvent(input.element, 'change');
+ }
- const updatedSelectedValues = this.allInputs.filter((input) => input.hasValue).map((input) => input.labelText);
- const deselectedValues = previousSelectedValues.filter((labelText) => !updatedSelectedValues.includes(labelText));
+ const updatedSelectedValues = this.allInputs.filter((input) => input.hasValue).map((input) => input.labelText);
+ const deselectedValues = previousSelectedValues.filter((labelText) => !updatedSelectedValues.includes(labelText));
- this.setDeselectMessage();
+ this.setDeselectMessage();
- // Must wait 300ms for screen reader to finish describing ticked item before trigging aria-alert
- setTimeout(() => this.setAriaLive(deselectedValues, adjective), 300);
+ // Must wait 300ms for screen reader to finish describing ticked item before trigging aria-alert
+ setTimeout(() => this.setAriaLive(deselectedValues, adjective), 300);
+ }
}
- }
- getElementLabelText(element) {
- let label = this.context.querySelector(`label[for=${element.id}]`);
+ getElementLabelText(element) {
+ let label = this.context.querySelector(`label[for=${element.id}]`);
- if (!label && this.numberOfGroupInputs > 1) {
- label = element.parentNode.querySelector(`.${inputAbbrClass}`);
- }
+ if (!label && this.numberOfGroupInputs > 1) {
+ label = element.parentNode.querySelector(`.${inputAbbrClass}`);
+ }
- if (!label) {
- label = this.context.querySelector(`.${inputLegendClass}`);
- }
+ if (!label) {
+ label = this.context.querySelector(`.${inputLegendClass}`);
+ }
- // This filter is used to strip out any text that is in 'ons-u-vh' elements for accessibility
- let labelText;
-
- if (label) {
- if (label.classList.contains(inputAbbrClass) && label.querySelector('abbr')) {
- labelText = label.getAttribute('title');
- } else if (label.classList.contains(inputAbbrClass) && label.querySelector('span')) {
- labelText = label.querySelector('span').innerText;
- } else if (label.classList.contains(inputLegendClass) && label.querySelector('h1')) {
- labelText = label.querySelector('h1').innerText;
- } else {
- labelText = [...label.childNodes].filter((node) => node.nodeType === 3 && node.textContent.trim())[0].textContent.trim();
- }
- }
+ // This filter is used to strip out any text that is in 'ons-u-vh' elements for accessibility
+ let labelText;
- return labelText;
- }
+ if (label) {
+ if (label.classList.contains(inputAbbrClass) && label.querySelector('abbr')) {
+ labelText = label.getAttribute('title');
+ } else if (label.classList.contains(inputAbbrClass) && label.querySelector('span')) {
+ labelText = label.querySelector('span').innerText;
+ } else if (label.classList.contains(inputLegendClass) && label.querySelector('h1')) {
+ labelText = label.querySelector('h1').innerText;
+ } else {
+ labelText = [...label.childNodes].filter((node) => node.nodeType === 3 && node.textContent.trim())[0].textContent.trim();
+ }
+ }
- inputHasValue(input) {
- if (['checkbox', 'radio'].includes(input.type)) {
- return !!input.checked;
- } else {
- return !!input.value.trim().length;
+ return labelText;
}
- }
- setAriaLive(deselectedValues, adjective) {
- const deselectionMessage = deselectedValues.map((label) => `${label} ${adjective}.`).join(' ');
+ inputHasValue(input) {
+ if (['checkbox', 'radio'].includes(input.type)) {
+ return !!input.checked;
+ } else {
+ return !!input.value.trim().length;
+ }
+ }
- // Only update aria-live if value changes to prevent typing from clearing the message before it's read
- if (deselectionMessage) {
- this.voiceOverAlertElement.innerHTML = deselectionMessage;
+ setAriaLive(deselectedValues, adjective) {
+ const deselectionMessage = deselectedValues.map((label) => `${label} ${adjective}.`).join(' ');
+
+ // Only update aria-live if value changes to prevent typing from clearing the message before it's read
+ if (deselectionMessage) {
+ this.voiceOverAlertElement.innerHTML = deselectionMessage;
+ }
}
- }
- setDeselectMessage() {
- const groupElementSelected = this.groupInputs.find((input) => input.hasValue);
+ setDeselectMessage() {
+ const groupElementSelected = this.groupInputs.find((input) => input.hasValue);
- if (!this.deselectMessageElement && groupElementSelected) {
- const deselectMessageElement = document.createElement('span');
- deselectMessageElement.classList.add('ons-u-vh');
- deselectMessageElement.innerHTML = `. ${this.deselectMessage}`;
+ if (!this.deselectMessageElement && groupElementSelected) {
+ const deselectMessageElement = document.createElement('span');
+ deselectMessageElement.classList.add('ons-u-vh');
+ deselectMessageElement.innerHTML = `. ${this.deselectMessage}`;
- this.deselectMessageElement = deselectMessageElement;
- this.exclusiveElements[0].label.appendChild(deselectMessageElement);
- } else if (this.deselectMessageElement && !groupElementSelected) {
- this.deselectMessageElement.remove();
- this.deselectMessageElement = null;
- }
- }
-
- triggerEvent(input, eventType) {
- let event;
- if (typeof Event === 'function') {
- event = new Event(eventType, { bubbles: false, cancelable: true });
- } else {
- // IE11 requires a custom event
- event = document.createEvent('HTMLEvents');
- event.initEvent(eventType, false, true);
+ this.deselectMessageElement = deselectMessageElement;
+ this.exclusiveElements[0].label.appendChild(deselectMessageElement);
+ } else if (this.deselectMessageElement && !groupElementSelected) {
+ this.deselectMessageElement.remove();
+ this.deselectMessageElement = null;
+ }
}
- input.dispatchEvent(event);
- }
+ triggerEvent(input, eventType) {
+ let event;
+ if (typeof Event === 'function') {
+ event = new Event(eventType, { bubbles: false, cancelable: true });
+ } else {
+ // IE11 requires a custom event
+ event = document.createEvent('HTMLEvents');
+ event.initEvent(eventType, false, true);
+ }
+
+ input.dispatchEvent(event);
+ }
}
diff --git a/src/components/mutually-exclusive/mutually-exclusive.multiple-options.checkboxes.spec.js b/src/components/mutually-exclusive/mutually-exclusive.multiple-options.checkboxes.spec.js
index fd100ea411..2a7b6f79f3 100644
--- a/src/components/mutually-exclusive/mutually-exclusive.multiple-options.checkboxes.spec.js
+++ b/src/components/mutually-exclusive/mutually-exclusive.multiple-options.checkboxes.spec.js
@@ -3,211 +3,211 @@ import { renderComponent, setTestPage } from '../../tests/helpers/rendering';
const SCREEN_READER_TIMEOUT_DELAY = 300;
const EXAMPLE_MUTUALLY_EXCLUSIVE_CHECKBOXES_PARAMS = {
- legend: 'What type of central heating do you have?',
- checkboxesLabel: 'Select all that apply',
- name: 'mutually-exclusive',
- checkboxes: [
- {
- id: 'gas',
- label: {
- text: 'Gas',
- },
- value: 'gas',
- },
- {
- id: 'electric',
- label: {
- text: 'Electric',
- },
- value: 'electric',
- },
- {
- id: 'solid-fuel',
- label: {
- text: 'Solid fuel',
- },
- value: 'solid-fuel',
- },
- {
- id: 'other-fuel',
- label: {
- text: 'Other',
- },
- value: 'other',
- other: {
- id: 'other-fuel-textbox',
- name: 'other-fuel-answer',
- label: {
- text: 'Please specify',
+ legend: 'What type of central heating do you have?',
+ checkboxesLabel: 'Select all that apply',
+ name: 'mutually-exclusive',
+ checkboxes: [
+ {
+ id: 'gas',
+ label: {
+ text: 'Gas',
+ },
+ value: 'gas',
},
- },
- },
- ],
- mutuallyExclusive: {
- or: 'or',
- deselectMessage: 'Selecting this will uncheck all other checkboxes',
- deselectGroupAdjective: 'deselected',
- deselectExclusiveOptionAdjective: 'deselected',
- exclusiveOptions: [
- {
- id: 'no-central-heating',
- label: {
- text: 'No central heating',
+ {
+ id: 'electric',
+ label: {
+ text: 'Electric',
+ },
+ value: 'electric',
},
- value: 'no-central-heating',
- },
- {
- id: 'dont-know',
- label: {
- text: 'Dont know',
+ {
+ id: 'solid-fuel',
+ label: {
+ text: 'Solid fuel',
+ },
+ value: 'solid-fuel',
+ },
+ {
+ id: 'other-fuel',
+ label: {
+ text: 'Other',
+ },
+ value: 'other',
+ other: {
+ id: 'other-fuel-textbox',
+ name: 'other-fuel-answer',
+ label: {
+ text: 'Please specify',
+ },
+ },
},
- value: 'dont-know',
- },
],
- },
+ mutuallyExclusive: {
+ or: 'or',
+ deselectMessage: 'Selecting this will uncheck all other checkboxes',
+ deselectGroupAdjective: 'deselected',
+ deselectExclusiveOptionAdjective: 'deselected',
+ exclusiveOptions: [
+ {
+ id: 'no-central-heating',
+ label: {
+ text: 'No central heating',
+ },
+ value: 'no-central-heating',
+ },
+ {
+ id: 'dont-know',
+ label: {
+ text: 'Dont know',
+ },
+ value: 'dont-know',
+ },
+ ],
+ },
};
describe('script: mutually-exclusive', () => {
- describe('checkboxes', () => {
- beforeEach(async () => {
- await setTestPage('/test', renderComponent('checkboxes', EXAMPLE_MUTUALLY_EXCLUSIVE_CHECKBOXES_PARAMS));
- });
-
- describe('Given the user has clicked multiple non-exclusive options', () => {
- beforeEach(async () => {
- await page.click('#gas');
- await page.click('#electric');
- await page.click('#other-fuel');
- await page.type('#other-fuel-textbox', 'Biofuel');
- });
-
- describe('when the user clicks a mutually exclusive option', () => {
- beforeEach(async () => {
- await page.click('#dont-know');
- });
-
- it('then the mutually exclusive option is checked', async () => {
- const isChecked = await page.$eval('#dont-know', (node) => node.checked);
- expect(isChecked).toBe(true);
- });
-
- it('then the checkboxes are not checked', async () => {
- expect(await page.$eval('#gas', (node) => node.checked)).toBe(false);
- expect(await page.$eval('#electric', (node) => node.checked)).toBe(false);
- expect(await page.$eval('#solid-fuel', (node) => node.checked)).toBe(false);
- expect(await page.$eval('#other-fuel', (node) => node.checked)).toBe(false);
- expect(await page.$eval('#other-fuel-textbox', (node) => node.value)).toBe('');
- });
-
- it('then the aria-live message should reflect the removed non exclusive options', async () => {
- await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
-
- const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
- expect(alertText).toBe('Gas deselected. Electric deselected. Other deselected. Please specify deselected.');
- });
- });
- });
-
- describe('Given the user has clicked the mutually exclusive option', () => {
- beforeEach(async () => {
- await page.click('#dont-know');
- });
-
- describe('when the user clicks the non-exclusive options', () => {
- beforeEach(async () => {
- await page.click('#gas');
- await page.click('#electric');
- await page.click('#other-fuel');
- });
-
- it('then the expected checkboxes are checked', async () => {
- expect(await page.$eval('#gas', (node) => node.checked)).toBe(true);
- expect(await page.$eval('#electric', (node) => node.checked)).toBe(true);
- expect(await page.$eval('#solid-fuel', (node) => node.checked)).toBe(false);
- expect(await page.$eval('#other-fuel', (node) => node.checked)).toBe(true);
- });
-
- it('then the exclusive options should not be checked', async () => {
- expect(await page.$eval('#no-central-heating', (node) => node.checked)).toBe(false);
- expect(await page.$eval('#dont-know', (node) => node.checked)).toBe(false);
- });
-
- it('then the aria-live message should reflect the removed exclusive option', async () => {
- await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
-
- const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
- expect(alertText).toBe('Dont know deselected.');
- });
-
- describe('and the user deselects an non-exclusive option', () => {
- beforeEach(async () => {
- await page.click('#electric');
- });
-
- it('the aria-live message should not be updated', async () => {
- await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
-
- const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
- expect(alertText).toBe('Dont know deselected.');
- });
- });
- });
- });
-
- describe('Given the user has not clicked the mutually exclusive option', () => {
- describe('when the user clicks multiple non-exclusive options', () => {
+ describe('checkboxes', () => {
beforeEach(async () => {
- await page.click('#gas');
- await page.click('#electric');
- await page.click('#other-fuel');
- });
-
- it('then the expected checkboxes are checked', async () => {
- expect(await page.$eval('#gas', (node) => node.checked)).toBe(true);
- expect(await page.$eval('#electric', (node) => node.checked)).toBe(true);
- expect(await page.$eval('#solid-fuel', (node) => node.checked)).toBe(false);
- expect(await page.$eval('#other-fuel', (node) => node.checked)).toBe(true);
- });
-
- it('then the exclusive options should not be checked', async () => {
- expect(await page.$eval('#no-central-heating', (node) => node.checked)).toBe(false);
- expect(await page.$eval('#dont-know', (node) => node.checked)).toBe(false);
- });
-
- it('then the aria-live message should say nothing', async () => {
- await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
-
- const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
- expect(alertText).toBe('');
- });
- });
- });
-
- describe('Given the user has not clicked any of the non-exclusive options', () => {
- describe('when the user clicks the mutually exclusive option', () => {
- beforeEach(async () => {
- await page.click('#dont-know');
- });
-
- it('then the expected checkboxes are not checked', async () => {
- expect(await page.$eval('#gas', (node) => node.checked)).toBe(false);
- expect(await page.$eval('#electric', (node) => node.checked)).toBe(false);
- expect(await page.$eval('#solid-fuel', (node) => node.checked)).toBe(false);
- expect(await page.$eval('#other-fuel', (node) => node.checked)).toBe(false);
- });
-
- it('then the exclusive option should be checked', async () => {
- expect(await page.$eval('#no-central-heating', (node) => node.checked)).toBe(false);
- expect(await page.$eval('#dont-know', (node) => node.checked)).toBe(true);
- });
-
- it('then the aria-live message should say nothing', async () => {
- await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
-
- const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
- expect(alertText).toBe('');
+ await setTestPage('/test', renderComponent('checkboxes', EXAMPLE_MUTUALLY_EXCLUSIVE_CHECKBOXES_PARAMS));
+ });
+
+ describe('Given the user has clicked multiple non-exclusive options', () => {
+ beforeEach(async () => {
+ await page.click('#gas');
+ await page.click('#electric');
+ await page.click('#other-fuel');
+ await page.type('#other-fuel-textbox', 'Biofuel');
+ });
+
+ describe('when the user clicks a mutually exclusive option', () => {
+ beforeEach(async () => {
+ await page.click('#dont-know');
+ });
+
+ it('then the mutually exclusive option is checked', async () => {
+ const isChecked = await page.$eval('#dont-know', (node) => node.checked);
+ expect(isChecked).toBe(true);
+ });
+
+ it('then the checkboxes are not checked', async () => {
+ expect(await page.$eval('#gas', (node) => node.checked)).toBe(false);
+ expect(await page.$eval('#electric', (node) => node.checked)).toBe(false);
+ expect(await page.$eval('#solid-fuel', (node) => node.checked)).toBe(false);
+ expect(await page.$eval('#other-fuel', (node) => node.checked)).toBe(false);
+ expect(await page.$eval('#other-fuel-textbox', (node) => node.value)).toBe('');
+ });
+
+ it('then the aria-live message should reflect the removed non exclusive options', async () => {
+ await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
+
+ const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
+ expect(alertText).toBe('Gas deselected. Electric deselected. Other deselected. Please specify deselected.');
+ });
+ });
+ });
+
+ describe('Given the user has clicked the mutually exclusive option', () => {
+ beforeEach(async () => {
+ await page.click('#dont-know');
+ });
+
+ describe('when the user clicks the non-exclusive options', () => {
+ beforeEach(async () => {
+ await page.click('#gas');
+ await page.click('#electric');
+ await page.click('#other-fuel');
+ });
+
+ it('then the expected checkboxes are checked', async () => {
+ expect(await page.$eval('#gas', (node) => node.checked)).toBe(true);
+ expect(await page.$eval('#electric', (node) => node.checked)).toBe(true);
+ expect(await page.$eval('#solid-fuel', (node) => node.checked)).toBe(false);
+ expect(await page.$eval('#other-fuel', (node) => node.checked)).toBe(true);
+ });
+
+ it('then the exclusive options should not be checked', async () => {
+ expect(await page.$eval('#no-central-heating', (node) => node.checked)).toBe(false);
+ expect(await page.$eval('#dont-know', (node) => node.checked)).toBe(false);
+ });
+
+ it('then the aria-live message should reflect the removed exclusive option', async () => {
+ await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
+
+ const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
+ expect(alertText).toBe('Dont know deselected.');
+ });
+
+ describe('and the user deselects an non-exclusive option', () => {
+ beforeEach(async () => {
+ await page.click('#electric');
+ });
+
+ it('the aria-live message should not be updated', async () => {
+ await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
+
+ const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
+ expect(alertText).toBe('Dont know deselected.');
+ });
+ });
+ });
+ });
+
+ describe('Given the user has not clicked the mutually exclusive option', () => {
+ describe('when the user clicks multiple non-exclusive options', () => {
+ beforeEach(async () => {
+ await page.click('#gas');
+ await page.click('#electric');
+ await page.click('#other-fuel');
+ });
+
+ it('then the expected checkboxes are checked', async () => {
+ expect(await page.$eval('#gas', (node) => node.checked)).toBe(true);
+ expect(await page.$eval('#electric', (node) => node.checked)).toBe(true);
+ expect(await page.$eval('#solid-fuel', (node) => node.checked)).toBe(false);
+ expect(await page.$eval('#other-fuel', (node) => node.checked)).toBe(true);
+ });
+
+ it('then the exclusive options should not be checked', async () => {
+ expect(await page.$eval('#no-central-heating', (node) => node.checked)).toBe(false);
+ expect(await page.$eval('#dont-know', (node) => node.checked)).toBe(false);
+ });
+
+ it('then the aria-live message should say nothing', async () => {
+ await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
+
+ const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
+ expect(alertText).toBe('');
+ });
+ });
+ });
+
+ describe('Given the user has not clicked any of the non-exclusive options', () => {
+ describe('when the user clicks the mutually exclusive option', () => {
+ beforeEach(async () => {
+ await page.click('#dont-know');
+ });
+
+ it('then the expected checkboxes are not checked', async () => {
+ expect(await page.$eval('#gas', (node) => node.checked)).toBe(false);
+ expect(await page.$eval('#electric', (node) => node.checked)).toBe(false);
+ expect(await page.$eval('#solid-fuel', (node) => node.checked)).toBe(false);
+ expect(await page.$eval('#other-fuel', (node) => node.checked)).toBe(false);
+ });
+
+ it('then the exclusive option should be checked', async () => {
+ expect(await page.$eval('#no-central-heating', (node) => node.checked)).toBe(false);
+ expect(await page.$eval('#dont-know', (node) => node.checked)).toBe(true);
+ });
+
+ it('then the aria-live message should say nothing', async () => {
+ await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
+
+ const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
+ expect(alertText).toBe('');
+ });
+ });
});
- });
});
- });
});
diff --git a/src/components/mutually-exclusive/mutually-exclusive.number.spec.js b/src/components/mutually-exclusive/mutually-exclusive.number.spec.js
index 1409688c19..d2dd0cbe16 100644
--- a/src/components/mutually-exclusive/mutually-exclusive.number.spec.js
+++ b/src/components/mutually-exclusive/mutually-exclusive.number.spec.js
@@ -3,121 +3,121 @@ import { renderComponent, setTestPage } from '../../tests/helpers/rendering';
const SCREEN_READER_TIMEOUT_DELAY = 300;
const EXAMPLE_MUTUALLY_EXCLUSIVE_NUMBER_INPUT_PARAMS = {
- id: 'currency',
- type: 'number',
- legend: 'What is your annual income before tax in 2018/19?',
- width: '5',
- label: {
- text: 'Gross annual income',
- },
- prefix: {
- title: 'Pounds',
- text: '£',
- id: 'currency-prefix',
- },
- mutuallyExclusive: {
- or: 'Or',
- deselectMessage: 'Selecting this will clear your inputted annual income',
- deselectGroupAdjective: 'cleared',
- deselectExclusiveOptionAdjective: 'deselected',
- exclusiveOptions: [
- {
- id: 'currency-exclusive-option',
- name: 'no-currency',
- value: 'no-currency',
- label: {
- text: 'I prefer not to say',
- },
- },
- ],
- },
+ id: 'currency',
+ type: 'number',
+ legend: 'What is your annual income before tax in 2018/19?',
+ width: '5',
+ label: {
+ text: 'Gross annual income',
+ },
+ prefix: {
+ title: 'Pounds',
+ text: '£',
+ id: 'currency-prefix',
+ },
+ mutuallyExclusive: {
+ or: 'Or',
+ deselectMessage: 'Selecting this will clear your inputted annual income',
+ deselectGroupAdjective: 'cleared',
+ deselectExclusiveOptionAdjective: 'deselected',
+ exclusiveOptions: [
+ {
+ id: 'currency-exclusive-option',
+ name: 'no-currency',
+ value: 'no-currency',
+ label: {
+ text: 'I prefer not to say',
+ },
+ },
+ ],
+ },
};
describe('script: mutually-exclusive', () => {
- describe('number input', () => {
- beforeEach(async () => {
- await setTestPage('/test', renderComponent('input', EXAMPLE_MUTUALLY_EXCLUSIVE_NUMBER_INPUT_PARAMS));
- });
-
- describe('Given the user populated the number input', () => {
- beforeEach(async () => {
- await page.type('#currency', '25000');
- });
-
- describe('when the user clicks the mutually exclusive option', () => {
+ describe('number input', () => {
beforeEach(async () => {
- await page.click('#currency-exclusive-option');
- });
-
- it('then the mutually exclusive option should be checked', async () => {
- const isChecked = await page.$eval('#currency-exclusive-option', (node) => node.checked);
- expect(isChecked).toBe(true);
+ await setTestPage('/test', renderComponent('input', EXAMPLE_MUTUALLY_EXCLUSIVE_NUMBER_INPUT_PARAMS));
});
- it('then the number input should be cleared', async () => {
- const inputValue = await page.$eval('#currency', (node) => node.value);
- expect(inputValue).toBe('');
+ describe('Given the user populated the number input', () => {
+ beforeEach(async () => {
+ await page.type('#currency', '25000');
+ });
+
+ describe('when the user clicks the mutually exclusive option', () => {
+ beforeEach(async () => {
+ await page.click('#currency-exclusive-option');
+ });
+
+ it('then the mutually exclusive option should be checked', async () => {
+ const isChecked = await page.$eval('#currency-exclusive-option', (node) => node.checked);
+ expect(isChecked).toBe(true);
+ });
+
+ it('then the number input should be cleared', async () => {
+ const inputValue = await page.$eval('#currency', (node) => node.value);
+ expect(inputValue).toBe('');
+ });
+
+ it('then the aria alert should tell the user that the number input has been cleared', async () => {
+ await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
+
+ const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
+ expect(alertText).toBe('Gross annual income cleared.');
+ });
+ });
});
- it('then the aria alert should tell the user that the number input has been cleared', async () => {
- await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
-
- const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
- expect(alertText).toBe('Gross annual income cleared.');
- });
- });
- });
-
- describe('Given the user has checked the mutually exclusive exclusive option', () => {
- beforeEach(async () => {
- await page.click('#currency-exclusive-option');
- });
+ describe('Given the user has checked the mutually exclusive exclusive option', () => {
+ beforeEach(async () => {
+ await page.click('#currency-exclusive-option');
+ });
- describe('when the user populates the number input', () => {
- beforeEach(async () => {
- await page.type('#currency', '25000');
- });
+ describe('when the user populates the number input', () => {
+ beforeEach(async () => {
+ await page.type('#currency', '25000');
+ });
- it('then the exclusive option should be unchecked', async () => {
- const isChecked = await page.$eval('#currency-exclusive-option', (node) => node.checked);
- expect(isChecked).toBe(false);
- });
+ it('then the exclusive option should be unchecked', async () => {
+ const isChecked = await page.$eval('#currency-exclusive-option', (node) => node.checked);
+ expect(isChecked).toBe(false);
+ });
- it('then the aria alert should tell the user that the exclusive option has been unchecked', async () => {
- await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
+ it('then the aria alert should tell the user that the exclusive option has been unchecked', async () => {
+ await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
- const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
- expect(alertText).toBe('I prefer not to say deselected.');
+ const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
+ expect(alertText).toBe('I prefer not to say deselected.');
+ });
+ });
});
- });
- });
- describe('Given the user has not populated the number input or checked the exclusive option', () => {
- describe('when the user populates the number input', () => {
- beforeEach(async () => {
- await page.type('#currency', '25000');
- });
+ describe('Given the user has not populated the number input or checked the exclusive option', () => {
+ describe('when the user populates the number input', () => {
+ beforeEach(async () => {
+ await page.type('#currency', '25000');
+ });
- it('then the aria alert shouldnt say anything', async () => {
- await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
+ it('then the aria alert shouldnt say anything', async () => {
+ await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
- const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
- expect(alertText).toBe('');
- });
- });
+ const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
+ expect(alertText).toBe('');
+ });
+ });
- describe('when the user clicks the mutually exclusive option', () => {
- beforeEach(async () => {
- await page.click('#currency-exclusive-option');
- });
+ describe('when the user clicks the mutually exclusive option', () => {
+ beforeEach(async () => {
+ await page.click('#currency-exclusive-option');
+ });
- it('then the aria alert shouldnt say anything', async () => {
- await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
+ it('then the aria alert shouldnt say anything', async () => {
+ await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
- const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
- expect(alertText).toBe('');
+ const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
+ expect(alertText).toBe('');
+ });
+ });
});
- });
});
- });
});
diff --git a/src/components/mutually-exclusive/mutually-exclusive.textarea.spec.js b/src/components/mutually-exclusive/mutually-exclusive.textarea.spec.js
index b26d36d049..d7fea99544 100644
--- a/src/components/mutually-exclusive/mutually-exclusive.textarea.spec.js
+++ b/src/components/mutually-exclusive/mutually-exclusive.textarea.spec.js
@@ -3,129 +3,129 @@ import { renderComponent, setTestPage } from '../../tests/helpers/rendering';
const SCREEN_READER_TIMEOUT_DELAY = 300;
const EXAMPLE_MUTUALLY_EXCLUSIVE_TEXTAREA_PARAMS = {
- id: 'feedback',
- name: 'feedback',
- width: '30',
- legend: 'What do you think of this service?',
- label: {
- text: 'Enter your feedback',
- description: 'For example describe any difficulties you experienced in the use of this service',
- },
- charCheckLimit: {
- limit: 200,
- charCountSingular: 'You have {x} character remaining',
- charCountPlural: 'You have {x} characters remaining',
- },
- mutuallyExclusive: {
- or: 'Or',
- deselectMessage: 'Selecting this will clear your feedback',
- deselectGroupAdjective: 'cleared',
- deselectExclusiveOptionAdjective: 'deselected',
- exclusiveOptions: [
- {
- id: 'feedback-exclusive-option',
- name: 'no-feedback',
- value: 'no-feedback',
- label: {
- text: 'I dont want to provide feedback',
- },
- },
- ],
- },
+ id: 'feedback',
+ name: 'feedback',
+ width: '30',
+ legend: 'What do you think of this service?',
+ label: {
+ text: 'Enter your feedback',
+ description: 'For example describe any difficulties you experienced in the use of this service',
+ },
+ charCheckLimit: {
+ limit: 200,
+ charCountSingular: 'You have {x} character remaining',
+ charCountPlural: 'You have {x} characters remaining',
+ },
+ mutuallyExclusive: {
+ or: 'Or',
+ deselectMessage: 'Selecting this will clear your feedback',
+ deselectGroupAdjective: 'cleared',
+ deselectExclusiveOptionAdjective: 'deselected',
+ exclusiveOptions: [
+ {
+ id: 'feedback-exclusive-option',
+ name: 'no-feedback',
+ value: 'no-feedback',
+ label: {
+ text: 'I dont want to provide feedback',
+ },
+ },
+ ],
+ },
};
const FAKE_TEXTAREA_INPUT = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.';
describe('script: mutually-exclusive', () => {
- describe('textarea', () => {
- beforeEach(async () => {
- await setTestPage('/test', renderComponent('textarea', EXAMPLE_MUTUALLY_EXCLUSIVE_TEXTAREA_PARAMS));
- });
-
- describe('Given the user populated the textarea', () => {
- beforeEach(async () => {
- await page.type('#feedback', FAKE_TEXTAREA_INPUT);
- });
-
- describe('when the user clicks the mutually exclusive option', () => {
+ describe('textarea', () => {
beforeEach(async () => {
- await page.click('#feedback-exclusive-option');
- });
-
- it('then the mutually exclusive option should be checked', async () => {
- const isChecked = await page.$eval('#feedback-exclusive-option', (node) => node.checked);
- expect(isChecked).toBe(true);
+ await setTestPage('/test', renderComponent('textarea', EXAMPLE_MUTUALLY_EXCLUSIVE_TEXTAREA_PARAMS));
});
- it('then the textarea should be cleared', async () => {
- const textareaValue = await page.$eval('#feedback', (node) => node.value);
- expect(textareaValue).toBe('');
- });
-
- it('then the characters remaining readout should be reset', async () => {
- const limitText = await page.$eval('#feedback-lim', (node) => node.textContent);
- expect(limitText).toBe('You have 200 characters remaining');
- });
-
- it('then the aria alert should tell the user that the textarea has been cleared', async () => {
- await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
-
- const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
- expect(alertText).toBe('Enter your feedback cleared.');
+ describe('Given the user populated the textarea', () => {
+ beforeEach(async () => {
+ await page.type('#feedback', FAKE_TEXTAREA_INPUT);
+ });
+
+ describe('when the user clicks the mutually exclusive option', () => {
+ beforeEach(async () => {
+ await page.click('#feedback-exclusive-option');
+ });
+
+ it('then the mutually exclusive option should be checked', async () => {
+ const isChecked = await page.$eval('#feedback-exclusive-option', (node) => node.checked);
+ expect(isChecked).toBe(true);
+ });
+
+ it('then the textarea should be cleared', async () => {
+ const textareaValue = await page.$eval('#feedback', (node) => node.value);
+ expect(textareaValue).toBe('');
+ });
+
+ it('then the characters remaining readout should be reset', async () => {
+ const limitText = await page.$eval('#feedback-lim', (node) => node.textContent);
+ expect(limitText).toBe('You have 200 characters remaining');
+ });
+
+ it('then the aria alert should tell the user that the textarea has been cleared', async () => {
+ await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
+
+ const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
+ expect(alertText).toBe('Enter your feedback cleared.');
+ });
+ });
});
- });
- });
- describe('Given the user has checked the mutually exclusive exclusiveOption', () => {
- beforeEach(async () => {
- await page.click('#feedback-exclusive-option');
- });
+ describe('Given the user has checked the mutually exclusive exclusiveOption', () => {
+ beforeEach(async () => {
+ await page.click('#feedback-exclusive-option');
+ });
- describe('when the user populates the textarea', () => {
- beforeEach(async () => {
- await page.type('#feedback', FAKE_TEXTAREA_INPUT);
- });
+ describe('when the user populates the textarea', () => {
+ beforeEach(async () => {
+ await page.type('#feedback', FAKE_TEXTAREA_INPUT);
+ });
- it('then the exclusive option should be unchecked', async () => {
- const isChecked = await page.$eval('#feedback-exclusive-option', (node) => node.checked);
- expect(isChecked).toBe(false);
- });
+ it('then the exclusive option should be unchecked', async () => {
+ const isChecked = await page.$eval('#feedback-exclusive-option', (node) => node.checked);
+ expect(isChecked).toBe(false);
+ });
- it('then the aria alert should tell the user that the exclusive option has been unchecked', async () => {
- await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
+ it('then the aria alert should tell the user that the exclusive option has been unchecked', async () => {
+ await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
- const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
- expect(alertText).toBe('I dont want to provide feedback deselected.');
+ const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
+ expect(alertText).toBe('I dont want to provide feedback deselected.');
+ });
+ });
});
- });
- });
- describe('Given the user has not populated the textarea or checked the exclusive option', () => {
- describe('when the user populates the textarea', () => {
- beforeEach(async () => {
- await page.type('#feedback', FAKE_TEXTAREA_INPUT);
- });
+ describe('Given the user has not populated the textarea or checked the exclusive option', () => {
+ describe('when the user populates the textarea', () => {
+ beforeEach(async () => {
+ await page.type('#feedback', FAKE_TEXTAREA_INPUT);
+ });
- it('then the aria alert shouldnt say anything', async () => {
- await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
+ it('then the aria alert shouldnt say anything', async () => {
+ await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
- const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
- expect(alertText).toBe('');
- });
- });
+ const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
+ expect(alertText).toBe('');
+ });
+ });
- describe('when the user clicks the mutually exclusive option', () => {
- beforeEach(async () => {
- await page.click('#feedback-exclusive-option');
- });
+ describe('when the user clicks the mutually exclusive option', () => {
+ beforeEach(async () => {
+ await page.click('#feedback-exclusive-option');
+ });
- it('then the aria alert shouldnt say anything', async () => {
- await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
+ it('then the aria alert shouldnt say anything', async () => {
+ await page.waitForTimeout(SCREEN_READER_TIMEOUT_DELAY);
- const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
- expect(alertText).toBe('');
+ const alertText = await page.$eval('.ons-js-exclusive-alert', (node) => node.textContent);
+ expect(alertText).toBe('');
+ });
+ });
});
- });
});
- });
});
diff --git a/src/components/navigation/_macro.spec.js b/src/components/navigation/_macro.spec.js
index fadf302405..d0187e037a 100644
--- a/src/components/navigation/_macro.spec.js
+++ b/src/components/navigation/_macro.spec.js
@@ -7,382 +7,388 @@ import { renderComponent, templateFaker } from '../../tests/helpers/rendering';
import { mapAll } from '../../tests/helpers/cheerio';
const PARAMS = {
- id: 'main-nav',
- ariaLabel: 'Main menu',
- currentPath: ['#1', '/sub-item-1', '/sub-item-2/child-item-1'],
- currentPageTitle: 'main nav item 2',
- itemsList: [
- {
- title: 'Main nav item 1',
- ariaLabel: 'Main nav ariaLabel 1',
- url: '#0',
- classes: 'custom-class-main-item-1',
- id: 'main-item-1',
- },
- {
- title: 'Main nav item 2',
- ariaLabel: 'Main nav ariaLabel 2',
- url: '#1',
- classes: 'custom-class-main-item-2',
- id: 'main-item-2',
- },
- ],
- subNavigation: {
- id: 'sub-nav',
- overviewURL: '#overview',
- overviewText: 'Overview',
- ariaLabel: 'Section menu',
+ id: 'main-nav',
+ ariaLabel: 'Main menu',
+ currentPath: ['#1', '/sub-item-1', '/sub-item-2/child-item-1'],
+ currentPageTitle: 'main nav item 2',
itemsList: [
- {
- title: 'Sub nav item 1',
- ariaLabel: 'Sub nav ariaLabel 1',
- url: '/sub-item-1',
- classes: 'custom-class-sub-item-1',
- id: 'sub-item-1',
- },
- {
- title: 'Sub nav item 2',
- ariaLabel: 'Sub nav ariaLabel 2',
- url: '/sub-item-2',
- classes: 'custom-class-sub-item-2',
- id: 'sub-item-2',
- sections: [
- {
- sectionTitle: 'Section 1',
- children: [
- {
- title: 'Child item 1',
- ariaLabel: 'Child item ariaLabel 1',
- url: '/sub-item-2/child-item-1',
- id: 'child-item-1',
- },
- {
- title: 'Child item 2',
- ariaLabel: 'Child item ariaLabel 2',
- url: '/sub-item-2/child-item-2',
- id: 'child-item-2',
- },
- ],
- },
- ],
- },
+ {
+ title: 'Main nav item 1',
+ ariaLabel: 'Main nav ariaLabel 1',
+ url: '#0',
+ classes: 'custom-class-main-item-1',
+ id: 'main-item-1',
+ },
+ {
+ title: 'Main nav item 2',
+ ariaLabel: 'Main nav ariaLabel 2',
+ url: '#1',
+ classes: 'custom-class-main-item-2',
+ id: 'main-item-2',
+ },
],
- },
+ subNavigation: {
+ id: 'sub-nav',
+ overviewURL: '#overview',
+ overviewText: 'Overview',
+ ariaLabel: 'Section menu',
+ itemsList: [
+ {
+ title: 'Sub nav item 1',
+ ariaLabel: 'Sub nav ariaLabel 1',
+ url: '/sub-item-1',
+ classes: 'custom-class-sub-item-1',
+ id: 'sub-item-1',
+ },
+ {
+ title: 'Sub nav item 2',
+ ariaLabel: 'Sub nav ariaLabel 2',
+ url: '/sub-item-2',
+ classes: 'custom-class-sub-item-2',
+ id: 'sub-item-2',
+ sections: [
+ {
+ sectionTitle: 'Section 1',
+ children: [
+ {
+ title: 'Child item 1',
+ ariaLabel: 'Child item ariaLabel 1',
+ url: '/sub-item-2/child-item-1',
+ id: 'child-item-1',
+ },
+ {
+ title: 'Child item 2',
+ ariaLabel: 'Child item ariaLabel 2',
+ url: '/sub-item-2/child-item-2',
+ id: 'child-item-2',
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
};
const SITE_SEARCH_AUTOSUGGEST = {
- instructions:
- "Use up and down keys to navigate results once you've typed more than two characters. Use the enter key to select a result. Touch device users, explore by touch or with swipe gestures.",
- ariaYouHaveSelected: 'You have selected',
- ariaMinChars: 'Enter 3 or more characters for results.',
- ariaResultsLabel: 'Search results',
- ariaOneResult: 'There is one result available.',
- ariaNResults: 'There are {n} results available.',
- ariaLimitedResults: 'Results have been limited to 10 results. Type more characters to improve your search',
- moreResults: 'Continue entering to improve results',
- resultsTitle: 'Search results',
- autosuggestData: '/search-index.json',
- noResults: 'No results found',
- typeMore: 'Continue entering to get results',
+ instructions:
+ "Use up and down keys to navigate results once you've typed more than two characters. Use the enter key to select a result. Touch device users, explore by touch or with swipe gestures.",
+ ariaYouHaveSelected: 'You have selected',
+ ariaMinChars: 'Enter 3 or more characters for results.',
+ ariaResultsLabel: 'Search results',
+ ariaOneResult: 'There is one result available.',
+ ariaNResults: 'There are {n} results available.',
+ ariaLimitedResults: 'Results have been limited to 10 results. Type more characters to improve your search',
+ moreResults: 'Continue entering to improve results',
+ resultsTitle: 'Search results',
+ autosuggestData: '/search-index.json',
+ noResults: 'No results found',
+ typeMore: 'Continue entering to get results',
};
describe('macro: navigation', () => {
- describe('level: container', () => {
- it('passes jest-axe checks', async () => {
- const $ = cheerio.load(renderComponent('navigation', { navigation: PARAMS }));
-
- const results = await axe($.html());
- expect(results).toHaveNoViolations();
- });
-
- it('has the correct container if `fullWidth`', () => {
- const $ = cheerio.load(renderComponent('navigation', { navigation: { ...PARAMS, fullWidth: true } }));
-
- expect($('.ons-container').hasClass('ons-container--full-width')).toBe(true);
- });
+ describe('level: container', () => {
+ it('passes jest-axe checks', async () => {
+ const $ = cheerio.load(renderComponent('navigation', { navigation: PARAMS }));
+
+ const results = await axe($.html());
+ expect(results).toHaveNoViolations();
+ });
+
+ it('has the correct container if `fullWidth`', () => {
+ const $ = cheerio.load(renderComponent('navigation', { navigation: { ...PARAMS, fullWidth: true } }));
+
+ expect($('.ons-container').hasClass('ons-container--full-width')).toBe(true);
+ });
+
+ it('has the correct container if `params.wide` is provided', () => {
+ const $ = cheerio.load(renderComponent('navigation', { wide: true, navigation: PARAMS }));
+
+ expect($('.ons-navigation-wrapper .ons-container').hasClass('ons-container--wide')).toBe(true);
+ });
+
+ it('has the correct container if `params.navigation.wide` is provided', () => {
+ const $ = cheerio.load(renderComponent('navigation', { navigation: { ...PARAMS, wide: true } }));
+
+ expect($('.ons-navigation-wrapper .ons-container').hasClass('ons-container--wide')).toBe(true);
+ });
+
+ it('has the search autosuggest with correct output', () => {
+ const faker = templateFaker();
+ const autosuggestSpy = faker.spy('autosuggest', { suppressOutput: true });
+
+ faker.renderComponent('navigation', {
+ navigation: PARAMS,
+ siteSearchAutosuggest: {
+ ...SITE_SEARCH_AUTOSUGGEST,
+ input: {
+ label: 'Search the design system',
+ },
+ },
+ });
+
+ expect(autosuggestSpy.occurrences[0]).toEqual({
+ ...SITE_SEARCH_AUTOSUGGEST,
+ id: 'ons-site-search',
+ containerClasses: 'ons-autosuggest--header',
+ input: {
+ accessiblePlaceholder: true,
+ autocomplete: 'off',
+ label: 'Search the design system',
+ classes: 'ons-input-search ons-input-search--icon',
+ label: {
+ id: 'ons-site-search-label',
+ classes: 'ons-u-pl-m ons-label--white',
+ },
+ },
+ });
+ });
+ });
+
+ describe('level: main navigation', () => {
+ it('has the provided `id` attribute', () => {
+ const $ = cheerio.load(renderComponent('navigation', { navigation: PARAMS }));
+
+ expect($('.ons-navigation--main').attr('id')).toBe('main-nav');
+ });
+
+ it('has the provided `aria-label` attribute', () => {
+ const $ = cheerio.load(renderComponent('navigation', { navigation: PARAMS }));
+
+ expect($('.ons-navigation--main').attr('aria-label')).toBe('Main menu');
+ });
+
+ it('has the correct link href for each list item', () => {
+ const $ = cheerio.load(renderComponent('navigation', { navigation: PARAMS }));
+
+ const values = mapAll($('.ons-navigation--main .ons-navigation__link'), (node) => node.attr('href'));
+ expect(values).toEqual(['#0', '#1']);
+ });
+
+ it('has the correct link text for each list item', () => {
+ const $ = cheerio.load(renderComponent('navigation', { navigation: PARAMS }));
+
+ const values = mapAll($('.ons-navigation--main .ons-navigation__link'), (node) => node.text().trim());
+ expect(values).toEqual(['Main nav item 1', 'Main nav item 2']);
+ });
+
+ it('has the correct aria-label for each list item', () => {
+ const $ = cheerio.load(renderComponent('navigation', { navigation: PARAMS }));
+
+ const values = mapAll($('.ons-navigation--main .ons-navigation__link'), (node) => node.attr('aria-label'));
+ expect(values).toEqual(['Main nav ariaLabel 1', 'Main nav ariaLabel 2']);
+ });
+
+ it('has the provided custom class for each list item', () => {
+ const $ = cheerio.load(renderComponent('navigation', { navigation: PARAMS }));
+
+ expect($('.ons-navigation--main .ons-navigation__list > .ons-navigation__item').hasClass('custom-class-main-item-1')).toBe(
+ true,
+ );
+ expect(
+ $('.ons-navigation--main .ons-navigation__list .ons-navigation__item:last-child').hasClass('custom-class-main-item-2'),
+ ).toBe(true);
+ });
+
+ it('has the provided id for each list item', () => {
+ const $ = cheerio.load(renderComponent('navigation', { navigation: PARAMS }));
+
+ expect($('.ons-navigation--main .ons-navigation__list > .ons-navigation__item .ons-navigation__link').attr('id')).toBe(
+ 'main-item-1',
+ );
+ expect($('.ons-navigation--main .ons-navigation__list .ons-navigation__item:last-child .ons-navigation__link').attr('id')).toBe(
+ 'main-item-2',
+ );
+ });
+
+ it('has the active class on the correct item', () => {
+ const $ = cheerio.load(renderComponent('navigation', { navigation: PARAMS }));
+
+ expect(
+ $('.ons-navigation--main .ons-navigation__list .ons-navigation__item:last-child').hasClass('ons-navigation__item--active'),
+ ).toBe(true);
+ });
+ });
+
+ describe('level: sub navigation', () => {
+ it('renders a button with expected parameters', () => {
+ const faker = templateFaker();
+ const buttonSpy = faker.spy('button', { suppressOutput: true });
+
+ faker.renderComponent('navigation', { navigation: PARAMS });
+
+ expect(buttonSpy.occurrences).toContainEqual({
+ text: 'main nav item 2',
+ classes: 'ons-u-d-no ons-js-sub-navigation-button',
+ variants: ['mobile', 'dropdown'],
+ type: 'button',
+ attributes: {
+ 'aria-label': 'Toggle main nav item 2 menu',
+ 'aria-controls': 'sub-nav',
+ 'aria-expanded': 'false',
+ },
+ });
+ });
+
+ it('has the correct container if `params.wide` is provided', () => {
+ const $ = cheerio.load(renderComponent('navigation', { wide: true, navigation: PARAMS }));
+
+ expect($('.ons-navigation--sub .ons-container').hasClass('ons-container--wide')).toBe(true);
+ });
+
+ it('has the correct container if `params.navigation.wide` is provided', () => {
+ const $ = cheerio.load(renderComponent('navigation', { navigation: { ...PARAMS, wide: true } }));
- it('has the correct container if `params.wide` is provided', () => {
- const $ = cheerio.load(renderComponent('navigation', { wide: true, navigation: PARAMS }));
+ expect($('.ons-navigation--sub .ons-container').hasClass('ons-container--wide')).toBe(true);
+ });
- expect($('.ons-navigation-wrapper .ons-container').hasClass('ons-container--wide')).toBe(true);
- });
-
- it('has the correct container if `params.navigation.wide` is provided', () => {
- const $ = cheerio.load(renderComponent('navigation', { navigation: { ...PARAMS, wide: true } }));
-
- expect($('.ons-navigation-wrapper .ons-container').hasClass('ons-container--wide')).toBe(true);
- });
-
- it('has the search autosuggest with correct output', () => {
- const faker = templateFaker();
- const autosuggestSpy = faker.spy('autosuggest', { suppressOutput: true });
-
- faker.renderComponent('navigation', {
- navigation: PARAMS,
- siteSearchAutosuggest: {
- ...SITE_SEARCH_AUTOSUGGEST,
- input: {
- label: 'Search the design system',
- },
- },
- });
-
- expect(autosuggestSpy.occurrences[0]).toEqual({
- ...SITE_SEARCH_AUTOSUGGEST,
- id: 'ons-site-search',
- containerClasses: 'ons-autosuggest--header',
- input: {
- accessiblePlaceholder: true,
- autocomplete: 'off',
- label: 'Search the design system',
- classes: 'ons-input-search ons-input-search--icon',
- label: {
- id: 'ons-site-search-label',
- classes: 'ons-u-pl-m ons-label--white',
- },
- },
- });
- });
- });
+ it('has the provided `id` attribute', () => {
+ const $ = cheerio.load(renderComponent('navigation', { navigation: PARAMS }));
- describe('level: main navigation', () => {
- it('has the provided `id` attribute', () => {
- const $ = cheerio.load(renderComponent('navigation', { navigation: PARAMS }));
+ expect($('.ons-navigation--sub').attr('id')).toBe('sub-nav');
+ });
- expect($('.ons-navigation--main').attr('id')).toBe('main-nav');
- });
+ it('has the provided `aria-label` attribute', () => {
+ const $ = cheerio.load(renderComponent('navigation', { navigation: PARAMS }));
+
+ expect($('.ons-navigation--sub').attr('aria-label')).toBe('Section menu');
+ });
+
+ it('has the correct link href for each list item', () => {
+ const $ = cheerio.load(renderComponent('navigation', { navigation: PARAMS }));
+
+ const values = mapAll($('.ons-navigation--sub .ons-navigation__link'), (node) => node.attr('href'));
+ expect(values).toEqual(['/sub-item-1', '/sub-item-2']);
+ });
+
+ it('has the correct link text for each list item', () => {
+ const $ = cheerio.load(renderComponent('navigation', { navigation: PARAMS }));
+
+ const values = mapAll($('.ons-navigation--sub .ons-navigation__link'), (node) => node.text().trim());
+ expect(values).toEqual(['Sub nav item 1', 'Sub nav item 2']);
+ });
+
+ it('has the correct aria-label for each list item', () => {
+ const $ = cheerio.load(renderComponent('navigation', { navigation: PARAMS }));
+
+ const values = mapAll($('.ons-navigation--sub .ons-navigation__link'), (node) => node.attr('aria-label'));
+ expect(values).toEqual(['Sub nav ariaLabel 1', 'Sub nav ariaLabel 2']);
+ });
+
+ it('has the provided custom class for each list item', () => {
+ const $ = cheerio.load(renderComponent('navigation', { navigation: PARAMS }));
+
+ expect($('.ons-navigation--sub .ons-navigation__list > .ons-navigation__item').hasClass('custom-class-sub-item-1')).toBe(true);
+ expect(
+ $('.ons-navigation--sub .ons-navigation__list .ons-navigation__item:last-child').hasClass('custom-class-sub-item-2'),
+ ).toBe(true);
+ });
+
+ it('has the provided id for each list item', () => {
+ const $ = cheerio.load(renderComponent('navigation', { navigation: PARAMS }));
+
+ expect($('.ons-navigation--sub .ons-navigation__list > .ons-navigation__item .ons-navigation__link').attr('id')).toBe(
+ 'sub-item-1',
+ );
+ expect($('.ons-navigation--sub .ons-navigation__list .ons-navigation__item:last-child .ons-navigation__link').attr('id')).toBe(
+ 'sub-item-2',
+ );
+ });
+
+ it('has the active class on the correct item when a single current path is provided', () => {
+ const $ = cheerio.load(
+ renderComponent('navigation', {
+ navigation: {
+ ...PARAMS,
+ currentPath: '/sub-item-1',
+ },
+ }),
+ );
+ expect($('#sub-nav .ons-navigation__item--active > #sub-item-1').length).toBe(1);
+ });
- it('has the provided `aria-label` attribute', () => {
- const $ = cheerio.load(renderComponent('navigation', { navigation: PARAMS }));
+ it('has the active class on the correct item when multiple current paths are provided', () => {
+ const $ = cheerio.load(renderComponent('navigation', { navigation: PARAMS }));
- expect($('.ons-navigation--main').attr('aria-label')).toBe('Main menu');
+ expect($('#sub-nav .ons-navigation__item--active > #sub-item-1').length).toBe(1);
+ });
});
- it('has the correct link href for each list item', () => {
- const $ = cheerio.load(renderComponent('navigation', { navigation: PARAMS }));
+ describe('level: sub navigation mobile', () => {
+ it('has the provided `id` attribute', () => {
+ const $ = cheerio.load(renderComponent('navigation', { navigation: PARAMS }));
+
+ expect($('.ons-navigation--sub-mobile').attr('id')).toBe('sub-nav--mobile');
+ });
- const values = mapAll($('.ons-navigation--main .ons-navigation__link'), (node) => node.attr('href'));
- expect(values).toEqual(['#0', '#1']);
- });
+ it('has the provided `aria-label` attribute', () => {
+ const $ = cheerio.load(renderComponent('navigation', { navigation: PARAMS }));
- it('has the correct link text for each list item', () => {
- const $ = cheerio.load(renderComponent('navigation', { navigation: PARAMS }));
+ expect($('.ons-navigation--sub-mobile').attr('aria-label')).toBe('Section menu');
+ });
- const values = mapAll($('.ons-navigation--main .ons-navigation__link'), (node) => node.text().trim());
- expect(values).toEqual(['Main nav item 1', 'Main nav item 2']);
- });
+ it('has the correct link href for each list item', () => {
+ const $ = cheerio.load(renderComponent('navigation', { navigation: PARAMS }));
- it('has the correct aria-label for each list item', () => {
- const $ = cheerio.load(renderComponent('navigation', { navigation: PARAMS }));
+ const values = mapAll(
+ $('.ons-navigation__list--parent > li a').not('.ons-navigation__list--parent li .ons-navigation__list--child a'),
+ (node) => node.attr('href'),
+ );
+ expect(values).toEqual(['#overview', '/sub-item-1', '/sub-item-2']);
+ });
- const values = mapAll($('.ons-navigation--main .ons-navigation__link'), (node) => node.attr('aria-label'));
- expect(values).toEqual(['Main nav ariaLabel 1', 'Main nav ariaLabel 2']);
- });
-
- it('has the provided custom class for each list item', () => {
- const $ = cheerio.load(renderComponent('navigation', { navigation: PARAMS }));
-
- expect($('.ons-navigation--main .ons-navigation__list > .ons-navigation__item').hasClass('custom-class-main-item-1')).toBe(true);
- expect($('.ons-navigation--main .ons-navigation__list .ons-navigation__item:last-child').hasClass('custom-class-main-item-2')).toBe(
- true,
- );
- });
-
- it('has the provided id for each list item', () => {
- const $ = cheerio.load(renderComponent('navigation', { navigation: PARAMS }));
-
- expect($('.ons-navigation--main .ons-navigation__list > .ons-navigation__item .ons-navigation__link').attr('id')).toBe('main-item-1');
- expect($('.ons-navigation--main .ons-navigation__list .ons-navigation__item:last-child .ons-navigation__link').attr('id')).toBe(
- 'main-item-2',
- );
- });
-
- it('has the active class on the correct item', () => {
- const $ = cheerio.load(renderComponent('navigation', { navigation: PARAMS }));
-
- expect(
- $('.ons-navigation--main .ons-navigation__list .ons-navigation__item:last-child').hasClass('ons-navigation__item--active'),
- ).toBe(true);
- });
- });
-
- describe('level: sub navigation', () => {
- it('renders a button with expected parameters', () => {
- const faker = templateFaker();
- const buttonSpy = faker.spy('button', { suppressOutput: true });
-
- faker.renderComponent('navigation', { navigation: PARAMS });
-
- expect(buttonSpy.occurrences).toContainEqual({
- text: 'main nav item 2',
- classes: 'ons-u-d-no ons-js-sub-navigation-button',
- variants: ['mobile', 'dropdown'],
- type: 'button',
- attributes: {
- 'aria-label': 'Toggle main nav item 2 menu',
- 'aria-controls': 'sub-nav',
- 'aria-expanded': 'false',
- },
- });
- });
-
- it('has the correct container if `params.wide` is provided', () => {
- const $ = cheerio.load(renderComponent('navigation', { wide: true, navigation: PARAMS }));
-
- expect($('.ons-navigation--sub .ons-container').hasClass('ons-container--wide')).toBe(true);
- });
-
- it('has the correct container if `params.navigation.wide` is provided', () => {
- const $ = cheerio.load(renderComponent('navigation', { navigation: { ...PARAMS, wide: true } }));
-
- expect($('.ons-navigation--sub .ons-container').hasClass('ons-container--wide')).toBe(true);
- });
-
- it('has the provided `id` attribute', () => {
- const $ = cheerio.load(renderComponent('navigation', { navigation: PARAMS }));
-
- expect($('.ons-navigation--sub').attr('id')).toBe('sub-nav');
- });
-
- it('has the provided `aria-label` attribute', () => {
- const $ = cheerio.load(renderComponent('navigation', { navigation: PARAMS }));
-
- expect($('.ons-navigation--sub').attr('aria-label')).toBe('Section menu');
- });
-
- it('has the correct link href for each list item', () => {
- const $ = cheerio.load(renderComponent('navigation', { navigation: PARAMS }));
-
- const values = mapAll($('.ons-navigation--sub .ons-navigation__link'), (node) => node.attr('href'));
- expect(values).toEqual(['/sub-item-1', '/sub-item-2']);
- });
-
- it('has the correct link text for each list item', () => {
- const $ = cheerio.load(renderComponent('navigation', { navigation: PARAMS }));
-
- const values = mapAll($('.ons-navigation--sub .ons-navigation__link'), (node) => node.text().trim());
- expect(values).toEqual(['Sub nav item 1', 'Sub nav item 2']);
- });
-
- it('has the correct aria-label for each list item', () => {
- const $ = cheerio.load(renderComponent('navigation', { navigation: PARAMS }));
-
- const values = mapAll($('.ons-navigation--sub .ons-navigation__link'), (node) => node.attr('aria-label'));
- expect(values).toEqual(['Sub nav ariaLabel 1', 'Sub nav ariaLabel 2']);
- });
-
- it('has the provided custom class for each list item', () => {
- const $ = cheerio.load(renderComponent('navigation', { navigation: PARAMS }));
-
- expect($('.ons-navigation--sub .ons-navigation__list > .ons-navigation__item').hasClass('custom-class-sub-item-1')).toBe(true);
- expect($('.ons-navigation--sub .ons-navigation__list .ons-navigation__item:last-child').hasClass('custom-class-sub-item-2')).toBe(
- true,
- );
- });
-
- it('has the provided id for each list item', () => {
- const $ = cheerio.load(renderComponent('navigation', { navigation: PARAMS }));
-
- expect($('.ons-navigation--sub .ons-navigation__list > .ons-navigation__item .ons-navigation__link').attr('id')).toBe('sub-item-1');
- expect($('.ons-navigation--sub .ons-navigation__list .ons-navigation__item:last-child .ons-navigation__link').attr('id')).toBe(
- 'sub-item-2',
- );
- });
-
- it('has the active class on the correct item when a single current path is provided', () => {
- const $ = cheerio.load(
- renderComponent('navigation', {
- navigation: {
- ...PARAMS,
- currentPath: '/sub-item-1',
- },
- }),
- );
- expect($('#sub-nav .ons-navigation__item--active > #sub-item-1').length).toBe(1);
- });
-
- it('has the active class on the correct item when multiple current paths are provided', () => {
- const $ = cheerio.load(renderComponent('navigation', { navigation: PARAMS }));
-
- expect($('#sub-nav .ons-navigation__item--active > #sub-item-1').length).toBe(1);
- });
- });
-
- describe('level: sub navigation mobile', () => {
- it('has the provided `id` attribute', () => {
- const $ = cheerio.load(renderComponent('navigation', { navigation: PARAMS }));
-
- expect($('.ons-navigation--sub-mobile').attr('id')).toBe('sub-nav--mobile');
- });
-
- it('has the provided `aria-label` attribute', () => {
- const $ = cheerio.load(renderComponent('navigation', { navigation: PARAMS }));
-
- expect($('.ons-navigation--sub-mobile').attr('aria-label')).toBe('Section menu');
- });
-
- it('has the correct link href for each list item', () => {
- const $ = cheerio.load(renderComponent('navigation', { navigation: PARAMS }));
-
- const values = mapAll(
- $('.ons-navigation__list--parent > li a').not('.ons-navigation__list--parent li .ons-navigation__list--child a'),
- (node) => node.attr('href'),
- );
- expect(values).toEqual(['#overview', '/sub-item-1', '/sub-item-2']);
- });
-
- it('has the correct link text for each list item', () => {
- const $ = cheerio.load(renderComponent('navigation', { navigation: PARAMS }));
-
- const values = mapAll(
- $('.ons-navigation__list--parent > li a').not('.ons-navigation__list--parent li .ons-navigation__list--child a'),
- (node) => node.text().trim(),
- );
- expect(values).toEqual(['Overview', 'Sub nav item 1', 'Sub nav item 2']);
- });
-
- it('has the active class on the correct item when a single current path is provided', () => {
- const $ = cheerio.load(
- renderComponent('navigation', {
- navigation: {
- ...PARAMS,
- currentPath: '/sub-item-1',
- },
- }),
- );
- expect($('#sub-nav--mobile .ons-navigation__item--active > #sub-item-1--mobile').length).toBe(1);
- });
-
- it('has the active class on the correct item when multiple current paths are provided', () => {
- const $ = cheerio.load(renderComponent('navigation', { navigation: PARAMS }));
-
- expect($('#sub-nav--mobile .ons-navigation__item--active > #sub-item-1--mobile').length).toBe(1);
- });
-
- it('has the correct text for the child section title', () => {
- const $ = cheerio.load(renderComponent('navigation', { navigation: PARAMS }));
-
- expect($('.ons-navigation__list-header').text()).toBe('Section 1');
- });
-
- it('has the correct link href for each child list item', () => {
- const $ = cheerio.load(renderComponent('navigation', { navigation: PARAMS }));
-
- const values = mapAll($('.ons-navigation__list--child > li a'), (node) => node.attr('href'));
- expect(values).toEqual(['/sub-item-2/child-item-1', '/sub-item-2/child-item-2']);
- });
-
- it('has the correct link text for each child list item', () => {
- const $ = cheerio.load(renderComponent('navigation', { navigation: PARAMS }));
-
- const values = mapAll($('.ons-navigation__list--child > li a'), (node) => node.text().trim());
- expect(values).toEqual(['Child item 1', 'Child item 2']);
- });
+ it('has the correct link text for each list item', () => {
+ const $ = cheerio.load(renderComponent('navigation', { navigation: PARAMS }));
- it('has the active class on the correct child item', () => {
- const $ = cheerio.load(renderComponent('navigation', { navigation: PARAMS }));
+ const values = mapAll(
+ $('.ons-navigation__list--parent > li a').not('.ons-navigation__list--parent li .ons-navigation__list--child a'),
+ (node) => node.text().trim(),
+ );
+ expect(values).toEqual(['Overview', 'Sub nav item 1', 'Sub nav item 2']);
+ });
- expect($('#sub-nav--mobile .ons-navigation__item--active > #child-item-1').length).toBe(1);
+ it('has the active class on the correct item when a single current path is provided', () => {
+ const $ = cheerio.load(
+ renderComponent('navigation', {
+ navigation: {
+ ...PARAMS,
+ currentPath: '/sub-item-1',
+ },
+ }),
+ );
+ expect($('#sub-nav--mobile .ons-navigation__item--active > #sub-item-1--mobile').length).toBe(1);
+ });
+
+ it('has the active class on the correct item when multiple current paths are provided', () => {
+ const $ = cheerio.load(renderComponent('navigation', { navigation: PARAMS }));
+
+ expect($('#sub-nav--mobile .ons-navigation__item--active > #sub-item-1--mobile').length).toBe(1);
+ });
+
+ it('has the correct text for the child section title', () => {
+ const $ = cheerio.load(renderComponent('navigation', { navigation: PARAMS }));
+
+ expect($('.ons-navigation__list-header').text()).toBe('Section 1');
+ });
+
+ it('has the correct link href for each child list item', () => {
+ const $ = cheerio.load(renderComponent('navigation', { navigation: PARAMS }));
+
+ const values = mapAll($('.ons-navigation__list--child > li a'), (node) => node.attr('href'));
+ expect(values).toEqual(['/sub-item-2/child-item-1', '/sub-item-2/child-item-2']);
+ });
+
+ it('has the correct link text for each child list item', () => {
+ const $ = cheerio.load(renderComponent('navigation', { navigation: PARAMS }));
+
+ const values = mapAll($('.ons-navigation__list--child > li a'), (node) => node.text().trim());
+ expect(values).toEqual(['Child item 1', 'Child item 2']);
+ });
+
+ it('has the active class on the correct child item', () => {
+ const $ = cheerio.load(renderComponent('navigation', { navigation: PARAMS }));
+
+ expect($('#sub-nav--mobile .ons-navigation__item--active > #child-item-1').length).toBe(1);
+ });
});
- });
});
diff --git a/src/components/navigation/_navigation.scss b/src/components/navigation/_navigation.scss
index b40fd83fff..64c5ac3fbe 100644
--- a/src/components/navigation/_navigation.scss
+++ b/src/components/navigation/_navigation.scss
@@ -1,145 +1,144 @@
.ons-navigation {
- position: relative;
- &-wrapper {
- background: var(--ons-color-header);
- margin-top: -0.03rem;
- }
-
- &--sub {
- background: var(--ons-color-branded-tint);
- padding-top: 0.5rem;
- }
-
- &--sub-mobile {
- border-bottom: 4px solid var(--ons-color-branded-tint);
- }
-
- &-search {
- padding: 1rem;
-
- @include mq(l) {
- background: none;
- bottom: 65px;
- padding: 0;
- position: absolute;
- right: 1rem;
- width: 20rem;
+ position: relative;
+
+ &-wrapper {
+ background: var(--ons-color-header);
+ margin-top: -0.03rem;
+
+ &--neutral {
+ background: var(--ons-color-header-neutral);
+ .ons-navigation__item {
+ &--active,
+ &:hover {
+ border-color: var(--ons-color-links);
+ }
+ .ons-navigation__link {
+ color: var(--ons-color-header-navigation-links);
+ }
+ }
+ }
}
- }
-
- &__list {
- list-style: none;
- margin: 0;
- padding: 0 0 0.5rem;
- @include mq(l) {
- padding: 0;
+ &--sub {
+ background: var(--ons-color-branded-tint);
+ padding-top: 0.5rem;
}
- &-header {
- font-size: 1rem;
- margin: 0.75rem 0 0.6rem;
- padding: 0 0 0 1rem;
- }
- }
-
- &__item {
- border-left: 4px solid transparent;
- display: block;
- margin: 0 0 0.2rem;
- &--active,
- &:hover {
- border-color: var(--ons-color-white);
+ &--sub-mobile {
+ border-bottom: 4px solid var(--ons-color-branded-tint);
}
- .ons-navigation--sub-mobile & {
- &--active,
- &:hover {
- border-color: var(--ons-color-branded);
- li {
- border: 0;
+ &-search {
+ padding: 1rem;
+
+ @include mq(l) {
+ background: none;
+ bottom: 65px;
+ padding: 0;
+ position: absolute;
+ right: 1rem;
+ width: 20rem;
}
- }
}
- @include mq(l) {
- border-bottom: 4px solid transparent;
- border-left: 0;
- display: inline-block;
- margin: 0 0 0 1rem;
- padding: 0 0 0.3rem;
- position: relative;
- text-align: center;
-
- &:first-child {
- margin-left: 0;
- }
-
- &--active,
- &:hover {
- border-color: var(--ons-color-white);
- }
-
- .ons-navigation--sub & {
- &:hover,
- &--active {
- border-color: var(--ons-color-branded);
+ &__list {
+ list-style: none;
+ margin: 0;
+ padding: 0 0 0.5rem;
+
+ @include mq(l) {
+ padding: 0;
}
- }
- }
- }
-
- &__link {
- color: var(--ons-color-white);
- display: block;
- margin: 0 0.75rem;
- padding: 0.1rem 0;
- text-decoration: none;
-
- &--section {
- margin: 0;
- padding: 0;
- }
- &:hover,
- &:focus {
- color: var(--ons-color-white);
- text-decoration: none;
+ &-header {
+ font-size: 1rem;
+ margin: 0.75rem 0 0.6rem;
+ padding: 0 0 0 1rem;
+ }
}
- .ons-navigation--sub &,
- .ons-navigation--sub-mobile & {
- color: var(--ons-color-text-link);
- &:hover,
- &:focus {
- color: var(--ons-color-text-link-hover);
- }
- }
+ &__item {
+ border-left: 4px solid transparent;
+ display: block;
+ margin: 0 0 0.2rem;
+ &--active,
+ &:hover {
+ border-color: var(--ons-color-white);
+ }
- @include mq(l) {
- display: inline-block;
- font-size: 1rem;
- margin: 0;
- padding: 0;
+ .ons-navigation--sub-mobile & {
+ &--active,
+ &:hover {
+ border-color: var(--ons-color-branded);
+ li {
+ border: 0;
+ }
+ }
+ }
+
+ @include mq(l) {
+ border-bottom: 4px solid transparent;
+ border-left: 0;
+ display: inline-block;
+ margin: 0 0 0 1rem;
+ padding: 0 0 0.3rem;
+ position: relative;
+ text-align: center;
+
+ &:first-child {
+ margin-left: 0;
+ }
+
+ &--active,
+ &:hover {
+ border-color: var(--ons-color-white);
+ }
+
+ .ons-navigation--sub & {
+ &:hover,
+ &--active {
+ border-color: var(--ons-color-branded);
+ }
+ }
+ }
}
- }
- &__item--active > &__link {
- font-weight: $font-weight-bold;
- }
+ &__link {
+ color: var(--ons-color-white);
+ display: block;
+ margin: 0 0.75rem;
+ padding: 0.1rem 0;
+ text-decoration: none;
- &-wrapper {
- &--neutral {
- background: var(--ons-color-header-neutral);
- .ons-navigation__item {
- &--active,
- &:hover {
- border-color: var(--ons-color-links);
+ &--section {
+ margin: 0;
+ padding: 0;
}
- .ons-navigation__link {
- color: var(--ons-color-header-navigation-links);
+
+ &:hover,
+ &:focus {
+ color: var(--ons-color-white);
+ text-decoration: none;
}
- }
+
+ .ons-navigation--sub &,
+ .ons-navigation--sub-mobile & {
+ color: var(--ons-color-text-link);
+ &:hover,
+ &:focus {
+ color: var(--ons-color-text-link-hover);
+ }
+ }
+
+ @include mq(l) {
+ display: inline-block;
+ font-size: 1rem;
+ margin: 0;
+ padding: 0;
+ }
+ }
+
+ &__item--active > &__link {
+ font-weight: $font-weight-bold;
}
- }
}
diff --git a/src/components/navigation/navigation.dom.js b/src/components/navigation/navigation.dom.js
index c9591c43b4..cd566e552b 100644
--- a/src/components/navigation/navigation.dom.js
+++ b/src/components/navigation/navigation.dom.js
@@ -1,39 +1,39 @@
import domready from '../../js/domready';
domready(async () => {
- const toggleNavigationBtn = document.querySelector('.ons-js-navigation-button');
- const navigationEl = document.querySelector('.ons-js-navigation');
- const navigationHideClass = 'ons-u-d-no@xxs@l';
- const toggleSubNavigationBtn = document.querySelector('.ons-js-sub-navigation-button');
- const subNavigationEl = document.querySelector('.ons-js-secondary-nav');
- const subNavigationHideClass = 'ons-u-d-no';
- const toggleSearchBtn = document.querySelector('.ons-js-toggle-search');
- const searchEl = document.querySelector('.ons-js-navigation-search');
- const searchHideClass = 'ons-u-d-no@xs@l';
- const toggleServicesBtn = document.querySelector('.ons-js-toggle-services');
- const servicesEl = document.querySelector('.ons-js-services-mobile-nav');
- const servicesHideClass = 'ons-u-d-no';
-
- if (toggleNavigationBtn) {
- const NavigationToggle = (await import('./navigation')).default;
-
- new NavigationToggle(toggleNavigationBtn, navigationEl, navigationHideClass).registerEvents();
- }
-
- if (toggleSubNavigationBtn) {
- const SubNavigationToggle = (await import('./navigation')).default;
- new SubNavigationToggle(toggleSubNavigationBtn, subNavigationEl, subNavigationHideClass).registerEvents();
- }
-
- if (toggleSearchBtn) {
- const searchToggle = (await import('./navigation')).default;
-
- new searchToggle(toggleSearchBtn, searchEl, searchHideClass).registerEvents();
- }
-
- if (toggleServicesBtn) {
- const servicesToggle = (await import('./navigation')).default;
-
- new servicesToggle(toggleServicesBtn, servicesEl, servicesHideClass).registerEvents();
- }
+ const toggleNavigationBtn = document.querySelector('.ons-js-navigation-button');
+ const navigationEl = document.querySelector('.ons-js-navigation');
+ const navigationHideClass = 'ons-u-d-no@xxs@l';
+ const toggleSubNavigationBtn = document.querySelector('.ons-js-sub-navigation-button');
+ const subNavigationEl = document.querySelector('.ons-js-secondary-nav');
+ const subNavigationHideClass = 'ons-u-d-no';
+ const toggleSearchBtn = document.querySelector('.ons-js-toggle-search');
+ const searchEl = document.querySelector('.ons-js-navigation-search');
+ const searchHideClass = 'ons-u-d-no@xs@l';
+ const toggleServicesBtn = document.querySelector('.ons-js-toggle-services');
+ const servicesEl = document.querySelector('.ons-js-services-mobile-nav');
+ const servicesHideClass = 'ons-u-d-no';
+
+ if (toggleNavigationBtn) {
+ const NavigationToggle = (await import('./navigation')).default;
+
+ new NavigationToggle(toggleNavigationBtn, navigationEl, navigationHideClass).registerEvents();
+ }
+
+ if (toggleSubNavigationBtn) {
+ const SubNavigationToggle = (await import('./navigation')).default;
+ new SubNavigationToggle(toggleSubNavigationBtn, subNavigationEl, subNavigationHideClass).registerEvents();
+ }
+
+ if (toggleSearchBtn) {
+ const searchToggle = (await import('./navigation')).default;
+
+ new searchToggle(toggleSearchBtn, searchEl, searchHideClass).registerEvents();
+ }
+
+ if (toggleServicesBtn) {
+ const servicesToggle = (await import('./navigation')).default;
+
+ new servicesToggle(toggleServicesBtn, servicesEl, servicesHideClass).registerEvents();
+ }
});
diff --git a/src/components/navigation/navigation.js b/src/components/navigation/navigation.js
index 131f568798..2002088cce 100644
--- a/src/components/navigation/navigation.js
+++ b/src/components/navigation/navigation.js
@@ -4,64 +4,64 @@ const attrExpanded = 'aria-expanded';
const attrHidden = 'aria-hidden';
export default class NavigationToggle {
- constructor(toggle, navigation, hideClass) {
- this.toggle = toggle;
- this.navigation = navigation;
- this.hideClass = hideClass;
- this.toggle.classList.remove('ons-u-d-no');
- this.setAria();
- onViewportChange(this.setAria.bind(this));
- }
+ constructor(toggle, navigation, hideClass) {
+ this.toggle = toggle;
+ this.navigation = navigation;
+ this.hideClass = hideClass;
+ this.toggle.classList.remove('ons-u-d-no');
+ this.setAria();
+ onViewportChange(this.setAria.bind(this));
+ }
- registerEvents() {
- this.toggle.addEventListener('click', this.toggleNav.bind(this));
- }
+ registerEvents() {
+ this.toggle.addEventListener('click', this.toggleNav.bind(this));
+ }
- toggleNav() {
- const isHidden = this.navigation.getAttribute(attrHidden);
- isHidden === 'false' ? this.closeNav() : this.openNav();
- }
+ toggleNav() {
+ const isHidden = this.navigation.getAttribute(attrHidden);
+ isHidden === 'false' ? this.closeNav() : this.openNav();
+ }
- openNav() {
- const input = [...this.navigation.getElementsByTagName('INPUT')][0];
+ openNav() {
+ const input = [...this.navigation.getElementsByTagName('INPUT')][0];
- this.toggle.setAttribute(attrExpanded, 'true');
- this.toggle.classList.add('active');
- this.navigation.setAttribute(attrHidden, 'false');
- this.navigation.classList.remove(this.hideClass);
+ this.toggle.setAttribute(attrExpanded, 'true');
+ this.toggle.classList.add('active');
+ this.navigation.setAttribute(attrHidden, 'false');
+ this.navigation.classList.remove(this.hideClass);
- if (input) {
- input.focus();
+ if (input) {
+ input.focus();
+ }
}
- }
- closeNav() {
- this.toggle.setAttribute(attrExpanded, 'false');
- this.toggle.classList.remove('active');
- this.navigation.setAttribute(attrHidden, 'true');
- this.navigation.classList.add(this.hideClass);
- }
+ closeNav() {
+ this.toggle.setAttribute(attrExpanded, 'false');
+ this.toggle.classList.remove('active');
+ this.navigation.setAttribute(attrHidden, 'true');
+ this.navigation.classList.add(this.hideClass);
+ }
- isHidden(el) {
- return el.offsetParent === null;
- }
+ isHidden(el) {
+ return el.offsetParent === null;
+ }
- setAria() {
- const isToggleHidden = this.isHidden(this.toggle);
- const hasAria = this.navigation.hasAttribute(attrHidden);
+ setAria() {
+ const isToggleHidden = this.isHidden(this.toggle);
+ const hasAria = this.navigation.hasAttribute(attrHidden);
- if (!isToggleHidden) {
- if (!hasAria) {
- this.closeNav();
- }
- } else if (hasAria) {
- this.toggle.removeAttribute(attrExpanded);
- this.navigation.removeAttribute(attrHidden);
- if (this.hideClass !== 'ons-u-d-no') {
- this.navigation.classList.remove(this.hideClass);
- } else {
- this.closeNav();
- }
+ if (!isToggleHidden) {
+ if (!hasAria) {
+ this.closeNav();
+ }
+ } else if (hasAria) {
+ this.toggle.removeAttribute(attrExpanded);
+ this.navigation.removeAttribute(attrHidden);
+ if (this.hideClass !== 'ons-u-d-no') {
+ this.navigation.classList.remove(this.hideClass);
+ } else {
+ this.closeNav();
+ }
+ }
}
- }
}
diff --git a/src/components/navigation/navigation.spec.js b/src/components/navigation/navigation.spec.js
index 89360f38bf..39709977ab 100644
--- a/src/components/navigation/navigation.spec.js
+++ b/src/components/navigation/navigation.spec.js
@@ -2,304 +2,304 @@ import { setViewport } from '../../tests/helpers/puppeteer';
import { renderComponent, setTestPage } from '../../tests/helpers/rendering';
const EXAMPLE_NAVIGATION = {
- navigation: {
- id: 'main-nav',
- ariaLabel: 'Main menu',
- currentPath: '#0',
- itemsList: [
- {
- title: 'Main nav item 1',
- url: '#0',
- classes: 'custom-class-main-item-1',
- id: 'main-item-1',
- },
- {
- title: 'Main nav item 2',
- url: '#1',
- classes: 'custom-class-main-item-2',
- id: 'main-item-2',
- },
- ],
- toggleNavigationButton: {
- text: 'Menu',
- ariaLabel: 'Toggle main navigation',
+ navigation: {
+ id: 'main-nav',
+ ariaLabel: 'Main menu',
+ currentPath: '#0',
+ itemsList: [
+ {
+ title: 'Main nav item 1',
+ url: '#0',
+ classes: 'custom-class-main-item-1',
+ id: 'main-item-1',
+ },
+ {
+ title: 'Main nav item 2',
+ url: '#1',
+ classes: 'custom-class-main-item-2',
+ id: 'main-item-2',
+ },
+ ],
+ toggleNavigationButton: {
+ text: 'Menu',
+ ariaLabel: 'Toggle main navigation',
+ },
},
- },
};
const EXAMPLE_NAVIGATION_WITH_SUBNAVIGATION = {
- navigation: {
- id: 'main-nav',
- ariaLabel: 'Main menu',
- currentPath: '#1',
- currentPageTitle: 'Main nav item 2',
- itemsList: [
- {
- title: 'Main nav item 1',
- url: '#0',
- classes: 'custom-class-main-item-1',
- id: 'main-item-1',
- },
- {
- title: 'Main nav item 2',
- url: '#1',
- classes: 'custom-class-main-item-2',
- id: 'main-item-2',
- },
- ],
- subNavigation: {
- id: 'sub-nav',
- overviewURL: '#overview',
- overviewText: 'Overview',
- ariaLabel: 'Section menu',
- currentPath: '#1',
- itemsList: [
- {
- title: 'Sub nav item 1',
- url: '#0',
- classes: 'custom-class-sub-item-1',
- id: 'sub-item-1',
- },
- {
- title: 'Sub nav item 2',
- url: '#1',
- classes: 'custom-class-sub-item-2',
- id: 'sub-item-2',
- sections: [
+ navigation: {
+ id: 'main-nav',
+ ariaLabel: 'Main menu',
+ currentPath: '#1',
+ currentPageTitle: 'Main nav item 2',
+ itemsList: [
{
- sectionTitle: 'Section 1',
- children: [
+ title: 'Main nav item 1',
+ url: '#0',
+ classes: 'custom-class-main-item-1',
+ id: 'main-item-1',
+ },
+ {
+ title: 'Main nav item 2',
+ url: '#1',
+ classes: 'custom-class-main-item-2',
+ id: 'main-item-2',
+ },
+ ],
+ subNavigation: {
+ id: 'sub-nav',
+ overviewURL: '#overview',
+ overviewText: 'Overview',
+ ariaLabel: 'Section menu',
+ currentPath: '#1',
+ itemsList: [
{
- title: 'Child item 1',
- url: '#0',
+ title: 'Sub nav item 1',
+ url: '#0',
+ classes: 'custom-class-sub-item-1',
+ id: 'sub-item-1',
},
{
- title: 'Child item 2',
- url: '#1',
+ title: 'Sub nav item 2',
+ url: '#1',
+ classes: 'custom-class-sub-item-2',
+ id: 'sub-item-2',
+ sections: [
+ {
+ sectionTitle: 'Section 1',
+ children: [
+ {
+ title: 'Child item 1',
+ url: '#0',
+ },
+ {
+ title: 'Child item 2',
+ url: '#1',
+ },
+ ],
+ },
+ ],
},
- ],
- },
- ],
+ ],
},
- ],
},
- },
};
const EXAMPLE_NAVIGATION_WITH_SUBNAVIGATION_REMOVED = {
- navigation: {
- id: 'main-nav',
- ariaLabel: 'Main menu',
- currentPath: '#1',
- currentPageTitle: 'Main nav item 2',
- itemsList: [
- {
- title: 'Main nav item 1',
- url: '#0',
- classes: 'custom-class-main-item-1',
- id: 'main-item-1',
- },
- {
- title: 'Main nav item 2',
- url: '#1',
- classes: 'custom-class-main-item-2',
- id: 'main-item-2',
- },
- ],
- subNavigation: {
- id: 'sub-nav-hidden',
- overviewURL: '#overview',
- overviewText: 'Overview',
- ariaLabel: 'Section menu',
- currentPath: '#1',
- removeHorizontalSubNav: true,
- itemsList: [
- {
- title: 'Sub nav item 1',
- url: '#0',
- classes: 'custom-class-sub-item-1',
- id: 'sub-item-1',
- },
- {
- title: 'Sub nav item 2',
- url: '#1',
- classes: 'custom-class-sub-item-2',
- id: 'sub-item-2',
- sections: [
+ navigation: {
+ id: 'main-nav',
+ ariaLabel: 'Main menu',
+ currentPath: '#1',
+ currentPageTitle: 'Main nav item 2',
+ itemsList: [
{
- sectionTitle: 'Section 1',
- children: [
+ title: 'Main nav item 1',
+ url: '#0',
+ classes: 'custom-class-main-item-1',
+ id: 'main-item-1',
+ },
+ {
+ title: 'Main nav item 2',
+ url: '#1',
+ classes: 'custom-class-main-item-2',
+ id: 'main-item-2',
+ },
+ ],
+ subNavigation: {
+ id: 'sub-nav-hidden',
+ overviewURL: '#overview',
+ overviewText: 'Overview',
+ ariaLabel: 'Section menu',
+ currentPath: '#1',
+ removeHorizontalSubNav: true,
+ itemsList: [
{
- title: 'Child item 1',
- url: '#0',
+ title: 'Sub nav item 1',
+ url: '#0',
+ classes: 'custom-class-sub-item-1',
+ id: 'sub-item-1',
},
{
- title: 'Child item 2',
- url: '#1',
+ title: 'Sub nav item 2',
+ url: '#1',
+ classes: 'custom-class-sub-item-2',
+ id: 'sub-item-2',
+ sections: [
+ {
+ sectionTitle: 'Section 1',
+ children: [
+ {
+ title: 'Child item 1',
+ url: '#0',
+ },
+ {
+ title: 'Child item 2',
+ url: '#1',
+ },
+ ],
+ },
+ ],
},
- ],
- },
- ],
+ ],
},
- ],
},
- },
};
describe('script: navigation', () => {
- afterEach(async () => {
- // Clear viewport size and browser emulation after each test.
- await jestPuppeteer.resetPage();
- });
+ afterEach(async () => {
+ // Clear viewport size and browser emulation after each test.
+ await jestPuppeteer.resetPage();
+ });
- describe.each([
- ['main', EXAMPLE_NAVIGATION, '.ons-navigation--main', '.ons-js-navigation-button', false],
- ['sub', EXAMPLE_NAVIGATION_WITH_SUBNAVIGATION, '.ons-navigation--sub-mobile', '.ons-js-sub-navigation-button', true],
- ])('level: %s navigation', (_, params, navEl, buttonEl, ariaStatus) => {
- describe('when the component initialises', () => {
- beforeEach(async () => {
- await setTestPage('/test', renderComponent('header', params));
- });
+ describe.each([
+ ['main', EXAMPLE_NAVIGATION, '.ons-navigation--main', '.ons-js-navigation-button', false],
+ ['sub', EXAMPLE_NAVIGATION_WITH_SUBNAVIGATION, '.ons-navigation--sub-mobile', '.ons-js-sub-navigation-button', true],
+ ])('level: %s navigation', (_, params, navEl, buttonEl, ariaStatus) => {
+ describe('when the component initialises', () => {
+ beforeEach(async () => {
+ await setTestPage('/test', renderComponent('header', params));
+ });
- it('has removed the display class from the menu toggle button', async () => {
- const hasClass = await page.$eval(buttonEl, (node) => node.classList.contains('ons-u-d-no'));
- expect(hasClass).toBe(false);
- });
- });
+ it('has removed the display class from the menu toggle button', async () => {
+ const hasClass = await page.$eval(buttonEl, (node) => node.classList.contains('ons-u-d-no'));
+ expect(hasClass).toBe(false);
+ });
+ });
- describe('when the viewport is large', () => {
- beforeEach(async () => {
- await setViewport(page, { width: 1650, height: 1050 });
- await setTestPage('/test', renderComponent('header', params));
- });
+ describe('when the viewport is large', () => {
+ beforeEach(async () => {
+ await setViewport(page, { width: 1650, height: 1050 });
+ await setTestPage('/test', renderComponent('header', params));
+ });
- it('has the correct aria hidden attribute on the navigation list', async () => {
- const nav = await page.$(navEl);
- const hasAriaAttribute = await nav.evaluate((node) => node.getAttribute('aria-hidden') !== null);
- expect(hasAriaAttribute).toBe(ariaStatus);
- });
+ it('has the correct aria hidden attribute on the navigation list', async () => {
+ const nav = await page.$(navEl);
+ const hasAriaAttribute = await nav.evaluate((node) => node.getAttribute('aria-hidden') !== null);
+ expect(hasAriaAttribute).toBe(ariaStatus);
+ });
- it('has aria-expanded set as `false` on the navigation toggle button', async () => {
- const button = await page.$(buttonEl);
- const ariaExpandedIsFalse = await button.evaluate((node) => node.getAttribute('aria-expanded') === 'false');
- expect(ariaExpandedIsFalse).toBe(true);
- });
- });
+ it('has aria-expanded set as `false` on the navigation toggle button', async () => {
+ const button = await page.$(buttonEl);
+ const ariaExpandedIsFalse = await button.evaluate((node) => node.getAttribute('aria-expanded') === 'false');
+ expect(ariaExpandedIsFalse).toBe(true);
+ });
+ });
- describe('when the viewport is small', () => {
- beforeEach(async () => {
- await setViewport(page, { width: 600, height: 1050 });
- await setTestPage('/test', renderComponent('header', params));
- });
+ describe('when the viewport is small', () => {
+ beforeEach(async () => {
+ await setViewport(page, { width: 600, height: 1050 });
+ await setTestPage('/test', renderComponent('header', params));
+ });
- it('has aria-hidden set as `true` on the navigation list', async () => {
- const nav = await page.$(navEl);
- const hasAriaAttribute = await nav.evaluate((node) => node.getAttribute('aria-hidden') === 'true');
- expect(hasAriaAttribute).toBe(true);
- });
+ it('has aria-hidden set as `true` on the navigation list', async () => {
+ const nav = await page.$(navEl);
+ const hasAriaAttribute = await nav.evaluate((node) => node.getAttribute('aria-hidden') === 'true');
+ expect(hasAriaAttribute).toBe(true);
+ });
- describe('when the toggle button is clicked to open the navigation list', () => {
- beforeEach(async () => {
- await page.focus(buttonEl);
- await page.keyboard.press('Enter');
- });
+ describe('when the toggle button is clicked to open the navigation list', () => {
+ beforeEach(async () => {
+ await page.focus(buttonEl);
+ await page.keyboard.press('Enter');
+ });
- it('has aria-hidden set as `false` on the navigation list', async () => {
- const nav = await page.$(navEl);
- const hasAriaAttribute = await nav.evaluate((node) => node.getAttribute('aria-hidden') === 'false');
- expect(hasAriaAttribute).toBe(true);
- });
+ it('has aria-hidden set as `false` on the navigation list', async () => {
+ const nav = await page.$(navEl);
+ const hasAriaAttribute = await nav.evaluate((node) => node.getAttribute('aria-hidden') === 'false');
+ expect(hasAriaAttribute).toBe(true);
+ });
- it('has the hide class removed from the navigation list', async () => {
- const hasClass = await page.$eval(navEl, (node) =>
- node.classList.contains('ons-u-d-no@xxs@l' || 'ons-u-d-no' || 'ons-u-d-no@xs@l'),
- );
- expect(hasClass).toBe(false);
- });
+ it('has the hide class removed from the navigation list', async () => {
+ const hasClass = await page.$eval(navEl, (node) =>
+ node.classList.contains('ons-u-d-no@xxs@l' || 'ons-u-d-no' || 'ons-u-d-no@xs@l'),
+ );
+ expect(hasClass).toBe(false);
+ });
- it('has aria-expanded set as `true` on the navigation toggle button', async () => {
- const button = await page.$(buttonEl);
- const ariaExpandedIsTrue = await button.evaluate((node) => node.getAttribute('aria-expanded') === 'true');
- expect(ariaExpandedIsTrue).toBe(true);
- });
+ it('has aria-expanded set as `true` on the navigation toggle button', async () => {
+ const button = await page.$(buttonEl);
+ const ariaExpandedIsTrue = await button.evaluate((node) => node.getAttribute('aria-expanded') === 'true');
+ expect(ariaExpandedIsTrue).toBe(true);
+ });
- it('has the correct class applied to the navigation toggle button', async () => {
- const hasClass = await page.$eval(buttonEl, (node) => node.classList.contains('active'));
- expect(hasClass).toBe(true);
- });
- });
+ it('has the correct class applied to the navigation toggle button', async () => {
+ const hasClass = await page.$eval(buttonEl, (node) => node.classList.contains('active'));
+ expect(hasClass).toBe(true);
+ });
+ });
- describe('when the toggle button is clicked to close the navigation list', () => {
- beforeEach(async () => {
- await page.focus(buttonEl);
- await page.keyboard.press('Enter');
- await page.waitForTimeout(100);
- await page.keyboard.press('Enter');
- await page.waitForTimeout(100);
- });
+ describe('when the toggle button is clicked to close the navigation list', () => {
+ beforeEach(async () => {
+ await page.focus(buttonEl);
+ await page.keyboard.press('Enter');
+ await page.waitForTimeout(100);
+ await page.keyboard.press('Enter');
+ await page.waitForTimeout(100);
+ });
- it('has aria-hidden set as `true` on the navigation list', async () => {
- const nav = await page.$(navEl);
- const hasAriaAttribute = await nav.evaluate((node) => node.getAttribute('aria-hidden') === 'true');
- expect(hasAriaAttribute).toBe(true);
- });
+ it('has aria-hidden set as `true` on the navigation list', async () => {
+ const nav = await page.$(navEl);
+ const hasAriaAttribute = await nav.evaluate((node) => node.getAttribute('aria-hidden') === 'true');
+ expect(hasAriaAttribute).toBe(true);
+ });
- it('has aria-expanded set as `false` on the navigation toggle button', async () => {
- const button = await page.$(buttonEl);
- const ariaExpandedIsTrue = await button.evaluate((node) => node.getAttribute('aria-expanded') === 'false');
- expect(ariaExpandedIsTrue).toBe(true);
- });
+ it('has aria-expanded set as `false` on the navigation toggle button', async () => {
+ const button = await page.$(buttonEl);
+ const ariaExpandedIsTrue = await button.evaluate((node) => node.getAttribute('aria-expanded') === 'false');
+ expect(ariaExpandedIsTrue).toBe(true);
+ });
- it('has the active class removed from the navigation toggle button', async () => {
- const hasClass = await page.$eval(buttonEl, (node) => node.classList.contains('active'));
- expect(hasClass).toBe(false);
+ it('has the active class removed from the navigation toggle button', async () => {
+ const hasClass = await page.$eval(buttonEl, (node) => node.classList.contains('active'));
+ expect(hasClass).toBe(false);
+ });
+ });
});
- });
});
- });
- describe.each([['main', EXAMPLE_NAVIGATION, '.ons-navigation--main', '.ons-js-navigation-button']])(
- 'level: %s navigation',
- (_, params, navEl, buttonEl) => {
- describe('when the viewport is small and manually made wider', () => {
- beforeEach(async () => {
- await setViewport(page, { width: 600, height: 1050 });
- await setTestPage('/test', renderComponent('header', params));
- await setViewport(page, { width: 1200, height: 1050 });
- });
+ describe.each([['main', EXAMPLE_NAVIGATION, '.ons-navigation--main', '.ons-js-navigation-button']])(
+ 'level: %s navigation',
+ (_, params, navEl, buttonEl) => {
+ describe('when the viewport is small and manually made wider', () => {
+ beforeEach(async () => {
+ await setViewport(page, { width: 600, height: 1050 });
+ await setTestPage('/test', renderComponent('header', params));
+ await setViewport(page, { width: 1200, height: 1050 });
+ });
- it('has the aria-hidden attribute removed from the navigation list', async () => {
- const nav = await page.$(navEl);
- const hasAriaAttribute = await nav.evaluate((node) => node.getAttribute('aria-hidden') !== null);
- expect(hasAriaAttribute).toBe(false);
- });
+ it('has the aria-hidden attribute removed from the navigation list', async () => {
+ const nav = await page.$(navEl);
+ const hasAriaAttribute = await nav.evaluate((node) => node.getAttribute('aria-hidden') !== null);
+ expect(hasAriaAttribute).toBe(false);
+ });
- it('has aria-expanded removed from the navigation toggle button', async () => {
- const button = await page.$(buttonEl);
- const hasAriaExpanded = await button.evaluate((node) => node.getAttribute('aria-expanded') !== null);
- expect(hasAriaExpanded).toBe(false);
- });
+ it('has aria-expanded removed from the navigation toggle button', async () => {
+ const button = await page.$(buttonEl);
+ const hasAriaExpanded = await button.evaluate((node) => node.getAttribute('aria-expanded') !== null);
+ expect(hasAriaExpanded).toBe(false);
+ });
- it('has the hide class removed from the navigation list', async () => {
- const hasClass = await page.$eval(navEl, (node) =>
- node.classList.contains('ons-u-d-no@xxs@l' || 'ons-u-d-no' || 'ons-u-d-no@xs@l'),
- );
- expect(hasClass).toBe(false);
- });
- });
- },
- );
+ it('has the hide class removed from the navigation list', async () => {
+ const hasClass = await page.$eval(navEl, (node) =>
+ node.classList.contains('ons-u-d-no@xxs@l' || 'ons-u-d-no' || 'ons-u-d-no@xs@l'),
+ );
+ expect(hasClass).toBe(false);
+ });
+ });
+ },
+ );
});
describe('level: sub navigation', () => {
- describe('when removeHorizontalSubNav is set to true', () => {
- beforeEach(async () => {
- await setTestPage('/test', renderComponent('header', EXAMPLE_NAVIGATION_WITH_SUBNAVIGATION_REMOVED));
- });
+ describe('when removeHorizontalSubNav is set to true', () => {
+ beforeEach(async () => {
+ await setTestPage('/test', renderComponent('header', EXAMPLE_NAVIGATION_WITH_SUBNAVIGATION_REMOVED));
+ });
- it('does not render the sub-nav element', async () => {
- const hasSubNavEl = (await page.content()).includes('.ons-navigation--sub');
- expect(hasSubNavEl).toBe(false);
+ it('does not render the sub-nav element', async () => {
+ const hasSubNavEl = (await page.content()).includes('.ons-navigation--sub');
+ expect(hasSubNavEl).toBe(false);
+ });
});
- });
});
diff --git a/src/components/pagination/_macro.spec.js b/src/components/pagination/_macro.spec.js
index 7d03a291f8..1af7b8239f 100644
--- a/src/components/pagination/_macro.spec.js
+++ b/src/components/pagination/_macro.spec.js
@@ -6,361 +6,368 @@ import axe from '../../tests/helpers/axe';
import { renderComponent } from '../../tests/helpers/rendering';
const PAGINATION_PREV_NEXT_LABELS = {
- previous: 'Previous page',
- next: 'Next page',
+ previous: 'Previous page',
+ next: 'Next page',
};
const EXAMPLE_PAGINATION_MINIMAL = {
- ...PAGINATION_PREV_NEXT_LABELS,
- currentPageNumber: 1,
- pages: [{ url: '/page/1' }],
+ ...PAGINATION_PREV_NEXT_LABELS,
+ currentPageNumber: 1,
+ pages: [{ url: '/page/1' }],
};
describe('macro: pagination', () => {
- it('has additionally provided style classes', () => {
- const $ = cheerio.load(
- renderComponent('pagination', {
- ...EXAMPLE_PAGINATION_MINIMAL,
- classes: 'extra-class another-extra-class',
- }),
- );
-
- expect($('.ons-pagination').hasClass('extra-class')).toBe(true);
- expect($('.ons-pagination').hasClass('another-extra-class')).toBe(true);
- });
-
- it('has `ons-pagination--no-indicator` style class when `hideRangeIndicator` is `true`', () => {
- const $ = cheerio.load(
- renderComponent('pagination', {
- ...EXAMPLE_PAGINATION_MINIMAL,
- hideRangeIndicator: true,
- }),
- );
-
- expect($('.ons-pagination').hasClass('ons-pagination--no-indicator')).toBe(true);
- });
-
- it('does not have `ons-pagination--no-indicator` style class when `hideRangeIndicator` is `false`', () => {
- const $ = cheerio.load(
- renderComponent('pagination', {
- ...EXAMPLE_PAGINATION_MINIMAL,
- hideRangeIndicator: false,
- }),
- );
-
- expect($('.ons-pagination').hasClass('ons-pagination--no-indicator')).toBe(false);
- });
-
- describe('one page', () => {
- const $ = cheerio.load(
- renderComponent('pagination', {
- ...PAGINATION_PREV_NEXT_LABELS,
- currentPageNumber: 1,
- pages: [{ url: '/page/1' }],
- }),
- );
-
- it('passes jest-axe checks', async () => {
- const results = await axe($.html());
- expect(results).toHaveNoViolations();
+ it('has additionally provided style classes', () => {
+ const $ = cheerio.load(
+ renderComponent('pagination', {
+ ...EXAMPLE_PAGINATION_MINIMAL,
+ classes: 'extra-class another-extra-class',
+ }),
+ );
+
+ expect($('.ons-pagination').hasClass('extra-class')).toBe(true);
+ expect($('.ons-pagination').hasClass('another-extra-class')).toBe(true);
+ });
+
+ it('has `ons-pagination--no-indicator` style class when `hideRangeIndicator` is `true`', () => {
+ const $ = cheerio.load(
+ renderComponent('pagination', {
+ ...EXAMPLE_PAGINATION_MINIMAL,
+ hideRangeIndicator: true,
+ }),
+ );
+
+ expect($('.ons-pagination').hasClass('ons-pagination--no-indicator')).toBe(true);
+ });
+
+ it('does not have `ons-pagination--no-indicator` style class when `hideRangeIndicator` is `false`', () => {
+ const $ = cheerio.load(
+ renderComponent('pagination', {
+ ...EXAMPLE_PAGINATION_MINIMAL,
+ hideRangeIndicator: false,
+ }),
+ );
+
+ expect($('.ons-pagination').hasClass('ons-pagination--no-indicator')).toBe(false);
+ });
+
+ describe('one page', () => {
+ const $ = cheerio.load(
+ renderComponent('pagination', {
+ ...PAGINATION_PREV_NEXT_LABELS,
+ currentPageNumber: 1,
+ pages: [{ url: '/page/1' }],
+ }),
+ );
+
+ it('passes jest-axe checks', async () => {
+ const results = await axe($.html());
+ expect(results).toHaveNoViolations();
+ });
+
+ it('has `aria-label` attribute indicating position within pagination', () => {
+ expect($('.ons-pagination').attr('aria-label')).toBe('Pagination (Page 1 of 1)');
+ });
+
+ it('renders element indicating position within pagination', () => {
+ expect($('.ons-pagination__position').text().trim()).toBe('Page 1 of 1');
+ });
+
+ it('has a single list item', () => {
+ expect($('li').length).toBe(1);
+ assertIsCurrentPage($('.ons-pagination__item'), '/page/1', 'Current page (Page 1 of 1)', '1');
+ });
+ });
+
+ describe('two pages where first is the current page', () => {
+ const $ = cheerio.load(
+ renderComponent('pagination', {
+ ...PAGINATION_PREV_NEXT_LABELS,
+ currentPageNumber: 1,
+ pages: [{ url: '/page/1' }, { url: '/page/2' }],
+ }),
+ );
+
+ it('passes jest-axe checks', async () => {
+ const results = await axe($.html());
+ expect(results).toHaveNoViolations();
+ });
+
+ it('has `aria-label` attribute indicating position within pagination', () => {
+ expect($('.ons-pagination').attr('aria-label')).toBe('Pagination (Page 1 of 2)');
+ });
+
+ it('renders element indicating position within pagination', () => {
+ expect($('.ons-pagination__position').text().trim()).toBe('Page 1 of 2');
+ });
+
+ it('has a 3 list items ("1", "2", "Next page")', () => {
+ expect($('li').length).toBe(3);
+ assertIsCurrentPage($('.ons-pagination__item:nth-child(1)'), '/page/1', 'Current page (Page 1 of 2)', '1');
+ assertIsNextPage($('.ons-pagination__item:nth-child(2)'), '/page/2', 'Go to page 2', '2');
+ assertIsNextPage($('.ons-pagination__item:nth-child(3)'), '/page/2', 'Go to the next page (Page 2)', 'Next page');
+ });
+ });
+
+ describe('two pages where second is the current page', () => {
+ const $ = cheerio.load(
+ renderComponent('pagination', {
+ ...PAGINATION_PREV_NEXT_LABELS,
+ currentPageNumber: 2,
+ pages: [{ url: '/page/1' }, { url: '/page/2' }],
+ }),
+ );
+
+ it('passes jest-axe checks', async () => {
+ const results = await axe($.html());
+ expect(results).toHaveNoViolations();
+ });
+
+ it('has `aria-label` attribute indicating position within pagination', () => {
+ expect($('.ons-pagination').attr('aria-label')).toBe('Pagination (Page 2 of 2)');
+ });
+
+ it('renders element indicating position within pagination', () => {
+ expect($('.ons-pagination__position').text().trim()).toBe('Page 2 of 2');
+ });
+
+ it('has a 3 list items ("Previous page", "1", "2")', () => {
+ expect($('li').length).toBe(3);
+ assertIsPreviousPage($('.ons-pagination__item:nth-child(1)'), '/page/1', 'Go to the previous page (Page 1)', 'Previous page');
+ assertIsPreviousPage($('.ons-pagination__item:nth-child(2)'), '/page/1', 'Go to page 1', '1');
+ assertIsCurrentPage($('.ons-pagination__item:nth-child(3)'), '/page/2', 'Current page (Page 2 of 2)', '2');
+ });
+ });
+
+ describe('three pages where second is the current page', () => {
+ const $ = cheerio.load(
+ renderComponent('pagination', {
+ ...PAGINATION_PREV_NEXT_LABELS,
+ currentPageNumber: 2,
+ pages: [{ url: '/page/1' }, { url: '/page/2' }, { url: '/page/3' }],
+ }),
+ );
+
+ it('passes jest-axe checks', async () => {
+ const results = await axe($.html());
+ expect(results).toHaveNoViolations();
+ });
+
+ it('has `aria-label` attribute indicating position within pagination', () => {
+ expect($('.ons-pagination').attr('aria-label')).toBe('Pagination (Page 2 of 3)');
+ });
+
+ it('renders element indicating position within pagination', () => {
+ expect($('.ons-pagination__position').text().trim()).toBe('Page 2 of 3');
+ });
+
+ it('has a 5 list items ("Previous page", "1", "2", "3", "Next page")', () => {
+ expect($('li').length).toBe(5);
+ assertIsPreviousPage($('.ons-pagination__item:nth-child(1)'), '/page/1', 'Go to the previous page (Page 1)', 'Previous page');
+ assertIsPreviousPage($('.ons-pagination__item:nth-child(2)'), '/page/1', 'Go to page 1', '1');
+ assertIsCurrentPage($('.ons-pagination__item:nth-child(3)'), '/page/2', 'Current page (Page 2 of 3)', '2');
+ assertIsNextPage($('.ons-pagination__item:nth-child(4)'), '/page/3', 'Go to page 3', '3');
+ assertIsNextPage($('.ons-pagination__item:nth-child(5)'), '/page/3', 'Go to the next page (Page 3)', 'Next page');
+ });
+ });
+
+ describe('five pages where second is the current page', () => {
+ const $ = cheerio.load(
+ renderComponent('pagination', {
+ ...PAGINATION_PREV_NEXT_LABELS,
+ currentPageNumber: 2,
+ pages: [{ url: '/page/1' }, { url: '/page/2' }, { url: '/page/3' }, { url: '/page/4' }, { url: '/page/5' }],
+ }),
+ );
+
+ it('passes jest-axe checks', async () => {
+ const results = await axe($.html());
+ expect(results).toHaveNoViolations();
+ });
+
+ it('has `aria-label` attribute indicating position within pagination', () => {
+ expect($('.ons-pagination').attr('aria-label')).toBe('Pagination (Page 2 of 5)');
+ });
+
+ it('renders element indicating position within pagination', () => {
+ expect($('.ons-pagination__position').text().trim()).toBe('Page 2 of 5');
+ });
+
+ it('has a 7 list items ("Previous page", "1", "2", "3", "4", "5", "Next page")', () => {
+ expect($('li').length).toBe(7);
+ assertIsPreviousPage($('.ons-pagination__item:nth-child(1)'), '/page/1', 'Go to the previous page (Page 1)', 'Previous page');
+ assertIsPreviousPage($('.ons-pagination__item:nth-child(2)'), '/page/1', 'Go to page 1', '1');
+ assertIsCurrentPage($('.ons-pagination__item:nth-child(3)'), '/page/2', 'Current page (Page 2 of 5)', '2');
+ assertIsNextPage($('.ons-pagination__item:nth-child(4)'), '/page/3', 'Go to page 3', '3');
+ assertIsOtherPage($('.ons-pagination__item:nth-child(5)'), '/page/4', 'Go to page 4', '4');
+ assertIsOtherPage($('.ons-pagination__item:nth-child(6)'), '/page/5', 'Go to the last page (Page 5)', '5');
+ assertIsNextPage($('.ons-pagination__item:nth-child(7)'), '/page/3', 'Go to the next page (Page 3)', 'Next page');
+ });
+ });
+
+ describe('six pages where second is the current page', () => {
+ const $ = cheerio.load(
+ renderComponent('pagination', {
+ ...PAGINATION_PREV_NEXT_LABELS,
+ currentPageNumber: 2,
+ pages: [
+ { url: '/page/1' },
+ { url: '/page/2' },
+ { url: '/page/3' },
+ { url: '/page/4' },
+ { url: '/page/5' },
+ { url: '/page/6' },
+ ],
+ }),
+ );
+
+ it('passes jest-axe checks', async () => {
+ const results = await axe($.html());
+ expect(results).toHaveNoViolations();
+ });
+
+ it('has `aria-label` attribute indicating position within pagination', () => {
+ expect($('.ons-pagination').attr('aria-label')).toBe('Pagination (Page 2 of 6)');
+ });
+
+ it('renders element indicating position within pagination', () => {
+ expect($('.ons-pagination__position').text().trim()).toBe('Page 2 of 6');
+ });
+
+ it('has 7 list items ("Previous page", "1", "2", "3", "...", "6", "Next page")', () => {
+ expect($('li').length).toBe(7);
+ assertIsPreviousPage($('.ons-pagination__item:nth-child(1)'), '/page/1', 'Go to the previous page (Page 1)', 'Previous page');
+ assertIsPreviousPage($('.ons-pagination__item:nth-child(2)'), '/page/1', 'Go to page 1', '1');
+ assertIsCurrentPage($('.ons-pagination__item:nth-child(3)'), '/page/2', 'Current page (Page 2 of 6)', '2');
+ assertIsNextPage($('.ons-pagination__item:nth-child(4)'), '/page/3', 'Go to page 3', '3');
+ assertIsGap($('.ons-pagination__item:nth-child(5)'));
+ assertIsOtherPage($('.ons-pagination__item:nth-child(6)'), '/page/6', 'Go to the last page (Page 6)', '6');
+ assertIsNextPage($('.ons-pagination__item:nth-child(7)'), '/page/3', 'Go to the next page (Page 3)', 'Next page');
+ });
+ });
+
+ describe('eleven pages where fifth is the current page', () => {
+ const $ = cheerio.load(
+ renderComponent('pagination', {
+ ...PAGINATION_PREV_NEXT_LABELS,
+ currentPageNumber: 5,
+ pages: [
+ { url: '/page/1' },
+ { url: '/page/2' },
+ { url: '/page/3' },
+ { url: '/page/4' },
+ { url: '/page/5' },
+ { url: '/page/6' },
+ { url: '/page/7' },
+ { url: '/page/8' },
+ { url: '/page/9' },
+ { url: '/page/10' },
+ { url: '/page/11' },
+ ],
+ }),
+ );
+
+ it('passes jest-axe checks', async () => {
+ const results = await axe($.html());
+ expect(results).toHaveNoViolations();
+ });
+
+ it('has `aria-label` attribute indicating position within pagination', () => {
+ expect($('.ons-pagination').attr('aria-label')).toBe('Pagination (Page 5 of 11)');
+ });
+
+ it('renders element indicating position within pagination', () => {
+ expect($('.ons-pagination__position').text().trim()).toBe('Page 5 of 11');
+ });
+
+ it('has a 9 list items ("Previous page", "1", "...", "4", "5", "6", "...", "11", "Next page")', () => {
+ expect($('li').length).toBe(9);
+ assertIsPreviousPage($('.ons-pagination__item:nth-child(1)'), '/page/4', 'Go to the previous page (Page 4)', 'Previous page');
+ assertIsOtherPage($('.ons-pagination__item:nth-child(2)'), '/page/1', 'Go to the first page (Page 1)', '1');
+ assertIsGap($('.ons-pagination__item:nth-child(3)'));
+ assertIsPreviousPage($('.ons-pagination__item:nth-child(4)'), '/page/4', 'Go to page 4', '4');
+ assertIsCurrentPage($('.ons-pagination__item:nth-child(5)'), '/page/5', 'Current page (Page 5 of 11)', '5');
+ assertIsNextPage($('.ons-pagination__item:nth-child(6)'), '/page/6', 'Go to page 6', '6');
+ assertIsGap($('.ons-pagination__item:nth-child(7)'));
+ assertIsOtherPage($('.ons-pagination__item:nth-child(8)'), '/page/11', 'Go to the last page (Page 11)', '11');
+ assertIsNextPage($('.ons-pagination__item:nth-child(9)'), '/page/6', 'Go to the next page (Page 6)', 'Next page');
+ });
+ });
+
+ describe('eleven pages where tenth is the current page', () => {
+ const $ = cheerio.load(
+ renderComponent('pagination', {
+ ...PAGINATION_PREV_NEXT_LABELS,
+ currentPageNumber: 10,
+ pages: [
+ { url: '/page/1' },
+ { url: '/page/2' },
+ { url: '/page/3' },
+ { url: '/page/4' },
+ { url: '/page/5' },
+ { url: '/page/6' },
+ { url: '/page/7' },
+ { url: '/page/8' },
+ { url: '/page/9' },
+ { url: '/page/10' },
+ { url: '/page/11' },
+ ],
+ }),
+ );
+
+ it('passes jest-axe checks', async () => {
+ const results = await axe($.html());
+ expect(results).toHaveNoViolations();
+ });
+
+ it('has `aria-label` attribute indicating position within pagination', () => {
+ expect($('.ons-pagination').attr('aria-label')).toBe('Pagination (Page 10 of 11)');
+ });
+
+ it('renders element indicating position within pagination', () => {
+ expect($('.ons-pagination__position').text().trim()).toBe('Page 10 of 11');
+ });
+
+ it('has a 7 list items ("Previous page", "1", "...", "9", "10", "11", "Next page")', () => {
+ expect($('li').length).toBe(7);
+ assertIsPreviousPage($('.ons-pagination__item:nth-child(1)'), '/page/9', 'Go to the previous page (Page 9)', 'Previous page');
+ assertIsOtherPage($('.ons-pagination__item:nth-child(2)'), '/page/1', 'Go to the first page (Page 1)', '1');
+ assertIsGap($('.ons-pagination__item:nth-child(3)'));
+ assertIsPreviousPage($('.ons-pagination__item:nth-child(4)'), '/page/9', 'Go to page 9', '9');
+ assertIsCurrentPage($('.ons-pagination__item:nth-child(5)'), '/page/10', 'Current page (Page 10 of 11)', '10');
+ assertIsNextPage($('.ons-pagination__item:nth-child(6)'), '/page/11', 'Go to page 11', '11');
+ assertIsNextPage($('.ons-pagination__item:nth-child(7)'), '/page/11', 'Go to the next page (Page 11)', 'Next page');
+ });
});
-
- it('has `aria-label` attribute indicating position within pagination', () => {
- expect($('.ons-pagination').attr('aria-label')).toBe('Pagination (Page 1 of 1)');
- });
-
- it('renders element indicating position within pagination', () => {
- expect($('.ons-pagination__position').text().trim()).toBe('Page 1 of 1');
- });
-
- it('has a single list item', () => {
- expect($('li').length).toBe(1);
- assertIsCurrentPage($('.ons-pagination__item'), '/page/1', 'Current page (Page 1 of 1)', '1');
- });
- });
-
- describe('two pages where first is the current page', () => {
- const $ = cheerio.load(
- renderComponent('pagination', {
- ...PAGINATION_PREV_NEXT_LABELS,
- currentPageNumber: 1,
- pages: [{ url: '/page/1' }, { url: '/page/2' }],
- }),
- );
-
- it('passes jest-axe checks', async () => {
- const results = await axe($.html());
- expect(results).toHaveNoViolations();
- });
-
- it('has `aria-label` attribute indicating position within pagination', () => {
- expect($('.ons-pagination').attr('aria-label')).toBe('Pagination (Page 1 of 2)');
- });
-
- it('renders element indicating position within pagination', () => {
- expect($('.ons-pagination__position').text().trim()).toBe('Page 1 of 2');
- });
-
- it('has a 3 list items ("1", "2", "Next page")', () => {
- expect($('li').length).toBe(3);
- assertIsCurrentPage($('.ons-pagination__item:nth-child(1)'), '/page/1', 'Current page (Page 1 of 2)', '1');
- assertIsNextPage($('.ons-pagination__item:nth-child(2)'), '/page/2', 'Go to page 2', '2');
- assertIsNextPage($('.ons-pagination__item:nth-child(3)'), '/page/2', 'Go to the next page (Page 2)', 'Next page');
- });
- });
-
- describe('two pages where second is the current page', () => {
- const $ = cheerio.load(
- renderComponent('pagination', {
- ...PAGINATION_PREV_NEXT_LABELS,
- currentPageNumber: 2,
- pages: [{ url: '/page/1' }, { url: '/page/2' }],
- }),
- );
-
- it('passes jest-axe checks', async () => {
- const results = await axe($.html());
- expect(results).toHaveNoViolations();
- });
-
- it('has `aria-label` attribute indicating position within pagination', () => {
- expect($('.ons-pagination').attr('aria-label')).toBe('Pagination (Page 2 of 2)');
- });
-
- it('renders element indicating position within pagination', () => {
- expect($('.ons-pagination__position').text().trim()).toBe('Page 2 of 2');
- });
-
- it('has a 3 list items ("Previous page", "1", "2")', () => {
- expect($('li').length).toBe(3);
- assertIsPreviousPage($('.ons-pagination__item:nth-child(1)'), '/page/1', 'Go to the previous page (Page 1)', 'Previous page');
- assertIsPreviousPage($('.ons-pagination__item:nth-child(2)'), '/page/1', 'Go to page 1', '1');
- assertIsCurrentPage($('.ons-pagination__item:nth-child(3)'), '/page/2', 'Current page (Page 2 of 2)', '2');
- });
- });
-
- describe('three pages where second is the current page', () => {
- const $ = cheerio.load(
- renderComponent('pagination', {
- ...PAGINATION_PREV_NEXT_LABELS,
- currentPageNumber: 2,
- pages: [{ url: '/page/1' }, { url: '/page/2' }, { url: '/page/3' }],
- }),
- );
-
- it('passes jest-axe checks', async () => {
- const results = await axe($.html());
- expect(results).toHaveNoViolations();
- });
-
- it('has `aria-label` attribute indicating position within pagination', () => {
- expect($('.ons-pagination').attr('aria-label')).toBe('Pagination (Page 2 of 3)');
- });
-
- it('renders element indicating position within pagination', () => {
- expect($('.ons-pagination__position').text().trim()).toBe('Page 2 of 3');
- });
-
- it('has a 5 list items ("Previous page", "1", "2", "3", "Next page")', () => {
- expect($('li').length).toBe(5);
- assertIsPreviousPage($('.ons-pagination__item:nth-child(1)'), '/page/1', 'Go to the previous page (Page 1)', 'Previous page');
- assertIsPreviousPage($('.ons-pagination__item:nth-child(2)'), '/page/1', 'Go to page 1', '1');
- assertIsCurrentPage($('.ons-pagination__item:nth-child(3)'), '/page/2', 'Current page (Page 2 of 3)', '2');
- assertIsNextPage($('.ons-pagination__item:nth-child(4)'), '/page/3', 'Go to page 3', '3');
- assertIsNextPage($('.ons-pagination__item:nth-child(5)'), '/page/3', 'Go to the next page (Page 3)', 'Next page');
- });
- });
-
- describe('five pages where second is the current page', () => {
- const $ = cheerio.load(
- renderComponent('pagination', {
- ...PAGINATION_PREV_NEXT_LABELS,
- currentPageNumber: 2,
- pages: [{ url: '/page/1' }, { url: '/page/2' }, { url: '/page/3' }, { url: '/page/4' }, { url: '/page/5' }],
- }),
- );
-
- it('passes jest-axe checks', async () => {
- const results = await axe($.html());
- expect(results).toHaveNoViolations();
- });
-
- it('has `aria-label` attribute indicating position within pagination', () => {
- expect($('.ons-pagination').attr('aria-label')).toBe('Pagination (Page 2 of 5)');
- });
-
- it('renders element indicating position within pagination', () => {
- expect($('.ons-pagination__position').text().trim()).toBe('Page 2 of 5');
- });
-
- it('has a 7 list items ("Previous page", "1", "2", "3", "4", "5", "Next page")', () => {
- expect($('li').length).toBe(7);
- assertIsPreviousPage($('.ons-pagination__item:nth-child(1)'), '/page/1', 'Go to the previous page (Page 1)', 'Previous page');
- assertIsPreviousPage($('.ons-pagination__item:nth-child(2)'), '/page/1', 'Go to page 1', '1');
- assertIsCurrentPage($('.ons-pagination__item:nth-child(3)'), '/page/2', 'Current page (Page 2 of 5)', '2');
- assertIsNextPage($('.ons-pagination__item:nth-child(4)'), '/page/3', 'Go to page 3', '3');
- assertIsOtherPage($('.ons-pagination__item:nth-child(5)'), '/page/4', 'Go to page 4', '4');
- assertIsOtherPage($('.ons-pagination__item:nth-child(6)'), '/page/5', 'Go to the last page (Page 5)', '5');
- assertIsNextPage($('.ons-pagination__item:nth-child(7)'), '/page/3', 'Go to the next page (Page 3)', 'Next page');
- });
- });
-
- describe('six pages where second is the current page', () => {
- const $ = cheerio.load(
- renderComponent('pagination', {
- ...PAGINATION_PREV_NEXT_LABELS,
- currentPageNumber: 2,
- pages: [{ url: '/page/1' }, { url: '/page/2' }, { url: '/page/3' }, { url: '/page/4' }, { url: '/page/5' }, { url: '/page/6' }],
- }),
- );
-
- it('passes jest-axe checks', async () => {
- const results = await axe($.html());
- expect(results).toHaveNoViolations();
- });
-
- it('has `aria-label` attribute indicating position within pagination', () => {
- expect($('.ons-pagination').attr('aria-label')).toBe('Pagination (Page 2 of 6)');
- });
-
- it('renders element indicating position within pagination', () => {
- expect($('.ons-pagination__position').text().trim()).toBe('Page 2 of 6');
- });
-
- it('has 7 list items ("Previous page", "1", "2", "3", "...", "6", "Next page")', () => {
- expect($('li').length).toBe(7);
- assertIsPreviousPage($('.ons-pagination__item:nth-child(1)'), '/page/1', 'Go to the previous page (Page 1)', 'Previous page');
- assertIsPreviousPage($('.ons-pagination__item:nth-child(2)'), '/page/1', 'Go to page 1', '1');
- assertIsCurrentPage($('.ons-pagination__item:nth-child(3)'), '/page/2', 'Current page (Page 2 of 6)', '2');
- assertIsNextPage($('.ons-pagination__item:nth-child(4)'), '/page/3', 'Go to page 3', '3');
- assertIsGap($('.ons-pagination__item:nth-child(5)'));
- assertIsOtherPage($('.ons-pagination__item:nth-child(6)'), '/page/6', 'Go to the last page (Page 6)', '6');
- assertIsNextPage($('.ons-pagination__item:nth-child(7)'), '/page/3', 'Go to the next page (Page 3)', 'Next page');
- });
- });
-
- describe('eleven pages where fifth is the current page', () => {
- const $ = cheerio.load(
- renderComponent('pagination', {
- ...PAGINATION_PREV_NEXT_LABELS,
- currentPageNumber: 5,
- pages: [
- { url: '/page/1' },
- { url: '/page/2' },
- { url: '/page/3' },
- { url: '/page/4' },
- { url: '/page/5' },
- { url: '/page/6' },
- { url: '/page/7' },
- { url: '/page/8' },
- { url: '/page/9' },
- { url: '/page/10' },
- { url: '/page/11' },
- ],
- }),
- );
-
- it('passes jest-axe checks', async () => {
- const results = await axe($.html());
- expect(results).toHaveNoViolations();
- });
-
- it('has `aria-label` attribute indicating position within pagination', () => {
- expect($('.ons-pagination').attr('aria-label')).toBe('Pagination (Page 5 of 11)');
- });
-
- it('renders element indicating position within pagination', () => {
- expect($('.ons-pagination__position').text().trim()).toBe('Page 5 of 11');
- });
-
- it('has a 9 list items ("Previous page", "1", "...", "4", "5", "6", "...", "11", "Next page")', () => {
- expect($('li').length).toBe(9);
- assertIsPreviousPage($('.ons-pagination__item:nth-child(1)'), '/page/4', 'Go to the previous page (Page 4)', 'Previous page');
- assertIsOtherPage($('.ons-pagination__item:nth-child(2)'), '/page/1', 'Go to the first page (Page 1)', '1');
- assertIsGap($('.ons-pagination__item:nth-child(3)'));
- assertIsPreviousPage($('.ons-pagination__item:nth-child(4)'), '/page/4', 'Go to page 4', '4');
- assertIsCurrentPage($('.ons-pagination__item:nth-child(5)'), '/page/5', 'Current page (Page 5 of 11)', '5');
- assertIsNextPage($('.ons-pagination__item:nth-child(6)'), '/page/6', 'Go to page 6', '6');
- assertIsGap($('.ons-pagination__item:nth-child(7)'));
- assertIsOtherPage($('.ons-pagination__item:nth-child(8)'), '/page/11', 'Go to the last page (Page 11)', '11');
- assertIsNextPage($('.ons-pagination__item:nth-child(9)'), '/page/6', 'Go to the next page (Page 6)', 'Next page');
- });
- });
-
- describe('eleven pages where tenth is the current page', () => {
- const $ = cheerio.load(
- renderComponent('pagination', {
- ...PAGINATION_PREV_NEXT_LABELS,
- currentPageNumber: 10,
- pages: [
- { url: '/page/1' },
- { url: '/page/2' },
- { url: '/page/3' },
- { url: '/page/4' },
- { url: '/page/5' },
- { url: '/page/6' },
- { url: '/page/7' },
- { url: '/page/8' },
- { url: '/page/9' },
- { url: '/page/10' },
- { url: '/page/11' },
- ],
- }),
- );
-
- it('passes jest-axe checks', async () => {
- const results = await axe($.html());
- expect(results).toHaveNoViolations();
- });
-
- it('has `aria-label` attribute indicating position within pagination', () => {
- expect($('.ons-pagination').attr('aria-label')).toBe('Pagination (Page 10 of 11)');
- });
-
- it('renders element indicating position within pagination', () => {
- expect($('.ons-pagination__position').text().trim()).toBe('Page 10 of 11');
- });
-
- it('has a 7 list items ("Previous page", "1", "...", "9", "10", "11", "Next page")', () => {
- expect($('li').length).toBe(7);
- assertIsPreviousPage($('.ons-pagination__item:nth-child(1)'), '/page/9', 'Go to the previous page (Page 9)', 'Previous page');
- assertIsOtherPage($('.ons-pagination__item:nth-child(2)'), '/page/1', 'Go to the first page (Page 1)', '1');
- assertIsGap($('.ons-pagination__item:nth-child(3)'));
- assertIsPreviousPage($('.ons-pagination__item:nth-child(4)'), '/page/9', 'Go to page 9', '9');
- assertIsCurrentPage($('.ons-pagination__item:nth-child(5)'), '/page/10', 'Current page (Page 10 of 11)', '10');
- assertIsNextPage($('.ons-pagination__item:nth-child(6)'), '/page/11', 'Go to page 11', '11');
- assertIsNextPage($('.ons-pagination__item:nth-child(7)'), '/page/11', 'Go to the next page (Page 11)', 'Next page');
- });
- });
});
function assertIsCurrentPage(pageItem, url, label, text) {
- expect(pageItem.hasClass('ons-pagination__item--current')).toBe(true);
- expect(pageItem.find('.ons-pagination__link').attr('href')).toBe(url);
- expect(pageItem.find('.ons-pagination__link').attr('aria-current')).toBe('true');
- expect(pageItem.find('.ons-pagination__link').attr('aria-label')).toBe(label);
- expect(pageItem.find('.ons-pagination__link').text().trim()).toBe(text);
+ expect(pageItem.hasClass('ons-pagination__item--current')).toBe(true);
+ expect(pageItem.find('.ons-pagination__link').attr('href')).toBe(url);
+ expect(pageItem.find('.ons-pagination__link').attr('aria-current')).toBe('true');
+ expect(pageItem.find('.ons-pagination__link').attr('aria-label')).toBe(label);
+ expect(pageItem.find('.ons-pagination__link').text().trim()).toBe(text);
}
function assertIsOtherPage(pageItem, url, label, text) {
- expect(pageItem.hasClass('ons-pagination__item--current')).toBe(false);
- expect(pageItem.find('.ons-pagination__link').attr('href')).toBe(url);
- expect(pageItem.find('.ons-pagination__link').attr('aria-current')).toBeUndefined();
- expect(pageItem.find('.ons-pagination__link').attr('aria-label')).toBe(label);
- expect(pageItem.find('.ons-pagination__link').text().trim()).toBe(text);
+ expect(pageItem.hasClass('ons-pagination__item--current')).toBe(false);
+ expect(pageItem.find('.ons-pagination__link').attr('href')).toBe(url);
+ expect(pageItem.find('.ons-pagination__link').attr('aria-current')).toBeUndefined();
+ expect(pageItem.find('.ons-pagination__link').attr('aria-label')).toBe(label);
+ expect(pageItem.find('.ons-pagination__link').text().trim()).toBe(text);
}
function assertIsPreviousPage(pageItem, url, label, text) {
- assertIsOtherPage(pageItem, url, label, text);
- expect(pageItem.find('.ons-pagination__link').attr('rel')).toBe('prev');
+ assertIsOtherPage(pageItem, url, label, text);
+ expect(pageItem.find('.ons-pagination__link').attr('rel')).toBe('prev');
}
function assertIsNextPage(pageItem, url, label, text) {
- assertIsOtherPage(pageItem, url, label, text);
- expect(pageItem.find('.ons-pagination__link').attr('rel')).toBe('next');
+ assertIsOtherPage(pageItem, url, label, text);
+ expect(pageItem.find('.ons-pagination__link').attr('rel')).toBe('next');
}
function assertIsGap(pageItem) {
- expect(pageItem.hasClass('ons-pagination__item--gap')).toBe(true);
- expect(pageItem.text().trim()).toBe('…');
+ expect(pageItem.hasClass('ons-pagination__item--gap')).toBe(true);
+ expect(pageItem.text().trim()).toBe('…');
}
diff --git a/src/components/pagination/_pagination.scss b/src/components/pagination/_pagination.scss
index 6e40e3aaa2..9a61f88c65 100644
--- a/src/components/pagination/_pagination.scss
+++ b/src/components/pagination/_pagination.scss
@@ -2,80 +2,80 @@ $pagination-item-padding: 0.5rem;
$pagination-item-width: 2.5rem;
.ons-pagination {
- &__position {
- margin: 0 0 0.4rem;
- }
+ &__position {
+ margin: 0 0 0.4rem;
+ }
- &__items {
- margin: 0 $pagination-item-padding * -1;
- padding: 0;
- }
+ &__items {
+ margin: 0 $pagination-item-padding * -1;
+ padding: 0;
+ }
- &__item {
- display: none;
- text-align: center;
+ &__item {
+ display: none;
+ text-align: center;
- &--previous,
- &--next {
- display: inline-block;
- }
+ &--previous,
+ &--next {
+ display: inline-block;
+ }
- @include mq(m) {
- &:not(&--previous) {
- display: inline-block;
- }
+ @include mq(m) {
+ &:not(&--previous) {
+ display: inline-block;
+ }
+ }
}
- }
- &__items > & {
- &__item--current {
- margin: 0 0 0 $pagination-item-padding;
+ &__items > & {
+ &__item--current {
+ margin: 0 0 0 $pagination-item-padding;
+ }
}
- }
- &__item,
- &__link {
- height: $pagination-item-width;
- min-width: $pagination-item-width;
- }
+ &__item,
+ &__link {
+ height: $pagination-item-width;
+ min-width: $pagination-item-width;
+ }
- &__link--no-underline {
- text-decoration: none;
- &:hover {
- text-decoration: none;
+ &__link--no-underline {
+ text-decoration: none;
+ &:hover {
+ text-decoration: none;
+ }
}
- }
- &__link {
- border-radius: 3px;
- box-shadow: none;
- display: block;
- padding: $pagination-item-padding;
+ &__link {
+ border-radius: 3px;
+ box-shadow: none;
+ display: block;
+ padding: $pagination-item-padding;
- &-text {
- @extend .ons-u-fs-r--b;
+ &-text {
+ @extend .ons-u-fs-r--b;
- display: inline-block;
- vertical-align: middle;
- }
+ display: inline-block;
+ vertical-align: middle;
+ }
- &:focus {
- border-radius: 0;
+ &:focus {
+ border-radius: 0;
+ }
}
- }
- &__item--current &__link {
- background: var(--ons-color-text-link-active);
- color: var(--ons-color-white);
- outline: 2px solid transparent; // Add transparent outline because Windows High Contrast Mode doesn't show backgrounds
- text-decoration: none;
- }
+ &__item--current &__link {
+ background: var(--ons-color-text-link-active);
+ color: var(--ons-color-white);
+ outline: 2px solid transparent; // Add transparent outline because Windows High Contrast Mode doesn't show backgrounds
+ text-decoration: none;
+ }
- &--no-indicator & {
- &__position {
- @include mq(m) {
- display: none;
- }
+ &--no-indicator & {
+ &__position {
+ @include mq(m) {
+ display: none;
+ }
+ }
}
- }
}
diff --git a/src/components/panel/_macro.spec.js b/src/components/panel/_macro.spec.js
index 091e242244..33f582c214 100644
--- a/src/components/panel/_macro.spec.js
+++ b/src/components/panel/_macro.spec.js
@@ -6,394 +6,394 @@ import axe from '../../tests/helpers/axe';
import { renderComponent, templateFaker } from '../../tests/helpers/rendering';
const EXAMPLE_PANEL_BASIC = {
- id: 'panel',
- body: 'Some panel text',
+ id: 'panel',
+ body: 'Some panel text',
};
describe('macro: panel', () => {
- describe.each([
- ['info', 'Important information:'],
- ['bare', 'Important information:'],
- ['error', 'Error:'],
- ['warn', 'Warning:'],
- ['warn-branded', 'Warning:'],
- ['branded', 'Important information:'],
- ['success', 'Completed:'],
- ['announcement', 'Announcement:'],
- ])('mode: %s', (panelVariant, accessibleText) => {
- it('passes jest-axe checks', async () => {
- const $ = cheerio.load(
- renderComponent('panel', {
- ...EXAMPLE_PANEL_BASIC,
- variant: panelVariant,
- }),
- );
- const results = await axe($.html());
- expect(results).toHaveNoViolations();
+ describe.each([
+ ['info', 'Important information:'],
+ ['bare', 'Important information:'],
+ ['error', 'Error:'],
+ ['warn', 'Warning:'],
+ ['warn-branded', 'Warning:'],
+ ['branded', 'Important information:'],
+ ['success', 'Completed:'],
+ ['announcement', 'Announcement:'],
+ ])('mode: %s', (panelVariant, accessibleText) => {
+ it('passes jest-axe checks', async () => {
+ const $ = cheerio.load(
+ renderComponent('panel', {
+ ...EXAMPLE_PANEL_BASIC,
+ variant: panelVariant,
+ }),
+ );
+ const results = await axe($.html());
+ expect(results).toHaveNoViolations();
+ });
+
+ it('has correct class', () => {
+ const $ = cheerio.load(
+ renderComponent('panel', {
+ ...EXAMPLE_PANEL_BASIC,
+ variant: panelVariant,
+ }),
+ );
+
+ expect($('.ons-panel').hasClass(`ons-panel--${panelVariant}`)).toBe(true);
+ });
+
+ it('has the provided `body` text', () => {
+ const $ = cheerio.load(
+ renderComponent('panel', {
+ ...EXAMPLE_PANEL_BASIC,
+ variant: panelVariant,
+ }),
+ );
+
+ expect($('.ons-panel__body').text().trim()).toBe('Some panel text');
+ });
+
+ it('calls with content', () => {
+ const $ = cheerio.load(renderComponent('panel', { EXAMPLE_PANEL_BASIC, variant: panelVariant }, 'Example content...'));
+
+ const content = $('.ons-panel__body').text().trim();
+ expect(content).toBe('Example content...');
+ });
+
+ it('has the provided `id` attribute', () => {
+ const $ = cheerio.load(
+ renderComponent('panel', {
+ ...EXAMPLE_PANEL_BASIC,
+ variant: panelVariant,
+ }),
+ );
+
+ expect($('#panel').length).toBe(1);
+ });
+
+ it('has custom classes applied', () => {
+ const $ = cheerio.load(
+ renderComponent('panel', {
+ ...EXAMPLE_PANEL_BASIC,
+ variant: panelVariant,
+ classes: 'ons-custom-class',
+ }),
+ );
+
+ expect($('.ons-panel').hasClass('ons-custom-class')).toBe(true);
+ });
+
+ it('has additionally provided `attributes`', () => {
+ const $ = cheerio.load(
+ renderComponent('panel', {
+ ...EXAMPLE_PANEL_BASIC,
+ variant: panelVariant,
+ attributes: {
+ a: '123',
+ b: '456',
+ },
+ }),
+ );
+ expect($('.ons-panel').attr('a')).toBe('123');
+ expect($('.ons-panel').attr('b')).toBe('456');
+ });
+
+ it('has visually hidden accessible element', () => {
+ const $ = cheerio.load(
+ renderComponent('panel', {
+ ...EXAMPLE_PANEL_BASIC,
+ variant: panelVariant,
+ }),
+ );
+
+ expect($('.ons-panel__assistive-text').length).toBe(1);
+ });
+
+ it('has the default visually hidden accessible text', () => {
+ const $ = cheerio.load(
+ renderComponent('panel', {
+ ...EXAMPLE_PANEL_BASIC,
+ variant: panelVariant,
+ }),
+ );
+
+ expect($('.ons-panel__assistive-text').text().trim()).toBe(accessibleText);
+ });
+
+ it('has the provided visually hidden accessible text', () => {
+ const $ = cheerio.load(
+ renderComponent('panel', {
+ ...EXAMPLE_PANEL_BASIC,
+ variant: panelVariant,
+ assistiveTextPrefix: 'Some helpful text:',
+ }),
+ );
+
+ expect($('.ons-panel__assistive-text').text().trim()).toBe('Some helpful text:');
+ });
});
- it('has correct class', () => {
- const $ = cheerio.load(
- renderComponent('panel', {
- ...EXAMPLE_PANEL_BASIC,
- variant: panelVariant,
- }),
- );
-
- expect($('.ons-panel').hasClass(`ons-panel--${panelVariant}`)).toBe(true);
- });
-
- it('has the provided `body` text', () => {
- const $ = cheerio.load(
- renderComponent('panel', {
- ...EXAMPLE_PANEL_BASIC,
- variant: panelVariant,
- }),
- );
-
- expect($('.ons-panel__body').text().trim()).toBe('Some panel text');
+ describe('mode: info', () => {
+ it('has the default title tag', () => {
+ const $ = cheerio.load(
+ renderComponent('panel', {
+ ...EXAMPLE_PANEL_BASIC,
+ title: 'Panel title',
+ }),
+ );
+
+ const titleTag = $('.ons-panel__title')[0].tagName;
+ expect(titleTag).toBe('div');
+ });
+
+ it('has the provided `titleTag`', () => {
+ const $ = cheerio.load(
+ renderComponent('panel', {
+ ...EXAMPLE_PANEL_BASIC,
+ title: 'Panel title',
+ titleTag: 'h3',
+ }),
+ );
+
+ const titleTag = $('.ons-panel__title')[0].tagName;
+ expect(titleTag).toBe('h3');
+ });
+
+ it('has the provided `title` text', () => {
+ const $ = cheerio.load(
+ renderComponent('panel', {
+ ...EXAMPLE_PANEL_BASIC,
+ title: 'Panel title',
+ }),
+ );
+
+ const titleText = $('.ons-panel__title').text();
+ expect(titleText).toBe('Panel title');
+ });
});
- it('calls with content', () => {
- const $ = cheerio.load(renderComponent('panel', { EXAMPLE_PANEL_BASIC, variant: panelVariant }, 'Example content...'));
-
- const content = $('.ons-panel__body').text().trim();
- expect(content).toBe('Example content...');
+ describe.each([
+ ['error', 'h2'],
+ ['success', 'div'],
+ ])('mode: %s', (panelVariant, tagEl) => {
+ it('has the default id set', () => {
+ const $ = cheerio.load(
+ renderComponent('panel', {
+ ...EXAMPLE_PANEL_BASIC,
+ title: 'Title',
+ variant: panelVariant,
+ }),
+ );
+
+ expect($('#alert').length).toBe(1);
+ });
+
+ it('has the correct default title tag', () => {
+ const $ = cheerio.load(
+ renderComponent('panel', {
+ ...EXAMPLE_PANEL_BASIC,
+ title: 'Title',
+ variant: panelVariant,
+ }),
+ );
+
+ const titleTag = $('.ons-panel__title')[0].tagName;
+ expect(titleTag).toBe(tagEl);
+ });
+
+ it('has aria-labelledby attribute set with default value', () => {
+ const $ = cheerio.load(
+ renderComponent('panel', {
+ ...EXAMPLE_PANEL_BASIC,
+ title: 'Title',
+ variant: panelVariant,
+ }),
+ );
+
+ expect($('.ons-panel').attr('aria-labelledby')).toBe('alert');
+ });
+
+ it('has the role attribute set to alert', () => {
+ const $ = cheerio.load(
+ renderComponent('panel', {
+ ...EXAMPLE_PANEL_BASIC,
+ title: 'Title',
+ variant: panelVariant,
+ }),
+ );
+
+ expect($('.ons-panel').attr('role')).toBe('alert');
+ });
+
+ it('has the tabindex attribute set to -1', () => {
+ const $ = cheerio.load(
+ renderComponent('panel', {
+ ...EXAMPLE_PANEL_BASIC,
+ title: 'Title',
+ variant: panelVariant,
+ }),
+ );
+
+ expect($('.ons-panel').attr('tabindex')).toBe('-1');
+ });
+
+ it('has the autofocus attribute set to autofocus', () => {
+ const $ = cheerio.load(
+ renderComponent('panel', {
+ ...EXAMPLE_PANEL_BASIC,
+ title: 'Title',
+ variant: panelVariant,
+ }),
+ );
+
+ expect($('.ons-panel').attr('autofocus')).toBe('autofocus');
+ });
+
+ it('does not have the autofocus attribute set if `isDesignSystemExample` is provided', () => {
+ const isDesignSystemExample = true;
+
+ const $ = cheerio.load(
+ renderComponent(
+ 'panel',
+ {
+ ...EXAMPLE_PANEL_BASIC,
+ title: 'Title',
+ variant: panelVariant,
+ },
+ null,
+ null,
+ isDesignSystemExample,
+ ),
+ );
+
+ expect($('.ons-panel').attr('autofocus')).toBe(undefined);
+ });
});
- it('has the provided `id` attribute', () => {
- const $ = cheerio.load(
- renderComponent('panel', {
- ...EXAMPLE_PANEL_BASIC,
- variant: panelVariant,
- }),
- );
-
- expect($('#panel').length).toBe(1);
+ describe('mode: spacious', () => {
+ it('has the correct class set', () => {
+ const $ = cheerio.load(
+ renderComponent('panel', {
+ ...EXAMPLE_PANEL_BASIC,
+ spacious: true,
+ }),
+ );
+
+ expect($('.ons-panel').hasClass('ons-panel--spacious')).toBe(true);
+ });
});
- it('has custom classes applied', () => {
- const $ = cheerio.load(
- renderComponent('panel', {
- ...EXAMPLE_PANEL_BASIC,
- variant: panelVariant,
- classes: 'ons-custom-class',
- }),
- );
-
- expect($('.ons-panel').hasClass('ons-custom-class')).toBe(true);
+ describe('mode: announcement', () => {
+ it('creates containers with the correct classes', () => {
+ const $ = cheerio.load(
+ renderComponent('panel', {
+ ...EXAMPLE_PANEL_BASIC,
+ variant: 'announcement',
+ }),
+ );
+
+ expect($('.ons-announcement').length).toBe(1);
+ expect($('.ons-container').length).toBe(1);
+ });
+
+ it('has `arrow-forward` icon', () => {
+ const faker = templateFaker();
+ const iconsSpy = faker.spy('icon');
+
+ faker.renderComponent('panel', {
+ ...EXAMPLE_PANEL_BASIC,
+ variant: 'announcement',
+ });
+
+ expect(iconsSpy.occurrences[0].iconType).toBe('arrow-forward');
+ });
});
- it('has additionally provided `attributes`', () => {
- const $ = cheerio.load(
- renderComponent('panel', {
- ...EXAMPLE_PANEL_BASIC,
- variant: panelVariant,
- attributes: {
- a: '123',
- b: '456',
- },
- }),
- );
- expect($('.ons-panel').attr('a')).toBe('123');
- expect($('.ons-panel').attr('b')).toBe('456');
+ describe('mode: warn', () => {
+ it('has a default "!" prefix', () => {
+ const $ = cheerio.load(
+ renderComponent('panel', {
+ ...EXAMPLE_PANEL_BASIC,
+ variant: 'warn',
+ }),
+ );
+
+ expect($('.ons-panel__icon').text().trim()).toBe('!');
+ });
});
- it('has visually hidden accessible element', () => {
- const $ = cheerio.load(
- renderComponent('panel', {
- ...EXAMPLE_PANEL_BASIC,
- variant: panelVariant,
- }),
- );
-
- expect($('.ons-panel__assistive-text').length).toBe(1);
- });
-
- it('has the default visually hidden accessible text', () => {
- const $ = cheerio.load(
- renderComponent('panel', {
- ...EXAMPLE_PANEL_BASIC,
- variant: panelVariant,
- }),
- );
-
- expect($('.ons-panel__assistive-text').text().trim()).toBe(accessibleText);
- });
-
- it('has the provided visually hidden accessible text', () => {
- const $ = cheerio.load(
- renderComponent('panel', {
- ...EXAMPLE_PANEL_BASIC,
- variant: panelVariant,
- assistiveTextPrefix: 'Some helpful text:',
- }),
- );
-
- expect($('.ons-panel__assistive-text').text().trim()).toBe('Some helpful text:');
- });
- });
-
- describe('mode: info', () => {
- it('has the default title tag', () => {
- const $ = cheerio.load(
- renderComponent('panel', {
- ...EXAMPLE_PANEL_BASIC,
- title: 'Panel title',
- }),
- );
-
- const titleTag = $('.ons-panel__title')[0].tagName;
- expect(titleTag).toBe('div');
- });
-
- it('has the provided `titleTag`', () => {
- const $ = cheerio.load(
- renderComponent('panel', {
- ...EXAMPLE_PANEL_BASIC,
- title: 'Panel title',
- titleTag: 'h3',
- }),
- );
-
- const titleTag = $('.ons-panel__title')[0].tagName;
- expect(titleTag).toBe('h3');
- });
-
- it('has the provided `title` text', () => {
- const $ = cheerio.load(
- renderComponent('panel', {
- ...EXAMPLE_PANEL_BASIC,
- title: 'Panel title',
- }),
- );
-
- const titleText = $('.ons-panel__title').text();
- expect(titleText).toBe('Panel title');
- });
- });
-
- describe.each([
- ['error', 'h2'],
- ['success', 'div'],
- ])('mode: %s', (panelVariant, tagEl) => {
- it('has the default id set', () => {
- const $ = cheerio.load(
- renderComponent('panel', {
- ...EXAMPLE_PANEL_BASIC,
- title: 'Title',
- variant: panelVariant,
- }),
- );
-
- expect($('#alert').length).toBe(1);
- });
-
- it('has the correct default title tag', () => {
- const $ = cheerio.load(
- renderComponent('panel', {
- ...EXAMPLE_PANEL_BASIC,
- title: 'Title',
- variant: panelVariant,
- }),
- );
-
- const titleTag = $('.ons-panel__title')[0].tagName;
- expect(titleTag).toBe(tagEl);
- });
-
- it('has aria-labelledby attribute set with default value', () => {
- const $ = cheerio.load(
- renderComponent('panel', {
- ...EXAMPLE_PANEL_BASIC,
- title: 'Title',
- variant: panelVariant,
- }),
- );
-
- expect($('.ons-panel').attr('aria-labelledby')).toBe('alert');
- });
-
- it('has the role attribute set to alert', () => {
- const $ = cheerio.load(
- renderComponent('panel', {
- ...EXAMPLE_PANEL_BASIC,
- title: 'Title',
- variant: panelVariant,
- }),
- );
-
- expect($('.ons-panel').attr('role')).toBe('alert');
+ describe('mode: warn-branded', () => {
+ it('creates a container div', () => {
+ const $ = cheerio.load(
+ renderComponent('panel', {
+ ...EXAMPLE_PANEL_BASIC,
+ variant: 'warn-branded',
+ }),
+ );
+
+ expect($('.ons-branded-warning').length).toBe(1);
+ expect($('.ons-container').length).toBe(1);
+ });
+
+ it('has a default "!" prefix', () => {
+ const $ = cheerio.load(
+ renderComponent('panel', {
+ ...EXAMPLE_PANEL_BASIC,
+ variant: 'warn-branded',
+ }),
+ );
+
+ expect($('.ons-panel__icon').text().trim()).toBe('!');
+ });
});
- it('has the tabindex attribute set to -1', () => {
- const $ = cheerio.load(
- renderComponent('panel', {
- ...EXAMPLE_PANEL_BASIC,
- title: 'Title',
- variant: panelVariant,
- }),
- );
-
- expect($('.ons-panel').attr('tabindex')).toBe('-1');
- });
-
- it('has the autofocus attribute set to autofocus', () => {
- const $ = cheerio.load(
- renderComponent('panel', {
- ...EXAMPLE_PANEL_BASIC,
- title: 'Title',
- variant: panelVariant,
- }),
- );
-
- expect($('.ons-panel').attr('autofocus')).toBe('autofocus');
- });
-
- it('does not have the autofocus attribute set if `isDesignSystemExample` is provided', () => {
- const isDesignSystemExample = true;
-
- const $ = cheerio.load(
- renderComponent(
- 'panel',
- {
- ...EXAMPLE_PANEL_BASIC,
- title: 'Title',
- variant: panelVariant,
- },
- null,
- null,
- isDesignSystemExample,
- ),
- );
-
- expect($('.ons-panel').attr('autofocus')).toBe(undefined);
- });
- });
-
- describe('mode: spacious', () => {
- it('has the correct class set', () => {
- const $ = cheerio.load(
- renderComponent('panel', {
- ...EXAMPLE_PANEL_BASIC,
- spacious: true,
- }),
- );
-
- expect($('.ons-panel').hasClass('ons-panel--spacious')).toBe(true);
- });
- });
-
- describe('mode: announcement', () => {
- it('creates containers with the correct classes', () => {
- const $ = cheerio.load(
- renderComponent('panel', {
- ...EXAMPLE_PANEL_BASIC,
- variant: 'announcement',
- }),
- );
-
- expect($('.ons-announcement').length).toBe(1);
- expect($('.ons-container').length).toBe(1);
- });
-
- it('has `arrow-forward` icon', () => {
- const faker = templateFaker();
- const iconsSpy = faker.spy('icon');
-
- faker.renderComponent('panel', {
- ...EXAMPLE_PANEL_BASIC,
- variant: 'announcement',
- });
-
- expect(iconsSpy.occurrences[0].iconType).toBe('arrow-forward');
- });
- });
-
- describe('mode: warn', () => {
- it('has a default "!" prefix', () => {
- const $ = cheerio.load(
- renderComponent('panel', {
- ...EXAMPLE_PANEL_BASIC,
- variant: 'warn',
- }),
- );
-
- expect($('.ons-panel__icon').text().trim()).toBe('!');
- });
- });
-
- describe('mode: warn-branded', () => {
- it('creates a container div', () => {
- const $ = cheerio.load(
- renderComponent('panel', {
- ...EXAMPLE_PANEL_BASIC,
- variant: 'warn-branded',
- }),
- );
-
- expect($('.ons-branded-warning').length).toBe(1);
- expect($('.ons-container').length).toBe(1);
- });
-
- it('has a default "!" prefix', () => {
- const $ = cheerio.load(
- renderComponent('panel', {
- ...EXAMPLE_PANEL_BASIC,
- variant: 'warn-branded',
- }),
- );
-
- expect($('.ons-panel__icon').text().trim()).toBe('!');
- });
- });
-
- describe('mode: custom icon', () => {
- it('passes jest-axe checks', async () => {
- const $ = cheerio.load(
- renderComponent('panel', {
- ...EXAMPLE_PANEL_BASIC,
- iconType: 'check',
- }),
- );
-
- const results = await axe($.html());
- expect(results).toHaveNoViolations();
- });
-
- it('has a custom icon `iconType`', () => {
- const faker = templateFaker();
- const iconsSpy = faker.spy('icon');
-
- faker.renderComponent('panel', {
- ...EXAMPLE_PANEL_BASIC,
- iconType: 'check',
- });
-
- expect(iconsSpy.occurrences[0].iconType).toBe('check');
- });
-
- it('has the default icon size set', () => {
- const $ = cheerio.load(
- renderComponent('panel', {
- ...EXAMPLE_PANEL_BASIC,
- iconType: 'check',
- }),
- );
-
- expect($('.ons-panel__icon').hasClass('ons-u-fs-r')).toBe(true);
- });
-
- it.each(['r', 'm', 'l', 'xl'])('has the correct class for the provided `iconSize` override (%s)', (customIconSize) => {
- const $ = cheerio.load(
- renderComponent('panel', {
- ...EXAMPLE_PANEL_BASIC,
- iconType: 'check',
- iconSize: customIconSize,
- }),
- );
-
- expect($('.ons-panel__icon').hasClass(`ons-u-fs-${customIconSize}`)).toBe(true);
+ describe('mode: custom icon', () => {
+ it('passes jest-axe checks', async () => {
+ const $ = cheerio.load(
+ renderComponent('panel', {
+ ...EXAMPLE_PANEL_BASIC,
+ iconType: 'check',
+ }),
+ );
+
+ const results = await axe($.html());
+ expect(results).toHaveNoViolations();
+ });
+
+ it('has a custom icon `iconType`', () => {
+ const faker = templateFaker();
+ const iconsSpy = faker.spy('icon');
+
+ faker.renderComponent('panel', {
+ ...EXAMPLE_PANEL_BASIC,
+ iconType: 'check',
+ });
+
+ expect(iconsSpy.occurrences[0].iconType).toBe('check');
+ });
+
+ it('has the default icon size set', () => {
+ const $ = cheerio.load(
+ renderComponent('panel', {
+ ...EXAMPLE_PANEL_BASIC,
+ iconType: 'check',
+ }),
+ );
+
+ expect($('.ons-panel__icon').hasClass('ons-u-fs-r')).toBe(true);
+ });
+
+ it.each(['r', 'm', 'l', 'xl'])('has the correct class for the provided `iconSize` override (%s)', (customIconSize) => {
+ const $ = cheerio.load(
+ renderComponent('panel', {
+ ...EXAMPLE_PANEL_BASIC,
+ iconType: 'check',
+ iconSize: customIconSize,
+ }),
+ );
+
+ expect($('.ons-panel__icon').hasClass(`ons-u-fs-${customIconSize}`)).toBe(true);
+ });
});
- });
});
diff --git a/src/components/panel/_panel.scss b/src/components/panel/_panel.scss
index 4d7da2a312..4655a20306 100644
--- a/src/components/panel/_panel.scss
+++ b/src/components/panel/_panel.scss
@@ -1,253 +1,254 @@
@mixin panel-variant($name, $color, $color-bg) {
- .ons-panel {
- &--#{$name} {
- background: $color-bg;
- border-color: $color;
- outline: 1px solid transparent; // Add transparent outline because Windows High Contrast Mode doesn't show background
+ .ons-panel {
+ &--#{$name} {
+ background: $color-bg;
+ border-color: $color;
+ outline: 1px solid transparent; // Add transparent outline because Windows High Contrast Mode doesn't show background
+ }
+
+ &--#{$name} & {
+ &__header {
+ background: $color;
+ }
+ }
}
-
- &--#{$name} & {
- &__header {
- background: $color;
- }
- }
- }
}
.ons-branded-warning {
- background: var(--ons-color-branded-tertiary);
+ background: var(--ons-color-branded-tertiary);
}
.ons-announcement {
- background-color: var(--ons-color-black);
+ background-color: var(--ons-color-black);
}
.ons-branded-warning,
.ons-announcement {
- outline: 2px solid transparent; // Add transparent outline because Windows High Contrast Mode doesn't show background
+ outline: 2px solid transparent; // Add transparent outline because Windows High Contrast Mode doesn't show background
}
.ons-panel {
- border-radius: 0;
- position: relative;
-
- // Removes inherited bottom margin to make whitespace inside panel equal
- > *:last-child {
- margin-bottom: 0;
- }
-
- .ons-field {
- margin-bottom: 0;
- }
-
- &:focus {
- box-shadow: none;
- outline: 4px solid var(--ons-color-focus) !important;
- }
-
- &__header {
border-radius: 0;
- color: var(--ons-color-text-inverse);
- margin: 0;
- padding: 0.75rem 1rem;
- }
-
- &__timer {
- white-space: nowrap;
- }
-
- &__title {
- margin: 0;
- }
-
- &__body {
- padding: 1rem;
+ position: relative;
// Removes inherited bottom margin to make whitespace inside panel equal
- > *:last-child,
- strong > *:last-child {
- margin-bottom: 0;
+ > *:last-child {
+ margin-bottom: 0;
}
- &.ons-icon-margin--xxxl {
- padding-left: 2.7rem !important;
-
- @include mq(m) {
- padding-left: 3.5rem !important;
- }
+ .ons-field {
+ margin-bottom: 0;
}
- &.ons-icon-margin--xxl {
- padding-left: 2.45rem !important;
- @include mq(m) {
- padding-left: 2.9rem !important;
- }
+ &:focus {
+ box-shadow: none;
+ outline: 4px solid var(--ons-color-focus) !important;
}
- &.ons-icon-margin--xl {
- padding-left: 2.35rem !important;
- @include mq(m) {
- padding-left: 2.55rem !important;
- }
+ &__header {
+ border-radius: 0;
+ color: var(--ons-color-text-inverse);
+ margin: 0;
+ padding: 0.75rem 1rem;
}
- &.ons-icon-margin--l {
- padding-left: 2.25rem !important;
- @include mq(m) {
- padding-left: 2.35rem !important;
- }
+ &__timer {
+ white-space: nowrap;
}
- &.ons-icon-margin--m {
- padding-left: 2.05rem !important;
- @include mq(m) {
- padding-left: 2.2rem !important;
- }
- }
- &.ons-icon-margin--s {
- padding-left: 1.7rem !important;
+ &__title {
+ margin: 0;
}
- }
-
- &__error {
- color: var(--ons-color-errors);
- }
-
- &--warn {
- border: 0 !important;
- margin-bottom: 1rem;
- outline: none !important;
- padding: 0;
-
- &--footer {
- background-color: var(--ons-color-grey-15) !important;
- margin-bottom: 0;
- padding: 1rem 0 !important;
+
+ &__body {
+ padding: 1rem;
+
+ // Removes inherited bottom margin to make whitespace inside panel equal
+ > *:last-child,
+ strong > *:last-child {
+ margin-bottom: 0;
+ }
+
+ &.ons-icon-margin--xxxl {
+ padding-left: 2.7rem !important;
+
+ @include mq(m) {
+ padding-left: 3.5rem !important;
+ }
+ }
+ &.ons-icon-margin--xxl {
+ padding-left: 2.45rem !important;
+
+ @include mq(m) {
+ padding-left: 2.9rem !important;
+ }
+ }
+ &.ons-icon-margin--xl {
+ padding-left: 2.35rem !important;
+
+ @include mq(m) {
+ padding-left: 2.55rem !important;
+ }
+ }
+ &.ons-icon-margin--l {
+ padding-left: 2.25rem !important;
+
+ @include mq(m) {
+ padding-left: 2.35rem !important;
+ }
+ }
+ &.ons-icon-margin--m {
+ padding-left: 2.05rem !important;
+
+ @include mq(m) {
+ padding-left: 2.2rem !important;
+ }
+ }
+ &.ons-icon-margin--s {
+ padding-left: 1.7rem !important;
+ }
}
- }
-
- &--warn-branded,
- &--announcement {
- border: 0 !important;
- color: var(--ons-color-white);
- margin-bottom: 0;
- outline: none !important;
- padding: 1rem 0 !important;
- a {
- color: inherit;
- text-decoration: underline solid var(--ons-color-white) 1px;
+
+ &__error {
+ color: var(--ons-color-errors);
}
- a:hover {
- text-decoration-thickness: 2px;
+
+ &--warn {
+ border: 0 !important;
+ margin-bottom: 1rem;
+ outline: none !important;
+ padding: 0;
+
+ &--footer {
+ background-color: var(--ons-color-grey-15) !important;
+ margin-bottom: 0;
+ padding: 1rem 0 !important;
+ }
}
- }
- &--announcement {
- a:focus {
- box-shadow: 0 -2px var(--ons-color-focus),
- 0 4px (--ons-color-text-inverse-link) !important; // Override focus style because the black border is not visible on a black background
+ &--warn-branded,
+ &--announcement {
+ border: 0 !important;
+ color: var(--ons-color-white);
+ margin-bottom: 0;
+ outline: none !important;
+ padding: 1rem 0 !important;
+ a {
+ color: inherit;
+ text-decoration: underline solid var(--ons-color-white) 1px;
+ }
+ a:hover {
+ text-decoration-thickness: 2px;
+ }
}
- }
-
- &--no-title {
- border-left: 8px solid transparent;
- padding: 1rem;
- .ons-panel__body {
- background: none;
- padding: 0;
+
+ &--announcement {
+ a:focus {
+ box-shadow:
+ 0 -2px var(--ons-color-focus),
+ 0 4px (--ons-color-text-inverse-link) !important; // Override focus style because the black border is not visible on a black background
+ }
}
- &.ons-panel--warn {
- padding: 0;
+
+ &--no-title {
+ border-left: 8px solid transparent;
+ padding: 1rem;
+ .ons-panel__body {
+ background: none;
+ padding: 0;
+ }
+ &.ons-panel--warn {
+ padding: 0;
+ }
}
- }
- &--spacious {
- padding: 1rem;
+ &--spacious {
+ padding: 1rem;
- @include mq(m) {
- padding: 2rem;
+ @include mq(m) {
+ padding: 2rem;
+ }
}
- }
- &--warn &,
- &--warn-branded &,
- &--announcement & {
- &__body {
- font-weight: $font-weight-bold;
- min-height: 2rem; // Height of icon
- padding: 0.222rem 0 0.222rem 2.8rem; // Alignment tweak
+ &--warn &,
+ &--warn-branded &,
+ &--announcement & {
+ &__body {
+ font-weight: $font-weight-bold;
+ min-height: 2rem; // Height of icon
+ padding: 0.222rem 0 0.222rem 2.8rem; // Alignment tweak
+ }
+ &__icon {
+ background: var(--ons-color-black);
+ border-radius: 50%;
+ color: var(--ons-color-white);
+ font-size: 1.5rem;
+ font-weight: $font-weight-bold;
+ line-height: 2rem;
+ min-height: 2rem;
+ min-width: 2rem;
+ outline: 2px solid transparent; // Add transparent outline because Windows High Contrast Mode doesn't show background
+ text-align: center;
+ }
}
- &__icon {
- background: var(--ons-color-black);
- border-radius: 50%;
- color: var(--ons-color-white);
- font-size: 1.5rem;
- font-weight: $font-weight-bold;
- line-height: 2rem;
- min-height: 2rem;
- min-width: 2rem;
- outline: 2px solid transparent; // Add transparent outline because Windows High Contrast Mode doesn't show background
- text-align: center;
- }
- }
- &--announcement & {
- &__body div > *:last-child {
- margin-bottom: 0;
+ &--announcement & {
+ &__body div > *:last-child {
+ margin-bottom: 0;
+ }
+
+ &__icon {
+ background-color: var(--ons-color-white);
+ color: var(--ons-color-black);
+ display: flex;
+ svg {
+ margin: auto;
+ }
+ }
}
- &__icon {
- background-color: var(--ons-color-white);
- color: var(--ons-color-black);
- display: flex;
- svg {
- margin: auto;
- }
+ &--warn-branded & {
+ &__icon {
+ background-color: var(--ons-color-white);
+ color: var(--ons-color-branded-tertiary);
+ }
}
- }
- &--warn-branded & {
- &__icon {
- background-color: var(--ons-color-white);
- color: var(--ons-color-branded-tertiary);
- }
- }
-
- &--success & {
- &__icon {
- left: 0;
- padding-left: 1rem;
- .ons-icon {
- fill: var(--ons-color-success) !important;
- margin-top: -15% !important;
- }
+ &--success & {
+ &__icon {
+ left: 0;
+ padding-left: 1rem;
+ .ons-icon {
+ fill: var(--ons-color-success) !important;
+ margin-top: -15% !important;
+ }
+ }
}
- }
-
- &__icon + &__body {
- padding-left: 2rem;
- }
- &--bare & {
- &__icon {
- height: 1.3rem;
- width: 1.3rem;
+ &__icon + &__body {
+ padding-left: 2rem;
}
- &__body {
- padding: 0 0 0 1.5rem;
+
+ &--bare & {
+ &__icon {
+ height: 1.3rem;
+ width: 1.3rem;
+ }
+ &__body {
+ padding: 0 0 0 1.5rem;
+ }
}
- }
-
- &--info,
- &--bare,
- &--success,
- &--warn,
- &--warn-branded,
- &--announcement {
- .ons-panel__icon {
- position: absolute;
+
+ &--info,
+ &--bare,
+ &--success,
+ &--warn,
+ &--warn-branded,
+ &--announcement {
+ .ons-panel__icon {
+ position: absolute;
+ }
}
- }
}
@include panel-variant(error, var(--ons-color-errors), var(--ons-color-errors-tint));
diff --git a/src/components/password/_macro.spec.js b/src/components/password/_macro.spec.js
index 2b292c23fb..03acca9af9 100644
--- a/src/components/password/_macro.spec.js
+++ b/src/components/password/_macro.spec.js
@@ -6,132 +6,132 @@ import axe from '../../tests/helpers/axe';
import { renderComponent, templateFaker } from '../../tests/helpers/rendering';
const EXAMPLE_PASSWORD_MINIMAL = {
- id: 'example-password',
- name: 'example-password-name',
- label: {
- text: 'Label text',
- description: 'Description text',
- classes: 'extra-label-class',
- },
- showPasswordText: 'Show password',
+ id: 'example-password',
+ name: 'example-password-name',
+ label: {
+ text: 'Label text',
+ description: 'Description text',
+ classes: 'extra-label-class',
+ },
+ showPasswordText: 'Show password',
};
const EXAMPLE_PASSWORD = {
- ...EXAMPLE_PASSWORD_MINIMAL,
- fieldId: 'example-password-field',
- fieldClasses: 'extra-field-class',
+ ...EXAMPLE_PASSWORD_MINIMAL,
+ fieldId: 'example-password-field',
+ fieldClasses: 'extra-field-class',
};
const EXAMPLE_PASSWORD_WITH_ERROR = {
- ...EXAMPLE_PASSWORD,
- error: {
- id: 'example-error',
- text: 'Error text...',
- },
+ ...EXAMPLE_PASSWORD,
+ error: {
+ id: 'example-error',
+ text: 'Error text...',
+ },
};
describe('macro: password', () => {
- it('passes jest-axe checks', async () => {
- const $ = cheerio.load(renderComponent('password', EXAMPLE_PASSWORD));
+ it('passes jest-axe checks', async () => {
+ const $ = cheerio.load(renderComponent('password', EXAMPLE_PASSWORD));
- const results = await axe($.html());
- expect(results).toHaveNoViolations();
- });
+ const results = await axe($.html());
+ expect(results).toHaveNoViolations();
+ });
- it('passes jest-axe checks when error is shown', async () => {
- const $ = cheerio.load(renderComponent('password', EXAMPLE_PASSWORD_WITH_ERROR));
+ it('passes jest-axe checks when error is shown', async () => {
+ const $ = cheerio.load(renderComponent('password', EXAMPLE_PASSWORD_WITH_ERROR));
- const results = await axe($.html());
- expect(results).toHaveNoViolations();
- });
+ const results = await axe($.html());
+ expect(results).toHaveNoViolations();
+ });
- it('provides expected parameters to the inner `field` component', () => {
- const faker = templateFaker();
- const fieldSpy = faker.spy('field');
+ it('provides expected parameters to the inner `field` component', () => {
+ const faker = templateFaker();
+ const fieldSpy = faker.spy('field');
- cheerio.load(faker.renderComponent('password', EXAMPLE_PASSWORD));
+ cheerio.load(faker.renderComponent('password', EXAMPLE_PASSWORD));
- expect(fieldSpy.occurrences[0]).toEqual({
- id: 'example-password-field',
- classes: 'ons-js-password extra-field-class',
- error: undefined,
+ expect(fieldSpy.occurrences[0]).toEqual({
+ id: 'example-password-field',
+ classes: 'ons-js-password extra-field-class',
+ error: undefined,
+ });
});
- });
- it('provides expected parameters to the inner `field` component when there is an error', () => {
- const faker = templateFaker();
- const fieldSpy = faker.spy('field');
+ it('provides expected parameters to the inner `field` component when there is an error', () => {
+ const faker = templateFaker();
+ const fieldSpy = faker.spy('field');
- cheerio.load(faker.renderComponent('password', EXAMPLE_PASSWORD_WITH_ERROR));
+ cheerio.load(faker.renderComponent('password', EXAMPLE_PASSWORD_WITH_ERROR));
- expect(fieldSpy.occurrences[0]).toEqual({
- id: 'example-password-field',
- classes: 'ons-js-password extra-field-class',
- error: {
- id: 'example-error',
- text: 'Error text...',
- },
+ expect(fieldSpy.occurrences[0]).toEqual({
+ id: 'example-password-field',
+ classes: 'ons-js-password extra-field-class',
+ error: {
+ id: 'example-error',
+ text: 'Error text...',
+ },
+ });
});
- });
- it('provides expected parameters to the inner `label` component', () => {
- const faker = templateFaker();
- const labelSpy = faker.spy('label');
+ it('provides expected parameters to the inner `label` component', () => {
+ const faker = templateFaker();
+ const labelSpy = faker.spy('label');
- cheerio.load(faker.renderComponent('password', EXAMPLE_PASSWORD));
+ cheerio.load(faker.renderComponent('password', EXAMPLE_PASSWORD));
- expect(labelSpy.occurrences[0]).toEqual({
- for: 'example-password',
- text: 'Label text',
- description: 'Description text',
- classes: 'extra-label-class',
+ expect(labelSpy.occurrences[0]).toEqual({
+ for: 'example-password',
+ text: 'Label text',
+ description: 'Description text',
+ classes: 'extra-label-class',
+ });
});
- });
-
- it('provides expected parameters to the inner `checkbox` component', () => {
- const faker = templateFaker();
- const checkboxSpy = faker.spy('checkboxes/checkbox');
-
- cheerio.load(faker.renderComponent('password', EXAMPLE_PASSWORD));
-
- expect(checkboxSpy.occurrences[0]).toEqual({
- id: 'example-password-toggle',
- classes: 'ons-js-password-toggle-wrap ons-checkbox--toggle ons-u-d-no',
- inputClasses: 'ons-js-password-toggle',
- name: 'show-password',
- label: {
- text: 'Show password',
- },
+
+ it('provides expected parameters to the inner `checkbox` component', () => {
+ const faker = templateFaker();
+ const checkboxSpy = faker.spy('checkboxes/checkbox');
+
+ cheerio.load(faker.renderComponent('password', EXAMPLE_PASSWORD));
+
+ expect(checkboxSpy.occurrences[0]).toEqual({
+ id: 'example-password-toggle',
+ classes: 'ons-js-password-toggle-wrap ons-checkbox--toggle ons-u-d-no',
+ inputClasses: 'ons-js-password-toggle',
+ name: 'show-password',
+ label: {
+ text: 'Show password',
+ },
+ });
});
- });
- it('provides expected parameters to the inner `input` component', () => {
- const faker = templateFaker();
- const inputSpy = faker.spy('input');
+ it('provides expected parameters to the inner `input` component', () => {
+ const faker = templateFaker();
+ const inputSpy = faker.spy('input');
- cheerio.load(faker.renderComponent('password', EXAMPLE_PASSWORD));
+ cheerio.load(faker.renderComponent('password', EXAMPLE_PASSWORD));
- expect(inputSpy.occurrences[0]).toEqual({
- id: 'example-password',
- name: 'example-password-name',
- type: 'password',
- classes: 'ons-u-mt-xs ons-js-password-input',
- dontWrap: true,
+ expect(inputSpy.occurrences[0]).toEqual({
+ id: 'example-password',
+ name: 'example-password-name',
+ type: 'password',
+ classes: 'ons-u-mt-xs ons-js-password-input',
+ dontWrap: true,
+ });
});
- });
- it('provides expected parameters to the inner `input` component when error is shown', () => {
- const faker = templateFaker();
- const inputSpy = faker.spy('input');
+ it('provides expected parameters to the inner `input` component when error is shown', () => {
+ const faker = templateFaker();
+ const inputSpy = faker.spy('input');
- cheerio.load(faker.renderComponent('password', EXAMPLE_PASSWORD_WITH_ERROR));
+ cheerio.load(faker.renderComponent('password', EXAMPLE_PASSWORD_WITH_ERROR));
- expect(inputSpy.occurrences[0]).toEqual({
- id: 'example-password',
- name: 'example-password-name',
- type: 'password',
- classes: 'ons-u-mt-xs ons-js-password-input ons-input--error',
- dontWrap: true,
+ expect(inputSpy.occurrences[0]).toEqual({
+ id: 'example-password',
+ name: 'example-password-name',
+ type: 'password',
+ classes: 'ons-u-mt-xs ons-js-password-input ons-input--error',
+ dontWrap: true,
+ });
});
- });
});
diff --git a/src/components/password/password.dom.js b/src/components/password/password.dom.js
index 0010baf232..085ae4acd7 100644
--- a/src/components/password/password.dom.js
+++ b/src/components/password/password.dom.js
@@ -1,13 +1,13 @@
import domready from '../../js/domready';
async function initialisePasswords() {
- const passwordFields = [...document.querySelectorAll('.ons-js-password')];
+ const passwordFields = [...document.querySelectorAll('.ons-js-password')];
- if (passwordFields.length) {
- const Password = (await import('./password')).default;
+ if (passwordFields.length) {
+ const Password = (await import('./password')).default;
- passwordFields.forEach((field) => new Password(field));
- }
+ passwordFields.forEach((field) => new Password(field));
+ }
}
domready(initialisePasswords);
diff --git a/src/components/password/password.js b/src/components/password/password.js
index d5b09227e8..8ddde761d4 100644
--- a/src/components/password/password.js
+++ b/src/components/password/password.js
@@ -3,16 +3,16 @@ const classToggle = 'ons-js-password-toggle';
const classInput = 'ons-js-password-input';
export default class Password {
- constructor(context) {
- this.toggleWrap = context.querySelector(`.${classToggleWrap}`);
- this.toggle = this.toggleWrap.querySelector(`.${classToggle}`);
- this.input = context.querySelector(`.${classInput}`);
+ constructor(context) {
+ this.toggleWrap = context.querySelector(`.${classToggleWrap}`);
+ this.toggle = this.toggleWrap.querySelector(`.${classToggle}`);
+ this.input = context.querySelector(`.${classInput}`);
- this.toggle.addEventListener('change', this.handleToggleChange.bind(this));
- this.toggleWrap.classList.remove('ons-u-d-no');
- }
+ this.toggle.addEventListener('change', this.handleToggleChange.bind(this));
+ this.toggleWrap.classList.remove('ons-u-d-no');
+ }
- handleToggleChange() {
- this.input.type = this.toggle.checked ? 'text' : 'password';
- }
+ handleToggleChange() {
+ this.input.type = this.toggle.checked ? 'text' : 'password';
+ }
}
diff --git a/src/components/password/password.spec.js b/src/components/password/password.spec.js
index fa0a2d037a..f93994742c 100644
--- a/src/components/password/password.spec.js
+++ b/src/components/password/password.spec.js
@@ -1,40 +1,40 @@
import { renderComponent, setTestPage } from '../../tests/helpers/rendering';
const EXAMPLE_PASSWORD_MINIMAL = {
- id: 'example-password',
- name: 'example-password-name',
- label: {
- text: 'Label text',
- description: 'Description text',
- classes: 'extra-label-class',
- },
- showPasswordText: 'Show password',
+ id: 'example-password',
+ name: 'example-password-name',
+ label: {
+ text: 'Label text',
+ description: 'Description text',
+ classes: 'extra-label-class',
+ },
+ showPasswordText: 'Show password',
};
describe('script: password', () => {
- it('has input of type `password` initially', async () => {
- await setTestPage('/test', renderComponent('password', EXAMPLE_PASSWORD_MINIMAL));
+ it('has input of type `password` initially', async () => {
+ await setTestPage('/test', renderComponent('password', EXAMPLE_PASSWORD_MINIMAL));
- const inputType = await page.evaluate(() => document.querySelector('#example-password').type);
- expect(inputType).toBe('password');
- });
+ const inputType = await page.evaluate(() => document.querySelector('#example-password').type);
+ expect(inputType).toBe('password');
+ });
- it('has input of type `text` when "Show password" toggle is clicked', async () => {
- await setTestPage('/test', renderComponent('password', EXAMPLE_PASSWORD_MINIMAL));
+ it('has input of type `text` when "Show password" toggle is clicked', async () => {
+ await setTestPage('/test', renderComponent('password', EXAMPLE_PASSWORD_MINIMAL));
- await page.click('#example-password-toggle');
+ await page.click('#example-password-toggle');
- const inputType = await page.evaluate(() => document.querySelector('#example-password').type);
- expect(inputType).toBe('text');
- });
+ const inputType = await page.evaluate(() => document.querySelector('#example-password').type);
+ expect(inputType).toBe('text');
+ });
- it('has input of type `password` when "Show password" toggle is clicked twice', async () => {
- await setTestPage('/test', renderComponent('password', EXAMPLE_PASSWORD_MINIMAL));
+ it('has input of type `password` when "Show password" toggle is clicked twice', async () => {
+ await setTestPage('/test', renderComponent('password', EXAMPLE_PASSWORD_MINIMAL));
- await page.click('#example-password-toggle');
- await page.click('#example-password-toggle');
+ await page.click('#example-password-toggle');
+ await page.click('#example-password-toggle');
- const inputType = await page.evaluate(() => document.querySelector('#example-password').type);
- expect(inputType).toBe('password');
- });
+ const inputType = await page.evaluate(() => document.querySelector('#example-password').type);
+ expect(inputType).toBe('password');
+ });
});
diff --git a/src/components/phase-banner/_macro.spec.js b/src/components/phase-banner/_macro.spec.js
index 1ae226d861..afac8f8512 100644
--- a/src/components/phase-banner/_macro.spec.js
+++ b/src/components/phase-banner/_macro.spec.js
@@ -6,93 +6,93 @@ import axe from '../../tests/helpers/axe';
import { renderComponent } from '../../tests/helpers/rendering';
const EXAMPLE_PHASE_BANNER_MINIMAL = {
- html: 'Example content with a
link ',
+ html: 'Example content with a
link ',
};
describe('macro: phase-banner', () => {
- it('passes jest-axe checks with', async () => {
- const $ = cheerio.load(renderComponent('phase-banner', EXAMPLE_PHASE_BANNER_MINIMAL));
-
- const results = await axe($.html());
- expect(results).toHaveNoViolations();
- });
-
- it('has expected html content', () => {
- const $ = cheerio.load(renderComponent('phase-banner', EXAMPLE_PHASE_BANNER_MINIMAL));
-
- const htmlContent = $('.ons-phase-banner__desc').html().trim();
- expect(htmlContent).toBe('Example content with a
link ');
- });
-
- it('has the "Beta" badge by default', () => {
- const $ = cheerio.load(renderComponent('phase-banner', EXAMPLE_PHASE_BANNER_MINIMAL));
-
- const badgeText = $('.ons-phase-banner__badge').text().trim();
- expect(badgeText).toBe('Beta');
- });
-
- it('has the provided `badge` text', () => {
- const $ = cheerio.load(
- renderComponent('phase-banner', {
- ...EXAMPLE_PHASE_BANNER_MINIMAL,
- badge: 'Alpha',
- }),
- );
-
- const badgeText = $('.ons-phase-banner__badge').text().trim();
- expect(badgeText).toBe('Alpha');
- });
-
- it('has no badge when `hideBadge` is true', () => {
- const $ = cheerio.load(
- renderComponent('phase-banner', {
- ...EXAMPLE_PHASE_BANNER_MINIMAL,
- hideBadge: true,
- }),
- );
-
- expect($('.ons-phase-banner__badge').length).toBe(0);
- });
-
- it('has `container--wide` class when `wide` is true', () => {
- const $ = cheerio.load(
- renderComponent('phase-banner', {
- ...EXAMPLE_PHASE_BANNER_MINIMAL,
- wide: true,
- }),
- );
-
- expect($('.ons-container').hasClass('ons-container--wide')).toBe(true);
- });
-
- it('does not have `container--wide` class when `wide` is not set', () => {
- const $ = cheerio.load(
- renderComponent('phase-banner', {
- ...EXAMPLE_PHASE_BANNER_MINIMAL,
- }),
- );
-
- expect($('.ons-container').hasClass('ons-container--wide')).toBe(false);
- });
-
- it('has `container--full-width` class when `fullWidth` is true', () => {
- const $ = cheerio.load(
- renderComponent('phase-banner', {
- ...EXAMPLE_PHASE_BANNER_MINIMAL,
- fullWidth: true,
- }),
- );
-
- expect($('.ons-container').hasClass('ons-container--full-width')).toBe(true);
- });
-
- it('does not have `container--full-width` class when `fullWidth` is not set', () => {
- const $ = cheerio.load(
- renderComponent('phase-banner', {
- ...EXAMPLE_PHASE_BANNER_MINIMAL,
- }),
- );
-
- expect($('.ons-container').hasClass('ons-container--full-width')).toBe(false);
- });
+ it('passes jest-axe checks with', async () => {
+ const $ = cheerio.load(renderComponent('phase-banner', EXAMPLE_PHASE_BANNER_MINIMAL));
+
+ const results = await axe($.html());
+ expect(results).toHaveNoViolations();
+ });
+
+ it('has expected html content', () => {
+ const $ = cheerio.load(renderComponent('phase-banner', EXAMPLE_PHASE_BANNER_MINIMAL));
+
+ const htmlContent = $('.ons-phase-banner__desc').html().trim();
+ expect(htmlContent).toBe('Example content with a
link ');
+ });
+
+ it('has the "Beta" badge by default', () => {
+ const $ = cheerio.load(renderComponent('phase-banner', EXAMPLE_PHASE_BANNER_MINIMAL));
+
+ const badgeText = $('.ons-phase-banner__badge').text().trim();
+ expect(badgeText).toBe('Beta');
+ });
+
+ it('has the provided `badge` text', () => {
+ const $ = cheerio.load(
+ renderComponent('phase-banner', {
+ ...EXAMPLE_PHASE_BANNER_MINIMAL,
+ badge: 'Alpha',
+ }),
+ );
+
+ const badgeText = $('.ons-phase-banner__badge').text().trim();
+ expect(badgeText).toBe('Alpha');
+ });
+
+ it('has no badge when `hideBadge` is true', () => {
+ const $ = cheerio.load(
+ renderComponent('phase-banner', {
+ ...EXAMPLE_PHASE_BANNER_MINIMAL,
+ hideBadge: true,
+ }),
+ );
+
+ expect($('.ons-phase-banner__badge').length).toBe(0);
+ });
+
+ it('has `container--wide` class when `wide` is true', () => {
+ const $ = cheerio.load(
+ renderComponent('phase-banner', {
+ ...EXAMPLE_PHASE_BANNER_MINIMAL,
+ wide: true,
+ }),
+ );
+
+ expect($('.ons-container').hasClass('ons-container--wide')).toBe(true);
+ });
+
+ it('does not have `container--wide` class when `wide` is not set', () => {
+ const $ = cheerio.load(
+ renderComponent('phase-banner', {
+ ...EXAMPLE_PHASE_BANNER_MINIMAL,
+ }),
+ );
+
+ expect($('.ons-container').hasClass('ons-container--wide')).toBe(false);
+ });
+
+ it('has `container--full-width` class when `fullWidth` is true', () => {
+ const $ = cheerio.load(
+ renderComponent('phase-banner', {
+ ...EXAMPLE_PHASE_BANNER_MINIMAL,
+ fullWidth: true,
+ }),
+ );
+
+ expect($('.ons-container').hasClass('ons-container--full-width')).toBe(true);
+ });
+
+ it('does not have `container--full-width` class when `fullWidth` is not set', () => {
+ const $ = cheerio.load(
+ renderComponent('phase-banner', {
+ ...EXAMPLE_PHASE_BANNER_MINIMAL,
+ }),
+ );
+
+ expect($('.ons-container').hasClass('ons-container--full-width')).toBe(false);
+ });
});
diff --git a/src/components/phase-banner/_phase-banner.scss b/src/components/phase-banner/_phase-banner.scss
index 4d3442a079..5e999c0d0e 100644
--- a/src/components/phase-banner/_phase-banner.scss
+++ b/src/components/phase-banner/_phase-banner.scss
@@ -1,20 +1,20 @@
.ons-phase-banner {
- background: var(--ons-color-white);
- border-bottom: 1px solid var(--ons-color-borders);
- padding: 0.5rem 0;
+ background: var(--ons-color-white);
+ border-bottom: 1px solid var(--ons-color-borders);
+ padding: 0.5rem 0;
- &__badge {
- background: var(--ons-color-black);
- color: var(--ons-color-white);
- font-size: 0.85rem;
- line-height: 1em;
- margin: 0 0.5rem 0 0;
- outline: 2px solid transparent; // Add transparent outline because Windows High Contrast Mode doesn't show background
- padding: 0.4rem;
- text-transform: uppercase;
- }
+ &__badge {
+ background: var(--ons-color-black);
+ color: var(--ons-color-white);
+ font-size: 0.85rem;
+ line-height: 1em;
+ margin: 0 0.5rem 0 0;
+ outline: 2px solid transparent; // Add transparent outline because Windows High Contrast Mode doesn't show background
+ padding: 0.4rem;
+ text-transform: uppercase;
+ }
- &__desc {
- margin-top: 0.3rem;
- }
+ &__desc {
+ margin-top: 0.3rem;
+ }
}
diff --git a/src/components/question/_macro.spec.js b/src/components/question/_macro.spec.js
index 5cedd3c281..d23a7b2b72 100644
--- a/src/components/question/_macro.spec.js
+++ b/src/components/question/_macro.spec.js
@@ -6,318 +6,318 @@ import axe from '../../tests/helpers/axe';
import { renderComponent, templateFaker } from '../../tests/helpers/rendering';
const EXAMPLE_QUESTION_BASIC = {
- id: 'example-question',
- title: 'Question title',
- description: 'Question description',
+ id: 'example-question',
+ title: 'Question title',
+ description: 'Question description',
};
const EXAMPLE_QUESTION_WARNING = {
- ...EXAMPLE_QUESTION_BASIC,
- warning: {
- id: 'warning-id',
- body: 'Warning content',
- },
+ ...EXAMPLE_QUESTION_BASIC,
+ warning: {
+ id: 'warning-id',
+ body: 'Warning content',
+ },
};
const EXAMPLE_QUESTION_DEFINITION = {
- ...EXAMPLE_QUESTION_BASIC,
- definition: {
- id: 'definition-id',
- title: 'Definition title',
- content: '
Definition content
',
- },
+ ...EXAMPLE_QUESTION_BASIC,
+ definition: {
+ id: 'definition-id',
+ title: 'Definition title',
+ content: '
Definition content
',
+ },
};
const EXAMPLE_QUESTION_GUIDANCE = {
- ...EXAMPLE_QUESTION_BASIC,
- guidance: {
- content: '
Guidance content ',
- lists: [
- {
- listHeading: 'List heading 1',
- listLeadingLine: 'List leading line 1',
- itemsList: [{ text: 'Test item 1' }, { text: 'Test item 2' }],
- },
- ],
- },
+ ...EXAMPLE_QUESTION_BASIC,
+ guidance: {
+ content: '
Guidance content ',
+ lists: [
+ {
+ listHeading: 'List heading 1',
+ listLeadingLine: 'List leading line 1',
+ itemsList: [{ text: 'Test item 1' }, { text: 'Test item 2' }],
+ },
+ ],
+ },
};
const EXAMPLE_QUESTION_JUSTIFICATION = {
- ...EXAMPLE_QUESTION_BASIC,
- justification: {
- id: 'justification-id',
- title: 'Justification title',
- content: '
Justification content
',
- },
+ ...EXAMPLE_QUESTION_BASIC,
+ justification: {
+ id: 'justification-id',
+ title: 'Justification title',
+ content: '
Justification content
',
+ },
};
const EXAMPLE_QUESTION_BUTTON = {
- ...EXAMPLE_QUESTION_BASIC,
- submitButton: {
- id: 'button-id',
- variants: 'timer',
- text: 'Button text',
- },
+ ...EXAMPLE_QUESTION_BASIC,
+ submitButton: {
+ id: 'button-id',
+ variants: 'timer',
+ text: 'Button text',
+ },
};
const EXAMPLE_QUESTION_INSTRUCTION = {
- ...EXAMPLE_QUESTION_BASIC,
- instruction: 'Instruction text',
+ ...EXAMPLE_QUESTION_BASIC,
+ instruction: 'Instruction text',
};
const EXAMPLE_QUESTION_LEGENDISQUESTIONTITLE = {
- ...EXAMPLE_QUESTION_BASIC,
- legendIsQuestionTitle: true,
+ ...EXAMPLE_QUESTION_BASIC,
+ legendIsQuestionTitle: true,
};
const EXAMPLE_QUESTION_DESCRIPTION_FIRST = {
- ...EXAMPLE_QUESTION_BASIC,
- readDescriptionFirst: true,
+ ...EXAMPLE_QUESTION_BASIC,
+ readDescriptionFirst: true,
};
describe('macro: question', () => {
- describe.each([
- ['with basic parameters', EXAMPLE_QUESTION_BASIC],
- ['with warning', EXAMPLE_QUESTION_WARNING],
- ['with definition', EXAMPLE_QUESTION_DEFINITION],
- ['with guidance', EXAMPLE_QUESTION_GUIDANCE],
- ['with justification', EXAMPLE_QUESTION_JUSTIFICATION],
- ['with button', EXAMPLE_QUESTION_BUTTON],
- ['with instruction', EXAMPLE_QUESTION_INSTRUCTION],
- ['with `legendIsQuestionTitle`', EXAMPLE_QUESTION_LEGENDISQUESTIONTITLE],
- ['with `readDescriptionFirst`', EXAMPLE_QUESTION_DESCRIPTION_FIRST],
- [
- 'with all options combined',
- {
- ...EXAMPLE_QUESTION_WARNING,
- ...EXAMPLE_QUESTION_DEFINITION,
- ...EXAMPLE_QUESTION_GUIDANCE,
- ...EXAMPLE_QUESTION_JUSTIFICATION,
- ...EXAMPLE_QUESTION_BUTTON,
- ...EXAMPLE_QUESTION_INSTRUCTION,
- ...EXAMPLE_QUESTION_LEGENDISQUESTIONTITLE,
- ...EXAMPLE_QUESTION_DESCRIPTION_FIRST,
- },
- ],
- ])('mode: %s', (_, params) => {
- it('passes jest-axe checks with', async () => {
- const $ = cheerio.load(renderComponent('question', params));
-
- const results = await axe($.html());
- expect(results).toHaveNoViolations();
+ describe.each([
+ ['with basic parameters', EXAMPLE_QUESTION_BASIC],
+ ['with warning', EXAMPLE_QUESTION_WARNING],
+ ['with definition', EXAMPLE_QUESTION_DEFINITION],
+ ['with guidance', EXAMPLE_QUESTION_GUIDANCE],
+ ['with justification', EXAMPLE_QUESTION_JUSTIFICATION],
+ ['with button', EXAMPLE_QUESTION_BUTTON],
+ ['with instruction', EXAMPLE_QUESTION_INSTRUCTION],
+ ['with `legendIsQuestionTitle`', EXAMPLE_QUESTION_LEGENDISQUESTIONTITLE],
+ ['with `readDescriptionFirst`', EXAMPLE_QUESTION_DESCRIPTION_FIRST],
+ [
+ 'with all options combined',
+ {
+ ...EXAMPLE_QUESTION_WARNING,
+ ...EXAMPLE_QUESTION_DEFINITION,
+ ...EXAMPLE_QUESTION_GUIDANCE,
+ ...EXAMPLE_QUESTION_JUSTIFICATION,
+ ...EXAMPLE_QUESTION_BUTTON,
+ ...EXAMPLE_QUESTION_INSTRUCTION,
+ ...EXAMPLE_QUESTION_LEGENDISQUESTIONTITLE,
+ ...EXAMPLE_QUESTION_DESCRIPTION_FIRST,
+ },
+ ],
+ ])('mode: %s', (_, params) => {
+ it('passes jest-axe checks with', async () => {
+ const $ = cheerio.load(renderComponent('question', params));
+
+ const results = await axe($.html());
+ expect(results).toHaveNoViolations();
+ });
});
- });
- describe('mode: with basic parameters', () => {
- it('has the `title` text', () => {
- const $ = cheerio.load(renderComponent('question', EXAMPLE_QUESTION_BASIC));
-
- expect($('.ons-question__title').text().trim()).toBe('Question title');
+ describe('mode: with basic parameters', () => {
+ it('has the `title` text', () => {
+ const $ = cheerio.load(renderComponent('question', EXAMPLE_QUESTION_BASIC));
+
+ expect($('.ons-question__title').text().trim()).toBe('Question title');
+ });
+
+ it('has the provided `id` attribute', () => {
+ const $ = cheerio.load(renderComponent('question', EXAMPLE_QUESTION_BASIC));
+
+ expect($('.ons-question').attr('id')).toBe('example-question');
+ });
+
+ it('has additionally provided `attributes`', () => {
+ const $ = cheerio.load(
+ renderComponent('question', {
+ ...EXAMPLE_QUESTION_BASIC,
+ attributes: {
+ a: 123,
+ b: 456,
+ },
+ }),
+ );
+
+ expect($('.ons-question').attr('a')).toBe('123');
+ expect($('.ons-question').attr('b')).toBe('456');
+ });
+
+ it('has additionally provided style classes', () => {
+ const $ = cheerio.load(
+ renderComponent('question', {
+ ...EXAMPLE_QUESTION_BASIC,
+ classes: 'extra-class another-extra-class',
+ }),
+ );
+
+ expect($('.ons-question').hasClass('extra-class')).toBe(true);
+ expect($('.ons-question').hasClass('another-extra-class')).toBe(true);
+ });
+
+ it('has the `description` text', () => {
+ const $ = cheerio.load(renderComponent('question', EXAMPLE_QUESTION_BASIC));
+
+ expect($('.ons-question__description').text().trim()).toBe('Question description');
+ });
+
+ it('calls with content', () => {
+ const $ = cheerio.load(renderComponent('question', { EXAMPLE_QUESTION_BASIC }, 'Example content...'));
+
+ const content = $('.ons-question__answer').text().trim();
+ expect(content).toEqual(expect.stringContaining('Example content...'));
+ });
});
- it('has the provided `id` attribute', () => {
- const $ = cheerio.load(renderComponent('question', EXAMPLE_QUESTION_BASIC));
+ describe('mode: with warning', () => {
+ it('outputs the expected panel', () => {
+ const faker = templateFaker();
+ const panelSpy = faker.spy('panel');
- expect($('.ons-question').attr('id')).toBe('example-question');
- });
+ faker.renderComponent('question', EXAMPLE_QUESTION_WARNING);
- it('has additionally provided `attributes`', () => {
- const $ = cheerio.load(
- renderComponent('question', {
- ...EXAMPLE_QUESTION_BASIC,
- attributes: {
- a: 123,
- b: 456,
- },
- }),
- );
-
- expect($('.ons-question').attr('a')).toBe('123');
- expect($('.ons-question').attr('b')).toBe('456');
- });
+ expect(panelSpy.occurrences[0]).toHaveProperty('id', 'warning-id');
+ expect(panelSpy.occurrences[0]).toHaveProperty('variant', 'warn');
+ });
- it('has additionally provided style classes', () => {
- const $ = cheerio.load(
- renderComponent('question', {
- ...EXAMPLE_QUESTION_BASIC,
- classes: 'extra-class another-extra-class',
- }),
- );
+ it('outputs the expected panel content', () => {
+ const $ = cheerio.load(renderComponent('question', EXAMPLE_QUESTION_WARNING));
- expect($('.ons-question').hasClass('extra-class')).toBe(true);
- expect($('.ons-question').hasClass('another-extra-class')).toBe(true);
+ expect($('.ons-panel__body > p').text()).toBe('Warning content');
+ });
});
- it('has the `description` text', () => {
- const $ = cheerio.load(renderComponent('question', EXAMPLE_QUESTION_BASIC));
+ describe('mode: with definition', () => {
+ it('outputs the expected details', () => {
+ const faker = templateFaker();
+ const detailsSpy = faker.spy('details');
- expect($('.ons-question__description').text().trim()).toBe('Question description');
- });
-
- it('calls with content', () => {
- const $ = cheerio.load(renderComponent('question', { EXAMPLE_QUESTION_BASIC }, 'Example content...'));
+ faker.renderComponent('question', EXAMPLE_QUESTION_DEFINITION);
- const content = $('.ons-question__answer').text().trim();
- expect(content).toEqual(expect.stringContaining('Example content...'));
- });
- });
+ expect(detailsSpy.occurrences[0]).toHaveProperty('classes', 'ons-u-mb-m');
+ expect(detailsSpy.occurrences[0]).toHaveProperty('id', 'definition-id');
+ expect(detailsSpy.occurrences[0]).toHaveProperty('title', 'Definition title');
+ });
- describe('mode: with warning', () => {
- it('outputs the expected panel', () => {
- const faker = templateFaker();
- const panelSpy = faker.spy('panel');
+ it('outputs the expected details call content', () => {
+ const $ = cheerio.load(renderComponent('question', EXAMPLE_QUESTION_DEFINITION));
- faker.renderComponent('question', EXAMPLE_QUESTION_WARNING);
-
- expect(panelSpy.occurrences[0]).toHaveProperty('id', 'warning-id');
- expect(panelSpy.occurrences[0]).toHaveProperty('variant', 'warn');
+ expect($('.ons-details__content > p').text()).toBe('Definition content');
+ });
});
- it('outputs the expected panel content', () => {
- const $ = cheerio.load(renderComponent('question', EXAMPLE_QUESTION_WARNING));
+ describe('mode: with guidance', () => {
+ it('outputs the expected panel', () => {
+ const faker = templateFaker();
+ const panelSpy = faker.spy('panel');
- expect($('.ons-panel__body > p').text()).toBe('Warning content');
- });
- });
+ faker.renderComponent('question', EXAMPLE_QUESTION_GUIDANCE);
- describe('mode: with definition', () => {
- it('outputs the expected details', () => {
- const faker = templateFaker();
- const detailsSpy = faker.spy('details');
+ expect(panelSpy.occurrences[0]).toHaveProperty('classes', 'ons-question-guidance ons-u-mb-m');
+ });
- faker.renderComponent('question', EXAMPLE_QUESTION_DEFINITION);
+ it('outputs the expected panel call content', () => {
+ const $ = cheerio.load(renderComponent('question', EXAMPLE_QUESTION_GUIDANCE));
- expect(detailsSpy.occurrences[0]).toHaveProperty('classes', 'ons-u-mb-m');
- expect(detailsSpy.occurrences[0]).toHaveProperty('id', 'definition-id');
- expect(detailsSpy.occurrences[0]).toHaveProperty('title', 'Definition title');
- });
+ expect($('.ons-panel__body .fake-content').text()).toBe('Guidance content');
+ });
- it('outputs the expected details call content', () => {
- const $ = cheerio.load(renderComponent('question', EXAMPLE_QUESTION_DEFINITION));
+ it('outputs the expected `listHeading`', () => {
+ const $ = cheerio.load(renderComponent('question', EXAMPLE_QUESTION_GUIDANCE));
- expect($('.ons-details__content > p').text()).toBe('Definition content');
- });
- });
+ expect($('.ons-question-guidance__list-heading').text()).toBe('List heading 1');
+ });
- describe('mode: with guidance', () => {
- it('outputs the expected panel', () => {
- const faker = templateFaker();
- const panelSpy = faker.spy('panel');
+ it('outputs the expected `listLeadingLine`', () => {
+ const $ = cheerio.load(renderComponent('question', EXAMPLE_QUESTION_GUIDANCE));
- faker.renderComponent('question', EXAMPLE_QUESTION_GUIDANCE);
+ expect($('.ons-question-guidance__list-leading-line').text()).toBe('List leading line 1');
+ });
- expect(panelSpy.occurrences[0]).toHaveProperty('classes', 'ons-question-guidance ons-u-mb-m');
- });
+ it('outputs the expected list', () => {
+ const faker = templateFaker();
+ const listSpy = faker.spy('list');
- it('outputs the expected panel call content', () => {
- const $ = cheerio.load(renderComponent('question', EXAMPLE_QUESTION_GUIDANCE));
+ faker.renderComponent('question', EXAMPLE_QUESTION_GUIDANCE);
- expect($('.ons-panel__body .fake-content').text()).toBe('Guidance content');
+ expect(listSpy.occurrences[0].itemsList).toEqual([
+ {
+ text: 'Test item 1',
+ },
+ {
+ text: 'Test item 2',
+ },
+ ]);
+ });
});
- it('outputs the expected `listHeading`', () => {
- const $ = cheerio.load(renderComponent('question', EXAMPLE_QUESTION_GUIDANCE));
+ describe('mode: with justification', () => {
+ it('outputs the expected details', () => {
+ const faker = templateFaker();
+ const detailsSpy = faker.spy('details');
- expect($('.ons-question-guidance__list-heading').text()).toBe('List heading 1');
- });
-
- it('outputs the expected `listLeadingLine`', () => {
- const $ = cheerio.load(renderComponent('question', EXAMPLE_QUESTION_GUIDANCE));
+ faker.renderComponent('question', EXAMPLE_QUESTION_JUSTIFICATION);
- expect($('.ons-question-guidance__list-leading-line').text()).toBe('List leading line 1');
- });
+ expect(detailsSpy.occurrences[0]).toHaveProperty('classes', 'ons-u-mb-m');
+ expect(detailsSpy.occurrences[0]).toHaveProperty('id', 'justification-id');
+ expect(detailsSpy.occurrences[0]).toHaveProperty('title', 'Justification title');
+ });
- it('outputs the expected list', () => {
- const faker = templateFaker();
- const listSpy = faker.spy('list');
+ it('outputs the expected details call content', () => {
+ const $ = cheerio.load(renderComponent('question', EXAMPLE_QUESTION_JUSTIFICATION));
- faker.renderComponent('question', EXAMPLE_QUESTION_GUIDANCE);
-
- expect(listSpy.occurrences[0].itemsList).toEqual([
- {
- text: 'Test item 1',
- },
- {
- text: 'Test item 2',
- },
- ]);
+ expect($('.ons-details__content > p').text()).toBe('Justification content');
+ });
});
- });
- describe('mode: with justification', () => {
- it('outputs the expected details', () => {
- const faker = templateFaker();
- const detailsSpy = faker.spy('details');
+ describe('mode: with button', () => {
+ it('outputs the expected button', () => {
+ const faker = templateFaker();
+ const buttonSpy = faker.spy('button');
- faker.renderComponent('question', EXAMPLE_QUESTION_JUSTIFICATION);
+ faker.renderComponent('question', EXAMPLE_QUESTION_BUTTON);
- expect(detailsSpy.occurrences[0]).toHaveProperty('classes', 'ons-u-mb-m');
- expect(detailsSpy.occurrences[0]).toHaveProperty('id', 'justification-id');
- expect(detailsSpy.occurrences[0]).toHaveProperty('title', 'Justification title');
+ expect(buttonSpy.occurrences[0]).toHaveProperty('variants', 'timer');
+ expect(buttonSpy.occurrences[0]).toHaveProperty('id', 'button-id');
+ expect(buttonSpy.occurrences[0]).toHaveProperty('text', 'Button text');
+ });
});
- it('outputs the expected details call content', () => {
- const $ = cheerio.load(renderComponent('question', EXAMPLE_QUESTION_JUSTIFICATION));
+ describe('mode: with instruction', () => {
+ it('has the instruction text', () => {
+ const $ = cheerio.load(renderComponent('question', EXAMPLE_QUESTION_INSTRUCTION));
- expect($('.ons-details__content > p').text()).toBe('Justification content');
+ expect($('.ons-question__instruction').text()).toBe('Instruction text');
+ });
});
- });
-
- describe('mode: with button', () => {
- it('outputs the expected button', () => {
- const faker = templateFaker();
- const buttonSpy = faker.spy('button');
- faker.renderComponent('question', EXAMPLE_QUESTION_BUTTON);
+ describe('mode: with `legendIsQuestionTitle`', () => {
+ it('has the expected `fieldset` output', () => {
+ const faker = templateFaker();
+ const fieldsetSpy = faker.spy('fieldset');
- expect(buttonSpy.occurrences[0]).toHaveProperty('variants', 'timer');
- expect(buttonSpy.occurrences[0]).toHaveProperty('id', 'button-id');
- expect(buttonSpy.occurrences[0]).toHaveProperty('text', 'Button text');
- });
- });
+ faker.renderComponent('question', EXAMPLE_QUESTION_LEGENDISQUESTIONTITLE);
- describe('mode: with instruction', () => {
- it('has the instruction text', () => {
- const $ = cheerio.load(renderComponent('question', EXAMPLE_QUESTION_INSTRUCTION));
+ expect(fieldsetSpy.occurrences[0]).toEqual({
+ legendIsQuestionTitle: true,
+ legend: 'Question title',
+ description: 'Question description',
+ legendClasses: undefined,
+ legendTitleClasses: undefined,
+ });
+ });
- expect($('.ons-question__instruction').text()).toBe('Instruction text');
- });
- });
-
- describe('mode: with `legendIsQuestionTitle`', () => {
- it('has the expected `fieldset` output', () => {
- const faker = templateFaker();
- const fieldsetSpy = faker.spy('fieldset');
-
- faker.renderComponent('question', EXAMPLE_QUESTION_LEGENDISQUESTIONTITLE);
-
- expect(fieldsetSpy.occurrences[0]).toEqual({
- legendIsQuestionTitle: true,
- legend: 'Question title',
- description: 'Question description',
- legendClasses: undefined,
- legendTitleClasses: undefined,
- });
- });
+ it('does not add the description style class', () => {
+ const $ = cheerio.load(renderComponent('question', EXAMPLE_QUESTION_LEGENDISQUESTIONTITLE));
- it('does not add the description style class', () => {
- const $ = cheerio.load(renderComponent('question', EXAMPLE_QUESTION_LEGENDISQUESTIONTITLE));
-
- expect($('.ons-question__description').length).toBe(0);
+ expect($('.ons-question__description').length).toBe(0);
+ });
});
- });
- describe('mode: with `readDescriptionFirst`', () => {
- it('has a description element visually hidden before the title', () => {
- const $ = cheerio.load(renderComponent('question', EXAMPLE_QUESTION_DESCRIPTION_FIRST));
+ describe('mode: with `readDescriptionFirst`', () => {
+ it('has a description element visually hidden before the title', () => {
+ const $ = cheerio.load(renderComponent('question', EXAMPLE_QUESTION_DESCRIPTION_FIRST));
- expect($('.ons-question__title').text().trim()).toBe('Question description Question title');
- });
+ expect($('.ons-question__title').text().trim()).toBe('Question description Question title');
+ });
- it('has the visible description element with aria-hidden attribute', () => {
- const $ = cheerio.load(renderComponent('question', EXAMPLE_QUESTION_DESCRIPTION_FIRST));
+ it('has the visible description element with aria-hidden attribute', () => {
+ const $ = cheerio.load(renderComponent('question', EXAMPLE_QUESTION_DESCRIPTION_FIRST));
- expect($('.ons-question__description--aria-hidden').attr('aria-hidden')).toBe('true');
+ expect($('.ons-question__description--aria-hidden').attr('aria-hidden')).toBe('true');
+ });
});
- });
});
diff --git a/src/components/question/_question.scss b/src/components/question/_question.scss
index cf61ae2765..bd123808e0 100644
--- a/src/components/question/_question.scss
+++ b/src/components/question/_question.scss
@@ -1,34 +1,34 @@
.ons-question {
- margin: 1rem 0 0;
+ margin: 1rem 0 0;
- &__title {
- @extend .ons-u-mb-m;
+ &__title {
+ @extend .ons-u-mb-m;
- mark,
- .ons-instruction {
- background-color: var(--ons-color-instruction);
- color: var(--ons-color-text-inverse);
- margin-right: 0.5rem;
- padding: 0 0.5rem;
- box-shadow: none;
+ mark,
+ .ons-instruction {
+ background-color: var(--ons-color-instruction);
+ color: var(--ons-color-text-inverse);
+ margin-right: 0.5rem;
+ padding: 0 0.5rem;
+ box-shadow: none;
+ }
}
- }
- &__description {
- p:last-of-type {
- margin-bottom: 0 !important;
+ &__description {
+ p:last-of-type {
+ margin-bottom: 0 !important;
+ }
}
- }
- &__instruction {
- background-color: var(--ons-color-instruction-tint);
- border: 5px solid var(--ons-color-instruction);
- display: block;
- font-weight: $font-weight-regular;
- padding: 1rem;
+ &__instruction {
+ background-color: var(--ons-color-instruction-tint);
+ border: 5px solid var(--ons-color-instruction);
+ display: block;
+ font-weight: $font-weight-regular;
+ padding: 1rem;
- p:last-of-type {
- margin-bottom: 0;
+ p:last-of-type {
+ margin-bottom: 0;
+ }
}
- }
}
diff --git a/src/components/quote/_macro.spec.js b/src/components/quote/_macro.spec.js
index f0f0701153..f98f3ac239 100644
--- a/src/components/quote/_macro.spec.js
+++ b/src/components/quote/_macro.spec.js
@@ -6,68 +6,68 @@ import axe from '../../tests/helpers/axe';
import { renderComponent, templateFaker } from '../../tests/helpers/rendering';
describe('macro: quote', () => {
- it('passes jest-axe checks', async () => {
- const $ = cheerio.load(
- renderComponent('quote', {
- text: 'Example quote text.',
- ref: 'Example quote reference.',
- }),
- );
+ it('passes jest-axe checks', async () => {
+ const $ = cheerio.load(
+ renderComponent('quote', {
+ text: 'Example quote text.',
+ ref: 'Example quote reference.',
+ }),
+ );
- const results = await axe($.html());
- expect(results).toHaveNoViolations();
- });
+ const results = await axe($.html());
+ expect(results).toHaveNoViolations();
+ });
- it('has the provided `text` text', () => {
- const $ = cheerio.load(
- renderComponent('quote', {
- text: 'Example quote text.',
- }),
- );
+ it('has the provided `text` text', () => {
+ const $ = cheerio.load(
+ renderComponent('quote', {
+ text: 'Example quote text.',
+ }),
+ );
- expect($('.ons-quote__text').text().trim()).toBe('Example quote text.');
- });
+ expect($('.ons-quote__text').text().trim()).toBe('Example quote text.');
+ });
- it('has the provided `ref` text with a leading "—" character', () => {
- const $ = cheerio.load(
- renderComponent('quote', {
- text: 'Example quote text.',
- ref: 'Example quote reference.',
- }),
- );
+ it('has the provided `ref` text with a leading "—" character', () => {
+ const $ = cheerio.load(
+ renderComponent('quote', {
+ text: 'Example quote text.',
+ ref: 'Example quote reference.',
+ }),
+ );
- expect($('.ons-quote__ref').text().trim()).toBe('— Example quote reference.');
- });
+ expect($('.ons-quote__ref').text().trim()).toBe('— Example quote reference.');
+ });
- it('has a default `textFontSize` of "l"', () => {
- const $ = cheerio.load(
- renderComponent('quote', {
- text: 'Example quote text.',
- }),
- );
+ it('has a default `textFontSize` of "l"', () => {
+ const $ = cheerio.load(
+ renderComponent('quote', {
+ text: 'Example quote text.',
+ }),
+ );
- expect($('.ons-quote__text').hasClass('ons-u-fs-l')).toBe(true);
- });
+ expect($('.ons-quote__text').hasClass('ons-u-fs-l')).toBe(true);
+ });
- it('has the provided `textFontSize`', () => {
- const $ = cheerio.load(
- renderComponent('quote', {
- text: 'Example quote text.',
- textFontSize: 's',
- }),
- );
+ it('has the provided `textFontSize`', () => {
+ const $ = cheerio.load(
+ renderComponent('quote', {
+ text: 'Example quote text.',
+ textFontSize: 's',
+ }),
+ );
- expect($('.ons-quote__text').hasClass('ons-u-fs-s')).toBe(true);
- });
+ expect($('.ons-quote__text').hasClass('ons-u-fs-s')).toBe(true);
+ });
- it('has `quote` icon', () => {
- const faker = templateFaker();
- const iconsSpy = faker.spy('icon');
+ it('has `quote` icon', () => {
+ const faker = templateFaker();
+ const iconsSpy = faker.spy('icon');
- faker.renderComponent('quote', {
- text: 'Example quote text.',
- });
+ faker.renderComponent('quote', {
+ text: 'Example quote text.',
+ });
- expect(iconsSpy.occurrences[0].iconType).toBe('quote');
- });
+ expect(iconsSpy.occurrences[0].iconType).toBe('quote');
+ });
});
diff --git a/src/components/quote/_quote.scss b/src/components/quote/_quote.scss
index 46df6e5584..9adb8a5e2b 100644
--- a/src/components/quote/_quote.scss
+++ b/src/components/quote/_quote.scss
@@ -1,32 +1,32 @@
.ons-quote {
- background-size: 2rem;
- margin: 0 0 1rem;
- padding: 0 0 0 3em;
- position: relative;
+ background-size: 2rem;
+ margin: 0 0 1rem;
+ padding: 0 0 0 3em;
+ position: relative;
- &__text {
- display: block;
- }
+ &__text {
+ display: block;
+ }
- &__ref {
- @extend .ons-u-fs-s;
+ &__ref {
+ @extend .ons-u-fs-s;
- color: var(--ons-color-text-light);
- display: block;
- margin-top: 0.5rem;
- position: relative;
- }
+ color: var(--ons-color-text-light);
+ display: block;
+ margin-top: 0.5rem;
+ position: relative;
+ }
- .ons-icon {
- fill: var(--ons-color-grey-75) !important;
- height: 1.85rem;
- left: 0;
- position: absolute;
- top: -0.125rem;
- width: 1.85rem;
+ .ons-icon {
+ fill: var(--ons-color-grey-75) !important;
+ height: 1.85rem;
+ left: 0;
+ position: absolute;
+ top: -0.125rem;
+ width: 1.85rem;
- @include mq(m) {
- top: 0;
+ @include mq(m) {
+ top: 0;
+ }
}
- }
}
diff --git a/src/components/radios/_macro.spec.js b/src/components/radios/_macro.spec.js
index 8ca494b08b..efd9dbf2d4 100644
--- a/src/components/radios/_macro.spec.js
+++ b/src/components/radios/_macro.spec.js
@@ -6,568 +6,568 @@ import axe from '../../tests/helpers/axe';
import { renderComponent, templateFaker } from '../../tests/helpers/rendering';
const EXAMPLE_RADIOS_MINIMAL = {
- name: 'example-radios-name',
- legend: 'Legend text',
- radios: [],
+ name: 'example-radios-name',
+ legend: 'Legend text',
+ radios: [],
};
const EXAMPLE_RADIO_ITEM = {
- id: 'example-item-id',
- value: 'plain',
- label: {
- classes: 'extra-label-class',
- text: 'Example item',
- description: 'An example description.',
- },
-};
-
-const EXAMPLE_RADIO_ITEM_INPUT = {
- id: 'example-item-input',
- name: 'example-item-input',
- value: 'input',
- label: {
- text: 'Example item with input',
- },
- other: {
- id: 'example-text-input',
- name: 'example-text-input-name',
- type: 'text',
+ id: 'example-item-id',
+ value: 'plain',
label: {
- text: 'Enter your own answer',
+ classes: 'extra-label-class',
+ text: 'Example item',
+ description: 'An example description.',
},
- classes: 'extra-textbox-class',
- width: 42,
- value: '42',
- attributes: { a: 42 },
- },
};
-const EXAMPLE_RADIO_ITEM_SELECT = {
- id: 'example-item-select',
- name: 'example-item-select',
- value: 'select',
- label: {
- text: 'Example item with select',
- },
- other: {
- otherType: 'select',
- id: 'example-select',
- name: 'example-select-name',
+const EXAMPLE_RADIO_ITEM_INPUT = {
+ id: 'example-item-input',
+ name: 'example-item-input',
+ value: 'input',
label: {
- text: 'Enter your own answer',
- },
- classes: 'extra-select-class',
- options: [
- { text: 'First', value: '1' },
- { text: 'Second', value: '2' },
- ],
- value: '1',
- },
-};
-
-const EXAMPLE_RADIO_ITEM_CHECKBOXES = {
- id: 'example-item-checkboxes',
- name: 'example-item-checkboxes',
- value: 'checkboxes',
- label: {
- text: 'Example item with checkboxes',
- },
- other: {
- otherType: 'checkboxes',
- selectAllChildren: true,
- id: 'example-checkboxes',
- name: 'example-checkboxes-name',
- legend: 'Select preferred times of day',
- legendClasses: 'extra-legend-class',
- attributes: { a: 42 },
- checkboxes: [
- {
- value: 'morning',
- id: 'morning',
- label: {
- text: 'Morning',
- },
- },
- {
- value: 'afternoon',
- id: 'afternoon',
- label: {
- text: 'Afternoon',
- },
- },
- ],
- autoSelect: {
- selectAllText: 'Select all',
- unselectAllText: 'Unselect all',
- context: 'checkboxes',
+ text: 'Example item with input',
},
- },
-};
-
-const EXAMPLE_RADIO_ITEM_RADIOS = {
- id: 'example-item-radios',
- name: 'example-item-radios',
- value: 'radios',
- label: {
- text: 'Example item with radios',
- },
- other: {
- otherType: 'radios',
- id: 'example-radios',
- name: 'example-radios-name',
- legend: 'Select preferred times of day',
- legendClasses: 'extra-legend-class',
- attributes: { a: 42 },
- radios: [EXAMPLE_RADIO_ITEM],
- },
-};
-
-describe('macro: radios', () => {
- it.each([
- ['plain', EXAMPLE_RADIO_ITEM],
- ['input', EXAMPLE_RADIO_ITEM_INPUT],
- ['select', EXAMPLE_RADIO_ITEM_SELECT],
- ['checkboxes', EXAMPLE_RADIO_ITEM_CHECKBOXES],
- ['radios', EXAMPLE_RADIO_ITEM_RADIOS],
- ])('passes jest-axe checks with a %s item', async (_, radioItem) => {
- const $ = cheerio.load(
- renderComponent('radios', {
- ...EXAMPLE_RADIOS_MINIMAL,
- radios: [radioItem],
- }),
- );
-
- const results = await axe($.html());
- expect(results).toHaveNoViolations();
- });
-
- it('renders `fieldset` component with the expected parameters', () => {
- const faker = templateFaker();
- const fieldsetSpy = faker.spy('fieldset');
-
- faker.renderComponent('radios', {
- ...EXAMPLE_RADIOS_MINIMAL,
- id: 'example-id',
- classes: 'extra-class',
- legendClasses: 'extra-legend-class',
- description: 'An example description.',
- dontWrap: true,
- legendIsQuestionTitle: false,
- error: {
- id: 'example-error-id',
- text: 'An unexpected error occurred.',
- },
- });
-
- expect(fieldsetSpy.occurrences[0]).toEqual({
- id: 'example-id',
- classes: 'extra-class',
- legend: 'Legend text',
- legendClasses: 'extra-legend-class',
- description: 'An example description.',
- dontWrap: true,
- legendIsQuestionTitle: false,
- error: {
- id: 'example-error-id',
- text: 'An unexpected error occurred.',
- },
- });
- });
-
- describe('radio item', () => {
- it('renders `or` label before last radio item', () => {
- const $ = cheerio.load(
- renderComponent('radios', {
- ...EXAMPLE_RADIOS_MINIMAL,
- radios: [EXAMPLE_RADIO_ITEM, EXAMPLE_RADIO_ITEM_INPUT, EXAMPLE_RADIO_ITEM_SELECT],
- or: 'Or',
- }),
- );
-
- const label = $('.ons-radios__item + br + .ons-radios__item + br + .ons-radios__label');
- expect(label.text().trim()).toBe('Or');
- });
-
- it('is of the "radio" input type', () => {
- const $ = cheerio.load(
- renderComponent('radios', {
- ...EXAMPLE_RADIOS_MINIMAL,
- radios: [EXAMPLE_RADIO_ITEM],
- }),
- );
-
- expect($('.ons-radio__input').attr('type')).toBe('radio');
- });
-
- it('has the provided `id` attribute', () => {
- const $ = cheerio.load(
- renderComponent('radios', {
- ...EXAMPLE_RADIOS_MINIMAL,
- radios: [EXAMPLE_RADIO_ITEM],
- }),
- );
-
- expect($('.ons-radio__input').attr('id')).toBe('example-item-id');
- });
-
- it('has additionally provided attributes', () => {
- const $ = cheerio.load(
- renderComponent('radios', {
- ...EXAMPLE_RADIOS_MINIMAL,
- inputClasses: 'extra-input-class another-extra-input-class',
- radios: [
- {
- ...EXAMPLE_RADIO_ITEM,
- attributes: { a: '123', b: '456' },
- },
- ],
- }),
- );
-
- expect($('.ons-radio__input').attr('a')).toBe('123');
- expect($('.ons-radio__input').attr('b')).toBe('456');
- });
-
- it('has the provided `name` attribute', () => {
- const $ = cheerio.load(
- renderComponent('radios', {
- ...EXAMPLE_RADIOS_MINIMAL,
- radios: [EXAMPLE_RADIO_ITEM],
- }),
- );
-
- expect($('.ons-radio__input').attr('name')).toBe('example-radios-name');
- });
-
- it('has the provided `value` attribute', () => {
- const $ = cheerio.load(
- renderComponent('radios', {
- ...EXAMPLE_RADIOS_MINIMAL,
- radios: [EXAMPLE_RADIO_ITEM],
- }),
- );
-
- expect($('.ons-radio__input').attr('value')).toBe('plain');
- });
-
- it('does not have a `checked` attribute', () => {
- const $ = cheerio.load(
- renderComponent('radios', {
- ...EXAMPLE_RADIOS_MINIMAL,
- radios: [EXAMPLE_RADIO_ITEM],
- }),
- );
-
- expect($('.ons-radio__input').attr('checked')).toBeUndefined();
- });
-
- it('has a `checked` attribute when `checked` parameter is provided', () => {
- const $ = cheerio.load(
- renderComponent('radios', {
- ...EXAMPLE_RADIOS_MINIMAL,
- radios: [
- {
- ...EXAMPLE_RADIO_ITEM,
- checked: true,
- },
- ],
- }),
- );
-
- expect($('.ons-radio__input').attr('checked')).toBe('checked');
- });
-
- it('has a `checked` attribute when `value` parameter matches root `value` parameter', () => {
- const $ = cheerio.load(
- renderComponent('radios', {
- ...EXAMPLE_RADIOS_MINIMAL,
- value: 'plain',
- radios: [EXAMPLE_RADIO_ITEM],
- }),
- );
-
- expect($('.ons-radio__input').attr('checked')).toBe('checked');
- });
-
- it('has additionally provided style classes (from `inputClasses` parameter)', () => {
- const $ = cheerio.load(
- renderComponent('radios', {
- ...EXAMPLE_RADIOS_MINIMAL,
- inputClasses: 'extra-input-class another-extra-input-class',
- radios: [EXAMPLE_RADIO_ITEM],
- }),
- );
-
- expect($('.ons-radio__input').hasClass('extra-input-class')).toBe(true);
- expect($('.ons-radio__input').hasClass('another-extra-input-class')).toBe(true);
- });
-
- it('has additionally provided style classes (from `radio.classes` parameter)', () => {
- const $ = cheerio.load(
- renderComponent('radios', {
- ...EXAMPLE_RADIOS_MINIMAL,
- radios: [
- {
- ...EXAMPLE_RADIO_ITEM,
- classes: 'extra-item-class another-extra-item-class',
- },
- ],
- }),
- );
-
- expect($('.ons-radio__input').hasClass('extra-item-class')).toBe(true);
- expect($('.ons-radio__input').hasClass('another-extra-item-class')).toBe(true);
- });
-
- it('renders a border around each item by default', () => {
- const $ = cheerio.load(
- renderComponent('radios', {
- ...EXAMPLE_RADIOS_MINIMAL,
- radios: [EXAMPLE_RADIO_ITEM],
- }),
- );
-
- expect($('.ons-radios__item').hasClass('ons-radios__item--no-border')).toBe(false);
- expect($('.ons-radio').hasClass('ons-radio--no-border')).toBe(false);
- });
-
- it('does not render a border around each item when `borderless` is `false`', () => {
- const $ = cheerio.load(
- renderComponent('radios', {
- ...EXAMPLE_RADIOS_MINIMAL,
- radios: [EXAMPLE_RADIO_ITEM],
- borderless: true,
- }),
- );
-
- expect($('.ons-radios__item').hasClass('ons-radios__item--no-border')).toBe(true);
- expect($('.ons-radio').hasClass('ons-radio--no-border')).toBe(true);
- });
-
- it('does not mark radio with a class indicating that all child options should be selected', () => {
- const $ = cheerio.load(
- renderComponent('radios', {
- ...EXAMPLE_RADIOS_MINIMAL,
- radios: [EXAMPLE_RADIO_ITEM],
- }),
- );
-
- expect($('.ons-radio__input').hasClass('ons-js-select-all-children')).toBe(false);
- });
-
- it('marks radio with a class indicating that all child options should be selected', () => {
- const $ = cheerio.load(
- renderComponent('radios', {
- ...EXAMPLE_RADIOS_MINIMAL,
- radios: [EXAMPLE_RADIO_ITEM_CHECKBOXES],
- }),
- );
-
- expect($('.ons-radio__input').hasClass('ons-js-select-all-children')).toBe(true);
- });
-
- it('does not mark radio with a class indicating that there is an `other` input', () => {
- const $ = cheerio.load(
- renderComponent('radios', {
- ...EXAMPLE_RADIOS_MINIMAL,
- radios: [EXAMPLE_RADIO_ITEM],
- }),
- );
-
- expect($('.ons-js-other').hasClass('ons-js-select-all-children')).toBe(false);
- });
-
- it('marks radio with a class indicating that there is an `other` input', () => {
- const $ = cheerio.load(
- renderComponent('radios', {
- ...EXAMPLE_RADIOS_MINIMAL,
- radios: [EXAMPLE_RADIO_ITEM_CHECKBOXES],
- }),
- );
-
- expect($('.ons-js-other').hasClass('ons-js-select-all-children')).toBe(true);
- });
-
- it('renders label for the radio item', () => {
- const faker = templateFaker();
- const labelSpy = faker.spy('label');
-
- faker.renderComponent('radios', {
- ...EXAMPLE_RADIOS_MINIMAL,
- radios: [EXAMPLE_RADIO_ITEM],
- });
-
- expect(labelSpy.occurrences).toContainEqual({
- id: 'example-item-id-label',
- for: 'example-item-id',
- inputType: 'radio',
- text: 'Example item',
- classes: 'ons-radio__label extra-label-class',
- description: 'An example description.',
- });
- });
-
- it('wraps `other` component without a class indicating that it is open', () => {
- const $ = cheerio.load(
- renderComponent('radios', {
- ...EXAMPLE_RADIOS_MINIMAL,
- radios: [EXAMPLE_RADIO_ITEM_INPUT],
- }),
- );
-
- expect($('.ons-radio__other').hasClass('ons-radio__other--open')).toBe(false);
- expect($('.ons-radio__other').attr('id')).toBe('example-item-input-other-wrap');
- expect($('.ons-radio__input').attr('aria-controls')).toBe('example-item-input-other-wrap');
- expect($('.ons-radio__input').attr('aria-haspopup')).toBe('true');
- });
-
- it('wraps `other` component with class indicating that it is open', () => {
- const $ = cheerio.load(
- renderComponent('radios', {
- ...EXAMPLE_RADIOS_MINIMAL,
- radios: [
- {
- ...EXAMPLE_RADIO_ITEM_INPUT,
- other: {
- ...EXAMPLE_RADIO_ITEM_INPUT.other,
- open: true,
- },
- },
- ],
- }),
- );
-
- expect($('.ons-radio__other').hasClass('ons-radio__other--open')).toBe(true);
- expect($('.ons-radio__other').attr('id')).toBe('example-item-input-other-wrap');
- expect($('.ons-radio__input').attr('aria-controls')).toBeUndefined();
- expect($('.ons-radio__input').attr('aria-haspopup')).toBeUndefined();
- });
-
- it('renders other "input" component for item', () => {
- const faker = templateFaker();
- const inputSpy = faker.spy('input');
-
- faker.renderComponent('radios', {
- ...EXAMPLE_RADIOS_MINIMAL,
- radios: [EXAMPLE_RADIO_ITEM_INPUT],
- });
-
- expect(inputSpy.occurrences).toContainEqual({
+ other: {
id: 'example-text-input',
name: 'example-text-input-name',
type: 'text',
label: {
- id: 'example-text-input-label',
- text: 'Enter your own answer',
- classes: 'ons-u-fw-n',
+ text: 'Enter your own answer',
},
classes: 'extra-textbox-class',
width: 42,
- attributes: EXAMPLE_RADIO_ITEM_INPUT.other.attributes,
- dontWrap: true,
value: '42',
- });
- });
-
- it('renders other "select" component for item', () => {
- const faker = templateFaker();
- const selectSpy = faker.spy('select');
-
- faker.renderComponent('radios', {
- ...EXAMPLE_RADIOS_MINIMAL,
- radios: [EXAMPLE_RADIO_ITEM_SELECT],
- });
+ attributes: { a: 42 },
+ },
+};
- expect(selectSpy.occurrences).toContainEqual({
+const EXAMPLE_RADIO_ITEM_SELECT = {
+ id: 'example-item-select',
+ name: 'example-item-select',
+ value: 'select',
+ label: {
+ text: 'Example item with select',
+ },
+ other: {
+ otherType: 'select',
id: 'example-select',
name: 'example-select-name',
label: {
- id: 'example-select-label',
- text: 'Enter your own answer',
- classes: 'ons-u-fw-n',
+ text: 'Enter your own answer',
},
classes: 'extra-select-class',
- dontWrap: true,
- options: EXAMPLE_RADIO_ITEM_SELECT.other.options,
+ options: [
+ { text: 'First', value: '1' },
+ { text: 'Second', value: '2' },
+ ],
value: '1',
- });
- });
-
- it('renders other "checkboxes" component for item', () => {
- const faker = templateFaker();
- const checkboxesSpy = faker.spy('checkboxes');
-
- faker.renderComponent('radios', {
- ...EXAMPLE_RADIOS_MINIMAL,
- radios: [EXAMPLE_RADIO_ITEM_CHECKBOXES],
- });
+ },
+};
- expect(checkboxesSpy.occurrences).toContainEqual({
+const EXAMPLE_RADIO_ITEM_CHECKBOXES = {
+ id: 'example-item-checkboxes',
+ name: 'example-item-checkboxes',
+ value: 'checkboxes',
+ label: {
+ text: 'Example item with checkboxes',
+ },
+ other: {
+ otherType: 'checkboxes',
+ selectAllChildren: true,
id: 'example-checkboxes',
name: 'example-checkboxes-name',
- checked: undefined,
- borderless: true,
legend: 'Select preferred times of day',
legendClasses: 'extra-legend-class',
attributes: { a: 42 },
- classes: 'ons-js-other-fieldset-radio',
- checkboxes: EXAMPLE_RADIO_ITEM_CHECKBOXES.other.checkboxes,
- autoSelect: EXAMPLE_RADIO_ITEM_CHECKBOXES.other.autoSelect,
- selectAllChildren: true,
- });
- });
-
- it('renders other "radios" component for item', () => {
- const faker = templateFaker();
- const radiosSpy = faker.spy('radios');
-
- faker.renderComponent('radios', {
- ...EXAMPLE_RADIOS_MINIMAL,
- radios: [EXAMPLE_RADIO_ITEM_RADIOS],
- });
+ checkboxes: [
+ {
+ value: 'morning',
+ id: 'morning',
+ label: {
+ text: 'Morning',
+ },
+ },
+ {
+ value: 'afternoon',
+ id: 'afternoon',
+ label: {
+ text: 'Afternoon',
+ },
+ },
+ ],
+ autoSelect: {
+ selectAllText: 'Select all',
+ unselectAllText: 'Unselect all',
+ context: 'checkboxes',
+ },
+ },
+};
- expect(radiosSpy.occurrences).toContainEqual({
+const EXAMPLE_RADIO_ITEM_RADIOS = {
+ id: 'example-item-radios',
+ name: 'example-item-radios',
+ value: 'radios',
+ label: {
+ text: 'Example item with radios',
+ },
+ other: {
+ otherType: 'radios',
id: 'example-radios',
name: 'example-radios-name',
- borderless: true,
legend: 'Select preferred times of day',
- legendClasses: 'extra-legend-class ons-u-mb-xs',
+ legendClasses: 'extra-legend-class',
attributes: { a: 42 },
- classes: 'ons-js-other-fieldset-radio',
- radios: EXAMPLE_RADIO_ITEM_RADIOS.other.radios,
- });
+ radios: [EXAMPLE_RADIO_ITEM],
+ },
+};
+
+describe('macro: radios', () => {
+ it.each([
+ ['plain', EXAMPLE_RADIO_ITEM],
+ ['input', EXAMPLE_RADIO_ITEM_INPUT],
+ ['select', EXAMPLE_RADIO_ITEM_SELECT],
+ ['checkboxes', EXAMPLE_RADIO_ITEM_CHECKBOXES],
+ ['radios', EXAMPLE_RADIO_ITEM_RADIOS],
+ ])('passes jest-axe checks with a %s item', async (_, radioItem) => {
+ const $ = cheerio.load(
+ renderComponent('radios', {
+ ...EXAMPLE_RADIOS_MINIMAL,
+ radios: [radioItem],
+ }),
+ );
+
+ const results = await axe($.html());
+ expect(results).toHaveNoViolations();
});
- });
-
- describe('clear radios button', () => {
- const params = {
- ...EXAMPLE_RADIOS_MINIMAL,
- clearRadios: {
- text: 'Clear selection',
- name: 'clear-radios-button',
- ariaClearText: 'You can clear your answer using the clear selection button after the radio inputs',
- ariaClearedText: 'You have cleared your answer',
- },
- };
-
- it('renders `button` component', () => {
- const faker = templateFaker();
- const buttonSpy = faker.spy('button');
-
- faker.renderComponent('radios', params);
-
- expect(buttonSpy.occurrences[0]).toHaveProperty('text', 'Clear selection');
- expect(buttonSpy.occurrences[0]).toHaveProperty('name', 'clear-radios-button');
- expect(buttonSpy.occurrences[0]).toHaveProperty('type', 'submit');
+
+ it('renders `fieldset` component with the expected parameters', () => {
+ const faker = templateFaker();
+ const fieldsetSpy = faker.spy('fieldset');
+
+ faker.renderComponent('radios', {
+ ...EXAMPLE_RADIOS_MINIMAL,
+ id: 'example-id',
+ classes: 'extra-class',
+ legendClasses: 'extra-legend-class',
+ description: 'An example description.',
+ dontWrap: true,
+ legendIsQuestionTitle: false,
+ error: {
+ id: 'example-error-id',
+ text: 'An unexpected error occurred.',
+ },
+ });
+
+ expect(fieldsetSpy.occurrences[0]).toEqual({
+ id: 'example-id',
+ classes: 'extra-class',
+ legend: 'Legend text',
+ legendClasses: 'extra-legend-class',
+ description: 'An example description.',
+ dontWrap: true,
+ legendIsQuestionTitle: false,
+ error: {
+ id: 'example-error-id',
+ text: 'An unexpected error occurred.',
+ },
+ });
+ });
+
+ describe('radio item', () => {
+ it('renders `or` label before last radio item', () => {
+ const $ = cheerio.load(
+ renderComponent('radios', {
+ ...EXAMPLE_RADIOS_MINIMAL,
+ radios: [EXAMPLE_RADIO_ITEM, EXAMPLE_RADIO_ITEM_INPUT, EXAMPLE_RADIO_ITEM_SELECT],
+ or: 'Or',
+ }),
+ );
+
+ const label = $('.ons-radios__item + br + .ons-radios__item + br + .ons-radios__label');
+ expect(label.text().trim()).toBe('Or');
+ });
+
+ it('is of the "radio" input type', () => {
+ const $ = cheerio.load(
+ renderComponent('radios', {
+ ...EXAMPLE_RADIOS_MINIMAL,
+ radios: [EXAMPLE_RADIO_ITEM],
+ }),
+ );
+
+ expect($('.ons-radio__input').attr('type')).toBe('radio');
+ });
+
+ it('has the provided `id` attribute', () => {
+ const $ = cheerio.load(
+ renderComponent('radios', {
+ ...EXAMPLE_RADIOS_MINIMAL,
+ radios: [EXAMPLE_RADIO_ITEM],
+ }),
+ );
+
+ expect($('.ons-radio__input').attr('id')).toBe('example-item-id');
+ });
+
+ it('has additionally provided attributes', () => {
+ const $ = cheerio.load(
+ renderComponent('radios', {
+ ...EXAMPLE_RADIOS_MINIMAL,
+ inputClasses: 'extra-input-class another-extra-input-class',
+ radios: [
+ {
+ ...EXAMPLE_RADIO_ITEM,
+ attributes: { a: '123', b: '456' },
+ },
+ ],
+ }),
+ );
+
+ expect($('.ons-radio__input').attr('a')).toBe('123');
+ expect($('.ons-radio__input').attr('b')).toBe('456');
+ });
+
+ it('has the provided `name` attribute', () => {
+ const $ = cheerio.load(
+ renderComponent('radios', {
+ ...EXAMPLE_RADIOS_MINIMAL,
+ radios: [EXAMPLE_RADIO_ITEM],
+ }),
+ );
+
+ expect($('.ons-radio__input').attr('name')).toBe('example-radios-name');
+ });
+
+ it('has the provided `value` attribute', () => {
+ const $ = cheerio.load(
+ renderComponent('radios', {
+ ...EXAMPLE_RADIOS_MINIMAL,
+ radios: [EXAMPLE_RADIO_ITEM],
+ }),
+ );
+
+ expect($('.ons-radio__input').attr('value')).toBe('plain');
+ });
+
+ it('does not have a `checked` attribute', () => {
+ const $ = cheerio.load(
+ renderComponent('radios', {
+ ...EXAMPLE_RADIOS_MINIMAL,
+ radios: [EXAMPLE_RADIO_ITEM],
+ }),
+ );
+
+ expect($('.ons-radio__input').attr('checked')).toBeUndefined();
+ });
+
+ it('has a `checked` attribute when `checked` parameter is provided', () => {
+ const $ = cheerio.load(
+ renderComponent('radios', {
+ ...EXAMPLE_RADIOS_MINIMAL,
+ radios: [
+ {
+ ...EXAMPLE_RADIO_ITEM,
+ checked: true,
+ },
+ ],
+ }),
+ );
+
+ expect($('.ons-radio__input').attr('checked')).toBe('checked');
+ });
+
+ it('has a `checked` attribute when `value` parameter matches root `value` parameter', () => {
+ const $ = cheerio.load(
+ renderComponent('radios', {
+ ...EXAMPLE_RADIOS_MINIMAL,
+ value: 'plain',
+ radios: [EXAMPLE_RADIO_ITEM],
+ }),
+ );
+
+ expect($('.ons-radio__input').attr('checked')).toBe('checked');
+ });
+
+ it('has additionally provided style classes (from `inputClasses` parameter)', () => {
+ const $ = cheerio.load(
+ renderComponent('radios', {
+ ...EXAMPLE_RADIOS_MINIMAL,
+ inputClasses: 'extra-input-class another-extra-input-class',
+ radios: [EXAMPLE_RADIO_ITEM],
+ }),
+ );
+
+ expect($('.ons-radio__input').hasClass('extra-input-class')).toBe(true);
+ expect($('.ons-radio__input').hasClass('another-extra-input-class')).toBe(true);
+ });
+
+ it('has additionally provided style classes (from `radio.classes` parameter)', () => {
+ const $ = cheerio.load(
+ renderComponent('radios', {
+ ...EXAMPLE_RADIOS_MINIMAL,
+ radios: [
+ {
+ ...EXAMPLE_RADIO_ITEM,
+ classes: 'extra-item-class another-extra-item-class',
+ },
+ ],
+ }),
+ );
+
+ expect($('.ons-radio__input').hasClass('extra-item-class')).toBe(true);
+ expect($('.ons-radio__input').hasClass('another-extra-item-class')).toBe(true);
+ });
+
+ it('renders a border around each item by default', () => {
+ const $ = cheerio.load(
+ renderComponent('radios', {
+ ...EXAMPLE_RADIOS_MINIMAL,
+ radios: [EXAMPLE_RADIO_ITEM],
+ }),
+ );
+
+ expect($('.ons-radios__item').hasClass('ons-radios__item--no-border')).toBe(false);
+ expect($('.ons-radio').hasClass('ons-radio--no-border')).toBe(false);
+ });
+
+ it('does not render a border around each item when `borderless` is `false`', () => {
+ const $ = cheerio.load(
+ renderComponent('radios', {
+ ...EXAMPLE_RADIOS_MINIMAL,
+ radios: [EXAMPLE_RADIO_ITEM],
+ borderless: true,
+ }),
+ );
+
+ expect($('.ons-radios__item').hasClass('ons-radios__item--no-border')).toBe(true);
+ expect($('.ons-radio').hasClass('ons-radio--no-border')).toBe(true);
+ });
+
+ it('does not mark radio with a class indicating that all child options should be selected', () => {
+ const $ = cheerio.load(
+ renderComponent('radios', {
+ ...EXAMPLE_RADIOS_MINIMAL,
+ radios: [EXAMPLE_RADIO_ITEM],
+ }),
+ );
+
+ expect($('.ons-radio__input').hasClass('ons-js-select-all-children')).toBe(false);
+ });
+
+ it('marks radio with a class indicating that all child options should be selected', () => {
+ const $ = cheerio.load(
+ renderComponent('radios', {
+ ...EXAMPLE_RADIOS_MINIMAL,
+ radios: [EXAMPLE_RADIO_ITEM_CHECKBOXES],
+ }),
+ );
+
+ expect($('.ons-radio__input').hasClass('ons-js-select-all-children')).toBe(true);
+ });
+
+ it('does not mark radio with a class indicating that there is an `other` input', () => {
+ const $ = cheerio.load(
+ renderComponent('radios', {
+ ...EXAMPLE_RADIOS_MINIMAL,
+ radios: [EXAMPLE_RADIO_ITEM],
+ }),
+ );
+
+ expect($('.ons-js-other').hasClass('ons-js-select-all-children')).toBe(false);
+ });
+
+ it('marks radio with a class indicating that there is an `other` input', () => {
+ const $ = cheerio.load(
+ renderComponent('radios', {
+ ...EXAMPLE_RADIOS_MINIMAL,
+ radios: [EXAMPLE_RADIO_ITEM_CHECKBOXES],
+ }),
+ );
+
+ expect($('.ons-js-other').hasClass('ons-js-select-all-children')).toBe(true);
+ });
+
+ it('renders label for the radio item', () => {
+ const faker = templateFaker();
+ const labelSpy = faker.spy('label');
+
+ faker.renderComponent('radios', {
+ ...EXAMPLE_RADIOS_MINIMAL,
+ radios: [EXAMPLE_RADIO_ITEM],
+ });
+
+ expect(labelSpy.occurrences).toContainEqual({
+ id: 'example-item-id-label',
+ for: 'example-item-id',
+ inputType: 'radio',
+ text: 'Example item',
+ classes: 'ons-radio__label extra-label-class',
+ description: 'An example description.',
+ });
+ });
+
+ it('wraps `other` component without a class indicating that it is open', () => {
+ const $ = cheerio.load(
+ renderComponent('radios', {
+ ...EXAMPLE_RADIOS_MINIMAL,
+ radios: [EXAMPLE_RADIO_ITEM_INPUT],
+ }),
+ );
+
+ expect($('.ons-radio__other').hasClass('ons-radio__other--open')).toBe(false);
+ expect($('.ons-radio__other').attr('id')).toBe('example-item-input-other-wrap');
+ expect($('.ons-radio__input').attr('aria-controls')).toBe('example-item-input-other-wrap');
+ expect($('.ons-radio__input').attr('aria-haspopup')).toBe('true');
+ });
+
+ it('wraps `other` component with class indicating that it is open', () => {
+ const $ = cheerio.load(
+ renderComponent('radios', {
+ ...EXAMPLE_RADIOS_MINIMAL,
+ radios: [
+ {
+ ...EXAMPLE_RADIO_ITEM_INPUT,
+ other: {
+ ...EXAMPLE_RADIO_ITEM_INPUT.other,
+ open: true,
+ },
+ },
+ ],
+ }),
+ );
+
+ expect($('.ons-radio__other').hasClass('ons-radio__other--open')).toBe(true);
+ expect($('.ons-radio__other').attr('id')).toBe('example-item-input-other-wrap');
+ expect($('.ons-radio__input').attr('aria-controls')).toBeUndefined();
+ expect($('.ons-radio__input').attr('aria-haspopup')).toBeUndefined();
+ });
+
+ it('renders other "input" component for item', () => {
+ const faker = templateFaker();
+ const inputSpy = faker.spy('input');
+
+ faker.renderComponent('radios', {
+ ...EXAMPLE_RADIOS_MINIMAL,
+ radios: [EXAMPLE_RADIO_ITEM_INPUT],
+ });
+
+ expect(inputSpy.occurrences).toContainEqual({
+ id: 'example-text-input',
+ name: 'example-text-input-name',
+ type: 'text',
+ label: {
+ id: 'example-text-input-label',
+ text: 'Enter your own answer',
+ classes: 'ons-u-fw-n',
+ },
+ classes: 'extra-textbox-class',
+ width: 42,
+ attributes: EXAMPLE_RADIO_ITEM_INPUT.other.attributes,
+ dontWrap: true,
+ value: '42',
+ });
+ });
+
+ it('renders other "select" component for item', () => {
+ const faker = templateFaker();
+ const selectSpy = faker.spy('select');
+
+ faker.renderComponent('radios', {
+ ...EXAMPLE_RADIOS_MINIMAL,
+ radios: [EXAMPLE_RADIO_ITEM_SELECT],
+ });
+
+ expect(selectSpy.occurrences).toContainEqual({
+ id: 'example-select',
+ name: 'example-select-name',
+ label: {
+ id: 'example-select-label',
+ text: 'Enter your own answer',
+ classes: 'ons-u-fw-n',
+ },
+ classes: 'extra-select-class',
+ dontWrap: true,
+ options: EXAMPLE_RADIO_ITEM_SELECT.other.options,
+ value: '1',
+ });
+ });
+
+ it('renders other "checkboxes" component for item', () => {
+ const faker = templateFaker();
+ const checkboxesSpy = faker.spy('checkboxes');
+
+ faker.renderComponent('radios', {
+ ...EXAMPLE_RADIOS_MINIMAL,
+ radios: [EXAMPLE_RADIO_ITEM_CHECKBOXES],
+ });
+
+ expect(checkboxesSpy.occurrences).toContainEqual({
+ id: 'example-checkboxes',
+ name: 'example-checkboxes-name',
+ checked: undefined,
+ borderless: true,
+ legend: 'Select preferred times of day',
+ legendClasses: 'extra-legend-class',
+ attributes: { a: 42 },
+ classes: 'ons-js-other-fieldset-radio',
+ checkboxes: EXAMPLE_RADIO_ITEM_CHECKBOXES.other.checkboxes,
+ autoSelect: EXAMPLE_RADIO_ITEM_CHECKBOXES.other.autoSelect,
+ selectAllChildren: true,
+ });
+ });
+
+ it('renders other "radios" component for item', () => {
+ const faker = templateFaker();
+ const radiosSpy = faker.spy('radios');
+
+ faker.renderComponent('radios', {
+ ...EXAMPLE_RADIOS_MINIMAL,
+ radios: [EXAMPLE_RADIO_ITEM_RADIOS],
+ });
+
+ expect(radiosSpy.occurrences).toContainEqual({
+ id: 'example-radios',
+ name: 'example-radios-name',
+ borderless: true,
+ legend: 'Select preferred times of day',
+ legendClasses: 'extra-legend-class ons-u-mb-xs',
+ attributes: { a: 42 },
+ classes: 'ons-js-other-fieldset-radio',
+ radios: EXAMPLE_RADIO_ITEM_RADIOS.other.radios,
+ });
+ });
});
- it('renders a visually hidden element for aria alerts', () => {
- const $ = cheerio.load(renderComponent('radios', params));
+ describe('clear radios button', () => {
+ const params = {
+ ...EXAMPLE_RADIOS_MINIMAL,
+ clearRadios: {
+ text: 'Clear selection',
+ name: 'clear-radios-button',
+ ariaClearText: 'You can clear your answer using the clear selection button after the radio inputs',
+ ariaClearedText: 'You have cleared your answer',
+ },
+ };
+
+ it('renders `button` component', () => {
+ const faker = templateFaker();
+ const buttonSpy = faker.spy('button');
+
+ faker.renderComponent('radios', params);
+
+ expect(buttonSpy.occurrences[0]).toHaveProperty('text', 'Clear selection');
+ expect(buttonSpy.occurrences[0]).toHaveProperty('name', 'clear-radios-button');
+ expect(buttonSpy.occurrences[0]).toHaveProperty('type', 'submit');
+ });
+
+ it('renders a visually hidden element for aria alerts', () => {
+ const $ = cheerio.load(renderComponent('radios', params));
- expect($('span[role=alert]').hasClass('ons-u-vh')).toBe(true);
- expect($('span[role=alert]').attr('aria-live')).toBe('polite');
- expect($('span[role=alert]').attr('data-clear')).toBe(params.clearRadios.ariaClearText);
- expect($('span[role=alert]').attr('data-cleared')).toBe(params.clearRadios.ariaClearedText);
+ expect($('span[role=alert]').hasClass('ons-u-vh')).toBe(true);
+ expect($('span[role=alert]').attr('aria-live')).toBe('polite');
+ expect($('span[role=alert]').attr('data-clear')).toBe(params.clearRadios.ariaClearText);
+ expect($('span[role=alert]').attr('data-cleared')).toBe(params.clearRadios.ariaClearedText);
+ });
});
- });
});
diff --git a/src/components/radios/_radio.scss b/src/components/radios/_radio.scss
index 996f379f4d..4f966907bb 100644
--- a/src/components/radios/_radio.scss
+++ b/src/components/radios/_radio.scss
@@ -1,65 +1,66 @@
.ons-radio {
- @extend .ons-checkbox;
+ @extend .ons-checkbox;
- &__input {
- @extend .ons-checkbox__input;
+ &__input {
+ @extend .ons-checkbox__input;
- background: var(--ons-color-grey-5);
- border-radius: 50%;
- box-shadow: inset 0 0 0 3px var(--ons-color-input-bg);
+ background: var(--ons-color-grey-5);
+ border-radius: 50%;
+ box-shadow: inset 0 0 0 3px var(--ons-color-input-bg);
- &::after {
- display: none;
- }
- &:checked {
- background-color: var(--ons-color-input-border);
+ &::after {
+ display: none;
+ }
+ &:checked {
+ background-color: var(--ons-color-input-border);
+ }
}
- }
- &.ons-radio--no-border {
- @extend .ons-checkbox--no-border;
+ &.ons-radio--no-border {
+ @extend .ons-checkbox--no-border;
- & > .ons-radio__input {
- @extend .ons-radio__input;
- &:focus,
- &:checked {
- & + .ons-radio__label::before {
- background: none;
- border: none;
- box-shadow: none;
- outline: none;
- }
- }
+ > .ons-radio__input {
+ @extend .ons-radio__input;
+ &:focus,
+ &:checked {
+ + .ons-radio__label::before {
+ background: none;
+ border: 0;
+ box-shadow: none;
+ outline: none;
+ }
+ }
- &:focus {
- box-shadow: inset 0 0 0 3px var(--ons-color-input-bg),
- 0 0 0 $input-border-width var(--ons-color-input-border),
- 0 0 0 4px var(--ons-color-focus);
- }
- }
+ &:focus {
+ box-shadow:
+ inset 0 0 0 3px var(--ons-color-input-bg),
+ 0 0 0 $input-border-width var(--ons-color-input-border),
+ 0 0 0 4px var(--ons-color-focus);
+ }
+ }
- .ons-radio__label--with-description {
- padding: 0;
+ .ons-radio__label--with-description {
+ padding: 0;
+ }
}
- }
- &__label {
- @extend .ons-checkbox__label;
+ &__label {
+ @extend .ons-checkbox__label;
- &--with-description {
- @extend .ons-checkbox__label--with-description;
+ &--with-description {
+ @extend .ons-checkbox__label--with-description;
+ }
}
- }
- &__description {
- @extend .ons-checkbox__description;
- }
+ &__description {
+ @extend .ons-checkbox__description;
+ }
- &__other {
- @extend .ons-checkbox__other;
- }
+ &__other {
+ @extend .ons-checkbox__other;
+ }
- &__input:not(:checked) ~ &__other--open {
- display: block;
- }
+ &__input:not(:checked) ~ &__other--open {
+ display: block;
+ }
}
diff --git a/src/components/radios/_radios.scss b/src/components/radios/_radios.scss
index 0fdccd885a..14b8bb1fe7 100644
--- a/src/components/radios/_radios.scss
+++ b/src/components/radios/_radios.scss
@@ -1,22 +1,22 @@
.ons-radios {
- &__items {
- @extend .ons-checkboxes__items;
- }
+ &__items {
+ @extend .ons-checkboxes__items;
+ }
- &__item {
- @extend .ons-checkboxes__item;
+ &__item {
+ @extend .ons-checkboxes__item;
- &--no-border {
- @extend .ons-checkboxes__item--no-border;
+ &--no-border {
+ @extend .ons-checkboxes__item--no-border;
- margin-bottom: 0.8rem;
- &:last-child {
- margin-bottom: 0;
- }
+ margin-bottom: 0.8rem;
+ &:last-child {
+ margin-bottom: 0;
+ }
+ }
}
- }
- &__label {
- @extend .ons-checkboxes__label;
- }
+ &__label {
+ @extend .ons-checkboxes__label;
+ }
}
diff --git a/src/components/radios/check-radios.js b/src/components/radios/check-radios.js
index db0047883c..5c93bfd7a1 100644
--- a/src/components/radios/check-radios.js
+++ b/src/components/radios/check-radios.js
@@ -1,28 +1,28 @@
export default class CheckRadios {
- constructor(radio, openOther) {
- this.radio = radio;
- this.openOther = openOther;
- this.input = this.openOther.querySelector('.ons-input');
- this.input.tabIndex = -1;
+ constructor(radio, openOther) {
+ this.radio = radio;
+ this.openOther = openOther;
+ this.input = this.openOther.querySelector('.ons-input');
+ this.input.tabIndex = -1;
- this.setInputBlurAttributes();
- this.input.addEventListener('focus', this.checkRadio.bind(this));
- this.radio.addEventListener('change', this.setInputFocusAttributes.bind(this));
- this.radio.addEventListener('focus', this.setInputFocusAttributes.bind(this));
- if (this.radio.type == 'radio') {
- this.input.addEventListener('blur', this.setInputBlurAttributes.bind(this));
+ this.setInputBlurAttributes();
+ this.input.addEventListener('focus', this.checkRadio.bind(this));
+ this.radio.addEventListener('change', this.setInputFocusAttributes.bind(this));
+ this.radio.addEventListener('focus', this.setInputFocusAttributes.bind(this));
+ if (this.radio.type == 'radio') {
+ this.input.addEventListener('blur', this.setInputBlurAttributes.bind(this));
+ }
}
- }
- checkRadio() {
- this.radio.checked = true;
- }
+ checkRadio() {
+ this.radio.checked = true;
+ }
- setInputFocusAttributes() {
- this.input.tabIndex = this.radio.checked ? 0 : -1;
- }
+ setInputFocusAttributes() {
+ this.input.tabIndex = this.radio.checked ? 0 : -1;
+ }
- setInputBlurAttributes() {
- this.input.tabIndex = -1;
- }
+ setInputBlurAttributes() {
+ this.input.tabIndex = -1;
+ }
}
diff --git a/src/components/radios/clear-radios.js b/src/components/radios/clear-radios.js
index 10f362f18f..f029b7a5ae 100644
--- a/src/components/radios/clear-radios.js
+++ b/src/components/radios/clear-radios.js
@@ -1,55 +1,55 @@
let clearAlertAnnounced;
export default class ClearRadios {
- constructor(inputs, button, otherInput) {
- this.inputs = inputs;
- this.button = button;
- this.otherInput = otherInput;
- this.ariaElement = document.querySelector('.ons-js-clear-radio-alert');
- this.clearAlert = this.ariaElement.getAttribute('data-clear');
- this.clearedAlert = this.ariaElement.getAttribute('data-cleared');
-
- this.inputs.forEach((input) => input.addEventListener('click', this.setClearAttributes.bind(this)));
- this.button.addEventListener('click', this.clearRadios.bind(this));
- this.checkRadios();
-
- if (this.otherInput) {
- const parent = this.otherInput.parentNode;
- this.otherField = parent.querySelector('.ons-input');
- this.otherField.addEventListener('focus', this.setClearAttributes.bind(this));
+ constructor(inputs, button, otherInput) {
+ this.inputs = inputs;
+ this.button = button;
+ this.otherInput = otherInput;
+ this.ariaElement = document.querySelector('.ons-js-clear-radio-alert');
+ this.clearAlert = this.ariaElement.getAttribute('data-clear');
+ this.clearedAlert = this.ariaElement.getAttribute('data-cleared');
+
+ this.inputs.forEach((input) => input.addEventListener('click', this.setClearAttributes.bind(this)));
+ this.button.addEventListener('click', this.clearRadios.bind(this));
+ this.checkRadios();
+
+ if (this.otherInput) {
+ const parent = this.otherInput.parentNode;
+ this.otherField = parent.querySelector('.ons-input');
+ this.otherField.addEventListener('focus', this.setClearAttributes.bind(this));
+ }
+
+ clearAlertAnnounced = false;
}
- clearAlertAnnounced = false;
- }
-
- checkRadios() {
- this.inputs.forEach((input) => {
- if (input.checked) {
- this.setClearAttributes();
- }
- });
- }
-
- setClearAttributes() {
- this.button.classList.remove('ons-u-db-no-js_enabled');
- if (clearAlertAnnounced === false) {
- this.ariaElement.innerHTML = this.clearAlert;
- clearAlertAnnounced = true;
+ checkRadios() {
+ this.inputs.forEach((input) => {
+ if (input.checked) {
+ this.setClearAttributes();
+ }
+ });
}
- }
- clearRadios() {
- event.preventDefault();
+ setClearAttributes() {
+ this.button.classList.remove('ons-u-db-no-js_enabled');
+ if (clearAlertAnnounced === false) {
+ this.ariaElement.innerHTML = this.clearAlert;
+ clearAlertAnnounced = true;
+ }
+ }
- this.inputs.forEach((input) => {
- input.checked = false;
- });
+ clearRadios() {
+ event.preventDefault();
- if (this.otherField) {
- this.otherField.value = '';
- }
+ this.inputs.forEach((input) => {
+ input.checked = false;
+ });
- this.button.classList.add('ons-u-db-no-js_enabled');
- this.ariaElement.innerHTML = this.clearedAlert;
- clearAlertAnnounced = false;
- }
+ if (this.otherField) {
+ this.otherField.value = '';
+ }
+
+ this.button.classList.add('ons-u-db-no-js_enabled');
+ this.ariaElement.innerHTML = this.clearedAlert;
+ clearAlertAnnounced = false;
+ }
}
diff --git a/src/components/radios/radio-with-fieldset.js b/src/components/radios/radio-with-fieldset.js
index 945951b6e3..a9ef2403ce 100644
--- a/src/components/radios/radio-with-fieldset.js
+++ b/src/components/radios/radio-with-fieldset.js
@@ -1,29 +1,29 @@
export default class RadioWithFieldset {
- constructor(context) {
- this.context = context;
- this.radios = [...context.closest('.ons-radios__items').querySelectorAll('.ons-js-radio')];
- this.childInputs = [...this.context.querySelectorAll('input')];
- this.selectAllChildrenInput = this.context.querySelector('.ons-js-select-all-children');
+ constructor(context) {
+ this.context = context;
+ this.radios = [...context.closest('.ons-radios__items').querySelectorAll('.ons-js-radio')];
+ this.childInputs = [...this.context.querySelectorAll('input')];
+ this.selectAllChildrenInput = this.context.querySelector('.ons-js-select-all-children');
- if (this.selectAllChildrenInput) {
- this.selectAllChildrenInput.addEventListener('change', this.checkChildInputsOnSelect.bind(this));
- } else {
- this.radios.forEach((radio) => radio.addEventListener('change', this.uncheckChildInputsOnDeselect.bind(this)));
+ if (this.selectAllChildrenInput) {
+ this.selectAllChildrenInput.addEventListener('change', this.checkChildInputsOnSelect.bind(this));
+ } else {
+ this.radios.forEach((radio) => radio.addEventListener('change', this.uncheckChildInputsOnDeselect.bind(this)));
+ }
}
- }
- checkChildInputsOnSelect() {
- this.childInputs.forEach((input) => {
- input.checked = this.selectAllChildrenInput.checked === true ? true : false;
- });
- }
+ checkChildInputsOnSelect() {
+ this.childInputs.forEach((input) => {
+ input.checked = this.selectAllChildrenInput.checked === true ? true : false;
+ });
+ }
- uncheckChildInputsOnDeselect() {
- const isOther = this.radios.find((radio) => radio.classList.contains('ons-js-other'));
- if (isOther && isOther.checked === false) {
- this.childInputs.forEach((input) => {
- input.checked = false;
- });
+ uncheckChildInputsOnDeselect() {
+ const isOther = this.radios.find((radio) => radio.classList.contains('ons-js-other'));
+ if (isOther && isOther.checked === false) {
+ this.childInputs.forEach((input) => {
+ input.checked = false;
+ });
+ }
}
- }
}
diff --git a/src/components/radios/radios.dom.js b/src/components/radios/radios.dom.js
index 64a9578dcc..d55dbb1e31 100644
--- a/src/components/radios/radios.dom.js
+++ b/src/components/radios/radios.dom.js
@@ -1,37 +1,37 @@
import domready from '../../js/domready';
domready(async () => {
- const radios = [...document.querySelectorAll('.ons-js-radio')];
-
- if (radios.length) {
- const Radios = (await import('../checkboxes/checkboxes-with-reveal')).default;
-
- new Radios(radios);
-
- const button = document.querySelector('.ons-js-clear-btn');
- const otherInput = document.querySelector('.ons-radio__other');
- if (button) {
- const ClearRadios = (await import('./clear-radios')).default;
-
- new ClearRadios(radios, button, otherInput);
- }
-
- const openOther = document.querySelector('.ons-radio__other--open');
- if (openOther) {
- const parent = openOther.parentNode;
- const radioInput = parent.querySelector('.ons-radio__input');
- const CheckRadios = (await import('./check-radios')).default;
-
- new CheckRadios(radioInput, openOther);
- }
-
- const otherFieldsets = [...document.querySelectorAll('.ons-js-other-fieldset-radio')];
- if (otherFieldsets) {
- const RadioWithInnerFieldset = (await import('./radio-with-fieldset')).default;
- otherFieldsets.forEach((otherFieldset) => {
- const context = otherFieldset.closest('.ons-radio');
- new RadioWithInnerFieldset(context);
- });
+ const radios = [...document.querySelectorAll('.ons-js-radio')];
+
+ if (radios.length) {
+ const Radios = (await import('../checkboxes/checkboxes-with-reveal')).default;
+
+ new Radios(radios);
+
+ const button = document.querySelector('.ons-js-clear-btn');
+ const otherInput = document.querySelector('.ons-radio__other');
+ if (button) {
+ const ClearRadios = (await import('./clear-radios')).default;
+
+ new ClearRadios(radios, button, otherInput);
+ }
+
+ const openOther = document.querySelector('.ons-radio__other--open');
+ if (openOther) {
+ const parent = openOther.parentNode;
+ const radioInput = parent.querySelector('.ons-radio__input');
+ const CheckRadios = (await import('./check-radios')).default;
+
+ new CheckRadios(radioInput, openOther);
+ }
+
+ const otherFieldsets = [...document.querySelectorAll('.ons-js-other-fieldset-radio')];
+ if (otherFieldsets) {
+ const RadioWithInnerFieldset = (await import('./radio-with-fieldset')).default;
+ otherFieldsets.forEach((otherFieldset) => {
+ const context = otherFieldset.closest('.ons-radio');
+ new RadioWithInnerFieldset(context);
+ });
+ }
}
- }
});
diff --git a/src/components/radios/radios.spec.js b/src/components/radios/radios.spec.js
index 1d6976a9dd..abb354126f 100644
--- a/src/components/radios/radios.spec.js
+++ b/src/components/radios/radios.spec.js
@@ -1,288 +1,288 @@
import { renderComponent, setTestPage } from '../../tests/helpers/rendering';
const EXAMPLE_RADIOS_PARAMS = {
- name: 'contact-preference',
- clearRadios: {
- text: 'Clear selection',
- url: '/',
- ariaClearText: 'You can clear your answer by clicking the clear selection button under the radio buttons',
- ariaClearedText: 'You have cleared your answer',
- },
- legend: 'How would you like us to contact you?',
- legendClasses: 'ons-u-vh',
- radios: [
- {
- id: 'email',
- value: 'email',
- label: {
- text: 'Email',
- },
- other: {
- id: 'email-other',
- type: 'email',
- label: {
- text: 'Enter your email address',
- },
- },
+ name: 'contact-preference',
+ clearRadios: {
+ text: 'Clear selection',
+ url: '/',
+ ariaClearText: 'You can clear your answer by clicking the clear selection button under the radio buttons',
+ ariaClearedText: 'You have cleared your answer',
},
- {
- id: 'phone',
- value: 'phone',
- label: {
- text: 'Phone',
- },
- other: {
- id: 'tel-other',
- type: 'tel',
- label: {
- text: 'Enter your phone number',
+ legend: 'How would you like us to contact you?',
+ legendClasses: 'ons-u-vh',
+ radios: [
+ {
+ id: 'email',
+ value: 'email',
+ label: {
+ text: 'Email',
+ },
+ other: {
+ id: 'email-other',
+ type: 'email',
+ label: {
+ text: 'Enter your email address',
+ },
+ },
},
- },
- },
- {
- id: 'text',
- value: 'Open input test',
- label: {
- text: 'Text',
- },
- other: {
- id: 'text-other',
- type: 'text',
- open: true,
- label: {
- text: 'Enter something else',
+ {
+ id: 'phone',
+ value: 'phone',
+ label: {
+ text: 'Phone',
+ },
+ other: {
+ id: 'tel-other',
+ type: 'tel',
+ label: {
+ text: 'Enter your phone number',
+ },
+ },
},
- },
- },
- ],
+ {
+ id: 'text',
+ value: 'Open input test',
+ label: {
+ text: 'Text',
+ },
+ other: {
+ id: 'text-other',
+ type: 'text',
+ open: true,
+ label: {
+ text: 'Enter something else',
+ },
+ },
+ },
+ ],
};
describe('script: radios', () => {
- describe('other options', () => {
- beforeEach(async () => {
- await setTestPage('/test', renderComponent('radios', EXAMPLE_RADIOS_PARAMS));
- });
-
- it('radios with other options should be given aria-expanded attributes', async () => {
- const ariaExpandedOption1 = await page.$eval('#email', (node) => node.getAttribute('aria-expanded'));
- expect(ariaExpandedOption1).toBe('false');
- const ariaExpandedOption2 = await page.$eval('#phone', (node) => node.getAttribute('aria-expanded'));
- expect(ariaExpandedOption2).toBe('false');
- });
-
- it('radios with "open" other options should not be given aria-expanded attributes', async () => {
- const hasAriaExpandedOption3 = await page.$eval('#text', (node) => node.hasAttribute('aria-expanded'));
- expect(hasAriaExpandedOption3).toBe(false);
- });
-
- describe('a radio checked', () => {
- beforeEach(async () => {
- await page.click('#email');
- });
-
- it('then the checked radio aria-expanded attribute should be set to true', async () => {
- const ariaExpandedOption1 = await page.$eval('#email', (node) => node.getAttribute('aria-expanded'));
- expect(ariaExpandedOption1).toBe('true');
- });
-
- it('then the unchecked radio aria-expanded attribute should be set to false', async () => {
- const ariaExpandedOption2 = await page.$eval('#phone', (node) => node.getAttribute('aria-expanded'));
- expect(ariaExpandedOption2).toBe('false');
- });
-
- it('then the unchecked radio aria-expanded attribute of "open" radio should not be set', async () => {
- const hasAriaExpanded = await page.$eval('#text', (node) => node.hasAttribute('aria-expanded'));
- expect(hasAriaExpanded).toBe(false);
- });
-
- it('then the clear button should be visible', async () => {
- const isHidden = await page.$eval('.ons-js-clear-btn', (node) => node.classList.contains('ons-u-db-no-js_enabled'));
- expect(isHidden).toBe(false);
- });
-
- it('then the aria live message should announce that the answer can be cleared', async () => {
- const alertText = await page.$eval('.ons-js-clear-radio-alert', (node) => node.innerHTML);
- expect(alertText).toBe('You can clear your answer by clicking the clear selection button under the radio buttons');
- });
-
- describe('and the radio selection is changed', () => {
+ describe('other options', () => {
beforeEach(async () => {
- await page.click('#phone');
+ await setTestPage('/test', renderComponent('radios', EXAMPLE_RADIOS_PARAMS));
});
- it('then the checked radio aria-expanded attribute should be set to true', async () => {
- const ariaExpandedOption2 = await page.$eval('#phone', (node) => node.getAttribute('aria-expanded'));
- expect(ariaExpandedOption2).toBe('true');
+ it('radios with other options should be given aria-expanded attributes', async () => {
+ const ariaExpandedOption1 = await page.$eval('#email', (node) => node.getAttribute('aria-expanded'));
+ expect(ariaExpandedOption1).toBe('false');
+ const ariaExpandedOption2 = await page.$eval('#phone', (node) => node.getAttribute('aria-expanded'));
+ expect(ariaExpandedOption2).toBe('false');
});
- it('then the unchecked radio aria-expanded attribute should be set to false', async () => {
- const ariaExpandedOption1 = await page.$eval('#email', (node) => node.getAttribute('aria-expanded'));
- expect(ariaExpandedOption1).toBe('false');
+ it('radios with "open" other options should not be given aria-expanded attributes', async () => {
+ const hasAriaExpandedOption3 = await page.$eval('#text', (node) => node.hasAttribute('aria-expanded'));
+ expect(hasAriaExpandedOption3).toBe(false);
});
- it('then the unchecked radio aria-expanded attribute of "open" radio should not be set', async () => {
- const hasAriaExpanded = await page.$eval('#text', (node) => node.hasAttribute('aria-expanded'));
- expect(hasAriaExpanded).toBe(false);
+ describe('a radio checked', () => {
+ beforeEach(async () => {
+ await page.click('#email');
+ });
+
+ it('then the checked radio aria-expanded attribute should be set to true', async () => {
+ const ariaExpandedOption1 = await page.$eval('#email', (node) => node.getAttribute('aria-expanded'));
+ expect(ariaExpandedOption1).toBe('true');
+ });
+
+ it('then the unchecked radio aria-expanded attribute should be set to false', async () => {
+ const ariaExpandedOption2 = await page.$eval('#phone', (node) => node.getAttribute('aria-expanded'));
+ expect(ariaExpandedOption2).toBe('false');
+ });
+
+ it('then the unchecked radio aria-expanded attribute of "open" radio should not be set', async () => {
+ const hasAriaExpanded = await page.$eval('#text', (node) => node.hasAttribute('aria-expanded'));
+ expect(hasAriaExpanded).toBe(false);
+ });
+
+ it('then the clear button should be visible', async () => {
+ const isHidden = await page.$eval('.ons-js-clear-btn', (node) => node.classList.contains('ons-u-db-no-js_enabled'));
+ expect(isHidden).toBe(false);
+ });
+
+ it('then the aria live message should announce that the answer can be cleared', async () => {
+ const alertText = await page.$eval('.ons-js-clear-radio-alert', (node) => node.innerHTML);
+ expect(alertText).toBe('You can clear your answer by clicking the clear selection button under the radio buttons');
+ });
+
+ describe('and the radio selection is changed', () => {
+ beforeEach(async () => {
+ await page.click('#phone');
+ });
+
+ it('then the checked radio aria-expanded attribute should be set to true', async () => {
+ const ariaExpandedOption2 = await page.$eval('#phone', (node) => node.getAttribute('aria-expanded'));
+ expect(ariaExpandedOption2).toBe('true');
+ });
+
+ it('then the unchecked radio aria-expanded attribute should be set to false', async () => {
+ const ariaExpandedOption1 = await page.$eval('#email', (node) => node.getAttribute('aria-expanded'));
+ expect(ariaExpandedOption1).toBe('false');
+ });
+
+ it('then the unchecked radio aria-expanded attribute of "open" radio should not be set', async () => {
+ const hasAriaExpanded = await page.$eval('#text', (node) => node.hasAttribute('aria-expanded'));
+ expect(hasAriaExpanded).toBe(false);
+ });
+ });
});
- });
- });
- describe('the clear button is clicked', () => {
- beforeEach(async () => {
- await page.$eval('.ons-js-clear-btn', (node) => node.click());
- });
-
- it('then the clear button should not be visible', async () => {
- const isHidden = await page.$eval('.ons-js-clear-btn', (node) => node.classList.contains('ons-u-db-no-js_enabled'));
- expect(isHidden).toBe(true);
- });
-
- it('then the aria live message should announce that the answer has been cleared', async () => {
- const alertText = await page.$eval('.ons-js-clear-radio-alert', (node) => node.innerHTML);
- expect(alertText).toBe('You have cleared your answer');
- });
-
- it('then all radios should not be checked', async () => {
- const checkedRadios = await page.$$eval('.ons-js-radio', (nodes) => nodes.map((node) => node.checked));
- expect(checkedRadios).not.toContain(true);
- });
-
- it('then all other input fields should be empty', async () => {
- const emailOtherValue = await page.$eval('#email-other', (node) => node.value);
- expect(emailOtherValue).toBe('');
- const telOtherValue = await page.$eval('#tel-other', (node) => node.value);
- expect(telOtherValue).toBe('');
- const textOtherValue = await page.$eval('#text-other', (node) => node.value);
- expect(textOtherValue).toBe('');
- });
- });
+ describe('the clear button is clicked', () => {
+ beforeEach(async () => {
+ await page.$eval('.ons-js-clear-btn', (node) => node.click());
+ });
+
+ it('then the clear button should not be visible', async () => {
+ const isHidden = await page.$eval('.ons-js-clear-btn', (node) => node.classList.contains('ons-u-db-no-js_enabled'));
+ expect(isHidden).toBe(true);
+ });
+
+ it('then the aria live message should announce that the answer has been cleared', async () => {
+ const alertText = await page.$eval('.ons-js-clear-radio-alert', (node) => node.innerHTML);
+ expect(alertText).toBe('You have cleared your answer');
+ });
+
+ it('then all radios should not be checked', async () => {
+ const checkedRadios = await page.$$eval('.ons-js-radio', (nodes) => nodes.map((node) => node.checked));
+ expect(checkedRadios).not.toContain(true);
+ });
+
+ it('then all other input fields should be empty', async () => {
+ const emailOtherValue = await page.$eval('#email-other', (node) => node.value);
+ expect(emailOtherValue).toBe('');
+ const telOtherValue = await page.$eval('#tel-other', (node) => node.value);
+ expect(telOtherValue).toBe('');
+ const textOtherValue = await page.$eval('#text-other', (node) => node.value);
+ expect(textOtherValue).toBe('');
+ });
+ });
- describe('there is a visible input which is focused', () => {
- beforeEach(async () => {
- await page.focus('#text-other');
- });
+ describe('there is a visible input which is focused', () => {
+ beforeEach(async () => {
+ await page.focus('#text-other');
+ });
- it('then the radio button should be checked', async () => {
- const isRadioChecked = await page.$eval('#text', (node) => node.checked);
- expect(isRadioChecked).toBe(true);
- });
- });
+ it('then the radio button should be checked', async () => {
+ const isRadioChecked = await page.$eval('#text', (node) => node.checked);
+ expect(isRadioChecked).toBe(true);
+ });
+ });
- describe('there is a visible input and the radio is checked', () => {
- beforeEach(async () => {
- await page.click('#text');
- });
+ describe('there is a visible input and the radio is checked', () => {
+ beforeEach(async () => {
+ await page.click('#text');
+ });
- it('then the input should have a tab index of 0', async () => {
- const tabIndex = await page.$eval('#text-other', (node) => node.getAttribute('tabindex'));
- expect(tabIndex).toBe('0');
- });
+ it('then the input should have a tab index of 0', async () => {
+ const tabIndex = await page.$eval('#text-other', (node) => node.getAttribute('tabindex'));
+ expect(tabIndex).toBe('0');
+ });
+ });
});
- });
- describe('reveal and fieldset', function () {
- const params = {
- legend: 'What are your favourite pizza toppings?',
- name: 'food-other',
- radios: [
- {
- id: 'bacon-other',
- label: {
- text: 'Bacon',
- },
- value: 'bacon',
- },
- {
- id: 'olives-other',
- label: {
- text: 'Olives',
- },
- value: 'olives',
- },
- {
- id: 'other-radio',
- label: {
- text: 'Other',
- description: 'An answer is required',
- },
- value: 'other',
- other: {
- id: 'other-textbox',
- name: 'other-answer',
- legend: 'Please specify other',
- otherType: 'checkboxes',
- checkboxes: [
- {
- id: 'inner-bacon-other',
- label: {
- text: 'Bacon',
+ describe('reveal and fieldset', function () {
+ const params = {
+ legend: 'What are your favourite pizza toppings?',
+ name: 'food-other',
+ radios: [
+ {
+ id: 'bacon-other',
+ label: {
+ text: 'Bacon',
+ },
+ value: 'bacon',
},
- value: 'bacon',
- },
- {
- id: 'inner-olives-other',
- label: {
- text: 'Olives',
+ {
+ id: 'olives-other',
+ label: {
+ text: 'Olives',
+ },
+ value: 'olives',
+ },
+ {
+ id: 'other-radio',
+ label: {
+ text: 'Other',
+ description: 'An answer is required',
+ },
+ value: 'other',
+ other: {
+ id: 'other-textbox',
+ name: 'other-answer',
+ legend: 'Please specify other',
+ otherType: 'checkboxes',
+ checkboxes: [
+ {
+ id: 'inner-bacon-other',
+ label: {
+ text: 'Bacon',
+ },
+ value: 'bacon',
+ },
+ {
+ id: 'inner-olives-other',
+ label: {
+ text: 'Olives',
+ },
+ value: 'olives',
+ },
+ ],
+ },
},
- value: 'olives',
- },
],
- },
- },
- ],
- };
-
- beforeEach(async () => {
- await setTestPage('/test', renderComponent('radios', params));
- });
+ };
- it('radios with other options should be given aria-expanded attributes', async () => {
- const ariaExpanded = await page.$eval('#other-radio', (node) => node.getAttribute('aria-expanded'));
- expect(ariaExpanded).toBe('false');
- });
-
- describe('and a radio with an other input is checked', () => {
- beforeEach(async () => {
- await page.click('#other-radio');
- });
-
- it('has aria-expanded attribute should be set to true', async () => {
- const ariaExpanded = await page.$eval('#other-radio', (node) => node.getAttribute('aria-expanded'));
- expect(ariaExpanded).toBe('true');
- });
-
- describe('and any other radio is changed', () => {
beforeEach(async () => {
- await page.click('#bacon-other');
- });
-
- it('the radio with an other input aria-expanded attribute changes', async () => {
- const ariaExpanded = await page.$eval('#other-radio', (node) => node.getAttribute('aria-expanded'));
- expect(ariaExpanded).toBe('false');
+ await setTestPage('/test', renderComponent('radios', params));
});
- });
- describe('and a child of other radio is checked', () => {
- beforeEach(async () => {
- await page.click('#inner-bacon-other');
- });
-
- describe('and another radio is checked', () => {
- beforeEach(async () => {
- await page.click('#olives-other');
- });
-
- it('the other radio aria-expanded attribute should be set to false', async () => {
+ it('radios with other options should be given aria-expanded attributes', async () => {
const ariaExpanded = await page.$eval('#other-radio', (node) => node.getAttribute('aria-expanded'));
expect(ariaExpanded).toBe('false');
- });
+ });
- it('the child of other checkbox should be unchecked', async () => {
- const innerInputChecked = await page.$eval('#inner-bacon-other', (node) => node.checked);
- expect(innerInputChecked).toBe(false);
- });
+ describe('and a radio with an other input is checked', () => {
+ beforeEach(async () => {
+ await page.click('#other-radio');
+ });
+
+ it('has aria-expanded attribute should be set to true', async () => {
+ const ariaExpanded = await page.$eval('#other-radio', (node) => node.getAttribute('aria-expanded'));
+ expect(ariaExpanded).toBe('true');
+ });
+
+ describe('and any other radio is changed', () => {
+ beforeEach(async () => {
+ await page.click('#bacon-other');
+ });
+
+ it('the radio with an other input aria-expanded attribute changes', async () => {
+ const ariaExpanded = await page.$eval('#other-radio', (node) => node.getAttribute('aria-expanded'));
+ expect(ariaExpanded).toBe('false');
+ });
+ });
+
+ describe('and a child of other radio is checked', () => {
+ beforeEach(async () => {
+ await page.click('#inner-bacon-other');
+ });
+
+ describe('and another radio is checked', () => {
+ beforeEach(async () => {
+ await page.click('#olives-other');
+ });
+
+ it('the other radio aria-expanded attribute should be set to false', async () => {
+ const ariaExpanded = await page.$eval('#other-radio', (node) => node.getAttribute('aria-expanded'));
+ expect(ariaExpanded).toBe('false');
+ });
+
+ it('the child of other checkbox should be unchecked', async () => {
+ const innerInputChecked = await page.$eval('#inner-bacon-other', (node) => node.checked);
+ expect(innerInputChecked).toBe(false);
+ });
+ });
+ });
});
- });
});
- });
});
diff --git a/src/components/related-content/_macro.spec.js b/src/components/related-content/_macro.spec.js
index 333ed296c9..58de76c69d 100644
--- a/src/components/related-content/_macro.spec.js
+++ b/src/components/related-content/_macro.spec.js
@@ -7,125 +7,125 @@ import { mapAll } from '../../tests/helpers/cheerio';
import { renderComponent, templateFaker } from '../../tests/helpers/rendering';
const EXAMPLE_RELATED_CONTENT_GENERAL = {
- title: 'Related information',
- id: 'related-general-content',
+ title: 'Related information',
+ id: 'related-general-content',
};
const EXAMPLE_RELATED_CONTENT_LINKS = {
- rows: [
- {
- id: 'related-articles',
- title: 'Related articles',
- iconPosition: 'before',
- iconSize: 'xl',
- itemsList: [
- { text: 'First', url: '/article/first' },
- { text: 'Second', url: '/article/second' },
- ],
- },
- {
- id: 'related-links',
- title: 'Related links',
- iconPosition: 'after',
- iconSize: 'xxl',
- itemsList: [
- { text: 'A', url: '/article/a' },
- { text: 'B', url: '/article/b' },
- ],
- },
- ],
+ rows: [
+ {
+ id: 'related-articles',
+ title: 'Related articles',
+ iconPosition: 'before',
+ iconSize: 'xl',
+ itemsList: [
+ { text: 'First', url: '/article/first' },
+ { text: 'Second', url: '/article/second' },
+ ],
+ },
+ {
+ id: 'related-links',
+ title: 'Related links',
+ iconPosition: 'after',
+ iconSize: 'xxl',
+ itemsList: [
+ { text: 'A', url: '/article/a' },
+ { text: 'B', url: '/article/b' },
+ ],
+ },
+ ],
};
describe('macro: related-content', () => {
- describe.each([
- ['general content', EXAMPLE_RELATED_CONTENT_GENERAL],
- ['list of links', EXAMPLE_RELATED_CONTENT_LINKS],
- ])('mode: %s', (_, params) => {
- it('passes jest-axe checks', async () => {
- const $ = cheerio.load(renderComponent('related-content', params));
-
- const results = await axe($.html());
- expect(results).toHaveNoViolations();
+ describe.each([
+ ['general content', EXAMPLE_RELATED_CONTENT_GENERAL],
+ ['list of links', EXAMPLE_RELATED_CONTENT_LINKS],
+ ])('mode: %s', (_, params) => {
+ it('passes jest-axe checks', async () => {
+ const $ = cheerio.load(renderComponent('related-content', params));
+
+ const results = await axe($.html());
+ expect(results).toHaveNoViolations();
+ });
+
+ it('has a default `aria-label` of "Related content"', () => {
+ const $ = cheerio.load(renderComponent('related-content', params));
+
+ expect($('.ons-related-content').attr('aria-label')).toBe('Related content');
+ });
+
+ it('has the provided `aria-label`', () => {
+ const $ = cheerio.load(
+ renderComponent('related-content', {
+ ...params,
+ ariaLabel: 'Related articles',
+ }),
+ );
+
+ expect($('.ons-related-content').attr('aria-label')).toBe('Related articles');
+ });
+
+ it('has additionally provided style classes', () => {
+ const $ = cheerio.load(
+ renderComponent('related-content', {
+ ...params,
+ classes: 'extra-class another-extra-class',
+ }),
+ );
+
+ expect($('.ons-related-content').hasClass('extra-class')).toBe(true);
+ expect($('.ons-related-content').hasClass('another-extra-class')).toBe(true);
+ });
});
- it('has a default `aria-label` of "Related content"', () => {
- const $ = cheerio.load(renderComponent('related-content', params));
+ describe('mode: general content', () => {
+ it('calls with content', () => {
+ const $ = cheerio.load(renderComponent('related-content', { EXAMPLE_RELATED_CONTENT_GENERAL }, 'Example content...'));
- expect($('.ons-related-content').attr('aria-label')).toBe('Related content');
+ const content = $('.ons-related-content').text().trim();
+ expect(content).toEqual(expect.stringContaining('Example content...'));
+ });
});
- it('has the provided `aria-label`', () => {
- const $ = cheerio.load(
- renderComponent('related-content', {
- ...params,
- ariaLabel: 'Related articles',
- }),
- );
-
- expect($('.ons-related-content').attr('aria-label')).toBe('Related articles');
- });
-
- it('has additionally provided style classes', () => {
- const $ = cheerio.load(
- renderComponent('related-content', {
- ...params,
- classes: 'extra-class another-extra-class',
- }),
- );
-
- expect($('.ons-related-content').hasClass('extra-class')).toBe(true);
- expect($('.ons-related-content').hasClass('another-extra-class')).toBe(true);
- });
- });
-
- describe('mode: general content', () => {
- it('calls with content', () => {
- const $ = cheerio.load(renderComponent('related-content', { EXAMPLE_RELATED_CONTENT_GENERAL }, 'Example content...'));
-
- const content = $('.ons-related-content').text().trim();
- expect(content).toEqual(expect.stringContaining('Example content...'));
- });
- });
-
- describe('mode: list of links', () => {
- it('renders the expected output using the related-content/section macro', () => {
- const faker = templateFaker();
- const sectionSpy = faker.spy('related-content/section');
-
- faker.renderComponent('related-content', EXAMPLE_RELATED_CONTENT_LINKS);
-
- expect(sectionSpy.occurrences[0]).toHaveProperty('title', 'Related articles');
- expect(sectionSpy.occurrences[0]).toHaveProperty('id', 'related-articles');
- expect(sectionSpy.occurrences[1]).toHaveProperty('title', 'Related links');
- expect(sectionSpy.occurrences[1]).toHaveProperty('id', 'related-links');
- });
-
- it('has the `aria-labelledby` attribute for each section of links', () => {
- const $ = cheerio.load(renderComponent('related-content', EXAMPLE_RELATED_CONTENT_LINKS));
-
- const values = mapAll($('.ons-related-content__navigation'), (node) => node.attr('aria-labelledby'));
- expect(values).toEqual(['related-articles', 'related-links']);
- });
-
- it('renders the expected list items using the list macro', () => {
- const faker = templateFaker();
- const listsSpy = faker.spy('list');
-
- faker.renderComponent('related-content', EXAMPLE_RELATED_CONTENT_LINKS);
-
- expect(listsSpy.occurrences[0]).toHaveProperty('iconPosition', 'before');
- expect(listsSpy.occurrences[0]).toHaveProperty('iconSize', 'xl');
- expect(listsSpy.occurrences[0]).toHaveProperty('itemsList', [
- { text: 'First', url: '/article/first' },
- { text: 'Second', url: '/article/second' },
- ]);
-
- expect(listsSpy.occurrences[1]).toHaveProperty('iconPosition', 'after');
- expect(listsSpy.occurrences[1]).toHaveProperty('iconSize', 'xxl');
- expect(listsSpy.occurrences[1]).toHaveProperty('itemsList', [
- { text: 'A', url: '/article/a' },
- { text: 'B', url: '/article/b' },
- ]);
+ describe('mode: list of links', () => {
+ it('renders the expected output using the related-content/section macro', () => {
+ const faker = templateFaker();
+ const sectionSpy = faker.spy('related-content/section');
+
+ faker.renderComponent('related-content', EXAMPLE_RELATED_CONTENT_LINKS);
+
+ expect(sectionSpy.occurrences[0]).toHaveProperty('title', 'Related articles');
+ expect(sectionSpy.occurrences[0]).toHaveProperty('id', 'related-articles');
+ expect(sectionSpy.occurrences[1]).toHaveProperty('title', 'Related links');
+ expect(sectionSpy.occurrences[1]).toHaveProperty('id', 'related-links');
+ });
+
+ it('has the `aria-labelledby` attribute for each section of links', () => {
+ const $ = cheerio.load(renderComponent('related-content', EXAMPLE_RELATED_CONTENT_LINKS));
+
+ const values = mapAll($('.ons-related-content__navigation'), (node) => node.attr('aria-labelledby'));
+ expect(values).toEqual(['related-articles', 'related-links']);
+ });
+
+ it('renders the expected list items using the list macro', () => {
+ const faker = templateFaker();
+ const listsSpy = faker.spy('list');
+
+ faker.renderComponent('related-content', EXAMPLE_RELATED_CONTENT_LINKS);
+
+ expect(listsSpy.occurrences[0]).toHaveProperty('iconPosition', 'before');
+ expect(listsSpy.occurrences[0]).toHaveProperty('iconSize', 'xl');
+ expect(listsSpy.occurrences[0]).toHaveProperty('itemsList', [
+ { text: 'First', url: '/article/first' },
+ { text: 'Second', url: '/article/second' },
+ ]);
+
+ expect(listsSpy.occurrences[1]).toHaveProperty('iconPosition', 'after');
+ expect(listsSpy.occurrences[1]).toHaveProperty('iconSize', 'xxl');
+ expect(listsSpy.occurrences[1]).toHaveProperty('itemsList', [
+ { text: 'A', url: '/article/a' },
+ { text: 'B', url: '/article/b' },
+ ]);
+ });
});
- });
});
diff --git a/src/components/related-content/_related-content.scss b/src/components/related-content/_related-content.scss
index a42efc92e2..bf68ef981d 100644
--- a/src/components/related-content/_related-content.scss
+++ b/src/components/related-content/_related-content.scss
@@ -1,18 +1,18 @@
.ons-related-content {
- @extend .ons-u-mt-m;
+ @extend .ons-u-mt-m;
- border-top: 5px solid var(--ons-color-branded);
- padding-top: 1rem;
+ border-top: 5px solid var(--ons-color-branded);
+ padding-top: 1rem;
- &__section {
- & + & {
- border-top: 1px solid var(--ons-color-grey-75);
- margin: 2rem 0 0;
- padding: 2em 0 0;
- }
+ &__section {
+ & + & {
+ border-top: 1px solid var(--ons-color-grey-75);
+ margin: 2rem 0 0;
+ padding: 2em 0 0;
+ }
- > :last-child {
- margin-bottom: 0;
+ > :last-child {
+ margin-bottom: 0;
+ }
}
- }
}
diff --git a/src/components/related-content/_section-macro.spec.js b/src/components/related-content/_section-macro.spec.js
index 84a6e80976..89f5c0f29b 100644
--- a/src/components/related-content/_section-macro.spec.js
+++ b/src/components/related-content/_section-macro.spec.js
@@ -6,32 +6,32 @@ import axe from '../../tests/helpers/axe';
import { renderComponent } from '../../tests/helpers/rendering';
const EXAMPLE_RELATED_SECTION_CONTENT = {
- title: 'Related information',
- id: 'related-general-content',
+ title: 'Related information',
+ id: 'related-general-content',
};
describe('macro: related-content/section', () => {
- it('passes jest-axe checks', async () => {
- const $ = cheerio.load(renderComponent('related-content/section', EXAMPLE_RELATED_SECTION_CONTENT));
+ it('passes jest-axe checks', async () => {
+ const $ = cheerio.load(renderComponent('related-content/section', EXAMPLE_RELATED_SECTION_CONTENT));
- const results = await axe($.html());
- expect(results).toHaveNoViolations();
- });
+ const results = await axe($.html());
+ expect(results).toHaveNoViolations();
+ });
- it('has the provided `title` text', () => {
- const $ = cheerio.load(renderComponent('related-content/section', EXAMPLE_RELATED_SECTION_CONTENT));
- expect($('.ons-related-content__title').text().trim()).toEqual('Related information');
- });
+ it('has the provided `title` text', () => {
+ const $ = cheerio.load(renderComponent('related-content/section', EXAMPLE_RELATED_SECTION_CONTENT));
+ expect($('.ons-related-content__title').text().trim()).toEqual('Related information');
+ });
- it('has the `id` attribute for the title', () => {
- const $ = cheerio.load(renderComponent('related-content/section', EXAMPLE_RELATED_SECTION_CONTENT));
- expect($('#related-general-content').length).toBe(1);
- });
+ it('has the `id` attribute for the title', () => {
+ const $ = cheerio.load(renderComponent('related-content/section', EXAMPLE_RELATED_SECTION_CONTENT));
+ expect($('#related-general-content').length).toBe(1);
+ });
- it('has the provided content', () => {
- const $ = cheerio.load(renderComponent('related-content/section', { EXAMPLE_RELATED_SECTION_CONTENT }, 'Example content...'));
+ it('has the provided content', () => {
+ const $ = cheerio.load(renderComponent('related-content/section', { EXAMPLE_RELATED_SECTION_CONTENT }, 'Example content...'));
- const content = $('.ons-related-content__content').text().trim();
- expect(content).toEqual(expect.stringContaining('Example content...'));
- });
+ const content = $('.ons-related-content__content').text().trim();
+ expect(content).toEqual(expect.stringContaining('Example content...'));
+ });
});
diff --git a/src/components/relationships/_macro.spec.js b/src/components/relationships/_macro.spec.js
index d86b296893..9f3003c439 100644
--- a/src/components/relationships/_macro.spec.js
+++ b/src/components/relationships/_macro.spec.js
@@ -6,101 +6,101 @@ import axe from '../../tests/helpers/axe';
import { renderComponent, templateFaker } from '../../tests/helpers/rendering';
const EXAMPLE_RELATIONSHIPS = {
- playback: "Amanda Bloggs is Joe Bloggs'
… ",
- name: 'relationship',
- dontWrap: true,
- legendIsQuestionTitle: true,
- radios: [
- {
- id: 'grandparent',
- value: 'grandparent',
- label: {
- text: 'Grandparent',
- },
- attributes: {
- 'data-title': 'Thinking of Joe Bloggs, Amanda Bloggs is their
grandparents ',
- 'data-playback': "Amanda Bloggs is Joe Bloggs'
grandparents ",
- },
- },
- {
- id: 'other-relation',
- value: 'other-relation',
- label: {
- text: 'Other relation',
- },
- attributes: {
- 'data-title': 'Thinking of Joe Bloggs, Amanda Bloggs is their
other relation ',
- 'data-playback': "Amanda Bloggs is Joe Bloggs'
other relation ",
- },
- },
- {
- id: 'unrelated',
- value: 'unrelated',
- label: {
- text: 'Unrelated',
- description: 'Including foster child',
- },
- attributes: {
- 'data-title': 'Thinking of Joe Bloggs, Amanda Bloggs is
unrelated to Joe Bloggs',
- 'data-playback': 'Amanda Bloggs is
unrelated to Joe Bloggs',
- },
- },
- ],
+ playback: "Amanda Bloggs is Joe Bloggs'
… ",
+ name: 'relationship',
+ dontWrap: true,
+ legendIsQuestionTitle: true,
+ radios: [
+ {
+ id: 'grandparent',
+ value: 'grandparent',
+ label: {
+ text: 'Grandparent',
+ },
+ attributes: {
+ 'data-title': 'Thinking of Joe Bloggs, Amanda Bloggs is their
grandparents ',
+ 'data-playback': "Amanda Bloggs is Joe Bloggs'
grandparents ",
+ },
+ },
+ {
+ id: 'other-relation',
+ value: 'other-relation',
+ label: {
+ text: 'Other relation',
+ },
+ attributes: {
+ 'data-title': 'Thinking of Joe Bloggs, Amanda Bloggs is their
other relation ',
+ 'data-playback': "Amanda Bloggs is Joe Bloggs'
other relation ",
+ },
+ },
+ {
+ id: 'unrelated',
+ value: 'unrelated',
+ label: {
+ text: 'Unrelated',
+ description: 'Including foster child',
+ },
+ attributes: {
+ 'data-title': 'Thinking of Joe Bloggs, Amanda Bloggs is
unrelated to Joe Bloggs',
+ 'data-playback': 'Amanda Bloggs is
unrelated to Joe Bloggs',
+ },
+ },
+ ],
};
describe('macro: relationships', () => {
- it('passes jest-axe checks', async () => {
- const $ = cheerio.load(renderComponent('relationships', EXAMPLE_RELATIONSHIPS));
-
- const results = await axe($.html());
- expect(results).toHaveNoViolations();
- });
-
- it('has the expected `id` attribute', () => {
- const $ = cheerio.load(
- renderComponent('relationships', {
- ...EXAMPLE_RELATIONSHIPS,
- id: 'example-relationships',
- }),
- );
-
- expect($('.ons-relationships').attr('id')).toBe('example-relationships');
- });
-
- it('has additionally provided style classes', () => {
- const $ = cheerio.load(
- renderComponent('relationships', {
- ...EXAMPLE_RELATIONSHIPS,
- classes: 'extra-class another-extra-class',
- }),
- );
-
- expect($('.ons-relationships').hasClass('extra-class')).toBe(true);
- expect($('.ons-relationships').hasClass('another-extra-class')).toBe(true);
- });
-
- it('has the provided `playback` text', () => {
- const $ = cheerio.load(renderComponent('relationships', EXAMPLE_RELATIONSHIPS));
-
- const playbackContent = $('.ons-relationships__playback').html().trim();
- expect(playbackContent).toBe("Amanda Bloggs is Joe Bloggs'
… ");
- });
-
- it('has playback paragraph hidden initially', async () => {
- const $ = cheerio.load(renderComponent('relationships', EXAMPLE_RELATIONSHIPS));
-
- expect($('.ons-relationships__playback').hasClass('ons-u-d-no')).toBe(true);
- });
-
- it('renders the expected radio items the radios macro', () => {
- const faker = templateFaker();
- const radiosSpy = faker.spy('radios');
-
- faker.renderComponent('relationships', EXAMPLE_RELATIONSHIPS);
-
- expect(radiosSpy.occurrences[0]).toHaveProperty('name', 'relationship');
- expect(radiosSpy.occurrences[0]).toHaveProperty('dontWrap', true);
- expect(radiosSpy.occurrences[0]).toHaveProperty('legendIsQuestionTitle', true);
- expect(radiosSpy.occurrences[0]).toHaveProperty('radios', EXAMPLE_RELATIONSHIPS.radios);
- });
+ it('passes jest-axe checks', async () => {
+ const $ = cheerio.load(renderComponent('relationships', EXAMPLE_RELATIONSHIPS));
+
+ const results = await axe($.html());
+ expect(results).toHaveNoViolations();
+ });
+
+ it('has the expected `id` attribute', () => {
+ const $ = cheerio.load(
+ renderComponent('relationships', {
+ ...EXAMPLE_RELATIONSHIPS,
+ id: 'example-relationships',
+ }),
+ );
+
+ expect($('.ons-relationships').attr('id')).toBe('example-relationships');
+ });
+
+ it('has additionally provided style classes', () => {
+ const $ = cheerio.load(
+ renderComponent('relationships', {
+ ...EXAMPLE_RELATIONSHIPS,
+ classes: 'extra-class another-extra-class',
+ }),
+ );
+
+ expect($('.ons-relationships').hasClass('extra-class')).toBe(true);
+ expect($('.ons-relationships').hasClass('another-extra-class')).toBe(true);
+ });
+
+ it('has the provided `playback` text', () => {
+ const $ = cheerio.load(renderComponent('relationships', EXAMPLE_RELATIONSHIPS));
+
+ const playbackContent = $('.ons-relationships__playback').html().trim();
+ expect(playbackContent).toBe("Amanda Bloggs is Joe Bloggs'
… ");
+ });
+
+ it('has playback paragraph hidden initially', async () => {
+ const $ = cheerio.load(renderComponent('relationships', EXAMPLE_RELATIONSHIPS));
+
+ expect($('.ons-relationships__playback').hasClass('ons-u-d-no')).toBe(true);
+ });
+
+ it('renders the expected radio items the radios macro', () => {
+ const faker = templateFaker();
+ const radiosSpy = faker.spy('radios');
+
+ faker.renderComponent('relationships', EXAMPLE_RELATIONSHIPS);
+
+ expect(radiosSpy.occurrences[0]).toHaveProperty('name', 'relationship');
+ expect(radiosSpy.occurrences[0]).toHaveProperty('dontWrap', true);
+ expect(radiosSpy.occurrences[0]).toHaveProperty('legendIsQuestionTitle', true);
+ expect(radiosSpy.occurrences[0]).toHaveProperty('radios', EXAMPLE_RELATIONSHIPS.radios);
+ });
});
diff --git a/src/components/relationships/_relationships.scss b/src/components/relationships/_relationships.scss
index 0a442caffb..274448a03a 100644
--- a/src/components/relationships/_relationships.scss
+++ b/src/components/relationships/_relationships.scss
@@ -1,13 +1,13 @@
.ons-relationships {
- &__playback {
- border-bottom: 1px solid var(--ons-color-input-border);
- border-top: 1px solid var(--ons-color-input-border);
- margin: 2rem 0 0;
- padding: 1rem 0;
+ &__playback {
+ border-bottom: 1px solid var(--ons-color-input-border);
+ border-top: 1px solid var(--ons-color-input-border);
+ margin: 2rem 0 0;
+ padding: 1rem 0;
- @include mq('m') {
- display: inline-block;
- min-width: 20rem;
+ @include mq('m') {
+ display: inline-block;
+ min-width: 20rem;
+ }
}
- }
}
diff --git a/src/components/relationships/relationships.dom.js b/src/components/relationships/relationships.dom.js
index 77126cbccd..39843b48a5 100644
--- a/src/components/relationships/relationships.dom.js
+++ b/src/components/relationships/relationships.dom.js
@@ -1,11 +1,11 @@
import domready from '../../js/domready';
domready(async () => {
- const relationships = [...document.querySelectorAll('.ons-js-relationships')];
+ const relationships = [...document.querySelectorAll('.ons-js-relationships')];
- if (relationships.length) {
- const Relationships = (await import('./relationships')).default;
+ if (relationships.length) {
+ const Relationships = (await import('./relationships')).default;
- relationships.forEach((element) => new Relationships(element));
- }
+ relationships.forEach((element) => new Relationships(element));
+ }
});
diff --git a/src/components/relationships/relationships.js b/src/components/relationships/relationships.js
index 6bcfd37d32..d9cb2b9094 100644
--- a/src/components/relationships/relationships.js
+++ b/src/components/relationships/relationships.js
@@ -1,28 +1,28 @@
export default class Relationships {
- constructor(context) {
- this.context = context;
- this.legend = document.getElementById('question-title')
- ? document.getElementById('question-title')
- : document.getElementById('fieldset-legend-title');
- this.radios = [...context.querySelectorAll('input[type=radio]')];
- this.playback = context.querySelector('.ons-js-relationships-playback');
+ constructor(context) {
+ this.context = context;
+ this.legend = document.getElementById('question-title')
+ ? document.getElementById('question-title')
+ : document.getElementById('fieldset-legend-title');
+ this.radios = [...context.querySelectorAll('input[type=radio]')];
+ this.playback = context.querySelector('.ons-js-relationships-playback');
- this.radios.forEach((radio) => radio.addEventListener('change', this.setPlayback.bind(this)));
+ this.radios.forEach((radio) => radio.addEventListener('change', this.setPlayback.bind(this)));
- this.setPlayback();
+ this.setPlayback();
- this.playback.classList.remove('ons-u-d-no');
- }
+ this.playback.classList.remove('ons-u-d-no');
+ }
- setPlayback() {
- const radio = this.radios.find((radio) => radio.checked);
+ setPlayback() {
+ const radio = this.radios.find((radio) => radio.checked);
- if (radio) {
- const title = radio.getAttribute('data-title');
+ if (radio) {
+ const title = radio.getAttribute('data-title');
- this.legend.innerHTML = title;
+ this.legend.innerHTML = title;
- this.playback.innerHTML = radio.getAttribute('data-playback');
+ this.playback.innerHTML = radio.getAttribute('data-playback');
+ }
}
- }
}
diff --git a/src/components/relationships/relationships.spec.js b/src/components/relationships/relationships.spec.js
index 0e259af85d..fc02de31a1 100644
--- a/src/components/relationships/relationships.spec.js
+++ b/src/components/relationships/relationships.spec.js
@@ -1,84 +1,84 @@
import { renderComponent, setTestPage } from '../../tests/helpers/rendering';
const EXAMPLE_RELATIONSHIPS = {
- dontWrap: true,
- playback: "Amanda Bloggs is Joe Bloggs'
… ",
- name: 'relationship',
- radios: [
- {
- id: 'grandparent',
- value: 'grandparent',
- label: {
- text: 'Grandparent',
- },
- attributes: {
- 'data-title': 'Thinking of Joe Bloggs, Amanda Bloggs is their
grandparents ',
- 'data-playback': "Amanda Bloggs is Joe Bloggs'
grandparents ",
- },
- },
- {
- id: 'other-relation',
- value: 'other-relation',
- label: {
- text: 'Other relation',
- },
- attributes: {
- 'data-title': 'Thinking of Joe Bloggs, Amanda Bloggs is their
other relation ',
- 'data-playback': "Amanda Bloggs is Joe Bloggs'
other relation ",
- },
- },
- {
- id: 'unrelated',
- value: 'unrelated',
- label: {
- text: 'Unrelated',
- description: 'Including foster child',
- },
- attributes: {
- 'data-title': 'Thinking of Joe Bloggs, Amanda Bloggs is
unrelated to Joe Bloggs',
- 'data-playback': 'Amanda Bloggs is
unrelated to Joe Bloggs',
- },
- },
- ],
-};
-
-describe('script: relationships', () => {
- beforeEach(async () => {
- await setTestPage(
- '/test',
- renderComponent(
- 'question',
+ dontWrap: true,
+ playback: "Amanda Bloggs is Joe Bloggs'
… ",
+ name: 'relationship',
+ radios: [
{
- title: 'Thinking of Joe Bloggs, Amanda Bloggs is their
… ',
- readDescriptionFirst: true,
- legendIsQuestionTitle: true,
- legendTitleClasses: 'ons-js-relationships-legend',
+ id: 'grandparent',
+ value: 'grandparent',
+ label: {
+ text: 'Grandparent',
+ },
+ attributes: {
+ 'data-title': 'Thinking of Joe Bloggs, Amanda Bloggs is their
grandparents ',
+ 'data-playback': "Amanda Bloggs is Joe Bloggs'
grandparents ",
+ },
},
- [renderComponent('relationships', EXAMPLE_RELATIONSHIPS)],
- ),
- );
- });
-
- describe('when the component initialises', () => {
- it('then the playback paragraph should become visible', async () => {
- const hasHideClass = await page.$eval('.ons-relationships__playback', (node) => node.classList.contains('ons-u-d-no'));
- expect(hasHideClass).toBe(false);
- });
- });
+ {
+ id: 'other-relation',
+ value: 'other-relation',
+ label: {
+ text: 'Other relation',
+ },
+ attributes: {
+ 'data-title': 'Thinking of Joe Bloggs, Amanda Bloggs is their
other relation ',
+ 'data-playback': "Amanda Bloggs is Joe Bloggs'
other relation ",
+ },
+ },
+ {
+ id: 'unrelated',
+ value: 'unrelated',
+ label: {
+ text: 'Unrelated',
+ description: 'Including foster child',
+ },
+ attributes: {
+ 'data-title': 'Thinking of Joe Bloggs, Amanda Bloggs is
unrelated to Joe Bloggs',
+ 'data-playback': 'Amanda Bloggs is
unrelated to Joe Bloggs',
+ },
+ },
+ ],
+};
- describe('when the user selects a relationship', () => {
+describe('script: relationships', () => {
beforeEach(async () => {
- await page.click('#other-relation');
+ await setTestPage(
+ '/test',
+ renderComponent(
+ 'question',
+ {
+ title: 'Thinking of Joe Bloggs, Amanda Bloggs is their
… ',
+ readDescriptionFirst: true,
+ legendIsQuestionTitle: true,
+ legendTitleClasses: 'ons-js-relationships-legend',
+ },
+ [renderComponent('relationships', EXAMPLE_RELATIONSHIPS)],
+ ),
+ );
});
- it('the question title should be changed to reflect the relationship', async () => {
- const headingText = await page.$eval('h1', (element) => element.innerHTML);
- expect(headingText.trim()).toBe('Thinking of Joe Bloggs, Amanda Bloggs is their
other relation ');
+ describe('when the component initialises', () => {
+ it('then the playback paragraph should become visible', async () => {
+ const hasHideClass = await page.$eval('.ons-relationships__playback', (node) => node.classList.contains('ons-u-d-no'));
+ expect(hasHideClass).toBe(false);
+ });
});
- it('the playback should be changed to reflect the relationship', async () => {
- const playbackText = await page.$eval('.ons-relationships__playback', (element) => element.innerHTML);
- expect(playbackText.trim()).toBe("Amanda Bloggs is Joe Bloggs'
other relation ");
+ describe('when the user selects a relationship', () => {
+ beforeEach(async () => {
+ await page.click('#other-relation');
+ });
+
+ it('the question title should be changed to reflect the relationship', async () => {
+ const headingText = await page.$eval('h1', (element) => element.innerHTML);
+ expect(headingText.trim()).toBe('Thinking of Joe Bloggs, Amanda Bloggs is their
other relation ');
+ });
+
+ it('the playback should be changed to reflect the relationship', async () => {
+ const playbackText = await page.$eval('.ons-relationships__playback', (element) => element.innerHTML);
+ expect(playbackText.trim()).toBe("Amanda Bloggs is Joe Bloggs'
other relation ");
+ });
});
- });
});
diff --git a/src/components/reply/_macro.spec.js b/src/components/reply/_macro.spec.js
index 1b842af055..405711d8a7 100644
--- a/src/components/reply/_macro.spec.js
+++ b/src/components/reply/_macro.spec.js
@@ -6,70 +6,70 @@ import axe from '../../tests/helpers/axe';
import { renderComponent, templateFaker } from '../../tests/helpers/rendering';
const EXAMPLE_TEXTAREA = {
- id: 'reply-textarea',
- name: 'reply',
- label: {
- text: 'Reply',
- description: 'Reply description',
- },
- charCheckLimit: {
- limit: 300,
- charCountSingular: '{x} more character needed',
- charCountPlural: '{x} more characters needed',
- },
- rows: 5,
+ id: 'reply-textarea',
+ name: 'reply',
+ label: {
+ text: 'Reply',
+ description: 'Reply description',
+ },
+ charCheckLimit: {
+ limit: 300,
+ charCountSingular: '{x} more character needed',
+ charCountPlural: '{x} more characters needed',
+ },
+ rows: 5,
};
const EXAMPLE_BUTTON = {
- id: 'reply-button',
- type: 'button',
- text: 'Send message',
- classes: 'u-mb-xs',
+ id: 'reply-button',
+ type: 'button',
+ text: 'Send message',
+ classes: 'u-mb-xs',
};
const EXAMPLE_REPLY = {
- textarea: EXAMPLE_TEXTAREA,
- button: EXAMPLE_BUTTON,
- closeLinkText: 'Close conversation',
- closeLinkUrl: '/close-conversation',
+ textarea: EXAMPLE_TEXTAREA,
+ button: EXAMPLE_BUTTON,
+ closeLinkText: 'Close conversation',
+ closeLinkUrl: '/close-conversation',
};
describe('macro: reply', () => {
- it('passes jest-axe checks', async () => {
- const $ = cheerio.load(renderComponent('reply', EXAMPLE_REPLY));
+ it('passes jest-axe checks', async () => {
+ const $ = cheerio.load(renderComponent('reply', EXAMPLE_REPLY));
- const results = await axe($.html());
- expect(results).toHaveNoViolations();
- });
+ const results = await axe($.html());
+ expect(results).toHaveNoViolations();
+ });
- it('renders the provided `textarea` using the `textarea` component', () => {
- const faker = templateFaker();
- const textareaSpy = faker.spy('textarea');
+ it('renders the provided `textarea` using the `textarea` component', () => {
+ const faker = templateFaker();
+ const textareaSpy = faker.spy('textarea');
- cheerio.load(faker.renderComponent('reply', EXAMPLE_REPLY));
- expect(textareaSpy.occurrences[0]).toEqual(EXAMPLE_TEXTAREA);
- });
+ cheerio.load(faker.renderComponent('reply', EXAMPLE_REPLY));
+ expect(textareaSpy.occurrences[0]).toEqual(EXAMPLE_TEXTAREA);
+ });
- it('renders the provided `button` using the `button` component', () => {
- const faker = templateFaker();
- const buttonSpy = faker.spy('button');
+ it('renders the provided `button` using the `button` component', () => {
+ const faker = templateFaker();
+ const buttonSpy = faker.spy('button');
- cheerio.load(faker.renderComponent('reply', EXAMPLE_REPLY));
+ cheerio.load(faker.renderComponent('reply', EXAMPLE_REPLY));
- expect(buttonSpy.occurrences[0]).toEqual(EXAMPLE_BUTTON);
- });
+ expect(buttonSpy.occurrences[0]).toEqual(EXAMPLE_BUTTON);
+ });
- it('has the expected hyperlink URL', async () => {
- const $ = cheerio.load(renderComponent('reply', EXAMPLE_REPLY));
+ it('has the expected hyperlink URL', async () => {
+ const $ = cheerio.load(renderComponent('reply', EXAMPLE_REPLY));
- const $el = $('.ons-reply__link');
- expect($el.attr('href')).toBe(EXAMPLE_REPLY.closeLinkUrl);
- });
+ const $el = $('.ons-reply__link');
+ expect($el.attr('href')).toBe(EXAMPLE_REPLY.closeLinkUrl);
+ });
- it('has the expected link text', async () => {
- const $ = cheerio.load(renderComponent('reply', EXAMPLE_REPLY));
+ it('has the expected link text', async () => {
+ const $ = cheerio.load(renderComponent('reply', EXAMPLE_REPLY));
- const $el = $('.ons-reply__link');
- expect($el.text()).toBe(EXAMPLE_REPLY.closeLinkText);
- });
+ const $el = $('.ons-reply__link');
+ expect($el.text()).toBe(EXAMPLE_REPLY.closeLinkText);
+ });
});
diff --git a/src/components/reply/reply-input.js b/src/components/reply/reply-input.js
index eb3bdf7743..f3dd764684 100644
--- a/src/components/reply/reply-input.js
+++ b/src/components/reply/reply-input.js
@@ -1,21 +1,21 @@
export default class ReplyInput {
- constructor(context) {
- this.context = context;
- this.input = this.context.querySelector('.ons-input');
- this.button = this.context.querySelector('.ons-btn');
+ constructor(context) {
+ this.context = context;
+ this.input = this.context.querySelector('.ons-input');
+ this.button = this.context.querySelector('.ons-btn');
- this.enableDisableButton(this.input);
+ this.enableDisableButton(this.input);
- this.input.addEventListener('input', this.enableDisableButton.bind(this));
- }
+ this.input.addEventListener('input', this.enableDisableButton.bind(this));
+ }
- enableDisableButton() {
- if (this.input.value.trim() != '') {
- this.button.disabled = false;
- this.button.classList.remove('ons-btn--disabled');
- } else {
- this.button.disabled = true;
- this.button.classList.add('ons-btn--disabled');
+ enableDisableButton() {
+ if (this.input.value.trim() != '') {
+ this.button.disabled = false;
+ this.button.classList.remove('ons-btn--disabled');
+ } else {
+ this.button.disabled = true;
+ this.button.classList.add('ons-btn--disabled');
+ }
}
- }
}
diff --git a/src/components/reply/reply.dom.js b/src/components/reply/reply.dom.js
index 2d4ecddb0b..dd079f72a2 100644
--- a/src/components/reply/reply.dom.js
+++ b/src/components/reply/reply.dom.js
@@ -1,13 +1,13 @@
import domready from '../../js/domready';
async function initialise() {
- const replyInputs = [...document.querySelectorAll('.ons-js-reply')];
+ const replyInputs = [...document.querySelectorAll('.ons-js-reply')];
- if (replyInputs.length) {
- const ReplyInput = (await import('./reply-input')).default;
+ if (replyInputs.length) {
+ const ReplyInput = (await import('./reply-input')).default;
- replyInputs.forEach((input) => new ReplyInput(input));
- }
+ replyInputs.forEach((input) => new ReplyInput(input));
+ }
}
domready(initialise);
diff --git a/src/components/reply/reply.spec.js b/src/components/reply/reply.spec.js
index 826148080b..0a4c4a7cbd 100644
--- a/src/components/reply/reply.spec.js
+++ b/src/components/reply/reply.spec.js
@@ -1,78 +1,78 @@
import { renderComponent, setTestPage } from '../../tests/helpers/rendering';
const EXAMPLE_REPLY = {
- textarea: {
- id: 'reply-textarea',
- name: 'reply',
- label: {
- text: 'Reply',
+ textarea: {
+ id: 'reply-textarea',
+ name: 'reply',
+ label: {
+ text: 'Reply',
+ },
},
- },
- button: {
- id: 'reply-button',
- type: 'button',
- text: 'Send message',
- classes: 'u-mb-xs',
- },
- closeLinkText: 'Close conversation',
- closeLinkUrl: '/close-conversation',
+ button: {
+ id: 'reply-button',
+ type: 'button',
+ text: 'Send message',
+ classes: 'u-mb-xs',
+ },
+ closeLinkText: 'Close conversation',
+ closeLinkUrl: '/close-conversation',
};
describe('script: reply', () => {
- describe('scenario: Empty textarea', () => {
- it('the button is disabled', async () => {
- await setTestPage('/test', renderComponent('reply', EXAMPLE_REPLY));
+ describe('scenario: Empty textarea', () => {
+ it('the button is disabled', async () => {
+ await setTestPage('/test', renderComponent('reply', EXAMPLE_REPLY));
- const disabledButton = await page.evaluate(() => document.querySelector('#reply-button').disabled);
- expect(disabledButton).toBe(true);
- });
+ const disabledButton = await page.evaluate(() => document.querySelector('#reply-button').disabled);
+ expect(disabledButton).toBe(true);
+ });
- it('the button has classes applied', async () => {
- await setTestPage('/test', renderComponent('reply', EXAMPLE_REPLY));
- const disabledButton = await page.$eval('#reply-button', (element) => element.classList.contains('ons-btn--disabled'));
- expect(disabledButton).toBe(true);
+ it('the button has classes applied', async () => {
+ await setTestPage('/test', renderComponent('reply', EXAMPLE_REPLY));
+ const disabledButton = await page.$eval('#reply-button', (element) => element.classList.contains('ons-btn--disabled'));
+ expect(disabledButton).toBe(true);
+ });
});
- });
- describe('scenario: Filled textarea', () => {
- it('the button is enabled', async () => {
- await setTestPage('/test', renderComponent('reply', EXAMPLE_REPLY));
- await page.focus('#reply-textarea');
- await page.keyboard.type('Sausages');
+ describe('scenario: Filled textarea', () => {
+ it('the button is enabled', async () => {
+ await setTestPage('/test', renderComponent('reply', EXAMPLE_REPLY));
+ await page.focus('#reply-textarea');
+ await page.keyboard.type('Sausages');
- const disabledButton = await page.evaluate(() => document.querySelector('#reply-button').disabled);
- expect(disabledButton).toBe(false);
- });
+ const disabledButton = await page.evaluate(() => document.querySelector('#reply-button').disabled);
+ expect(disabledButton).toBe(false);
+ });
- it('the button has classes removed', async () => {
- await setTestPage('/test', renderComponent('reply', EXAMPLE_REPLY));
- await page.focus('#reply-textarea');
- await page.keyboard.type('Sausages');
+ it('the button has classes removed', async () => {
+ await setTestPage('/test', renderComponent('reply', EXAMPLE_REPLY));
+ await page.focus('#reply-textarea');
+ await page.keyboard.type('Sausages');
- const disabledButton = await page.$eval('#reply-button', (element) => element.classList.contains('ons-btn--disabled'));
- expect(disabledButton).toBe(false);
+ const disabledButton = await page.$eval('#reply-button', (element) => element.classList.contains('ons-btn--disabled'));
+ expect(disabledButton).toBe(false);
+ });
});
- });
- describe('scenario: Filled then emptied textarea', () => {
- it('the button is disabled', async () => {
- await setTestPage('/test', renderComponent('reply', EXAMPLE_REPLY));
- await page.focus('#reply-textarea');
- await page.keyboard.type('s');
- await page.keyboard.press('Backspace');
+ describe('scenario: Filled then emptied textarea', () => {
+ it('the button is disabled', async () => {
+ await setTestPage('/test', renderComponent('reply', EXAMPLE_REPLY));
+ await page.focus('#reply-textarea');
+ await page.keyboard.type('s');
+ await page.keyboard.press('Backspace');
- const disabledButton = await page.evaluate(() => document.querySelector('#reply-button').disabled);
- expect(disabledButton).toBe(true);
- });
+ const disabledButton = await page.evaluate(() => document.querySelector('#reply-button').disabled);
+ expect(disabledButton).toBe(true);
+ });
- it('the button has classes applied', async () => {
- await setTestPage('/test', renderComponent('reply', EXAMPLE_REPLY));
- await page.focus('#reply-textarea');
- await page.keyboard.type('s');
- await page.keyboard.press('Backspace');
+ it('the button has classes applied', async () => {
+ await setTestPage('/test', renderComponent('reply', EXAMPLE_REPLY));
+ await page.focus('#reply-textarea');
+ await page.keyboard.type('s');
+ await page.keyboard.press('Backspace');
- const disabledButton = await page.$eval('#reply-button', (element) => element.classList.contains('ons-btn--disabled'));
- expect(disabledButton).toBe(true);
+ const disabledButton = await page.$eval('#reply-button', (element) => element.classList.contains('ons-btn--disabled'));
+ expect(disabledButton).toBe(true);
+ });
});
- });
});
diff --git a/src/components/section-navigation/_macro.spec.js b/src/components/section-navigation/_macro.spec.js
index 307e399698..80d02dc491 100644
--- a/src/components/section-navigation/_macro.spec.js
+++ b/src/components/section-navigation/_macro.spec.js
@@ -7,251 +7,251 @@ import { mapAll } from '../../tests/helpers/cheerio';
import { renderComponent } from '../../tests/helpers/rendering';
const EXAMPLE_SECTION_NAVIGATION = {
- id: 'section-menu',
- currentPath: '/results',
- itemsList: [
- {
- title: 'Results',
- url: '/results',
- },
- {
- title: 'Dashboard',
- url: '/results/dashboard',
- },
- ],
+ id: 'section-menu',
+ currentPath: '/results',
+ itemsList: [
+ {
+ title: 'Results',
+ url: '/results',
+ },
+ {
+ title: 'Dashboard',
+ url: '/results/dashboard',
+ },
+ ],
};
const EXAMPLE_SECTION_NAVIGATION_VERTICAL = {
- variants: 'vertical',
- currentPath: '#section-2',
- itemsList: [
- {
- title: 'Section 1',
- url: '#section-1',
- },
- {
- title: 'Section 2',
- url: '#section-2',
- anchors: [
+ variants: 'vertical',
+ currentPath: '#section-2',
+ itemsList: [
{
- title: 'Sub section 1',
- url: '#sub-section-1',
+ title: 'Section 1',
+ url: '#section-1',
},
{
- title: 'Sub section 2',
- url: '#sub-section-2',
+ title: 'Section 2',
+ url: '#section-2',
+ anchors: [
+ {
+ title: 'Sub section 1',
+ url: '#sub-section-1',
+ },
+ {
+ title: 'Sub section 2',
+ url: '#sub-section-2',
+ },
+ {
+ title: 'Sub section 3',
+ url: '#sub-section-3',
+ },
+ ],
},
{
- title: 'Sub section 3',
- url: '#sub-section-3',
+ title: 'Section 3',
+ url: '#0',
},
- ],
- },
- {
- title: 'Section 3',
- url: '#0',
- },
- ],
+ ],
};
const EXAMPLE_SECTION_NAVIGATION_VERTICAL_WITH_SECTIONS = {
- variants: 'vertical',
- currentPath: '#section-2',
- sections: [
- {
- title: 'Section Title',
- itemsList: [
+ variants: 'vertical',
+ currentPath: '#section-2',
+ sections: [
{
- title: 'Section 1',
- url: '#section-1',
+ title: 'Section Title',
+ itemsList: [
+ {
+ title: 'Section 1',
+ url: '#section-1',
+ },
+ {
+ title: 'Section 2',
+ url: '#section-2',
+ anchors: [
+ {
+ title: 'Sub section 1',
+ url: '#sub-section-1',
+ },
+ {
+ title: 'Sub section 2',
+ url: '#sub-section-2',
+ },
+ {
+ title: 'Sub section 3',
+ url: '#sub-section-3',
+ },
+ ],
+ },
+ {
+ title: 'Section 3',
+ url: '#0',
+ },
+ ],
},
- {
- title: 'Section 2',
- url: '#section-2',
- anchors: [
- {
- title: 'Sub section 1',
- url: '#sub-section-1',
- },
- {
- title: 'Sub section 2',
- url: '#sub-section-2',
- },
- {
- title: 'Sub section 3',
- url: '#sub-section-3',
- },
- ],
- },
- {
- title: 'Section 3',
- url: '#0',
- },
- ],
- },
- ],
+ ],
};
describe('macro: section-navigation', () => {
- describe('variant: horizontal', () => {
- it('passes jest-axe checks', async () => {
- const $ = cheerio.load(renderComponent('section-navigation', EXAMPLE_SECTION_NAVIGATION));
-
- const results = await axe($.html());
- expect(results).toHaveNoViolations();
- });
-
- it('does not have the `vertical` modifier class', async () => {
- const $ = cheerio.load(renderComponent('section-navigation', EXAMPLE_SECTION_NAVIGATION));
-
- expect($('.ons-section-nav').hasClass('ons-section-nav--vertical')).toBe(false);
- });
- });
-
- describe('variant: vertical', () => {
- it('passes jest-axe checks', async () => {
- const $ = cheerio.load(renderComponent('section-navigation', EXAMPLE_SECTION_NAVIGATION_VERTICAL));
-
- const results = await axe($.html());
- expect(results).toHaveNoViolations();
- });
-
- it('has the `vertical` modifier class', async () => {
- const $ = cheerio.load(renderComponent('section-navigation', EXAMPLE_SECTION_NAVIGATION_VERTICAL));
-
- expect($('.ons-section-nav').hasClass('ons-section-nav--vertical')).toBe(true);
- });
- });
-
- it('has the provided `id` attribute', () => {
- const $ = cheerio.load(renderComponent('section-navigation', EXAMPLE_SECTION_NAVIGATION));
-
- expect($('.ons-section-nav').attr('id')).toBe('section-menu');
- });
-
- it('has the provided custom `classes`', () => {
- const $ = cheerio.load(renderComponent('section-navigation', { ...EXAMPLE_SECTION_NAVIGATION, classes: 'custom-class' }));
-
- expect($('.ons-section-nav').hasClass('custom-class')).toBe(true);
- });
+ describe('variant: horizontal', () => {
+ it('passes jest-axe checks', async () => {
+ const $ = cheerio.load(renderComponent('section-navigation', EXAMPLE_SECTION_NAVIGATION));
- it('assumes a default `hiddenTitleId` of "Section menu"', () => {
- const $ = cheerio.load(renderComponent('section-navigation', EXAMPLE_SECTION_NAVIGATION));
+ const results = await axe($.html());
+ expect(results).toHaveNoViolations();
+ });
- expect($('.ons-section-nav').attr('aria-labelledby')).toBe('section-menu-nav-title');
- });
-
- describe('navigation items', () => {
- it('renders top level navigation items', () => {
- const $ = cheerio.load(renderComponent('section-navigation', EXAMPLE_SECTION_NAVIGATION));
-
- const itemLabels = mapAll($('.ons-section-nav__item .ons-section-nav__link'), (node) => node.text().trim());
- expect(itemLabels).toEqual(['Results', 'Dashboard']);
-
- const itemLinks = mapAll($('.ons-section-nav__item .ons-section-nav__link'), (node) => node.attr('href'));
- expect(itemLinks).toEqual(['/results', '/results/dashboard']);
- });
+ it('does not have the `vertical` modifier class', async () => {
+ const $ = cheerio.load(renderComponent('section-navigation', EXAMPLE_SECTION_NAVIGATION));
- it('has additionally provided style classes', () => {
- const $ = cheerio.load(
- renderComponent('section-navigation', {
- currentPath: '#section-1',
- itemsList: [
- {
- classes: 'extra-class another-extra-class',
- title: 'Section 1',
- url: '#section-1',
- },
- ],
- }),
- );
-
- expect($('.ons-section-nav__item').hasClass('extra-class')).toBe(true);
- expect($('.ons-section-nav__item').hasClass('another-extra-class')).toBe(true);
+ expect($('.ons-section-nav').hasClass('ons-section-nav--vertical')).toBe(false);
+ });
});
- it('marks the current item with a class when `currentPath` is provided', () => {
- const $ = cheerio.load(renderComponent('section-navigation', EXAMPLE_SECTION_NAVIGATION));
+ describe('variant: vertical', () => {
+ it('passes jest-axe checks', async () => {
+ const $ = cheerio.load(renderComponent('section-navigation', EXAMPLE_SECTION_NAVIGATION_VERTICAL));
- expect($('.ons-section-nav__item--active').text().trim()).toBe('Results');
- });
+ const results = await axe($.html());
+ expect(results).toHaveNoViolations();
+ });
- it('marks the current item with a class when `tabQuery` is provided', () => {
- const $ = cheerio.load(
- renderComponent('section-navigation', {
- ...EXAMPLE_SECTION_NAVIGATION,
- currentPath: undefined,
- tabQuery: 'dashboard',
- }),
- );
+ it('has the `vertical` modifier class', async () => {
+ const $ = cheerio.load(renderComponent('section-navigation', EXAMPLE_SECTION_NAVIGATION_VERTICAL));
- expect($('.ons-section-nav__item--active').text().trim()).toBe('Dashboard');
+ expect($('.ons-section-nav').hasClass('ons-section-nav--vertical')).toBe(true);
+ });
});
- it('marks the current item with `aria-current` when `currentPath` is provided', () => {
- const $ = cheerio.load(renderComponent('section-navigation', EXAMPLE_SECTION_NAVIGATION));
+ it('has the provided `id` attribute', () => {
+ const $ = cheerio.load(renderComponent('section-navigation', EXAMPLE_SECTION_NAVIGATION));
- expect($('.ons-section-nav__item--active .ons-section-nav__link').attr('aria-current')).toBe('location');
+ expect($('.ons-section-nav').attr('id')).toBe('section-menu');
});
- it('marks the current item with `aria-current` when `tabQuery` is provided', () => {
- const $ = cheerio.load(
- renderComponent('section-navigation', {
- ...EXAMPLE_SECTION_NAVIGATION,
- currentPath: undefined,
- tabQuery: 'dashboard',
- }),
- );
+ it('has the provided custom `classes`', () => {
+ const $ = cheerio.load(renderComponent('section-navigation', { ...EXAMPLE_SECTION_NAVIGATION, classes: 'custom-class' }));
- expect($('.ons-section-nav__item--active .ons-section-nav__link').attr('aria-current')).toBe('location');
+ expect($('.ons-section-nav').hasClass('custom-class')).toBe(true);
});
- describe('nested anchor navigation items', () => {
- it('renders anchor navigation list for top-level items that define `anchors`', () => {
- const $ = cheerio.load(renderComponent('section-navigation', EXAMPLE_SECTION_NAVIGATION_VERTICAL));
-
- expect($('.ons-section-nav__list > .ons-section-nav__item:nth-child(1) .ons-section-nav__sub-items').length).toBe(0);
- expect($('.ons-section-nav__list > .ons-section-nav__item:nth-child(2) .ons-section-nav__sub-items').length).toBe(1);
- expect($('.ons-section-nav__list > .ons-section-nav__item:nth-child(3) .ons-section-nav__sub-items').length).toBe(0);
- });
-
- it('renders the expected anchor navigation items', () => {
- const $ = cheerio.load(renderComponent('section-navigation', EXAMPLE_SECTION_NAVIGATION_VERTICAL));
+ it('assumes a default `hiddenTitleId` of "Section menu"', () => {
+ const $ = cheerio.load(renderComponent('section-navigation', EXAMPLE_SECTION_NAVIGATION));
- const itemLabels = mapAll($('.ons-section-nav__sub-items .ons-section-nav__item .ons-section-nav__link'), (node) =>
- node.text().trim(),
- );
- expect(itemLabels).toEqual(['Sub section 1', 'Sub section 2', 'Sub section 3']);
-
- const itemLinks = mapAll($('.ons-section-nav__sub-items .ons-section-nav__item .ons-section-nav__link'), (node) =>
- node.attr('href'),
- );
- expect(itemLinks).toEqual(['#sub-section-1', '#sub-section-2', '#sub-section-3']);
- });
+ expect($('.ons-section-nav').attr('aria-labelledby')).toBe('section-menu-nav-title');
});
- describe('Section Items', () => {
- it('passes jest-axe checks', async () => {
- const $ = cheerio.load(renderComponent('section-navigation', EXAMPLE_SECTION_NAVIGATION_VERTICAL_WITH_SECTIONS));
-
- const results = await axe($.html());
- expect(results).toHaveNoViolations();
- });
-
- it('renders itemsLists, anchors and heading for each section', () => {
- const $ = cheerio.load(renderComponent('section-navigation', EXAMPLE_SECTION_NAVIGATION_VERTICAL_WITH_SECTIONS));
-
- const anchors = mapAll($('.ons-section-nav__sub-items .ons-section-nav__item .ons-section-nav__link'), (node) =>
- node.text().trim(),
- );
- expect(anchors).toEqual(['Sub section 1', 'Sub section 2', 'Sub section 3']);
-
- const itemLists = mapAll($('.ons-section-nav__item .ons-section-nav__link'), (node) => node.text().trim());
- expect(itemLists).toEqual(['Section 1', 'Section 2', 'Sub section 1', 'Sub section 2', 'Sub section 3', 'Section 3']);
-
- const headings = mapAll($('h3'), (node) => $(node).text().trim());
- expect(headings).toEqual(['Section Title']);
- });
+ describe('navigation items', () => {
+ it('renders top level navigation items', () => {
+ const $ = cheerio.load(renderComponent('section-navigation', EXAMPLE_SECTION_NAVIGATION));
+
+ const itemLabels = mapAll($('.ons-section-nav__item .ons-section-nav__link'), (node) => node.text().trim());
+ expect(itemLabels).toEqual(['Results', 'Dashboard']);
+
+ const itemLinks = mapAll($('.ons-section-nav__item .ons-section-nav__link'), (node) => node.attr('href'));
+ expect(itemLinks).toEqual(['/results', '/results/dashboard']);
+ });
+
+ it('has additionally provided style classes', () => {
+ const $ = cheerio.load(
+ renderComponent('section-navigation', {
+ currentPath: '#section-1',
+ itemsList: [
+ {
+ classes: 'extra-class another-extra-class',
+ title: 'Section 1',
+ url: '#section-1',
+ },
+ ],
+ }),
+ );
+
+ expect($('.ons-section-nav__item').hasClass('extra-class')).toBe(true);
+ expect($('.ons-section-nav__item').hasClass('another-extra-class')).toBe(true);
+ });
+
+ it('marks the current item with a class when `currentPath` is provided', () => {
+ const $ = cheerio.load(renderComponent('section-navigation', EXAMPLE_SECTION_NAVIGATION));
+
+ expect($('.ons-section-nav__item--active').text().trim()).toBe('Results');
+ });
+
+ it('marks the current item with a class when `tabQuery` is provided', () => {
+ const $ = cheerio.load(
+ renderComponent('section-navigation', {
+ ...EXAMPLE_SECTION_NAVIGATION,
+ currentPath: undefined,
+ tabQuery: 'dashboard',
+ }),
+ );
+
+ expect($('.ons-section-nav__item--active').text().trim()).toBe('Dashboard');
+ });
+
+ it('marks the current item with `aria-current` when `currentPath` is provided', () => {
+ const $ = cheerio.load(renderComponent('section-navigation', EXAMPLE_SECTION_NAVIGATION));
+
+ expect($('.ons-section-nav__item--active .ons-section-nav__link').attr('aria-current')).toBe('location');
+ });
+
+ it('marks the current item with `aria-current` when `tabQuery` is provided', () => {
+ const $ = cheerio.load(
+ renderComponent('section-navigation', {
+ ...EXAMPLE_SECTION_NAVIGATION,
+ currentPath: undefined,
+ tabQuery: 'dashboard',
+ }),
+ );
+
+ expect($('.ons-section-nav__item--active .ons-section-nav__link').attr('aria-current')).toBe('location');
+ });
+
+ describe('nested anchor navigation items', () => {
+ it('renders anchor navigation list for top-level items that define `anchors`', () => {
+ const $ = cheerio.load(renderComponent('section-navigation', EXAMPLE_SECTION_NAVIGATION_VERTICAL));
+
+ expect($('.ons-section-nav__list > .ons-section-nav__item:nth-child(1) .ons-section-nav__sub-items').length).toBe(0);
+ expect($('.ons-section-nav__list > .ons-section-nav__item:nth-child(2) .ons-section-nav__sub-items').length).toBe(1);
+ expect($('.ons-section-nav__list > .ons-section-nav__item:nth-child(3) .ons-section-nav__sub-items').length).toBe(0);
+ });
+
+ it('renders the expected anchor navigation items', () => {
+ const $ = cheerio.load(renderComponent('section-navigation', EXAMPLE_SECTION_NAVIGATION_VERTICAL));
+
+ const itemLabels = mapAll($('.ons-section-nav__sub-items .ons-section-nav__item .ons-section-nav__link'), (node) =>
+ node.text().trim(),
+ );
+ expect(itemLabels).toEqual(['Sub section 1', 'Sub section 2', 'Sub section 3']);
+
+ const itemLinks = mapAll($('.ons-section-nav__sub-items .ons-section-nav__item .ons-section-nav__link'), (node) =>
+ node.attr('href'),
+ );
+ expect(itemLinks).toEqual(['#sub-section-1', '#sub-section-2', '#sub-section-3']);
+ });
+ });
+
+ describe('Section Items', () => {
+ it('passes jest-axe checks', async () => {
+ const $ = cheerio.load(renderComponent('section-navigation', EXAMPLE_SECTION_NAVIGATION_VERTICAL_WITH_SECTIONS));
+
+ const results = await axe($.html());
+ expect(results).toHaveNoViolations();
+ });
+
+ it('renders itemsLists, anchors and heading for each section', () => {
+ const $ = cheerio.load(renderComponent('section-navigation', EXAMPLE_SECTION_NAVIGATION_VERTICAL_WITH_SECTIONS));
+
+ const anchors = mapAll($('.ons-section-nav__sub-items .ons-section-nav__item .ons-section-nav__link'), (node) =>
+ node.text().trim(),
+ );
+ expect(anchors).toEqual(['Sub section 1', 'Sub section 2', 'Sub section 3']);
+
+ const itemLists = mapAll($('.ons-section-nav__item .ons-section-nav__link'), (node) => node.text().trim());
+ expect(itemLists).toEqual(['Section 1', 'Section 2', 'Sub section 1', 'Sub section 2', 'Sub section 3', 'Section 3']);
+
+ const headings = mapAll($('h3'), (node) => $(node).text().trim());
+ expect(headings).toEqual(['Section Title']);
+ });
+ });
});
- });
});
diff --git a/src/components/section-navigation/_section-navigation.scss b/src/components/section-navigation/_section-navigation.scss
index 9d751a4c71..0228c5c63f 100644
--- a/src/components/section-navigation/_section-navigation.scss
+++ b/src/components/section-navigation/_section-navigation.scss
@@ -1,92 +1,92 @@
.ons-section-nav {
- &:not(&--vertical) {
- border-bottom: 1px solid var(--ons-color-borders);
- }
-
- &__list {
- list-style: none;
- margin: 0;
- padding: 0;
- }
-
- &__sub {
- padding-bottom: 0.5rem;
- padding-top: 0.5rem;
- }
+ &:not(&--vertical) {
+ border-bottom: 1px solid var(--ons-color-borders);
+ }
- &__link {
- text-decoration: none;
- &:hover {
- color: var(--ons-color-text-link-hover);
- text-decoration: underline solid var(--ons-color-text-link-hover) 2px;
+ &__list {
+ list-style: none;
+ margin: 0;
+ padding: 0;
}
- }
- &__item {
- font-size: 1rem;
- padding: 0;
- &--active {
- border-left: 4px solid var(--ons-color-text-link-active);
- font-weight: $font-weight-bold;
- margin-left: -18px;
- padding-left: 14px;
- a,
- h2,
- h3,
- h4,
- h5,
- h6 {
- color: var(--ons-color-text-link-active);
- }
+ &__sub {
+ padding-bottom: 0.5rem;
+ padding-top: 0.5rem;
}
- & & {
- margin-bottom: 0.3rem;
+
+ &__link {
+ text-decoration: none;
+ &:hover {
+ color: var(--ons-color-text-link-hover);
+ text-decoration: underline solid var(--ons-color-text-link-hover) 2px;
+ }
}
- }
- &__item-header {
- @extend .ons-u-mb-no;
+ &__item {
+ font-size: 1rem;
+ padding: 0;
+ &--active {
+ border-left: 4px solid var(--ons-color-text-link-active);
+ font-weight: $font-weight-bold;
+ margin-left: -18px;
+ padding-left: 14px;
+ a,
+ h2,
+ h3,
+ h4,
+ h5,
+ h6 {
+ color: var(--ons-color-text-link-active);
+ }
+ }
+ & & {
+ margin-bottom: 0.3rem;
+ }
+ }
- font-size: 1rem;
- font-weight: 700;
- line-height: 1.6;
- }
+ &__item-header {
+ @extend .ons-u-mb-no;
- &__sub-items {
- a {
- color: var(--ons-color-text-link);
- font-weight: $font-weight-regular;
+ font-size: 1rem;
+ font-weight: 700;
+ line-height: 1.6;
}
- }
- &:not(&--vertical) & {
- &__item {
- border: none;
- border-bottom: 4px solid transparent;
- display: inline-block;
- margin: 0 1rem 0 0;
- text-align: center;
- &--active,
- &:hover {
- border-color: var(--ons-color-text-link-active);
- margin: 0 1rem 0 0;
- padding: 0;
- a,
- h2,
- h3,
- h4,
- h5,
- h6 {
- color: var(--ons-color-text-link-active);
+ &__sub-items {
+ a {
+ color: var(--ons-color-text-link);
+ font-weight: $font-weight-regular;
}
- }
}
- &__link {
- text-decoration: none;
- &:hover {
- text-decoration: none;
- }
+ &:not(&--vertical) & {
+ &__item {
+ border: 0;
+ border-bottom: 4px solid transparent;
+ display: inline-block;
+ margin: 0 1rem 0 0;
+ text-align: center;
+ &--active,
+ &:hover {
+ border-color: var(--ons-color-text-link-active);
+ margin: 0 1rem 0 0;
+ padding: 0;
+ a,
+ h2,
+ h3,
+ h4,
+ h5,
+ h6 {
+ color: var(--ons-color-text-link-active);
+ }
+ }
+ }
+
+ &__link {
+ text-decoration: none;
+ &:hover {
+ text-decoration: none;
+ }
+ }
}
- }
}
diff --git a/src/components/select/_macro.spec.js b/src/components/select/_macro.spec.js
index e9e57d5f79..6c18935c59 100644
--- a/src/components/select/_macro.spec.js
+++ b/src/components/select/_macro.spec.js
@@ -7,197 +7,197 @@ import { mapAll } from '../../tests/helpers/cheerio';
import { renderComponent, templateFaker } from '../../tests/helpers/rendering';
const EXAMPLE_SELECT_MINIMAL = {
- id: 'example-select',
- name: 'example-select-name',
- label: {
- text: 'Label text',
- description: 'Description text',
- classes: 'extra-label-class',
- },
- options: [
- {
- text: 'First option',
- value: 1,
+ id: 'example-select',
+ name: 'example-select-name',
+ label: {
+ text: 'Label text',
+ description: 'Description text',
+ classes: 'extra-label-class',
},
- {
- id: 'second-option',
- text: 'Second option',
- value: 2,
- selected: true,
- },
- {
- text: 'Disabled option',
- value: 3,
- disabled: true,
- },
- ],
+ options: [
+ {
+ text: 'First option',
+ value: 1,
+ },
+ {
+ id: 'second-option',
+ text: 'Second option',
+ value: 2,
+ selected: true,
+ },
+ {
+ text: 'Disabled option',
+ value: 3,
+ disabled: true,
+ },
+ ],
};
const EXAMPLE_SELECT = {
- ...EXAMPLE_SELECT_MINIMAL,
- fieldId: 'example-select-field',
- fieldClasses: 'extra-field-class',
- legendClasses: 'extra-legend-class',
- dontWrap: true,
+ ...EXAMPLE_SELECT_MINIMAL,
+ fieldId: 'example-select-field',
+ fieldClasses: 'extra-field-class',
+ legendClasses: 'extra-legend-class',
+ dontWrap: true,
};
const EXAMPLE_SELECT_WITH_ERROR = {
- ...EXAMPLE_SELECT,
- error: {
- id: 'example-error',
- text: 'Error text...',
- },
+ ...EXAMPLE_SELECT,
+ error: {
+ id: 'example-error',
+ text: 'Error text...',
+ },
};
describe('macro: select', () => {
- it('passes jest-axe checks', async () => {
- const $ = cheerio.load(renderComponent('select', EXAMPLE_SELECT));
-
- const results = await axe($.html());
- expect(results).toHaveNoViolations();
- });
-
- it('passes jest-axe checks when error is shown', async () => {
- const $ = cheerio.load(renderComponent('select', EXAMPLE_SELECT_WITH_ERROR));
-
- const results = await axe($.html());
- expect(results).toHaveNoViolations();
- });
-
- it('provides expected parameters to the inner `field` component', () => {
- const faker = templateFaker();
- const fieldSpy = faker.spy('field');
-
- cheerio.load(faker.renderComponent('select', EXAMPLE_SELECT));
+ it('passes jest-axe checks', async () => {
+ const $ = cheerio.load(renderComponent('select', EXAMPLE_SELECT));
- expect(fieldSpy.occurrences[0]).toEqual({
- id: 'example-select-field',
- classes: 'extra-field-class',
- legendClasses: 'extra-legend-class',
- dontWrap: true,
- error: undefined,
+ const results = await axe($.html());
+ expect(results).toHaveNoViolations();
});
- });
- it('provides expected parameters to the inner `field` component when there is an error', () => {
- const faker = templateFaker();
- const fieldSpy = faker.spy('field');
+ it('passes jest-axe checks when error is shown', async () => {
+ const $ = cheerio.load(renderComponent('select', EXAMPLE_SELECT_WITH_ERROR));
- cheerio.load(faker.renderComponent('select', EXAMPLE_SELECT_WITH_ERROR));
-
- expect(fieldSpy.occurrences[0]).toEqual({
- id: 'example-select-field',
- classes: 'extra-field-class',
- legendClasses: 'extra-legend-class',
- dontWrap: true,
- error: {
- id: 'example-error',
- text: 'Error text...',
- },
+ const results = await axe($.html());
+ expect(results).toHaveNoViolations();
});
- });
- it('provides expected parameters to the inner `label` component', () => {
- const faker = templateFaker();
- const labelSpy = faker.spy('label');
+ it('provides expected parameters to the inner `field` component', () => {
+ const faker = templateFaker();
+ const fieldSpy = faker.spy('field');
- cheerio.load(faker.renderComponent('select', EXAMPLE_SELECT));
+ cheerio.load(faker.renderComponent('select', EXAMPLE_SELECT));
- expect(labelSpy.occurrences[0]).toEqual({
- for: 'example-select',
- text: 'Label text',
- description: 'Description text',
- classes: 'extra-label-class',
+ expect(fieldSpy.occurrences[0]).toEqual({
+ id: 'example-select-field',
+ classes: 'extra-field-class',
+ legendClasses: 'extra-legend-class',
+ dontWrap: true,
+ error: undefined,
+ });
});
- });
-
- describe('select element', () => {
- it('has the provided `id` attribute', () => {
- const $ = cheerio.load(renderComponent('select', EXAMPLE_SELECT));
- expect($('select').attr('id')).toBe('example-select');
+ it('provides expected parameters to the inner `field` component when there is an error', () => {
+ const faker = templateFaker();
+ const fieldSpy = faker.spy('field');
+
+ cheerio.load(faker.renderComponent('select', EXAMPLE_SELECT_WITH_ERROR));
+
+ expect(fieldSpy.occurrences[0]).toEqual({
+ id: 'example-select-field',
+ classes: 'extra-field-class',
+ legendClasses: 'extra-legend-class',
+ dontWrap: true,
+ error: {
+ id: 'example-error',
+ text: 'Error text...',
+ },
+ });
});
- it('has the provided `name` attribute', () => {
- const $ = cheerio.load(renderComponent('select', EXAMPLE_SELECT));
-
- expect($('select').attr('name')).toBe('example-select-name');
- });
+ it('provides expected parameters to the inner `label` component', () => {
+ const faker = templateFaker();
+ const labelSpy = faker.spy('label');
- it('has additionally provided style classes', () => {
- const $ = cheerio.load(
- renderComponent('select', {
- ...EXAMPLE_SELECT,
- classes: 'extra-class another-extra-class',
- }),
- );
+ cheerio.load(faker.renderComponent('select', EXAMPLE_SELECT));
- expect($('select').hasClass('extra-class')).toBe(true);
- expect($('select').hasClass('another-extra-class')).toBe(true);
+ expect(labelSpy.occurrences[0]).toEqual({
+ for: 'example-select',
+ text: 'Label text',
+ description: 'Description text',
+ classes: 'extra-label-class',
+ });
});
- it('has additionally provided `attributes`', () => {
- const $ = cheerio.load(
- renderComponent('select', {
- ...EXAMPLE_SELECT,
- attributes: {
- a: 123,
- b: 456,
- },
- }),
- );
-
- expect($('select').attr('a')).toBe('123');
- expect($('select').attr('b')).toBe('456');
- });
-
- describe('option element', () => {
- it('has expected text', () => {
- const $ = cheerio.load(renderComponent('select', EXAMPLE_SELECT));
-
- const values = mapAll($('select > option'), (node) => node.text().trim());
- expect(values).toEqual(['First option', 'Second option', 'Disabled option']);
- });
-
- it('has a provided `id` attribute', () => {
- const $ = cheerio.load(renderComponent('select', EXAMPLE_SELECT));
-
- const values = mapAll($('select > option'), (node) => node.attr('id'));
- expect(values).toEqual([undefined, 'second-option', undefined]);
- });
-
- it('marks the correct option as selected', () => {
- const $ = cheerio.load(renderComponent('select', EXAMPLE_SELECT));
-
- const values = mapAll($('select > option'), (node) => node.attr('selected'));
- expect(values).toEqual([undefined, 'selected', undefined]);
- });
-
- it('marks the correct option as disabled', () => {
- const $ = cheerio.load(renderComponent('select', EXAMPLE_SELECT));
-
- const values = mapAll($('select > option'), (node) => node.attr('disabled'));
- expect(values).toEqual([undefined, undefined, 'disabled']);
- });
-
- it('has a provided `value` attribute', () => {
- const $ = cheerio.load(renderComponent('select', EXAMPLE_SELECT));
-
- const values = mapAll($('select > option'), (node) => node.attr('value'));
- expect(values).toEqual(['1', '2', '3']);
- });
-
- it('uses option text as a default `value` attribute', () => {
- const $ = cheerio.load(
- renderComponent('select', {
- ...EXAMPLE_SELECT,
- options: [{ text: 'First option' }, { text: 'Second option' }],
- }),
- );
-
- const values = mapAll($('select > option'), (node) => node.attr('value'));
- expect(values).toEqual(['First option', 'Second option']);
- });
+ describe('select element', () => {
+ it('has the provided `id` attribute', () => {
+ const $ = cheerio.load(renderComponent('select', EXAMPLE_SELECT));
+
+ expect($('select').attr('id')).toBe('example-select');
+ });
+
+ it('has the provided `name` attribute', () => {
+ const $ = cheerio.load(renderComponent('select', EXAMPLE_SELECT));
+
+ expect($('select').attr('name')).toBe('example-select-name');
+ });
+
+ it('has additionally provided style classes', () => {
+ const $ = cheerio.load(
+ renderComponent('select', {
+ ...EXAMPLE_SELECT,
+ classes: 'extra-class another-extra-class',
+ }),
+ );
+
+ expect($('select').hasClass('extra-class')).toBe(true);
+ expect($('select').hasClass('another-extra-class')).toBe(true);
+ });
+
+ it('has additionally provided `attributes`', () => {
+ const $ = cheerio.load(
+ renderComponent('select', {
+ ...EXAMPLE_SELECT,
+ attributes: {
+ a: 123,
+ b: 456,
+ },
+ }),
+ );
+
+ expect($('select').attr('a')).toBe('123');
+ expect($('select').attr('b')).toBe('456');
+ });
+
+ describe('option element', () => {
+ it('has expected text', () => {
+ const $ = cheerio.load(renderComponent('select', EXAMPLE_SELECT));
+
+ const values = mapAll($('select > option'), (node) => node.text().trim());
+ expect(values).toEqual(['First option', 'Second option', 'Disabled option']);
+ });
+
+ it('has a provided `id` attribute', () => {
+ const $ = cheerio.load(renderComponent('select', EXAMPLE_SELECT));
+
+ const values = mapAll($('select > option'), (node) => node.attr('id'));
+ expect(values).toEqual([undefined, 'second-option', undefined]);
+ });
+
+ it('marks the correct option as selected', () => {
+ const $ = cheerio.load(renderComponent('select', EXAMPLE_SELECT));
+
+ const values = mapAll($('select > option'), (node) => node.attr('selected'));
+ expect(values).toEqual([undefined, 'selected', undefined]);
+ });
+
+ it('marks the correct option as disabled', () => {
+ const $ = cheerio.load(renderComponent('select', EXAMPLE_SELECT));
+
+ const values = mapAll($('select > option'), (node) => node.attr('disabled'));
+ expect(values).toEqual([undefined, undefined, 'disabled']);
+ });
+
+ it('has a provided `value` attribute', () => {
+ const $ = cheerio.load(renderComponent('select', EXAMPLE_SELECT));
+
+ const values = mapAll($('select > option'), (node) => node.attr('value'));
+ expect(values).toEqual(['1', '2', '3']);
+ });
+
+ it('uses option text as a default `value` attribute', () => {
+ const $ = cheerio.load(
+ renderComponent('select', {
+ ...EXAMPLE_SELECT,
+ options: [{ text: 'First option' }, { text: 'Second option' }],
+ }),
+ );
+
+ const values = mapAll($('select > option'), (node) => node.attr('value'));
+ expect(values).toEqual(['First option', 'Second option']);
+ });
+ });
});
- });
});
diff --git a/src/components/share-page/_macro.spec.js b/src/components/share-page/_macro.spec.js
index eb29538e83..2420a96c01 100644
--- a/src/components/share-page/_macro.spec.js
+++ b/src/components/share-page/_macro.spec.js
@@ -6,97 +6,97 @@ import axe from '../../tests/helpers/axe';
import { renderComponent, templateFaker } from '../../tests/helpers/rendering';
const EXAMPLE_SHARE_PAGE = {
- title: 'Share page',
- pageTitle: 'An example page',
- pageURL: 'https://example.com/an-example-page',
- facebook: true,
- twitter: true,
+ title: 'Share page',
+ pageTitle: 'An example page',
+ pageURL: 'https://example.com/an-example-page',
+ facebook: true,
+ twitter: true,
};
describe('macro: share-page', () => {
- it('passes jest-axe checks with', async () => {
- const $ = cheerio.load(renderComponent('share-page', EXAMPLE_SHARE_PAGE));
+ it('passes jest-axe checks with', async () => {
+ const $ = cheerio.load(renderComponent('share-page', EXAMPLE_SHARE_PAGE));
- const results = await axe($.html());
- expect(results).toHaveNoViolations();
- });
+ const results = await axe($.html());
+ expect(results).toHaveNoViolations();
+ });
- it('wraps title in
element by default', () => {
- const $ = cheerio.load(renderComponent('share-page', EXAMPLE_SHARE_PAGE));
+ it('wraps title in element by default', () => {
+ const $ = cheerio.load(renderComponent('share-page', EXAMPLE_SHARE_PAGE));
- expect($('h2').text().trim()).toBe('Share page');
- });
+ expect($('h2').text().trim()).toBe('Share page');
+ });
- it('wraps title in custom element when `titleTag` is provided', () => {
- const $ = cheerio.load(
- renderComponent('share-page', {
- ...EXAMPLE_SHARE_PAGE,
- titleTag: 'h4',
- }),
- );
+ it('wraps title in custom element when `titleTag` is provided', () => {
+ const $ = cheerio.load(
+ renderComponent('share-page', {
+ ...EXAMPLE_SHARE_PAGE,
+ titleTag: 'h4',
+ }),
+ );
- expect($('h4').text().trim()).toBe('Share page');
- });
+ expect($('h4').text().trim()).toBe('Share page');
+ });
- it('uses the provided icon size', () => {
- const faker = templateFaker();
- const listsSpy = faker.spy('list');
+ it('uses the provided icon size', () => {
+ const faker = templateFaker();
+ const listsSpy = faker.spy('list');
- faker.renderComponent('share-page', {
- ...EXAMPLE_SHARE_PAGE,
- iconSize: 'xl',
- });
+ faker.renderComponent('share-page', {
+ ...EXAMPLE_SHARE_PAGE,
+ iconSize: 'xl',
+ });
- expect(listsSpy.occurrences[0].iconSize).toBe('xl');
- });
+ expect(listsSpy.occurrences[0].iconSize).toBe('xl');
+ });
- describe('Share on Twitter', () => {
- it('has a link with the expected url', () => {
- const faker = templateFaker();
- const listsSpy = faker.spy('list');
+ describe('Share on Twitter', () => {
+ it('has a link with the expected url', () => {
+ const faker = templateFaker();
+ const listsSpy = faker.spy('list');
- faker.renderComponent('share-page', EXAMPLE_SHARE_PAGE);
+ faker.renderComponent('share-page', EXAMPLE_SHARE_PAGE);
- const twitterItem = listsSpy.occurrences[0].itemsList.find((item) => item.text === 'Twitter');
- expect(twitterItem.url).toBe(
- 'https://twitter.com/intent/tweet?original_referer&text=An%20example%20page&url=https%3A%2F%2Fexample.com%2Fan-example-page',
- );
- });
+ const twitterItem = listsSpy.occurrences[0].itemsList.find((item) => item.text === 'Twitter');
+ expect(twitterItem.url).toBe(
+ 'https://twitter.com/intent/tweet?original_referer&text=An%20example%20page&url=https%3A%2F%2Fexample.com%2Fan-example-page',
+ );
+ });
- it('has a link which opens in a new tab', () => {
- const faker = templateFaker();
- const listsSpy = faker.spy('list');
+ it('has a link which opens in a new tab', () => {
+ const faker = templateFaker();
+ const listsSpy = faker.spy('list');
- faker.renderComponent('share-page', EXAMPLE_SHARE_PAGE);
+ faker.renderComponent('share-page', EXAMPLE_SHARE_PAGE);
- const twitterItem = listsSpy.occurrences[0].itemsList.find((item) => item.text === 'Twitter');
- expect(twitterItem.rel).toContain('noreferrer');
- expect(twitterItem.rel).toContain('external');
- expect(twitterItem.target).toBe('_blank');
+ const twitterItem = listsSpy.occurrences[0].itemsList.find((item) => item.text === 'Twitter');
+ expect(twitterItem.rel).toContain('noreferrer');
+ expect(twitterItem.rel).toContain('external');
+ expect(twitterItem.target).toBe('_blank');
+ });
});
- });
- describe('Share on Facebook', () => {
- it('has a link with the expected url', () => {
- const faker = templateFaker();
- const listsSpy = faker.spy('list');
+ describe('Share on Facebook', () => {
+ it('has a link with the expected url', () => {
+ const faker = templateFaker();
+ const listsSpy = faker.spy('list');
- faker.renderComponent('share-page', EXAMPLE_SHARE_PAGE);
+ faker.renderComponent('share-page', EXAMPLE_SHARE_PAGE);
- const facebookItem = listsSpy.occurrences[0].itemsList.find((item) => item.text === 'Facebook');
- expect(facebookItem.url).toBe('https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fexample.com%2Fan-example-page');
- });
+ const facebookItem = listsSpy.occurrences[0].itemsList.find((item) => item.text === 'Facebook');
+ expect(facebookItem.url).toBe('https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fexample.com%2Fan-example-page');
+ });
- it('has a link which opens in a new tab', () => {
- const faker = templateFaker();
- const listsSpy = faker.spy('list');
+ it('has a link which opens in a new tab', () => {
+ const faker = templateFaker();
+ const listsSpy = faker.spy('list');
- faker.renderComponent('share-page', EXAMPLE_SHARE_PAGE);
+ faker.renderComponent('share-page', EXAMPLE_SHARE_PAGE);
- const facebookItem = listsSpy.occurrences[0].itemsList.find((item) => item.text === 'Facebook');
- expect(facebookItem.rel).toContain('noreferrer');
- expect(facebookItem.rel).toContain('external');
- expect(facebookItem.target).toBe('_blank');
+ const facebookItem = listsSpy.occurrences[0].itemsList.find((item) => item.text === 'Facebook');
+ expect(facebookItem.rel).toContain('noreferrer');
+ expect(facebookItem.rel).toContain('external');
+ expect(facebookItem.target).toBe('_blank');
+ });
});
- });
});
diff --git a/src/components/skip-to-content/_macro.spec.js b/src/components/skip-to-content/_macro.spec.js
index 9d99332e49..c18274b000 100644
--- a/src/components/skip-to-content/_macro.spec.js
+++ b/src/components/skip-to-content/_macro.spec.js
@@ -6,58 +6,58 @@ import axe from '../../tests/helpers/axe';
import { renderComponent } from '../../tests/helpers/rendering';
describe('macro: skip-to-content', () => {
- it('passes jest-axe checks', async () => {
- const $ = cheerio.load(
- renderComponent('skip-to-content', {
- url: '#example-anchor',
- text: 'Skip to the content',
- }),
- );
-
- const results = await axe($.html());
- expect(results).toHaveNoViolations();
- });
-
- it('has a hyperlink element', async () => {
- const $ = cheerio.load(
- renderComponent('skip-to-content', {
- url: '#example-anchor',
- text: 'Skip to the content',
- }),
- );
-
- expect($('.ons-skip-to-content')[0].tagName).toBe('a');
- });
-
- it('has skip link with the provided `url`', async () => {
- const $ = cheerio.load(
- renderComponent('skip-to-content', {
- url: '#example-anchor',
- text: 'Skip to the content',
- }),
- );
-
- expect($('.ons-skip-to-content').attr('href')).toBe('#example-anchor');
- });
-
- it('has skip link with the provided `text`', async () => {
- const $ = cheerio.load(
- renderComponent('skip-to-content', {
- url: '#example-anchor',
- text: 'Skip to the content',
- }),
- );
-
- expect($('.ons-skip-to-content').text().trim()).toBe('Skip to the content');
- });
-
- it('has skip link with the default text if no text provided`', async () => {
- const $ = cheerio.load(
- renderComponent('skip-to-content', {
- url: '#example-anchor',
- }),
- );
-
- expect($('.ons-skip-to-content').text().trim()).toBe('Skip to content');
- });
+ it('passes jest-axe checks', async () => {
+ const $ = cheerio.load(
+ renderComponent('skip-to-content', {
+ url: '#example-anchor',
+ text: 'Skip to the content',
+ }),
+ );
+
+ const results = await axe($.html());
+ expect(results).toHaveNoViolations();
+ });
+
+ it('has a hyperlink element', async () => {
+ const $ = cheerio.load(
+ renderComponent('skip-to-content', {
+ url: '#example-anchor',
+ text: 'Skip to the content',
+ }),
+ );
+
+ expect($('.ons-skip-to-content')[0].tagName).toBe('a');
+ });
+
+ it('has skip link with the provided `url`', async () => {
+ const $ = cheerio.load(
+ renderComponent('skip-to-content', {
+ url: '#example-anchor',
+ text: 'Skip to the content',
+ }),
+ );
+
+ expect($('.ons-skip-to-content').attr('href')).toBe('#example-anchor');
+ });
+
+ it('has skip link with the provided `text`', async () => {
+ const $ = cheerio.load(
+ renderComponent('skip-to-content', {
+ url: '#example-anchor',
+ text: 'Skip to the content',
+ }),
+ );
+
+ expect($('.ons-skip-to-content').text().trim()).toBe('Skip to the content');
+ });
+
+ it('has skip link with the default text if no text provided`', async () => {
+ const $ = cheerio.load(
+ renderComponent('skip-to-content', {
+ url: '#example-anchor',
+ }),
+ );
+
+ expect($('.ons-skip-to-content').text().trim()).toBe('Skip to content');
+ });
});
diff --git a/src/components/skip-to-content/_skip.scss b/src/components/skip-to-content/_skip.scss
index 5368cf2061..b9c68a5541 100644
--- a/src/components/skip-to-content/_skip.scss
+++ b/src/components/skip-to-content/_skip.scss
@@ -1,33 +1,33 @@
.ons-skip-to-content {
- clip: rect(0 0 0 0);
- clip-path: inset(50%);
- display: block;
- font-size: 1.1rem;
- font-weight: $font-weight-bold;
- height: 1px;
- margin: 0;
- overflow: hidden;
- position: absolute;
- top: 0;
- white-space: nowrap;
- width: 1px;
+ clip: rect(0 0 0 0);
+ clip-path: inset(50%);
+ display: block;
+ font-size: 1.1rem;
+ font-weight: $font-weight-bold;
+ height: 1px;
+ margin: 0;
+ overflow: hidden;
+ position: absolute;
+ top: 0;
+ white-space: nowrap;
+ width: 1px;
- &:focus {
- background-color: var(--ons-color-focus);
- box-shadow: 0;
- clip: auto;
- clip-path: none;
- color: var(--ons-color-text-link-focus);
- height: auto;
- margin: inherit;
- max-height: 20em;
- outline: 3px solid transparent; // Add transparent outline because Windows High Contrast Mode doesn't show box-shadows
- outline-offset: -4px;
- overflow: visible;
- padding: 1rem;
- position: static;
- text-decoration: underline solid var(--ons-color-text-link-focus) 4px;
- white-space: inherit;
- width: auto;
- }
+ &:focus {
+ background-color: var(--ons-color-focus);
+ box-shadow: 0;
+ clip: auto;
+ clip-path: none;
+ color: var(--ons-color-text-link-focus);
+ height: auto;
+ margin: inherit;
+ max-height: 20em;
+ outline: 3px solid transparent; // Add transparent outline because Windows High Contrast Mode doesn't show box-shadows
+ outline-offset: -4px;
+ overflow: visible;
+ padding: 1rem;
+ position: static;
+ text-decoration: underline solid var(--ons-color-text-link-focus) 4px;
+ white-space: inherit;
+ width: auto;
+ }
}
diff --git a/src/components/skip-to-content/skip-to-content.dom.js b/src/components/skip-to-content/skip-to-content.dom.js
index 7d708f0750..eefad8d7b8 100644
--- a/src/components/skip-to-content/skip-to-content.dom.js
+++ b/src/components/skip-to-content/skip-to-content.dom.js
@@ -2,12 +2,12 @@ import domready from '../../js/domready';
import skipToMain from './skip-to-content';
async function initaliseSkipToLink() {
- const links = [...document.querySelectorAll('.ons-skip-to-content')];
- if (links.length) {
- links.forEach((link) => {
- skipToMain(link);
- });
- }
+ const links = [...document.querySelectorAll('.ons-skip-to-content')];
+ if (links.length) {
+ links.forEach((link) => {
+ skipToMain(link);
+ });
+ }
}
domready(initaliseSkipToLink);
diff --git a/src/components/skip-to-content/skip-to-content.js b/src/components/skip-to-content/skip-to-content.js
index 7c2ef5f9b5..9e8277b2bd 100644
--- a/src/components/skip-to-content/skip-to-content.js
+++ b/src/components/skip-to-content/skip-to-content.js
@@ -1,9 +1,9 @@
export default function skipToMain(link) {
- const id = link.getAttribute('href').replace('#', '');
- link.addEventListener('click', (event) => {
- event.preventDefault();
- document.getElementById(id).tabIndex = -1;
- document.getElementById(id).style.outline = 'none';
- document.getElementById(id).focus();
- });
+ const id = link.getAttribute('href').replace('#', '');
+ link.addEventListener('click', (event) => {
+ event.preventDefault();
+ document.getElementById(id).tabIndex = -1;
+ document.getElementById(id).style.outline = 'none';
+ document.getElementById(id).focus();
+ });
}
diff --git a/src/components/skip-to-content/skip-to-content.spec.js b/src/components/skip-to-content/skip-to-content.spec.js
index 754497833f..5e3ceb35a0 100644
--- a/src/components/skip-to-content/skip-to-content.spec.js
+++ b/src/components/skip-to-content/skip-to-content.spec.js
@@ -12,33 +12,33 @@ const EXAMPLE_SKIP_TO_LINK_TEMPLATE = `
`;
describe('script: skip-to-content', () => {
- it('sets `tabIndex` of target element to -1', async () => {
- await setTestPage('/test', EXAMPLE_SKIP_TO_LINK_TEMPLATE);
+ it('sets `tabIndex` of target element to -1', async () => {
+ await setTestPage('/test', EXAMPLE_SKIP_TO_LINK_TEMPLATE);
- await page.focus('.ons-skip-to-content');
- await page.keyboard.press('Enter');
+ await page.focus('.ons-skip-to-content');
+ await page.keyboard.press('Enter');
- const targetTabIndex = await page.$eval('#target-element', (el) => el.tabIndex);
- expect(targetTabIndex).toBe(-1);
- });
+ const targetTabIndex = await page.$eval('#target-element', (el) => el.tabIndex);
+ expect(targetTabIndex).toBe(-1);
+ });
- it('removes outline from target element on navigate', async () => {
- await setTestPage('/test', EXAMPLE_SKIP_TO_LINK_TEMPLATE);
+ it('removes outline from target element on navigate', async () => {
+ await setTestPage('/test', EXAMPLE_SKIP_TO_LINK_TEMPLATE);
- await page.focus('.ons-skip-to-content');
- await page.keyboard.press('Enter');
+ await page.focus('.ons-skip-to-content');
+ await page.keyboard.press('Enter');
- const targetOutline = await page.$eval('#target-element', (el) => el.style.outline);
- expect(targetOutline).toBe('none');
- });
+ const targetOutline = await page.$eval('#target-element', (el) => el.style.outline);
+ expect(targetOutline).toBe('none');
+ });
- it('focuses target element on navigate', async () => {
- await setTestPage('/test', EXAMPLE_SKIP_TO_LINK_TEMPLATE);
+ it('focuses target element on navigate', async () => {
+ await setTestPage('/test', EXAMPLE_SKIP_TO_LINK_TEMPLATE);
- await page.focus('.ons-skip-to-content');
- await page.keyboard.press('Enter');
+ await page.focus('.ons-skip-to-content');
+ await page.keyboard.press('Enter');
- const focusedElementId = await page.evaluate(() => document.activeElement.id);
- expect(focusedElementId).toBe('target-element');
- });
+ const focusedElementId = await page.evaluate(() => document.activeElement.id);
+ expect(focusedElementId).toBe('target-element');
+ });
});
diff --git a/src/components/status/_macro.spec.js b/src/components/status/_macro.spec.js
index b3cdb64031..a8fc2f478c 100644
--- a/src/components/status/_macro.spec.js
+++ b/src/components/status/_macro.spec.js
@@ -6,68 +6,68 @@ import axe from '../../tests/helpers/axe';
import { renderComponent } from '../../tests/helpers/rendering';
describe('macro: status', () => {
- it('passes jest-axe checks', async () => {
- const $ = cheerio.load(
- renderComponent('status', {
- variant: 'success',
- label: 'Example status message',
- size: 'small',
- }),
- );
+ it('passes jest-axe checks', async () => {
+ const $ = cheerio.load(
+ renderComponent('status', {
+ variant: 'success',
+ label: 'Example status message',
+ size: 'small',
+ }),
+ );
- const results = await axe($.html());
- expect(results).toHaveNoViolations();
- });
+ const results = await axe($.html());
+ expect(results).toHaveNoViolations();
+ });
- it('has the default variant "info" when `variant` is not provided', () => {
- const $ = cheerio.load(
- renderComponent('status', {
- label: 'Example status message',
- }),
- );
+ it('has the default variant "info" when `variant` is not provided', () => {
+ const $ = cheerio.load(
+ renderComponent('status', {
+ label: 'Example status message',
+ }),
+ );
- expect($('.ons-status').hasClass('ons-status--info')).toBe(true);
- });
+ expect($('.ons-status').hasClass('ons-status--info')).toBe(true);
+ });
- it('has the provided `variant`', () => {
- const $ = cheerio.load(
- renderComponent('status', {
- variant: 'success',
- label: 'Example status message',
- }),
- );
+ it('has the provided `variant`', () => {
+ const $ = cheerio.load(
+ renderComponent('status', {
+ variant: 'success',
+ label: 'Example status message',
+ }),
+ );
- expect($('.ons-status').hasClass('ons-status--success')).toBe(true);
- });
+ expect($('.ons-status').hasClass('ons-status--success')).toBe(true);
+ });
- it('does not have the `ons-status--small` modifier by default', () => {
- const $ = cheerio.load(
- renderComponent('status', {
- label: 'Example status message',
- }),
- );
+ it('does not have the `ons-status--small` modifier by default', () => {
+ const $ = cheerio.load(
+ renderComponent('status', {
+ label: 'Example status message',
+ }),
+ );
- expect($('.ons-status').hasClass('ons-status--small')).not.toBe(true);
- });
+ expect($('.ons-status').hasClass('ons-status--small')).not.toBe(true);
+ });
- it('has the `ons-status--small` when provided', () => {
- const $ = cheerio.load(
- renderComponent('status', {
- label: 'Example status message',
- size: 'small',
- }),
- );
+ it('has the `ons-status--small` when provided', () => {
+ const $ = cheerio.load(
+ renderComponent('status', {
+ label: 'Example status message',
+ size: 'small',
+ }),
+ );
- expect($('.ons-status').hasClass('ons-status--small')).toBe(true);
- });
+ expect($('.ons-status').hasClass('ons-status--small')).toBe(true);
+ });
- it('has the provided `label`', () => {
- const $ = cheerio.load(
- renderComponent('status', {
- label: 'Example status message',
- }),
- );
+ it('has the provided `label`', () => {
+ const $ = cheerio.load(
+ renderComponent('status', {
+ label: 'Example status message',
+ }),
+ );
- expect($('.ons-status').text().trim()).toBe('Example status message');
- });
+ expect($('.ons-status').text().trim()).toBe('Example status message');
+ });
});
diff --git a/src/components/status/_status.scss b/src/components/status/_status.scss
index e6eb8c8e2b..c83be6b41a 100644
--- a/src/components/status/_status.scss
+++ b/src/components/status/_status.scss
@@ -1,43 +1,43 @@
@mixin status-dot($status-color) {
- background-color: $status-color;
- border: 3px solid $status-color;
+ background-color: $status-color;
+ border: 3px solid $status-color;
}
@mixin status-size($status-size: 1rem, $status-mb: -0.15rem) {
- height: $status-size;
- margin-bottom: $status-mb;
- width: $status-size;
+ height: $status-size;
+ margin-bottom: $status-mb;
+ width: $status-size;
}
.ons-status {
- &::before {
- @include status-size;
+ &::before {
+ @include status-size;
- border-radius: 100%;
- box-sizing: border-box;
- content: '';
- display: inline-block;
- margin-right: 0.391rem;
- }
+ border-radius: 100%;
+ box-sizing: border-box;
+ content: '';
+ display: inline-block;
+ margin-right: 0.391rem;
+ }
- /* Small status indicators for information dense situations */
- &.ons-status--small::before {
- @include status-size(0.555555rem, $status-mb: 0.055556rem);
- }
+ /* Small status indicators for information dense situations */
+ &.ons-status--small::before {
+ @include status-size(0.5556rem, $status-mb: 0.0556rem);
+ }
- &.ons-status--success::before {
- @include status-dot(var(--ons-color-success-vibrant));
- }
- &.ons-status--pending::before {
- @include status-dot(var(--ons-color-pending-vibrant));
- }
- &.ons-status--error::before {
- @include status-dot(var(--ons-color-errors-vibrant));
- }
- &.ons-status--dead::before {
- @include status-dot(var(--ons-color-dead));
- }
- &.ons-status--info::before {
- @include status-dot(var(--ons-color-info-vibrant));
- }
+ &.ons-status--success::before {
+ @include status-dot(var(--ons-color-success-vibrant));
+ }
+ &.ons-status--pending::before {
+ @include status-dot(var(--ons-color-pending-vibrant));
+ }
+ &.ons-status--error::before {
+ @include status-dot(var(--ons-color-errors-vibrant));
+ }
+ &.ons-status--dead::before {
+ @include status-dot(var(--ons-color-dead));
+ }
+ &.ons-status--info::before {
+ @include status-dot(var(--ons-color-info-vibrant));
+ }
}
diff --git a/src/components/summary/_macro.spec.js b/src/components/summary/_macro.spec.js
index ec9133d74a..a518a24d17 100644
--- a/src/components/summary/_macro.spec.js
+++ b/src/components/summary/_macro.spec.js
@@ -6,647 +6,663 @@ import axe from '../../tests/helpers/axe';
import { renderComponent, templateFaker } from '../../tests/helpers/rendering';
const EXAMPLE_SUMMARY_ROWS = {
- rows: [
- {
- // Contains - row with icon, attributes and rowTitleAttributes, other value, no action
- id: 'row-id-1',
- rowTitle: 'row title 1',
- rowItems: [
+ rows: [
{
- rowTitleAttributes: {
- a: 123,
- b: 456,
- },
- attributes: {
- a: 'aaa',
- b: 'bbb',
- },
- iconType: 'check',
- iconVisuallyHiddenText: 'Section completed',
- id: 'item-id-1',
- valueList: [
- {
- text: 'row value 1',
- other: 'other value',
- },
- ],
+ // Contains - row with icon, attributes and rowTitleAttributes, other value, no action
+ id: 'row-id-1',
+ rowTitle: 'row title 1',
+ rowItems: [
+ {
+ rowTitleAttributes: {
+ a: 123,
+ b: 456,
+ },
+ attributes: {
+ a: 'aaa',
+ b: 'bbb',
+ },
+ iconType: 'check',
+ iconVisuallyHiddenText: 'Section completed',
+ id: 'item-id-1',
+ valueList: [
+ {
+ text: 'row value 1',
+ other: 'other value',
+ },
+ ],
+ },
+ ],
},
- ],
- },
- {
- // Contains - row with error and multiple actions
- id: 'row-id-2',
- rowTitle: 'row title 2',
- error: true,
- errorMessage: 'there are errors',
- rowItems: [
{
- id: 'item-id-2',
- valueList: [
- {
- text: 'row value 2',
- },
- ],
- actions: [
- {
- text: 'Action 1',
- visuallyHiddenText: 'action 1 for row title 2',
- attributes: {
- a: 'abc',
- b: 'def',
- },
- url: '#1',
- },
- {
- text: 'Action 2',
- visuallyHiddenText: 'action 2 for row title 2',
- url: '#2',
- },
- ],
+ // Contains - row with error and multiple actions
+ id: 'row-id-2',
+ rowTitle: 'row title 2',
+ error: true,
+ errorMessage: 'there are errors',
+ rowItems: [
+ {
+ id: 'item-id-2',
+ valueList: [
+ {
+ text: 'row value 2',
+ },
+ ],
+ actions: [
+ {
+ text: 'Action 1',
+ visuallyHiddenText: 'action 1 for row title 2',
+ attributes: {
+ a: 'abc',
+ b: 'def',
+ },
+ url: '#1',
+ },
+ {
+ text: 'Action 2',
+ visuallyHiddenText: 'action 2 for row title 2',
+ url: '#2',
+ },
+ ],
+ },
+ ],
},
- ],
- },
- {
- // Contains - row with multiple rows and multiple values
- id: 'row-id-3',
- rowTitle: 'row title 3',
- rowItems: [
{
- id: 'item-id-3',
- valueList: [
- {
- text: 'row value 3',
- },
- {
- text: 'row value 3b',
- },
- ],
- },
- {
- id: 'item-id-4',
- valueList: [
- {
- text: 'row value 4',
- },
- ],
+ // Contains - row with multiple rows and multiple values
+ id: 'row-id-3',
+ rowTitle: 'row title 3',
+ rowItems: [
+ {
+ id: 'item-id-3',
+ valueList: [
+ {
+ text: 'row value 3',
+ },
+ {
+ text: 'row value 3b',
+ },
+ ],
+ },
+ {
+ id: 'item-id-4',
+ valueList: [
+ {
+ text: 'row value 4',
+ },
+ ],
+ },
+ ],
},
- ],
- },
- {
- // Contains - row with total
- id: 'row-id-4',
- rowTitle: 'row title 4',
- total: true,
- rowItems: [
{
- id: 'item-id-5',
- valueList: [
- {
- text: '£234,000.00',
- },
- ],
+ // Contains - row with total
+ id: 'row-id-4',
+ rowTitle: 'row title 4',
+ total: true,
+ rowItems: [
+ {
+ id: 'item-id-5',
+ valueList: [
+ {
+ text: '£234,000.00',
+ },
+ ],
+ },
+ ],
},
- ],
- },
- ],
+ ],
};
const EXAMPLE_SUMMARY_GROUPS = {
- groups: [
- {
- id: 'group-id-1',
- groupTitle: 'group title',
- ...EXAMPLE_SUMMARY_ROWS,
- },
- ],
+ groups: [
+ {
+ id: 'group-id-1',
+ groupTitle: 'group title',
+ ...EXAMPLE_SUMMARY_ROWS,
+ },
+ ],
};
const EXAMPLE_SUMMARY_GROUPS_NO_ROWS = {
- groups: [
- {
- placeholderText: 'Placeholder text',
- summaryLink: {
- text: 'Summary link',
- url: '#0',
- attributes: {
- a: 'xyz',
+ groups: [
+ {
+ placeholderText: 'Placeholder text',
+ summaryLink: {
+ text: 'Summary link',
+ url: '#0',
+ attributes: {
+ a: 'xyz',
+ },
+ },
},
- },
- },
- ],
+ ],
};
const EXAMPLE_SUMMARY_HOUSEHOLD_GROUP = {
- rows: [
- {
- rowItems: [
+ rows: [
{
- rowTitle: 'row item 1',
- valueList: [
- {
- text: 'list item 1',
- },
- ],
- actions: [
- {
- text: 'Change',
- visuallyHiddenText: 'change list item',
- url: '#0',
- },
- {
- text: 'Remove',
- visuallyHiddenText: 'remove list item',
- url: '#0',
- },
- ],
+ rowItems: [
+ {
+ rowTitle: 'row item 1',
+ valueList: [
+ {
+ text: 'list item 1',
+ },
+ ],
+ actions: [
+ {
+ text: 'Change',
+ visuallyHiddenText: 'change list item',
+ url: '#0',
+ },
+ {
+ text: 'Remove',
+ visuallyHiddenText: 'remove list item',
+ url: '#0',
+ },
+ ],
+ },
+ {
+ rowTitle: 'row item 2',
+ valueList: [
+ {
+ text: 'list item 2',
+ },
+ ],
+ actions: [
+ {
+ text: 'Change',
+ visuallyHiddenText: 'change list item',
+ url: '#0',
+ },
+ ],
+ },
+ {
+ rowTitle: 'row item 3',
+ valueList: [
+ {
+ text: 'list item 3',
+ },
+ ],
+ actions: [
+ {
+ text: 'Change',
+ visuallyHiddenText: 'change list item',
+ url: '#0',
+ },
+ ],
+ },
+ ],
},
{
- rowTitle: 'row item 2',
- valueList: [
- {
- text: 'list item 2',
- },
- ],
- actions: [
- {
- text: 'Change',
- visuallyHiddenText: 'change list item',
- url: '#0',
- },
- ],
- },
- {
- rowTitle: 'row item 3',
- valueList: [
- {
- text: 'list item 3',
- },
- ],
- actions: [
- {
- text: 'Change',
- visuallyHiddenText: 'change list item',
- url: '#0',
- },
- ],
- },
- ],
- },
- {
- rowItems: [
- {
- rowTitle: 'row item 4',
- valueList: [
- {
- text: 'list item 4',
- },
- ],
- actions: [
- {
- text: 'Change',
- visuallyHiddenText: 'change answer',
- url: '#0',
- },
- {
- text: 'Remove',
- visuallyHiddenText: 'remove list item',
- url: '#0',
- },
- ],
+ rowItems: [
+ {
+ rowTitle: 'row item 4',
+ valueList: [
+ {
+ text: 'list item 4',
+ },
+ ],
+ actions: [
+ {
+ text: 'Change',
+ visuallyHiddenText: 'change answer',
+ url: '#0',
+ },
+ {
+ text: 'Remove',
+ visuallyHiddenText: 'remove list item',
+ url: '#0',
+ },
+ ],
+ },
+ {
+ rowTitle: 'row item 5',
+ valueList: [
+ {
+ text: 'list item 5',
+ },
+ ],
+ actions: [
+ {
+ text: 'Change',
+ visuallyHiddenText: 'change list item',
+ url: '#0',
+ },
+ ],
+ },
+ {
+ rowTitle: 'row item 6',
+ valueList: [
+ {
+ text: 'list item 6',
+ },
+ ],
+ actions: [
+ {
+ text: 'Change',
+ visuallyHiddenText: 'change list item',
+ url: '#0',
+ },
+ ],
+ },
+ ],
},
- {
- rowTitle: 'row item 5',
- valueList: [
- {
- text: 'list item 5',
- },
- ],
- actions: [
- {
- text: 'Change',
- visuallyHiddenText: 'change list item',
- url: '#0',
- },
- ],
- },
- {
- rowTitle: 'row item 6',
- valueList: [
- {
- text: 'list item 6',
- },
- ],
- actions: [
- {
- text: 'Change',
- visuallyHiddenText: 'change list item',
- url: '#0',
- },
- ],
- },
- ],
+ ],
+ summaryLink: {
+ text: 'Summary link',
+ url: '#0',
},
- ],
- summaryLink: {
- text: 'Summary link',
- url: '#0',
- },
};
const EXAMPLE_SUMMARY_BASIC = {
- summaries: [
- {
- ...EXAMPLE_SUMMARY_GROUPS,
- },
- ],
+ summaries: [
+ {
+ ...EXAMPLE_SUMMARY_GROUPS,
+ },
+ ],
};
const EXAMPLE_SUMMARY_WITH_TITLE = {
- summaries: [
- {
- summaryTitle: 'summary title',
- ...EXAMPLE_SUMMARY_GROUPS,
- },
- ],
+ summaries: [
+ {
+ summaryTitle: 'summary title',
+ ...EXAMPLE_SUMMARY_GROUPS,
+ },
+ ],
};
const EXAMPLE_SUMMARY_WITH_NO_ROWS = {
- summaries: [
- {
- summaryTitle: 'summary title',
- ...EXAMPLE_SUMMARY_GROUPS_NO_ROWS,
- },
- ],
+ summaries: [
+ {
+ summaryTitle: 'summary title',
+ ...EXAMPLE_SUMMARY_GROUPS_NO_ROWS,
+ },
+ ],
};
const EXAMPLE_SUMMARY_MULTIPLE_GROUPS = {
- summaries: [
- {
- summaryTitle: 'summary title',
- groups: [
+ summaries: [
{
- id: 'group-id-1',
- groupTitle: 'group title',
- ...EXAMPLE_SUMMARY_ROWS,
+ summaryTitle: 'summary title',
+ groups: [
+ {
+ id: 'group-id-1',
+ groupTitle: 'group title',
+ ...EXAMPLE_SUMMARY_ROWS,
+ },
+ {
+ id: 'group-id-2',
+ groupTitle: 'group title',
+ ...EXAMPLE_SUMMARY_HOUSEHOLD_GROUP,
+ },
+ {
+ id: 'group-id-3',
+ groupTitle: 'group title',
+ ...EXAMPLE_SUMMARY_ROWS,
+ },
+ ],
},
- {
- id: 'group-id-2',
- groupTitle: 'group title',
- ...EXAMPLE_SUMMARY_HOUSEHOLD_GROUP,
- },
- {
- id: 'group-id-3',
- groupTitle: 'group title',
- ...EXAMPLE_SUMMARY_ROWS,
- },
- ],
- },
- ],
+ ],
};
// To address a DAC issue, we've disabled specific axe definition list rules causing test failures.
// While resolving it would require a significant refactor, the failures are deemed non-critical for accessibility,
// leading to their removal in this context. [https://github.com/ONSdigital/design-system/issues/3027]
const axeRules = {
- rules: {
- dlitem: {
- enabled: false,
- },
- 'definition-list': {
- enabled: false,
+ rules: {
+ dlitem: {
+ enabled: false,
+ },
+ 'definition-list': {
+ enabled: false,
+ },
},
- },
};
describe('macro: summary', () => {
- describe('mode: general', () => {
- it('passes jest-axe checks', async () => {
- const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
- const results = await axe($.html(), axeRules);
-
- expect(results).toHaveNoViolations();
- });
-
- it('has custom classes applied', () => {
- const $ = cheerio.load(
- renderComponent('summary', {
- ...EXAMPLE_SUMMARY_BASIC,
- classes: 'ons-custom-class',
- }),
- );
+ describe('mode: general', () => {
+ it('passes jest-axe checks', async () => {
+ const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
+ const results = await axe($.html(), axeRules);
- expect($('.ons-summary').hasClass('ons-custom-class')).toBe(true);
- });
-
- describe('part: group', () => {
- it('has the correct group `id`', () => {
- const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
-
- expect($('#group-id-1').length).toBe(1);
- });
+ expect(results).toHaveNoViolations();
+ });
- it('has the correct `groupTitle` tag', () => {
- const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
+ it('has custom classes applied', () => {
+ const $ = cheerio.load(
+ renderComponent('summary', {
+ ...EXAMPLE_SUMMARY_BASIC,
+ classes: 'ons-custom-class',
+ }),
+ );
- expect($('.ons-summary__group-title')[0].tagName).toBe('h2');
- });
+ expect($('.ons-summary').hasClass('ons-custom-class')).toBe(true);
+ });
- it('has the `groupTitle` text', () => {
- const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
+ describe('part: group', () => {
+ it('has the correct group `id`', () => {
+ const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
- expect($('.ons-summary__group-title').text()).toBe('group title');
- });
+ expect($('#group-id-1').length).toBe(1);
+ });
- it('has larger margin between groups if the top one is a household style summary', () => {
- const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_MULTIPLE_GROUPS));
+ it('has the correct `groupTitle` tag', () => {
+ const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
- expect($('.ons-summary__group:nth-last-of-type(2) .ons-summary__link').hasClass('ons-u-mb-xl')).toBe(true);
- });
- });
+ expect($('.ons-summary__group-title')[0].tagName).toBe('h2');
+ });
- describe('part: row', () => {
- it('has the correct row class when `error` is `true`', () => {
- const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
+ it('has the `groupTitle` text', () => {
+ const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
- expect($('.ons-summary__items .ons-summary__item:nth-of-type(2)').hasClass('ons-summary__item--error')).toBe(true);
- });
+ expect($('.ons-summary__group-title').text()).toBe('group title');
+ });
- it('has the correct row class when `total` is `true`', () => {
- const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
+ it('has larger margin between groups if the top one is a household style summary', () => {
+ const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_MULTIPLE_GROUPS));
- expect($('.ons-summary__items .ons-summary__item:nth-of-type(4)').hasClass('ons-summary__item--total')).toBe(true);
- });
+ expect($('.ons-summary__group:nth-last-of-type(2) .ons-summary__link').hasClass('ons-u-mb-xl')).toBe(true);
+ });
+ });
- it('displays the `rowTitle` text', () => {
- const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
+ describe('part: row', () => {
+ it('has the correct row class when `error` is `true`', () => {
+ const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
- expect($('.ons-summary__items .ons-summary__item:nth-of-type(3) .ons-summary__row-title').text()).toBe('row title 3');
- });
+ expect($('.ons-summary__items .ons-summary__item:nth-of-type(2)').hasClass('ons-summary__item--error')).toBe(true);
+ });
- it('overrides the `rowTitle` with the `errorMessage` if provided', () => {
- const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_WITH_TITLE));
+ it('has the correct row class when `total` is `true`', () => {
+ const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
- expect($('.ons-summary__items .ons-summary__item:nth-of-type(2) .ons-summary__row-title--error').text()).toBe('there are errors');
- });
+ expect($('.ons-summary__items .ons-summary__item:nth-of-type(4)').hasClass('ons-summary__item--total')).toBe(true);
+ });
- it('has the correct row `id` for each row', () => {
- const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
+ it('displays the `rowTitle` text', () => {
+ const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
- expect($('#row-id-1').length).toBe(1);
- expect($('#row-id-2').length).toBe(1);
- expect($('#row-id-3').length).toBe(1);
- });
+ expect($('.ons-summary__items .ons-summary__item:nth-of-type(3) .ons-summary__row-title').text()).toBe('row title 3');
+ });
- it('has the correct class for each row when there is a `valueList`', () => {
- const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
+ it('overrides the `rowTitle` with the `errorMessage` if provided', () => {
+ const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_WITH_TITLE));
- expect($('.ons-summary__row--has-values').length).toBe(5);
- });
+ expect($('.ons-summary__items .ons-summary__item:nth-of-type(2) .ons-summary__row-title--error').text()).toBe(
+ 'there are errors',
+ );
+ });
- it('has custom `rowTitleAttributes`', () => {
- const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
+ it('has the correct row `id` for each row', () => {
+ const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
- expect($('.ons-summary__item-title').attr('a')).toBe('123');
- expect($('.ons-summary__item-title').attr('b')).toBe('456');
- });
- });
+ expect($('#row-id-1').length).toBe(1);
+ expect($('#row-id-2').length).toBe(1);
+ expect($('#row-id-3').length).toBe(1);
+ });
- describe('part: item title', () => {
- it('displays the `rowTitle` text', () => {
- const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
+ it('has the correct class for each row when there is a `valueList`', () => {
+ const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
- expect($('.ons-summary__items .ons-summary__item:nth-of-type(1) .ons-summary__item--text').text().trim()).toBe('row title 1');
- });
+ expect($('.ons-summary__row--has-values').length).toBe(5);
+ });
- it('has a custom icon `iconType`', () => {
- const faker = templateFaker();
- const iconsSpy = faker.spy('icon');
+ it('has custom `rowTitleAttributes`', () => {
+ const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
- faker.renderComponent('summary', EXAMPLE_SUMMARY_BASIC);
+ expect($('.ons-summary__item-title').attr('a')).toBe('123');
+ expect($('.ons-summary__item-title').attr('b')).toBe('456');
+ });
+ });
- expect(iconsSpy.occurrences[0].iconType).toBe('check');
- });
+ describe('part: item title', () => {
+ it('displays the `rowTitle` text', () => {
+ const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
- it('has the correct icon class when `iconType` is `check`', () => {
- const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
+ expect($('.ons-summary__items .ons-summary__item:nth-of-type(1) .ons-summary__item--text').text().trim()).toBe(
+ 'row title 1',
+ );
+ });
- expect($('.ons-summary__item-title-icon').hasClass('ons-summary__item-title-icon--check')).toBe(true);
- });
+ it('has a custom icon `iconType`', () => {
+ const faker = templateFaker();
+ const iconsSpy = faker.spy('icon');
- it('has the visually hidden text text when `iconType` is `check`', () => {
- const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
+ faker.renderComponent('summary', EXAMPLE_SUMMARY_BASIC);
- expect($('.ons-summary__item-title-icon').text().trim()).toBe('Section completed');
- });
+ expect(iconsSpy.occurrences[0].iconType).toBe('check');
+ });
- it('has the correct item text class when `iconType` is provided', () => {
- const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
+ it('has the correct icon class when `iconType` is `check`', () => {
+ const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
- expect($('.ons-summary__item--text').hasClass('ons-summary__item-title--text')).toBe(true);
- });
+ expect($('.ons-summary__item-title-icon').hasClass('ons-summary__item-title-icon--check')).toBe(true);
+ });
- it('has custom `attributes`', () => {
- const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
+ it('has the visually hidden text text when `iconType` is `check`', () => {
+ const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
- expect($('.ons-summary__values').attr('a')).toBe('aaa');
- expect($('.ons-summary__values').attr('b')).toBe('bbb');
- });
+ expect($('.ons-summary__item-title-icon').text().trim()).toBe('Section completed');
+ });
+
+ it('has the correct item text class when `iconType` is provided', () => {
+ const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
+
+ expect($('.ons-summary__item--text').hasClass('ons-summary__item-title--text')).toBe(true);
+ });
+
+ it('has custom `attributes`', () => {
+ const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
+
+ expect($('.ons-summary__values').attr('a')).toBe('aaa');
+ expect($('.ons-summary__values').attr('b')).toBe('bbb');
+ });
+ });
+
+ describe('part: item value', () => {
+ it('displays the `valueList` text', () => {
+ const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
+
+ expect(
+ $('.ons-summary__items .ons-summary__item:nth-of-type(1) .ons-summary__row .ons-summary__values .ons-summary__text')
+ .text()
+ .trim(),
+ ).toBe('row value 1');
+ });
+
+ it('displays the `other` text', () => {
+ const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
+
+ expect(
+ $('.ons-summary__items .ons-summary__item:nth-of-type(1) .ons-summary__row .ons-summary__values ul li').text().trim(),
+ ).toBe('other value');
+ });
+
+ it('wraps the `valueList` in a ul if multiple values provided', () => {
+ const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
+
+ expect($('.ons-summary__items .ons-summary__item:nth-of-type(3) .ons-summary__values ul').length).toBe(1);
+ });
+ });
+
+ describe('part: item action', () => {
+ it('has a spacer element if multiple actions are provided', () => {
+ const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
+
+ expect($('.ons-summary__items .ons-summary__item:nth-of-type(2) .ons-summary__actions .ons-summary__spacer').length).toBe(
+ 1,
+ );
+ });
+
+ it('has the correct `url` for each action provided', () => {
+ const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
+
+ expect(
+ $('.ons-summary__items .ons-summary__item:nth-of-type(2) .ons-summary__actions .ons-summary__button:first-child').attr(
+ 'href',
+ ),
+ ).toBe('#1');
+ expect(
+ $('.ons-summary__items .ons-summary__item:nth-of-type(2) .ons-summary__actions .ons-summary__button:last-child').attr(
+ 'href',
+ ),
+ ).toBe('#2');
+ });
+
+ it('has the action `text` for each action provided', () => {
+ const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
+
+ expect(
+ $(
+ '.ons-summary__items .ons-summary__item:nth-of-type(2) .ons-summary__actions .ons-summary__button:first-child .ons-summary__button-text',
+ ).text(),
+ ).toBe('Action 1');
+ expect(
+ $(
+ '.ons-summary__items .ons-summary__item:nth-of-type(2) .ons-summary__actions .ons-summary__button:last-child .ons-summary__button-text',
+ ).text(),
+ ).toBe('Action 2');
+ });
+
+ it('has the correct visually hidden text', () => {
+ const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
+
+ expect(
+ $(
+ '.ons-summary__items .ons-summary__item:nth-of-type(2) .ons-summary__actions .ons-summary__button:first-child .ons-u-vh',
+ ).text(),
+ ).toBe('action 1 for row title 2');
+ expect(
+ $(
+ '.ons-summary__items .ons-summary__item:nth-of-type(2) .ons-summary__actions .ons-summary__button:last-child .ons-u-vh',
+ ).text(),
+ ).toBe('action 2 for row title 2');
+ });
+
+ it('has custom `attributes`', () => {
+ const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
+
+ expect(
+ $('.ons-summary__items .ons-summary__item:nth-of-type(2) .ons-summary__actions .ons-summary__button:first-child').attr(
+ 'a',
+ ),
+ ).toBe('abc');
+ expect(
+ $('.ons-summary__items .ons-summary__item:nth-of-type(2) .ons-summary__actions .ons-summary__button:first-child').attr(
+ 'b',
+ ),
+ ).toBe('def');
+ });
+ });
});
- describe('part: item value', () => {
- it('displays the `valueList` text', () => {
- const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
+ describe('mode: with title', () => {
+ it('passes jest-axe checks', async () => {
+ const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_WITH_TITLE));
+ const results = await axe($.html(), axeRules);
- expect(
- $('.ons-summary__items .ons-summary__item:nth-of-type(1) .ons-summary__row .ons-summary__values .ons-summary__text')
- .text()
- .trim(),
- ).toBe('row value 1');
- });
+ expect(results).toHaveNoViolations();
+ });
- it('displays the `other` text', () => {
- const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
+ it('displays the `summaryTitle`', () => {
+ const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_WITH_TITLE));
- expect($('.ons-summary__items .ons-summary__item:nth-of-type(1) .ons-summary__row .ons-summary__values ul li').text().trim()).toBe(
- 'other value',
- );
- });
-
- it('wraps the `valueList` in a ul if multiple values provided', () => {
- const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
-
- expect($('.ons-summary__items .ons-summary__item:nth-of-type(3) .ons-summary__values ul').length).toBe(1);
- });
- });
-
- describe('part: item action', () => {
- it('has a spacer element if multiple actions are provided', () => {
- const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
-
- expect($('.ons-summary__items .ons-summary__item:nth-of-type(2) .ons-summary__actions .ons-summary__spacer').length).toBe(1);
- });
-
- it('has the correct `url` for each action provided', () => {
- const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
-
- expect(
- $('.ons-summary__items .ons-summary__item:nth-of-type(2) .ons-summary__actions .ons-summary__button:first-child').attr('href'),
- ).toBe('#1');
- expect(
- $('.ons-summary__items .ons-summary__item:nth-of-type(2) .ons-summary__actions .ons-summary__button:last-child').attr('href'),
- ).toBe('#2');
- });
-
- it('has the action `text` for each action provided', () => {
- const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
-
- expect(
- $(
- '.ons-summary__items .ons-summary__item:nth-of-type(2) .ons-summary__actions .ons-summary__button:first-child .ons-summary__button-text',
- ).text(),
- ).toBe('Action 1');
- expect(
- $(
- '.ons-summary__items .ons-summary__item:nth-of-type(2) .ons-summary__actions .ons-summary__button:last-child .ons-summary__button-text',
- ).text(),
- ).toBe('Action 2');
- });
-
- it('has the correct visually hidden text', () => {
- const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
-
- expect(
- $(
- '.ons-summary__items .ons-summary__item:nth-of-type(2) .ons-summary__actions .ons-summary__button:first-child .ons-u-vh',
- ).text(),
- ).toBe('action 1 for row title 2');
- expect(
- $('.ons-summary__items .ons-summary__item:nth-of-type(2) .ons-summary__actions .ons-summary__button:last-child .ons-u-vh').text(),
- ).toBe('action 2 for row title 2');
- });
-
- it('has custom `attributes`', () => {
- const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
-
- expect(
- $('.ons-summary__items .ons-summary__item:nth-of-type(2) .ons-summary__actions .ons-summary__button:first-child').attr('a'),
- ).toBe('abc');
- expect(
- $('.ons-summary__items .ons-summary__item:nth-of-type(2) .ons-summary__actions .ons-summary__button:first-child').attr('b'),
- ).toBe('def');
- });
- });
- });
-
- describe('mode: with title', () => {
- it('passes jest-axe checks', async () => {
- const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_WITH_TITLE));
- const results = await axe($.html(), axeRules);
-
- expect(results).toHaveNoViolations();
+ expect($('.ons-summary__title').text()).toBe('summary title');
+ });
});
- it('displays the `summaryTitle`', () => {
- const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_WITH_TITLE));
-
- expect($('.ons-summary__title').text()).toBe('summary title');
+ describe('mode: hub', () => {
+ it('passes jest-axe checks', async () => {
+ const $ = cheerio.load(
+ renderComponent('summary', {
+ ...EXAMPLE_SUMMARY_BASIC,
+ variant: 'hub',
+ }),
+ );
+ const results = await axe($.html(), axeRules);
+
+ expect(results).toHaveNoViolations();
+ });
+
+ it('has the correct class applied', () => {
+ const $ = cheerio.load(
+ renderComponent('summary', {
+ ...EXAMPLE_SUMMARY_BASIC,
+ variant: 'hub',
+ }),
+ );
+
+ expect($('.ons-summary').hasClass('ons-summary--hub')).toBe(true);
+ });
+
+ it('has the value rendered after the `rowTitle` that shows on mobile', () => {
+ const $ = cheerio.load(
+ renderComponent('summary', {
+ ...EXAMPLE_SUMMARY_BASIC,
+ variant: 'hub',
+ }),
+ );
+
+ expect($('.ons-summary__items .ons-summary__item:nth-of-type(2) .ons-summary__row .ons-summary__item-title span').text()).toBe(
+ ' — row value 2',
+ );
+ });
});
- });
- describe('mode: hub', () => {
- it('passes jest-axe checks', async () => {
- const $ = cheerio.load(
- renderComponent('summary', {
- ...EXAMPLE_SUMMARY_BASIC,
- variant: 'hub',
- }),
- );
- const results = await axe($.html(), axeRules);
-
- expect(results).toHaveNoViolations();
- });
+ describe('mode: no rows', () => {
+ it('passes jest-axe checks', async () => {
+ const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_WITH_NO_ROWS));
- it('has the correct class applied', () => {
- const $ = cheerio.load(
- renderComponent('summary', {
- ...EXAMPLE_SUMMARY_BASIC,
- variant: 'hub',
- }),
- );
+ const results = await axe($.html(), axeRules);
+ expect(results).toHaveNoViolations();
+ });
- expect($('.ons-summary').hasClass('ons-summary--hub')).toBe(true);
- });
+ it('has the `placeholderText` provided', () => {
+ const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_WITH_NO_ROWS));
- it('has the value rendered after the `rowTitle` that shows on mobile', () => {
- const $ = cheerio.load(
- renderComponent('summary', {
- ...EXAMPLE_SUMMARY_BASIC,
- variant: 'hub',
- }),
- );
-
- expect($('.ons-summary__items .ons-summary__item:nth-of-type(2) .ons-summary__row .ons-summary__item-title span').text()).toBe(
- ' — row value 2',
- );
- });
- });
+ expect($('.ons-summary__group .ons-summary__placeholder').text()).toBe('Placeholder text');
+ });
- describe('mode: no rows', () => {
- it('passes jest-axe checks', async () => {
- const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_WITH_NO_ROWS));
+ it('has the correct class added to the `summaryLink` when `placeholderText` is provided', () => {
+ const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_WITH_NO_ROWS));
- const results = await axe($.html(), axeRules);
- expect(results).toHaveNoViolations();
- });
+ expect($('.ons-summary__group .ons-summary__link').hasClass('ons-u-pt-s')).toBe(true);
+ });
- it('has the `placeholderText` provided', () => {
- const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_WITH_NO_ROWS));
+ it('has custom `attributes` on the `summaryLink`', () => {
+ const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_WITH_NO_ROWS));
- expect($('.ons-summary__group .ons-summary__placeholder').text()).toBe('Placeholder text');
- });
+ expect($('.ons-summary__group .ons-summary__link a').attr('a')).toBe('xyz');
+ });
- it('has the correct class added to the `summaryLink` when `placeholderText` is provided', () => {
- const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_WITH_NO_ROWS));
+ it('has the correct link `text`', () => {
+ const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_WITH_NO_ROWS));
- expect($('.ons-summary__group .ons-summary__link').hasClass('ons-u-pt-s')).toBe(true);
- });
+ expect($('.ons-summary__group .ons-summary__link a').text().trim()).toBe('Summary link');
+ });
- it('has custom `attributes` on the `summaryLink`', () => {
- const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_WITH_NO_ROWS));
+ it('has the correct link `url`', () => {
+ const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_WITH_NO_ROWS));
- expect($('.ons-summary__group .ons-summary__link a').attr('a')).toBe('xyz');
+ expect($('.ons-summary__group .ons-summary__link a').attr('href')).toBe('#0');
+ });
});
+});
- it('has the correct link `text`', () => {
- const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_WITH_NO_ROWS));
+describe('mode: card', () => {
+ it('passes jest-axe checks', async () => {
+ const $ = cheerio.load(renderComponent('summary', { ...EXAMPLE_SUMMARY_BASIC, variant: 'card' }));
+ const results = await axe($.html(), axeRules);
- expect($('.ons-summary__group .ons-summary__link a').text().trim()).toBe('Summary link');
+ expect(results).toHaveNoViolations();
});
- it('has the correct link `url`', () => {
- const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_WITH_NO_ROWS));
+ it('has the correct classes applied', () => {
+ const $ = cheerio.load(
+ renderComponent('summary', {
+ ...EXAMPLE_SUMMARY_MULTIPLE_GROUPS,
+ variant: 'card',
+ }),
+ );
- expect($('.ons-summary__group .ons-summary__link a').attr('href')).toBe('#0');
+ expect($('.ons-summary__group').hasClass('ons-summary__group--card')).toBe(true);
});
- });
-});
-
-describe('mode: card', () => {
- it('passes jest-axe checks', async () => {
- const $ = cheerio.load(renderComponent('summary', { ...EXAMPLE_SUMMARY_BASIC, variant: 'card' }));
- const results = await axe($.html(), axeRules);
-
- expect(results).toHaveNoViolations();
- });
-
- it('has the correct classes applied', () => {
- const $ = cheerio.load(
- renderComponent('summary', {
- ...EXAMPLE_SUMMARY_MULTIPLE_GROUPS,
- variant: 'card',
- }),
- );
-
- expect($('.ons-summary__group').hasClass('ons-summary__group--card')).toBe(true);
- });
});
diff --git a/src/components/summary/_summary.scss b/src/components/summary/_summary.scss
index 582c08f566..b931e8c383 100644
--- a/src/components/summary/_summary.scss
+++ b/src/components/summary/_summary.scss
@@ -5,239 +5,235 @@ $summary-col-spacing: 1rem;
$hub-row-spacing: 1.3rem;
.ons-summary {
- &__items {
- border-collapse: collapse;
- border-spacing: 0;
- display: flex;
- flex-direction: column;
- margin: 0;
- width: 100%;
-
- + .ons-summary__group-title {
- margin-top: 1.5rem;
+ &__items {
+ border-collapse: collapse;
+ border-spacing: 0;
+ display: flex;
+ flex-direction: column;
+ margin: 0;
+ width: 100%;
+
+ + .ons-summary__group-title {
+ margin-top: 1.5rem;
+ }
}
- }
- &__row {
- display: flex;
- margin: 0;
- }
+ &__row {
+ display: flex;
+ margin: 0;
+ }
- &__item {
- line-height: 1.4;
+ &__item {
+ line-height: 1.4;
- &:not(:last-child),
- &:not(.ons-summary__group--card .ons-summary__item):nth-of-type(1) {
- border-bottom: 1px solid var(--ons-color-borders);
- }
+ &:not(:last-child),
+ &:not(.ons-summary__group--card .ons-summary__item):nth-of-type(1) {
+ border-bottom: 1px solid var(--ons-color-borders);
+ }
- &--total {
- @extend .ons-u-fs-m;
+ &--total {
+ @extend .ons-u-fs-m;
- border-width: 2px;
- font-weight: $font-weight-bold;
+ border-width: 2px;
+ font-weight: $font-weight-bold;
- .ons-summary__values {
- padding-top: 23px;
- }
- }
+ .ons-summary__values {
+ padding-top: 23px;
- &--error {
- background: var(--ons-color-errors-tint);
- border-left: 8px solid var(--ons-color-errors);
- }
- }
-
- &__row-title {
- padding: $summary-row-spacing 0;
- text-align: left;
- }
- // reduces the gap between row title and summary title when there is no group title
- &__title + &__group &__row-title--no-group-title {
- padding-top: 0.5rem;
- }
-
- &__item-title,
- &__values,
- &__actions {
- hyphens: manual;
- margin: 0;
- overflow-wrap: break-word;
- padding: 0 0 $summary-row-spacing;
- vertical-align: top;
- word-wrap: break-word;
- }
-
- &__item-title {
- padding-top: $summary-row-spacing;
- position: relative;
- &--text {
- display: block;
- overflow: hidden;
- padding-left: 2rem;
- }
- &-icon {
- left: 0;
- position: absolute;
- text-align: center;
- &--check .ons-icon {
- fill: var(--ons-color-leaf-green) !important;
- }
- }
- }
-
- &__actions {
- white-space: nowrap;
- }
-
- &__spacer {
- background: var(--ons-color-black);
- display: inline-block;
- height: 1.15rem;
- margin: 0.18rem 0.25rem 0;
- vertical-align: middle;
- width: 1px;
- }
-
- // Item Modifiers
- &__item--total & {
- &__values {
- @extend .ons-u-fs-m;
+ @extend .ons-u-fs-m;
+ }
+ }
+
+ &--error {
+ background: var(--ons-color-errors-tint);
+ border-left: 8px solid var(--ons-color-errors);
+ }
}
- }
- &__item--error & {
- &__row-title--error {
- color: var(--ons-color-errors);
- font-weight: $font-weight-bold;
- padding: $summary-row-spacing $summary-col-spacing;
+ &__row-title {
+ padding: $summary-row-spacing 0;
+ text-align: left;
+ }
+ // reduces the gap between row title and summary title when there is no group title
+ &__title + &__group &__row-title--no-group-title {
+ padding-top: 0.5rem;
}
- &__row-title,
&__item-title,
&__values,
&__actions {
- padding-left: $summary-col-spacing;
- padding-right: $summary-col-spacing;
-
- @include mq('s') {
- padding-left: math.div($summary-col-spacing, 2);
- padding-right: math.div($summary-col-spacing, 2);
+ hyphens: manual;
+ margin: 0;
+ overflow-wrap: break-word;
+ padding: 0 0 $summary-row-spacing;
+ vertical-align: top;
+ word-wrap: break-word;
+ }
- &:first-child,
- & {
- padding-left: $summary-col-spacing;
+ &__item-title {
+ padding-top: $summary-row-spacing;
+ position: relative;
+ &--text {
+ display: block;
+ overflow: hidden;
+ padding-left: 2rem;
}
-
- &:last-child {
- padding-right: $summary-col-spacing;
+ &-icon {
+ left: 0;
+ position: absolute;
+ text-align: center;
+ &--check .ons-icon {
+ fill: var(--ons-color-leaf-green) !important;
+ }
}
- }
}
- }
- // Modifiers
- &--hub & {
&__actions {
- padding: 0 0 $hub-row-spacing;
+ white-space: nowrap;
}
- &__item-title {
- @extend .ons-u-fs-r--b;
-
- padding-top: $hub-row-spacing;
+ &__spacer {
+ background: var(--ons-color-black);
+ display: inline-block;
+ height: 1.15rem;
+ margin: 0.18rem 0.25rem 0;
+ vertical-align: middle;
+ width: 1px;
}
- }
- &:not(&--hub) & {
- &__values {
- @extend .ons-u-fs-r--b;
- }
- }
+ // Item Modifiers
+ &__item--error & {
+ &__row-title--error {
+ color: var(--ons-color-errors);
+ font-weight: $font-weight-bold;
+ padding: $summary-row-spacing $summary-col-spacing;
+ }
- // Breakpoints
- @include mq(xxs, m, none, '<') {
- &__item-title,
- &__values,
- &__actions {
- display: block;
+ &__row-title,
+ &__item-title,
+ &__values,
+ &__actions {
+ padding-left: $summary-col-spacing;
+ padding-right: $summary-col-spacing;
+
+ @include mq('s') {
+ padding-left: math.div($summary-col-spacing, 2);
+ padding-right: math.div($summary-col-spacing, 2);
+
+ &:first-child,
+ & {
+ padding-left: $summary-col-spacing;
+ }
+
+ &:last-child {
+ padding-right: $summary-col-spacing;
+ }
+ }
+ }
}
+ // Modifiers
&--hub & {
- &__values {
- display: none;
- }
- }
+ &__actions {
+ padding: 0 0 $hub-row-spacing;
+ }
- &__row {
- flex-direction: column;
- }
- }
+ &__item-title {
+ @extend .ons-u-fs-r--b;
- @include mq(m) {
- &__item-title,
- &__values,
- &__actions {
- overflow: auto;
- flex: 5 1 33%;
- padding-top: $summary-row-spacing;
- vertical-align: top;
- &:not(:last-child) {
- padding-right: $summary-col-spacing;
- }
+ padding-top: $hub-row-spacing;
+ }
}
- &__actions {
- display: flex;
- justify-content: right;
+ &:not(&--hub) & {
+ &__values {
+ @extend .ons-u-fs-r--b;
+ }
}
- &__button {
- align-self: flex-start;
- }
+ // Breakpoints
+ @include mq(xxs, m, none, '<') {
+ &__item-title,
+ &__values,
+ &__actions {
+ display: block;
+ }
+
+ &--hub & {
+ &__values {
+ display: none;
+ }
+ }
- &__values--2 {
- flex: 10.5 1 66%;
+ &__row {
+ flex-direction: column;
+ }
}
- &--hub & {
- &__item-title,
- &__values,
- &__actions {
- padding-top: $hub-row-spacing;
- }
+ @include mq(m) {
+ &__item-title,
+ &__values,
+ &__actions {
+ overflow: auto;
+ flex: 5 1 33%;
+ padding-top: $summary-row-spacing;
+ vertical-align: top;
+ &:not(:last-child) {
+ padding-right: $summary-col-spacing;
+ }
+ }
+
+ &__actions {
+ display: flex;
+ justify-content: right;
+ }
+
+ &__button {
+ align-self: flex-start;
+ }
+
+ &__values--2 {
+ flex: 10.5 1 66%;
+ }
+
+ &--hub & {
+ &__item-title,
+ &__values,
+ &__actions {
+ padding-top: $hub-row-spacing;
+ }
+ }
}
- }
-
- &__group {
- &--card {
- border: 1px solid var(--ons-color-borders-light);
- .ons-summary__items {
- padding: 0 1.25rem;
- .ons-summary__item:not(:last-child) {
- border-bottom: 1px solid var(--ons-color-borders-light);
- }
- }
- .ons-summary__group-title {
- background-color: var(--ons-color-grey-5);
- padding: 1rem 1.25rem;
- }
- .ons-summary__link {
- padding: 0 1.25rem;
- div {
- padding: 1rem 0;
- border-top: 1px solid var(--ons-color-borders-light);
- }
- }
- .ons-summary__placeholder {
- display: block;
- padding: 0.5rem 1.25rem 0;
- }
- .ons-summary__placeholder + .ons-summary__link {
- div {
- border-top: none;
- }
- }
+
+ &__group {
+ &--card {
+ border: 1px solid var(--ons-color-borders-light);
+ .ons-summary__items {
+ padding: 0 1.25rem;
+ .ons-summary__item:not(:last-child) {
+ border-bottom: 1px solid var(--ons-color-borders-light);
+ }
+ }
+ .ons-summary__group-title {
+ background-color: var(--ons-color-grey-5);
+ padding: 1rem 1.25rem;
+ }
+ .ons-summary__link {
+ padding: 0 1.25rem;
+ div {
+ padding: 1rem 0;
+ border-top: 1px solid var(--ons-color-borders-light);
+ }
+ }
+ .ons-summary__placeholder {
+ display: block;
+ padding: 0.5rem 1.25rem 0;
+ }
+ .ons-summary__placeholder + .ons-summary__link {
+ div {
+ border-top: 0;
+ }
+ }
+ }
}
- }
}
diff --git a/src/components/table-of-contents/_macro.spec.js b/src/components/table-of-contents/_macro.spec.js
index 9b4a57f0b9..c7f354d215 100644
--- a/src/components/table-of-contents/_macro.spec.js
+++ b/src/components/table-of-contents/_macro.spec.js
@@ -7,164 +7,164 @@ import { mapAll } from '../../tests/helpers/cheerio';
import { renderComponent, templateFaker } from '../../tests/helpers/rendering';
const EXAMPLE_TABLE_OF_CONTENTS_SKIP_LINK = {
- title: 'Contents',
- skipLink: {
- url: '#the-content',
- text: 'Skip to guide content',
- },
- itemsList: [],
+ title: 'Contents',
+ skipLink: {
+ url: '#the-content',
+ text: 'Skip to guide content',
+ },
+ itemsList: [],
};
const EXAMPLE_TABLE_OF_CONTENTS_SINGLE = {
- title: 'Contents',
- itemsList: [
- {
- url: '#overview',
- text: 'Overview',
- },
- {
- url: '#who-should-take-part-and-why',
- text: 'Who should take part and why',
- },
- ],
+ title: 'Contents',
+ itemsList: [
+ {
+ url: '#overview',
+ text: 'Overview',
+ },
+ {
+ url: '#who-should-take-part-and-why',
+ text: 'Who should take part and why',
+ },
+ ],
};
const EXAMPLE_TABLE_OF_CONTENTS_MULTIPLE = {
- title: 'Contents',
- lists: [
- {
- listHeading: 'Household questions',
- listHeadingHidden: 'help topics',
- itemsList: [
+ title: 'Contents',
+ lists: [
{
- url: '#household1',
- text: 'Household and who lives here',
+ listHeading: 'Household questions',
+ listHeadingHidden: 'help topics',
+ itemsList: [
+ {
+ url: '#household1',
+ text: 'Household and who lives here',
+ },
+ ],
},
- ],
- },
- {
- listHeading: 'Individual questions',
- listHeadingHidden: 'help topics',
- itemsList: [
{
- url: '#individual1',
- text: 'Name, date of birth and marital status',
+ listHeading: 'Individual questions',
+ listHeadingHidden: 'help topics',
+ itemsList: [
+ {
+ url: '#individual1',
+ text: 'Name, date of birth and marital status',
+ },
+ ],
},
- ],
- },
- ],
+ ],
};
describe('macro: table-of-contents', () => {
- it('renders a default aria label', () => {
- const $ = cheerio.load(renderComponent('table-of-contents', EXAMPLE_TABLE_OF_CONTENTS_SINGLE));
+ it('renders a default aria label', () => {
+ const $ = cheerio.load(renderComponent('table-of-contents', EXAMPLE_TABLE_OF_CONTENTS_SINGLE));
- expect($('.ons-toc').attr('aria-label')).toBe('Table of contents');
- });
-
- it('renders the provided `ariaLabel`', () => {
- const $ = cheerio.load(
- renderComponent('table-of-contents', {
- ...EXAMPLE_TABLE_OF_CONTENTS_SINGLE,
- ariaLabel: 'Contents',
- }),
- );
-
- expect($('.ons-toc').attr('aria-label')).toBe('Contents');
- });
-
- it('renders title as heading element', () => {
- const $ = cheerio.load(renderComponent('table-of-contents', EXAMPLE_TABLE_OF_CONTENTS_SINGLE));
-
- expect($('.ons-toc__title').text().trim()).toBe('Contents');
- });
-
- describe('skip to content when `skipLink` is provided', () => {
- it('passes jest-axe checks', async () => {
- const $ = cheerio.load(renderComponent('table-of-contents', EXAMPLE_TABLE_OF_CONTENTS_SKIP_LINK));
-
- const results = await axe($.html());
- expect(results).toHaveNoViolations();
+ expect($('.ons-toc').attr('aria-label')).toBe('Table of contents');
});
- it('outputs `skip-to-content` component', () => {
- const faker = templateFaker();
- const skipToContentSpy = faker.spy('skip-to-content');
+ it('renders the provided `ariaLabel`', () => {
+ const $ = cheerio.load(
+ renderComponent('table-of-contents', {
+ ...EXAMPLE_TABLE_OF_CONTENTS_SINGLE,
+ ariaLabel: 'Contents',
+ }),
+ );
- faker.renderComponent('table-of-contents', EXAMPLE_TABLE_OF_CONTENTS_SKIP_LINK);
-
- expect(skipToContentSpy.occurrences[0]).toEqual({
- url: '#the-content',
- text: 'Skip to guide content',
- });
+ expect($('.ons-toc').attr('aria-label')).toBe('Contents');
});
- });
- describe('single list', () => {
- it('passes jest-axe checks', async () => {
- const $ = cheerio.load(renderComponent('table-of-contents', EXAMPLE_TABLE_OF_CONTENTS_SINGLE));
+ it('renders title as heading element', () => {
+ const $ = cheerio.load(renderComponent('table-of-contents', EXAMPLE_TABLE_OF_CONTENTS_SINGLE));
- const results = await axe($.html());
- expect(results).toHaveNoViolations();
+ expect($('.ons-toc__title').text().trim()).toBe('Contents');
});
- it('outputs `lists` component', () => {
- const faker = templateFaker();
- const listsSpy = faker.spy('list');
+ describe('skip to content when `skipLink` is provided', () => {
+ it('passes jest-axe checks', async () => {
+ const $ = cheerio.load(renderComponent('table-of-contents', EXAMPLE_TABLE_OF_CONTENTS_SKIP_LINK));
- faker.renderComponent('table-of-contents', EXAMPLE_TABLE_OF_CONTENTS_SINGLE);
+ const results = await axe($.html());
+ expect(results).toHaveNoViolations();
+ });
- expect(listsSpy.occurrences[0]).toEqual({
- element: 'ol',
- classes: 'ons-u-mb-m',
- variants: 'dashed',
- itemsList: EXAMPLE_TABLE_OF_CONTENTS_SINGLE.itemsList,
- });
- });
- });
+ it('outputs `skip-to-content` component', () => {
+ const faker = templateFaker();
+ const skipToContentSpy = faker.spy('skip-to-content');
- describe('multiple lists', () => {
- it('passes jest-axe checks', async () => {
- const $ = cheerio.load(renderComponent('table-of-contents', EXAMPLE_TABLE_OF_CONTENTS_MULTIPLE));
+ faker.renderComponent('table-of-contents', EXAMPLE_TABLE_OF_CONTENTS_SKIP_LINK);
- const results = await axe($.html());
- expect(results).toHaveNoViolations();
+ expect(skipToContentSpy.occurrences[0]).toEqual({
+ url: '#the-content',
+ text: 'Skip to guide content',
+ });
+ });
});
- it('renders a heading for each list', () => {
- const $ = cheerio.load(renderComponent('table-of-contents', EXAMPLE_TABLE_OF_CONTENTS_MULTIPLE));
+ describe('single list', () => {
+ it('passes jest-axe checks', async () => {
+ const $ = cheerio.load(renderComponent('table-of-contents', EXAMPLE_TABLE_OF_CONTENTS_SINGLE));
- $('.ons-u-vh').remove();
+ const results = await axe($.html());
+ expect(results).toHaveNoViolations();
+ });
- const headings = mapAll($('h3'), (node) => $(node).text().trim());
- expect(headings).toEqual(['Household questions:', 'Individual questions:']);
- });
+ it('outputs `lists` component', () => {
+ const faker = templateFaker();
+ const listsSpy = faker.spy('list');
- it('renders visually hidden heading for each list', () => {
- const $ = cheerio.load(renderComponent('table-of-contents', EXAMPLE_TABLE_OF_CONTENTS_MULTIPLE));
+ faker.renderComponent('table-of-contents', EXAMPLE_TABLE_OF_CONTENTS_SINGLE);
- const headings = mapAll($('h3 .ons-u-vh'), (node) => node.text().trim());
- expect(headings).toEqual(['help topics', 'help topics']);
+ expect(listsSpy.occurrences[0]).toEqual({
+ element: 'ol',
+ classes: 'ons-u-mb-m',
+ variants: 'dashed',
+ itemsList: EXAMPLE_TABLE_OF_CONTENTS_SINGLE.itemsList,
+ });
+ });
});
- it('outputs `lists` component for each list', () => {
- const faker = templateFaker();
- const listsSpy = faker.spy('list');
-
- faker.renderComponent('table-of-contents', EXAMPLE_TABLE_OF_CONTENTS_MULTIPLE);
-
- expect(listsSpy.occurrences[0]).toEqual({
- element: 'ol',
- classes: 'ons-u-mb-m',
- variants: 'dashed',
- itemsList: EXAMPLE_TABLE_OF_CONTENTS_MULTIPLE.lists[0].itemsList,
- });
- expect(listsSpy.occurrences[1]).toEqual({
- element: 'ol',
- classes: 'ons-u-mb-m',
- variants: 'dashed',
- itemsList: EXAMPLE_TABLE_OF_CONTENTS_MULTIPLE.lists[1].itemsList,
- });
+ describe('multiple lists', () => {
+ it('passes jest-axe checks', async () => {
+ const $ = cheerio.load(renderComponent('table-of-contents', EXAMPLE_TABLE_OF_CONTENTS_MULTIPLE));
+
+ const results = await axe($.html());
+ expect(results).toHaveNoViolations();
+ });
+
+ it('renders a heading for each list', () => {
+ const $ = cheerio.load(renderComponent('table-of-contents', EXAMPLE_TABLE_OF_CONTENTS_MULTIPLE));
+
+ $('.ons-u-vh').remove();
+
+ const headings = mapAll($('h3'), (node) => $(node).text().trim());
+ expect(headings).toEqual(['Household questions:', 'Individual questions:']);
+ });
+
+ it('renders visually hidden heading for each list', () => {
+ const $ = cheerio.load(renderComponent('table-of-contents', EXAMPLE_TABLE_OF_CONTENTS_MULTIPLE));
+
+ const headings = mapAll($('h3 .ons-u-vh'), (node) => node.text().trim());
+ expect(headings).toEqual(['help topics', 'help topics']);
+ });
+
+ it('outputs `lists` component for each list', () => {
+ const faker = templateFaker();
+ const listsSpy = faker.spy('list');
+
+ faker.renderComponent('table-of-contents', EXAMPLE_TABLE_OF_CONTENTS_MULTIPLE);
+
+ expect(listsSpy.occurrences[0]).toEqual({
+ element: 'ol',
+ classes: 'ons-u-mb-m',
+ variants: 'dashed',
+ itemsList: EXAMPLE_TABLE_OF_CONTENTS_MULTIPLE.lists[0].itemsList,
+ });
+ expect(listsSpy.occurrences[1]).toEqual({
+ element: 'ol',
+ classes: 'ons-u-mb-m',
+ variants: 'dashed',
+ itemsList: EXAMPLE_TABLE_OF_CONTENTS_MULTIPLE.lists[1].itemsList,
+ });
+ });
});
- });
});
diff --git a/src/components/table-of-contents/_toc.scss b/src/components/table-of-contents/_toc.scss
index 2b69061596..769963845d 100644
--- a/src/components/table-of-contents/_toc.scss
+++ b/src/components/table-of-contents/_toc.scss
@@ -1,12 +1,12 @@
.ons-toc {
- &-container {
- border-bottom: 1px solid var(--ons-color-grey-15);
- margin-bottom: 2rem;
- padding-bottom: 1rem;
- }
+ &-container {
+ border-bottom: 1px solid var(--ons-color-grey-15);
+ margin-bottom: 2rem;
+ padding-bottom: 1rem;
+ }
- &__link-active {
- color: var(--ons-color-text-link-hover);
- text-decoration: underline solid var(--ons-color-text-link-hover) 2px;
- }
+ &__link-active {
+ color: var(--ons-color-text-link-hover);
+ text-decoration: underline solid var(--ons-color-text-link-hover) 2px;
+ }
}
diff --git a/src/components/table-of-contents/toc.dom.js b/src/components/table-of-contents/toc.dom.js
index e79a2dc497..3e8b073bc2 100644
--- a/src/components/table-of-contents/toc.dom.js
+++ b/src/components/table-of-contents/toc.dom.js
@@ -1,13 +1,13 @@
import domready from '../../js/domready';
async function toc() {
- const toc = [...document.querySelectorAll('.ons-js-toc-container')];
+ const toc = [...document.querySelectorAll('.ons-js-toc-container')];
- if (toc.length) {
- const Toc = (await import('./toc')).default;
+ if (toc.length) {
+ const Toc = (await import('./toc')).default;
- toc.forEach((component) => new Toc(component));
- }
+ toc.forEach((component) => new Toc(component));
+ }
}
domready(toc);
diff --git a/src/components/table-of-contents/toc.js b/src/components/table-of-contents/toc.js
index f48d369064..db31d30ac7 100644
--- a/src/components/table-of-contents/toc.js
+++ b/src/components/table-of-contents/toc.js
@@ -1,39 +1,39 @@
export default class Toc {
- constructor(component) {
- this.component = component;
- this.sections = [...this.component.querySelectorAll('section[id]')];
- this.refreshIntervalId = setInterval(() => this.setCurrent(), 100);
- this.setCurrent();
- }
+ constructor(component) {
+ this.component = component;
+ this.sections = [...this.component.querySelectorAll('section[id]')];
+ this.refreshIntervalId = setInterval(() => this.setCurrent(), 100);
+ this.setCurrent();
+ }
- setCurrent() {
- let activeSection = this.sections[0];
- for (let section of this.sections) {
- const top = section.getBoundingClientRect().top;
- if (top > 100) {
- break;
- }
+ setCurrent() {
+ let activeSection = this.sections[0];
+ for (let section of this.sections) {
+ const top = section.getBoundingClientRect().top;
+ if (top > 100) {
+ break;
+ }
- activeSection = section;
+ activeSection = section;
- if (top >= 0 && top <= 100) {
- break;
- }
- }
+ if (top >= 0 && top <= 100) {
+ break;
+ }
+ }
- if (activeSection === this.activeSection) {
- return;
- }
+ if (activeSection === this.activeSection) {
+ return;
+ }
- this.activeSection = activeSection;
+ this.activeSection = activeSection;
- for (let section of this.sections) {
- const tocItem = document.querySelector(`.ons-toc .ons-list__link[href="#${section.id}"]`);
- if (section === activeSection) {
- tocItem.classList.add('ons-toc__link-active');
- } else {
- tocItem.classList.remove('ons-toc__link-active');
- }
+ for (let section of this.sections) {
+ const tocItem = document.querySelector(`.ons-toc .ons-list__link[href="#${section.id}"]`);
+ if (section === activeSection) {
+ tocItem.classList.add('ons-toc__link-active');
+ } else {
+ tocItem.classList.remove('ons-toc__link-active');
+ }
+ }
}
- }
}
diff --git a/src/components/table-of-contents/toc.spec.js b/src/components/table-of-contents/toc.spec.js
index 8862291ebe..9b040392a9 100644
--- a/src/components/table-of-contents/toc.spec.js
+++ b/src/components/table-of-contents/toc.spec.js
@@ -1,31 +1,31 @@
import { renderComponent, setTestPage } from '../../tests/helpers/rendering';
describe('script: table-of-contents', () => {
- beforeEach(async () => {
- await setTestPage(
- '/test',
- /* eslint-disable indent */
- `
+ beforeEach(async () => {
+ await setTestPage(
+ '/test',
+ /* eslint-disable indent */
+ `
${renderComponent('table-of-contents', {
- title: 'Contents',
- ariaLabel: 'Sections in this page',
- itemsList: [
- {
- url: '#section1',
- text: 'First section',
- },
- {
- url: '#section2',
- text: 'Second section',
- },
- {
- url: '#section3',
- text: 'Third section',
- },
- ],
+ title: 'Contents',
+ ariaLabel: 'Sections in this page',
+ itemsList: [
+ {
+ url: '#section1',
+ text: 'First section',
+ },
+ {
+ url: '#section2',
+ text: 'Second section',
+ },
+ {
+ url: '#section3',
+ text: 'Third section',
+ },
+ ],
})}
@@ -45,57 +45,57 @@ describe('script: table-of-contents', () => {
`,
- /* eslint-enable indent */
- );
- });
-
- it.each([
- ['section1', 'First section'],
- ['section2', 'Second section'],
- ['section3', 'Third section'],
- ])('marks "%s" as the current section', async (sectionId, sectionTitle) => {
- await page.$eval(`#${sectionId}`, (node) => node.scrollIntoView());
- await page.waitForTimeout(250);
-
- const activeSection = await page.$eval('.ons-toc__link-active', (node) => node.innerText.trim());
- expect(activeSection).toBe(sectionTitle);
- });
+ /* eslint-enable indent */
+ );
+ });
+
+ it.each([
+ ['section1', 'First section'],
+ ['section2', 'Second section'],
+ ['section3', 'Third section'],
+ ])('marks "%s" as the current section', async (sectionId, sectionTitle) => {
+ await page.$eval(`#${sectionId}`, (node) => node.scrollIntoView());
+ await page.waitForTimeout(250);
+
+ const activeSection = await page.$eval('.ons-toc__link-active', (node) => node.innerText.trim());
+ expect(activeSection).toBe(sectionTitle);
+ });
});
describe('script: table-of-contents-fixed-position', () => {
- beforeEach(async () => {
- await setTestPage(
- '/test',
- /* eslint-disable indent */
- `
+ beforeEach(async () => {
+ await setTestPage(
+ '/test',
+ /* eslint-disable indent */
+ `
${renderComponent('table-of-contents', {
- title: 'Contents',
- ariaLabel: 'Sections in this page',
- itemsList: [
- {
- url: '#section1',
- text: 'What is the census?',
- },
- {
- url: '#section2',
- text: 'The online census has now closed',
- },
- {
- url: '#section3',
- text: 'What happens after Census Day',
- },
- {
- url: '#section4',
- text: 'The census in Northern Ireland and Scotland',
- },
- {
- url: '#section5',
- text: 'The last census',
- },
- ],
+ title: 'Contents',
+ ariaLabel: 'Sections in this page',
+ itemsList: [
+ {
+ url: '#section1',
+ text: 'What is the census?',
+ },
+ {
+ url: '#section2',
+ text: 'The online census has now closed',
+ },
+ {
+ url: '#section3',
+ text: 'What happens after Census Day',
+ },
+ {
+ url: '#section4',
+ text: 'The census in Northern Ireland and Scotland',
+ },
+ {
+ url: '#section5',
+ text: 'The last census',
+ },
+ ],
})}
@@ -144,32 +144,32 @@ describe('script: table-of-contents-fixed-position', () => {
`,
- /* eslint-enable indent */
- 'main',
- );
- });
-
- it('when the "ons-grid__col--sticky" class is used, then the ToC stays in a fixed position while scrolling', async () => {
- await page.evaluate(() => {
- window.scrollTo(0, 1000);
+ /* eslint-enable indent */
+ 'main',
+ );
});
- await page.waitForTimeout(250);
- const leftColumn = await page.$('#sticky-container');
- const boundingBox = await leftColumn.boundingBox();
+ it('when the "ons-grid__col--sticky" class is used, then the ToC stays in a fixed position while scrolling', async () => {
+ await page.evaluate(() => {
+ window.scrollTo(0, 1000);
+ });
+
+ await page.waitForTimeout(250);
+ const leftColumn = await page.$('#sticky-container');
+ const boundingBox = await leftColumn.boundingBox();
- const viewport = await page.evaluate(() => ({
- width: window.innerWidth,
- height: window.innerHeight,
- }));
+ const viewport = await page.evaluate(() => ({
+ width: window.innerWidth,
+ height: window.innerHeight,
+ }));
- const isInViewport =
- boundingBox &&
- boundingBox.x < viewport.width &&
- boundingBox.y < viewport.height &&
- boundingBox.x + boundingBox.width > 0 &&
- boundingBox.y + boundingBox.height > 0;
+ const isInViewport =
+ boundingBox &&
+ boundingBox.x < viewport.width &&
+ boundingBox.y < viewport.height &&
+ boundingBox.x + boundingBox.width > 0 &&
+ boundingBox.y + boundingBox.height > 0;
- expect(isInViewport).toBeTruthy();
- });
+ expect(isInViewport).toBeTruthy();
+ });
});
diff --git a/src/components/table/_macro.spec.js b/src/components/table/_macro.spec.js
index 32ca15fc8e..5c31a2ca35 100644
--- a/src/components/table/_macro.spec.js
+++ b/src/components/table/_macro.spec.js
@@ -7,538 +7,538 @@ import { mapAll } from '../../tests/helpers/cheerio';
import { renderComponent, templateFaker } from '../../tests/helpers/rendering';
const EXAMPLE_TABLE_MINIMAL = {
- ths: [],
- trs: [],
+ ths: [],
+ trs: [],
};
const EXAMPLE_TABLE = {
- ...EXAMPLE_TABLE_MINIMAL,
- ths: [{ value: 'Column 1' }, { value: 'Column 2' }, { value: 'Column 3' }],
- trs: [
- {
- tds: [{ value: 'Row 1 Cell 1' }, { value: 'Row 1 Cell 2' }, { value: 'Row 1 Cell 3' }],
- },
- {
- tds: [{ value: 'Row 2 Cell 1' }, { value: 'Row 2 Cell 2' }, { value: 'Row 2 Cell 3' }],
- },
- ],
+ ...EXAMPLE_TABLE_MINIMAL,
+ ths: [{ value: 'Column 1' }, { value: 'Column 2' }, { value: 'Column 3' }],
+ trs: [
+ {
+ tds: [{ value: 'Row 1 Cell 1' }, { value: 'Row 1 Cell 2' }, { value: 'Row 1 Cell 3' }],
+ },
+ {
+ tds: [{ value: 'Row 2 Cell 1' }, { value: 'Row 2 Cell 2' }, { value: 'Row 2 Cell 3' }],
+ },
+ ],
};
describe('macro: table', () => {
- it('passes jest-axe checks', async () => {
- const $ = cheerio.load(renderComponent('table', EXAMPLE_TABLE));
-
- const results = await axe($.html());
- expect(results).toHaveNoViolations();
- });
-
- it('has the provided `id` attribute', () => {
- const $ = cheerio.load(
- renderComponent('table', {
- ...EXAMPLE_TABLE,
- id: 'example-table',
- }),
- );
-
- expect($('.ons-table').attr('id')).toBe('example-table');
- });
-
- it('has additionally provided style classes', () => {
- const $ = cheerio.load(
- renderComponent('table', {
- ...EXAMPLE_TABLE,
- tableClasses: 'extra-class another-extra-class',
- }),
- );
-
- expect($('.ons-table').hasClass('extra-class')).toBe(true);
- expect($('.ons-table').hasClass('another-extra-class')).toBe(true);
- });
-
- describe('header row', () => {
- it('renders header cells with expected text', () => {
- const $ = cheerio.load(renderComponent('table', EXAMPLE_TABLE));
-
- const headerCellValues = mapAll($('.ons-table__header'), (node) => node.text().trim());
- expect(headerCellValues).toEqual(['Column 1', 'Column 2', 'Column 3']);
- });
-
- it('adds additionally provided class to column header', () => {
- const $ = cheerio.load(
- renderComponent('table', {
- ...EXAMPLE_TABLE,
- ths: [
- {
- value: 'Column 1',
- thClasses: 'extra-column-class another-extra-column-class',
- },
- ],
- }),
- );
-
- expect($('.ons-table__header').hasClass('extra-column-class')).toBe(true);
- expect($('.ons-table__header').hasClass('another-extra-column-class')).toBe(true);
- });
-
- it('adds additional width attribute to column header', () => {
- const $ = cheerio.load(
- renderComponent('table', {
- ...EXAMPLE_TABLE,
- ths: [
- {
- value: 'Column 1',
- widthPercentage: 50,
- },
- ],
- }),
- );
-
- expect($('.ons-table__header').attr('width')).toBe('50%');
- });
-
- it('does not add "numeric" modifier class to column header when `td.numeric` is not provided', () => {
- const $ = cheerio.load(renderComponent('table', EXAMPLE_TABLE));
-
- expect($('.ons-table__header').hasClass('ons-table__header--numeric')).toBe(false);
- });
-
- it('adds "numeric" modifier class to column header when `th.numeric` is `true`', () => {
- const $ = cheerio.load(
- renderComponent('table', {
- ...EXAMPLE_TABLE,
- ths: [
- {
- value: 'Column 1',
- numeric: true,
- },
- ],
- }),
- );
-
- expect($('.ons-table__header').hasClass('ons-table__header--numeric')).toBe(true);
- });
-
- it('does not add visually hidden class to column headers', () => {
- const $ = cheerio.load(renderComponent('table', EXAMPLE_TABLE));
-
- const hasClass = mapAll($('.ons-table__header > span'), (node) => node.hasClass('ons-u-vh'));
- expect(hasClass).toEqual([false, false, false]);
- });
-
- it('does not render "sort-sprite" icon', () => {
- const faker = templateFaker();
- const iconsSpy = faker.spy('icon');
-
- faker.renderComponent('table', EXAMPLE_TABLE);
-
- expect(iconsSpy.occurrences.length).toBe(0);
- });
- });
-
- describe('row', () => {
- it('renders expected row cells', () => {
- const $ = cheerio.load(renderComponent('table', EXAMPLE_TABLE));
-
- const row1Values = mapAll($('.ons-table__row:nth-child(1) .ons-table__cell'), (node) => node.text().trim());
- expect(row1Values).toEqual(['Row 1 Cell 1', 'Row 1 Cell 2', 'Row 1 Cell 3']);
- const row2Values = mapAll($('.ons-table__row:nth-child(2) .ons-table__cell'), (node) => node.text().trim());
- expect(row2Values).toEqual(['Row 2 Cell 1', 'Row 2 Cell 2', 'Row 2 Cell 3']);
- });
-
- it('does not render `id` attribute on a cell when `id` is not provided', () => {
- const $ = cheerio.load(renderComponent('table', EXAMPLE_TABLE));
-
- expect($('.ons-table__cell').attr('id')).toBeUndefined();
- });
-
- it('renders provided `id` attribute on a cell', () => {
- const $ = cheerio.load(
- renderComponent('table', {
- ...EXAMPLE_TABLE,
- trs: [
- {
- tds: [
- {
- id: 'example-cell',
- value: 'Example Cell',
- },
- ],
- },
- ],
- }),
- );
-
- expect($('.ons-table__cell').attr('id')).toBe('example-cell');
- });
-
- it('does not render `data-th` attribute on a cell when `data` is not provided', () => {
- const $ = cheerio.load(renderComponent('table', EXAMPLE_TABLE));
-
- expect($('.ons-table__cell').attr('data-th')).toBeUndefined();
- });
-
- it('renders `data-th` attribute on a cell when `data` is provided', () => {
- const $ = cheerio.load(
- renderComponent('table', {
- ...EXAMPLE_TABLE,
- trs: [
- {
- tds: [
- {
- value: 'Example Cell',
- data: '123',
- },
- ],
- },
- ],
- }),
- );
-
- expect($('.ons-table__cell').attr('data-th')).toBe('123');
- });
-
- it('does not render `data-sort-value` attribute on a cell when `dataSort` is not provided', () => {
- const $ = cheerio.load(renderComponent('table', EXAMPLE_TABLE));
-
- expect($('.ons-table__cell').attr('data-sort-value')).toBeUndefined();
- });
-
- it('renders `data-sort-value` attribute on a cell when `dataSort` is provided', () => {
- const $ = cheerio.load(
- renderComponent('table', {
- ...EXAMPLE_TABLE,
- trs: [
- {
- tds: [
- {
- value: 'Example Cell',
- dataSort: 42,
- },
- ],
- },
- ],
- }),
- );
-
- expect($('.ons-table__cell').attr('data-sort-value')).toBe('42');
- });
-
- it('does not add highlight class to row when `highlight` is not provided', () => {
- const $ = cheerio.load(renderComponent('table', EXAMPLE_TABLE));
-
- expect($('.ons-table__row').hasClass('ons-table__row--highlight')).toBe(false);
- });
-
- it('adds highlight class to row when `highlight` is `true`', () => {
- const $ = cheerio.load(
- renderComponent('table', {
- ...EXAMPLE_TABLE,
- trs: [
- {
- tds: [{ value: 'Row 1 Cell 1' }, { value: 'Row 1 Cell 2' }, { value: 'Row 1 Cell 3' }],
- highlight: true,
- },
- ],
- }),
- );
-
- expect($('.ons-table__row').hasClass('ons-table__row--highlight')).toBe(true);
- });
-
- it('adds additionally provided class to cell', () => {
- const $ = cheerio.load(
- renderComponent('table', {
- ...EXAMPLE_TABLE,
- trs: [
- {
- tds: [
- {
- value: 'Column 1',
- tdClasses: 'extra-cell-class another-extra-cell-class',
- },
- ],
- },
- ],
- }),
- );
-
- expect($('.ons-table__cell').hasClass('extra-cell-class')).toBe(true);
- expect($('.ons-table__cell').hasClass('another-extra-cell-class')).toBe(true);
- });
-
- it('does not add "numeric" modifier class to cell when `td.numeric` is not provided', () => {
- const $ = cheerio.load(renderComponent('table', EXAMPLE_TABLE));
-
- expect($('.ons-table__cell').hasClass('ons-table__cell--numeric')).toBe(false);
- });
-
- it('adds "numeric" modifier class to cell when `td.numeric` is `true`', () => {
- const $ = cheerio.load(
- renderComponent('table', {
- ...EXAMPLE_TABLE,
- trs: [
- {
- tds: [
- {
- value: 'Column 1',
- numeric: true,
- },
- ],
- },
- ],
- }),
- );
-
- expect($('.ons-table__cell').hasClass('ons-table__cell--numeric')).toBe(true);
- });
-
- describe('form', () => {
- const params = {
- ...EXAMPLE_TABLE,
- trs: [
- {
- tds: [
- {
- form: {
- action: 'https://example.com/form',
- button: {
- text: 'Submit form',
- id: 'submit-form-button',
- classes: 'custom-button-class',
- url: 'https://example.com/link',
- value: '42',
- name: 'submit-form-button-name',
- },
- hiddenFormField: {
- name: 'example-hidden-field-name',
- value: '123',
- },
- },
- },
- ],
- },
- ],
- };
-
- it('does not render form section when `form` is not provided', () => {
+ it('passes jest-axe checks', async () => {
const $ = cheerio.load(renderComponent('table', EXAMPLE_TABLE));
- expect($('form').length).toBe(0);
- });
-
- it('renders form section when `form` is provided', () => {
- const $ = cheerio.load(renderComponent('table', params));
-
- expect($('form').length).toBe(1);
- });
-
- it('renders the provided `action` attribute', () => {
- const $ = cheerio.load(renderComponent('table', params));
-
- expect($('form').attr('action')).toBe('https://example.com/form');
- });
-
- it('renders a default value of "POST" for the `method` attribute', () => {
- const $ = cheerio.load(renderComponent('table', params));
-
- expect($('form').attr('method')).toBe('POST');
- });
+ const results = await axe($.html());
+ expect(results).toHaveNoViolations();
+ });
- it('renders the provided `method` attribute', () => {
+ it('has the provided `id` attribute', () => {
const $ = cheerio.load(
- renderComponent('table', {
- ...EXAMPLE_TABLE,
- trs: [
- {
- tds: [
- {
- form: {
- ...params.trs[0].tds[0].form,
- method: 'PUT',
- },
- },
- ],
- },
- ],
- }),
+ renderComponent('table', {
+ ...EXAMPLE_TABLE,
+ id: 'example-table',
+ }),
);
- expect($('form').attr('method')).toBe('PUT');
- });
-
- it('renders `button` component with the expected parameters', () => {
- const faker = templateFaker();
- const buttonSpy = faker.spy('button');
-
- faker.renderComponent('table', params);
-
- expect(buttonSpy.occurrences).toEqual([
- {
- text: 'Submit form',
- id: 'submit-form-button',
- classes: 'custom-button-class',
- url: 'https://example.com/link',
- value: '42', // `| safe` filter is used in macro which makes a string
- name: 'submit-form-button-name',
- },
- ]);
- });
- });
- });
-
- describe('footer row', () => {
- it('does not render footer section when `tfoot` is not provided', () => {
- const $ = cheerio.load(renderComponent('table', EXAMPLE_TABLE));
-
- expect($('.ons-table__foot').length).toBe(0);
- });
-
- it('renders footer cells with expected text', () => {
- const $ = cheerio.load(
- renderComponent('table', {
- ...EXAMPLE_TABLE,
- tfoot: [{ value: 'Footer Cell 1' }, { value: 'Footer Cell 2' }, { value: 'Footer Cell 3' }],
- }),
- );
-
- const footerCellValues = mapAll($('.ons-table__foot .ons-table__cell'), (node) => node.text().trim());
- expect(footerCellValues).toEqual(['Footer Cell 1', 'Footer Cell 2', 'Footer Cell 3']);
- });
- });
-
- describe('scrollable variant', () => {
- const params = {
- ...EXAMPLE_TABLE,
- variants: ['scrollable'],
- caption: 'Example table caption',
- };
-
- it('has the "scrollable" variant class', () => {
- const $ = cheerio.load(renderComponent('table', params));
-
- expect($('.ons-table').hasClass('ons-table--scrollable')).toBe(true);
- });
-
- it('renders "scrollable" container element', () => {
- const $ = cheerio.load(renderComponent('table', params));
-
- expect($('.ons-table-scrollable').length).toBe(1);
- expect($('.ons-table-scrollable--on').length).toBe(1);
- });
-
- it('renders "content" container element', () => {
- const $ = cheerio.load(renderComponent('table', params));
-
- expect($('.ons-table-scrollable__content').length).toBe(1);
- });
-
- it('renders an appropriate `aria-label` attribute on the "content" container element', () => {
- const $ = cheerio.load(renderComponent('table', params));
-
- expect($('.ons-table-scrollable__content').attr('aria-label')).toBe('Example table caption. Scrollable table');
+ expect($('.ons-table').attr('id')).toBe('example-table');
});
- it('renders a custom `aria-label` attribute on the "content" container element', () => {
- const $ = cheerio.load(
- renderComponent('table', {
- ...params,
- ariaLabel: 'Special table',
- }),
- );
+ it('has additionally provided style classes', () => {
+ const $ = cheerio.load(
+ renderComponent('table', {
+ ...EXAMPLE_TABLE,
+ tableClasses: 'extra-class another-extra-class',
+ }),
+ );
- expect($('.ons-table-scrollable__content').attr('aria-label')).toBe('Example table caption. Special table');
+ expect($('.ons-table').hasClass('extra-class')).toBe(true);
+ expect($('.ons-table').hasClass('another-extra-class')).toBe(true);
+ });
+
+ describe('header row', () => {
+ it('renders header cells with expected text', () => {
+ const $ = cheerio.load(renderComponent('table', EXAMPLE_TABLE));
+
+ const headerCellValues = mapAll($('.ons-table__header'), (node) => node.text().trim());
+ expect(headerCellValues).toEqual(['Column 1', 'Column 2', 'Column 3']);
+ });
+
+ it('adds additionally provided class to column header', () => {
+ const $ = cheerio.load(
+ renderComponent('table', {
+ ...EXAMPLE_TABLE,
+ ths: [
+ {
+ value: 'Column 1',
+ thClasses: 'extra-column-class another-extra-column-class',
+ },
+ ],
+ }),
+ );
+
+ expect($('.ons-table__header').hasClass('extra-column-class')).toBe(true);
+ expect($('.ons-table__header').hasClass('another-extra-column-class')).toBe(true);
+ });
+
+ it('adds additional width attribute to column header', () => {
+ const $ = cheerio.load(
+ renderComponent('table', {
+ ...EXAMPLE_TABLE,
+ ths: [
+ {
+ value: 'Column 1',
+ widthPercentage: 50,
+ },
+ ],
+ }),
+ );
+
+ expect($('.ons-table__header').attr('width')).toBe('50%');
+ });
+
+ it('does not add "numeric" modifier class to column header when `td.numeric` is not provided', () => {
+ const $ = cheerio.load(renderComponent('table', EXAMPLE_TABLE));
+
+ expect($('.ons-table__header').hasClass('ons-table__header--numeric')).toBe(false);
+ });
+
+ it('adds "numeric" modifier class to column header when `th.numeric` is `true`', () => {
+ const $ = cheerio.load(
+ renderComponent('table', {
+ ...EXAMPLE_TABLE,
+ ths: [
+ {
+ value: 'Column 1',
+ numeric: true,
+ },
+ ],
+ }),
+ );
+
+ expect($('.ons-table__header').hasClass('ons-table__header--numeric')).toBe(true);
+ });
+
+ it('does not add visually hidden class to column headers', () => {
+ const $ = cheerio.load(renderComponent('table', EXAMPLE_TABLE));
+
+ const hasClass = mapAll($('.ons-table__header > span'), (node) => node.hasClass('ons-u-vh'));
+ expect(hasClass).toEqual([false, false, false]);
+ });
+
+ it('does not render "sort-sprite" icon', () => {
+ const faker = templateFaker();
+ const iconsSpy = faker.spy('icon');
+
+ faker.renderComponent('table', EXAMPLE_TABLE);
+
+ expect(iconsSpy.occurrences.length).toBe(0);
+ });
+ });
+
+ describe('row', () => {
+ it('renders expected row cells', () => {
+ const $ = cheerio.load(renderComponent('table', EXAMPLE_TABLE));
+
+ const row1Values = mapAll($('.ons-table__row:nth-child(1) .ons-table__cell'), (node) => node.text().trim());
+ expect(row1Values).toEqual(['Row 1 Cell 1', 'Row 1 Cell 2', 'Row 1 Cell 3']);
+ const row2Values = mapAll($('.ons-table__row:nth-child(2) .ons-table__cell'), (node) => node.text().trim());
+ expect(row2Values).toEqual(['Row 2 Cell 1', 'Row 2 Cell 2', 'Row 2 Cell 3']);
+ });
+
+ it('does not render `id` attribute on a cell when `id` is not provided', () => {
+ const $ = cheerio.load(renderComponent('table', EXAMPLE_TABLE));
+
+ expect($('.ons-table__cell').attr('id')).toBeUndefined();
+ });
+
+ it('renders provided `id` attribute on a cell', () => {
+ const $ = cheerio.load(
+ renderComponent('table', {
+ ...EXAMPLE_TABLE,
+ trs: [
+ {
+ tds: [
+ {
+ id: 'example-cell',
+ value: 'Example Cell',
+ },
+ ],
+ },
+ ],
+ }),
+ );
+
+ expect($('.ons-table__cell').attr('id')).toBe('example-cell');
+ });
+
+ it('does not render `data-th` attribute on a cell when `data` is not provided', () => {
+ const $ = cheerio.load(renderComponent('table', EXAMPLE_TABLE));
+
+ expect($('.ons-table__cell').attr('data-th')).toBeUndefined();
+ });
+
+ it('renders `data-th` attribute on a cell when `data` is provided', () => {
+ const $ = cheerio.load(
+ renderComponent('table', {
+ ...EXAMPLE_TABLE,
+ trs: [
+ {
+ tds: [
+ {
+ value: 'Example Cell',
+ data: '123',
+ },
+ ],
+ },
+ ],
+ }),
+ );
+
+ expect($('.ons-table__cell').attr('data-th')).toBe('123');
+ });
+
+ it('does not render `data-sort-value` attribute on a cell when `dataSort` is not provided', () => {
+ const $ = cheerio.load(renderComponent('table', EXAMPLE_TABLE));
+
+ expect($('.ons-table__cell').attr('data-sort-value')).toBeUndefined();
+ });
+
+ it('renders `data-sort-value` attribute on a cell when `dataSort` is provided', () => {
+ const $ = cheerio.load(
+ renderComponent('table', {
+ ...EXAMPLE_TABLE,
+ trs: [
+ {
+ tds: [
+ {
+ value: 'Example Cell',
+ dataSort: 42,
+ },
+ ],
+ },
+ ],
+ }),
+ );
+
+ expect($('.ons-table__cell').attr('data-sort-value')).toBe('42');
+ });
+
+ it('does not add highlight class to row when `highlight` is not provided', () => {
+ const $ = cheerio.load(renderComponent('table', EXAMPLE_TABLE));
+
+ expect($('.ons-table__row').hasClass('ons-table__row--highlight')).toBe(false);
+ });
+
+ it('adds highlight class to row when `highlight` is `true`', () => {
+ const $ = cheerio.load(
+ renderComponent('table', {
+ ...EXAMPLE_TABLE,
+ trs: [
+ {
+ tds: [{ value: 'Row 1 Cell 1' }, { value: 'Row 1 Cell 2' }, { value: 'Row 1 Cell 3' }],
+ highlight: true,
+ },
+ ],
+ }),
+ );
+
+ expect($('.ons-table__row').hasClass('ons-table__row--highlight')).toBe(true);
+ });
+
+ it('adds additionally provided class to cell', () => {
+ const $ = cheerio.load(
+ renderComponent('table', {
+ ...EXAMPLE_TABLE,
+ trs: [
+ {
+ tds: [
+ {
+ value: 'Column 1',
+ tdClasses: 'extra-cell-class another-extra-cell-class',
+ },
+ ],
+ },
+ ],
+ }),
+ );
+
+ expect($('.ons-table__cell').hasClass('extra-cell-class')).toBe(true);
+ expect($('.ons-table__cell').hasClass('another-extra-cell-class')).toBe(true);
+ });
+
+ it('does not add "numeric" modifier class to cell when `td.numeric` is not provided', () => {
+ const $ = cheerio.load(renderComponent('table', EXAMPLE_TABLE));
+
+ expect($('.ons-table__cell').hasClass('ons-table__cell--numeric')).toBe(false);
+ });
+
+ it('adds "numeric" modifier class to cell when `td.numeric` is `true`', () => {
+ const $ = cheerio.load(
+ renderComponent('table', {
+ ...EXAMPLE_TABLE,
+ trs: [
+ {
+ tds: [
+ {
+ value: 'Column 1',
+ numeric: true,
+ },
+ ],
+ },
+ ],
+ }),
+ );
+
+ expect($('.ons-table__cell').hasClass('ons-table__cell--numeric')).toBe(true);
+ });
+
+ describe('form', () => {
+ const params = {
+ ...EXAMPLE_TABLE,
+ trs: [
+ {
+ tds: [
+ {
+ form: {
+ action: 'https://example.com/form',
+ button: {
+ text: 'Submit form',
+ id: 'submit-form-button',
+ classes: 'custom-button-class',
+ url: 'https://example.com/link',
+ value: '42',
+ name: 'submit-form-button-name',
+ },
+ hiddenFormField: {
+ name: 'example-hidden-field-name',
+ value: '123',
+ },
+ },
+ },
+ ],
+ },
+ ],
+ };
+
+ it('does not render form section when `form` is not provided', () => {
+ const $ = cheerio.load(renderComponent('table', EXAMPLE_TABLE));
+
+ expect($('form').length).toBe(0);
+ });
+
+ it('renders form section when `form` is provided', () => {
+ const $ = cheerio.load(renderComponent('table', params));
+
+ expect($('form').length).toBe(1);
+ });
+
+ it('renders the provided `action` attribute', () => {
+ const $ = cheerio.load(renderComponent('table', params));
+
+ expect($('form').attr('action')).toBe('https://example.com/form');
+ });
+
+ it('renders a default value of "POST" for the `method` attribute', () => {
+ const $ = cheerio.load(renderComponent('table', params));
+
+ expect($('form').attr('method')).toBe('POST');
+ });
+
+ it('renders the provided `method` attribute', () => {
+ const $ = cheerio.load(
+ renderComponent('table', {
+ ...EXAMPLE_TABLE,
+ trs: [
+ {
+ tds: [
+ {
+ form: {
+ ...params.trs[0].tds[0].form,
+ method: 'PUT',
+ },
+ },
+ ],
+ },
+ ],
+ }),
+ );
+
+ expect($('form').attr('method')).toBe('PUT');
+ });
+
+ it('renders `button` component with the expected parameters', () => {
+ const faker = templateFaker();
+ const buttonSpy = faker.spy('button');
+
+ faker.renderComponent('table', params);
+
+ expect(buttonSpy.occurrences).toEqual([
+ {
+ text: 'Submit form',
+ id: 'submit-form-button',
+ classes: 'custom-button-class',
+ url: 'https://example.com/link',
+ value: '42', // `| safe` filter is used in macro which makes a string
+ name: 'submit-form-button-name',
+ },
+ ]);
+ });
+ });
});
- });
-
- describe('sortable variant', () => {
- const params = {
- ...EXAMPLE_TABLE,
- variants: ['sortable'],
- sortBy: 'Sort by',
- ariaAsc: 'ascending',
- ariaDesc: 'descending',
- };
- it('has the "sortable" variant class', () => {
- const $ = cheerio.load(renderComponent('table', params));
+ describe('footer row', () => {
+ it('does not render footer section when `tfoot` is not provided', () => {
+ const $ = cheerio.load(renderComponent('table', EXAMPLE_TABLE));
- expect($('.ons-table').hasClass('ons-table--sortable')).toBe(true);
- });
+ expect($('.ons-table__foot').length).toBe(0);
+ });
- it('has `data-aria-sort` attribute', () => {
- const $ = cheerio.load(renderComponent('table', params));
+ it('renders footer cells with expected text', () => {
+ const $ = cheerio.load(
+ renderComponent('table', {
+ ...EXAMPLE_TABLE,
+ tfoot: [{ value: 'Footer Cell 1' }, { value: 'Footer Cell 2' }, { value: 'Footer Cell 3' }],
+ }),
+ );
- expect($('.ons-table').attr('data-aria-sort')).toBe('Sort by');
+ const footerCellValues = mapAll($('.ons-table__foot .ons-table__cell'), (node) => node.text().trim());
+ expect(footerCellValues).toEqual(['Footer Cell 1', 'Footer Cell 2', 'Footer Cell 3']);
+ });
});
- it('adds `aria-sort` attribute when `ariaSort` is provided', () => {
- const $ = cheerio.load(
- renderComponent('table', {
- ...params,
- ths: [
- {
- value: 'Column 1',
- ariaSort: 'ascending',
- },
- ],
- }),
- );
-
- expect($('.ons-table__header').attr('aria-sort')).toBe('ascending');
- });
+ describe('scrollable variant', () => {
+ const params = {
+ ...EXAMPLE_TABLE,
+ variants: ['scrollable'],
+ caption: 'Example table caption',
+ };
- it('has `data-aria-asc` attribute', () => {
- const $ = cheerio.load(renderComponent('table', params));
+ it('has the "scrollable" variant class', () => {
+ const $ = cheerio.load(renderComponent('table', params));
- expect($('.ons-table').attr('data-aria-asc')).toBe('ascending');
- });
+ expect($('.ons-table').hasClass('ons-table--scrollable')).toBe(true);
+ });
- it('has `data-aria-desc` attribute', () => {
- const $ = cheerio.load(renderComponent('table', params));
+ it('renders "scrollable" container element', () => {
+ const $ = cheerio.load(renderComponent('table', params));
- expect($('.ons-table').attr('data-aria-desc')).toBe('descending');
- });
- });
+ expect($('.ons-table-scrollable').length).toBe(1);
+ expect($('.ons-table-scrollable--on').length).toBe(1);
+ });
- describe('table caption', () => {
- it('does not render caption element when not provided', () => {
- const $ = cheerio.load(renderComponent('table', EXAMPLE_TABLE));
+ it('renders "content" container element', () => {
+ const $ = cheerio.load(renderComponent('table', params));
- expect($('.ons-table__caption').length).toBe(0);
- });
+ expect($('.ons-table-scrollable__content').length).toBe(1);
+ });
- it('renders caption element when provided', () => {
- const $ = cheerio.load(
- renderComponent('table', {
- ...EXAMPLE_TABLE,
- caption: 'Example table caption',
- }),
- );
+ it('renders an appropriate `aria-label` attribute on the "content" container element', () => {
+ const $ = cheerio.load(renderComponent('table', params));
- expect($('.ons-table__caption').text().trim()).toBe('Example table caption');
- });
+ expect($('.ons-table-scrollable__content').attr('aria-label')).toBe('Example table caption. Scrollable table');
+ });
- it('does not visually hide caption when `hideCaption` is not provided', () => {
- const $ = cheerio.load(
- renderComponent('table', {
- ...EXAMPLE_TABLE,
- caption: 'Example table caption',
- }),
- );
+ it('renders a custom `aria-label` attribute on the "content" container element', () => {
+ const $ = cheerio.load(
+ renderComponent('table', {
+ ...params,
+ ariaLabel: 'Special table',
+ }),
+ );
- expect($('.ons-table__caption').hasClass('ons-u-vh')).toBe(false);
+ expect($('.ons-table-scrollable__content').attr('aria-label')).toBe('Example table caption. Special table');
+ });
});
- it('visually hides caption when `hideCaption` is `true`', () => {
- const $ = cheerio.load(
- renderComponent('table', {
- ...EXAMPLE_TABLE,
- caption: 'Example table caption',
- hideCaption: true,
- }),
- );
-
- expect($('.ons-table__caption').hasClass('ons-u-vh')).toBe(true);
+ describe('sortable variant', () => {
+ const params = {
+ ...EXAMPLE_TABLE,
+ variants: ['sortable'],
+ sortBy: 'Sort by',
+ ariaAsc: 'ascending',
+ ariaDesc: 'descending',
+ };
+
+ it('has the "sortable" variant class', () => {
+ const $ = cheerio.load(renderComponent('table', params));
+
+ expect($('.ons-table').hasClass('ons-table--sortable')).toBe(true);
+ });
+
+ it('has `data-aria-sort` attribute', () => {
+ const $ = cheerio.load(renderComponent('table', params));
+
+ expect($('.ons-table').attr('data-aria-sort')).toBe('Sort by');
+ });
+
+ it('adds `aria-sort` attribute when `ariaSort` is provided', () => {
+ const $ = cheerio.load(
+ renderComponent('table', {
+ ...params,
+ ths: [
+ {
+ value: 'Column 1',
+ ariaSort: 'ascending',
+ },
+ ],
+ }),
+ );
+
+ expect($('.ons-table__header').attr('aria-sort')).toBe('ascending');
+ });
+
+ it('has `data-aria-asc` attribute', () => {
+ const $ = cheerio.load(renderComponent('table', params));
+
+ expect($('.ons-table').attr('data-aria-asc')).toBe('ascending');
+ });
+
+ it('has `data-aria-desc` attribute', () => {
+ const $ = cheerio.load(renderComponent('table', params));
+
+ expect($('.ons-table').attr('data-aria-desc')).toBe('descending');
+ });
+ });
+
+ describe('table caption', () => {
+ it('does not render caption element when not provided', () => {
+ const $ = cheerio.load(renderComponent('table', EXAMPLE_TABLE));
+
+ expect($('.ons-table__caption').length).toBe(0);
+ });
+
+ it('renders caption element when provided', () => {
+ const $ = cheerio.load(
+ renderComponent('table', {
+ ...EXAMPLE_TABLE,
+ caption: 'Example table caption',
+ }),
+ );
+
+ expect($('.ons-table__caption').text().trim()).toBe('Example table caption');
+ });
+
+ it('does not visually hide caption when `hideCaption` is not provided', () => {
+ const $ = cheerio.load(
+ renderComponent('table', {
+ ...EXAMPLE_TABLE,
+ caption: 'Example table caption',
+ }),
+ );
+
+ expect($('.ons-table__caption').hasClass('ons-u-vh')).toBe(false);
+ });
+
+ it('visually hides caption when `hideCaption` is `true`', () => {
+ const $ = cheerio.load(
+ renderComponent('table', {
+ ...EXAMPLE_TABLE,
+ caption: 'Example table caption',
+ hideCaption: true,
+ }),
+ );
+
+ expect($('.ons-table__caption').hasClass('ons-u-vh')).toBe(true);
+ });
});
- });
});
diff --git a/src/components/table/_table.scss b/src/components/table/_table.scss
index 815ad08bab..1ee82ec8bb 100644
--- a/src/components/table/_table.scss
+++ b/src/components/table/_table.scss
@@ -1,234 +1,237 @@
.ons-table {
- border-collapse: collapse;
- border-spacing: 0;
- margin-bottom: 1rem;
- width: 100%;
-
- &__head {
- border-bottom: 2px solid var(--ons-color-grey-75);
- }
-
- &__caption {
- font-weight: $font-weight-bold;
- text-align: left;
- }
-
- &__header,
- &__cell {
- @include nth-element(1, 0);
-
- border-bottom: 2px solid var(--ons-color-grey-75);
- overflow: hidden;
- padding: 0.5rem 0 0.5rem 1rem;
- text-align: left;
- vertical-align: top;
- &--numeric {
- text-align: right;
- }
- }
-
- &__cell,
- &__header--row {
- border-bottom: 1px solid var(--ons-color-borders);
- }
-
- &__row--highlight {
- background: var(--ons-color-highlight);
- }
+ border-collapse: collapse;
+ border-spacing: 0;
+ margin-bottom: 1rem;
+ width: 100%;
- &:not(.ons-table--responsive) .ons-table__body .ons-table__row:last-child {
- .ons-table__cell,
- .ons-table__header--row {
- border: 0;
- }
- }
-
- &__foot .ons-table__cell {
- border-bottom: 0;
- border-top: 1px solid var(--ons-color-borders);
- }
-
- &--compact {
- .ons-table__head,
- .ons-table__body,
- .ons-table__foot {
- font-size: 81.25%;
+ &__head {
+ border-bottom: 2px solid var(--ons-color-grey-75);
}
- }
- &--row-hover {
- .ons-table__body .ons-table__row:hover {
- background: var(--ons-color-highlight);
+ &__caption {
+ font-weight: $font-weight-bold;
+ text-align: left;
}
- }
- &--responsive {
- @include mq(xxs, s) {
- .ons-table__header {
- display: none;
- }
+ &__header,
+ &__cell {
+ @include nth-element(1, 0);
- .ons-table__head {
- border-bottom: none;
- }
-
- .ons-table__body .ons-table__row {
border-bottom: 2px solid var(--ons-color-grey-75);
- display: block;
- margin-bottom: 1rem;
- }
-
- .ons-table__cell {
- display: block;
- padding-left: 0;
- text-align: right;
- &:last-child {
- border: 0;
- }
- &::before {
- content: attr(data-th);
- float: left;
- font-weight: $font-weight-bold;
- padding-right: 1rem;
+ overflow: hidden;
+ padding: 0.5rem 0 0.5rem 1rem;
+ text-align: left;
+ vertical-align: top;
+ &--numeric {
+ text-align: right;
}
- }
}
- }
- &-scrollable {
- position: relative;
- ::-webkit-scrollbar {
- height: 7px;
- }
- ::-webkit-scrollbar-thumb {
- background: var(--ons-color-grey-75);
- border-radius: 20px;
+ &__cell,
+ &__header--row {
+ border-bottom: 1px solid var(--ons-color-borders);
}
- &--on {
- .ons-table__header,
- .ons-table__cell {
- white-space: nowrap;
- }
+
+ &__row--highlight {
+ background: var(--ons-color-highlight);
}
- &__content {
- overflow: visible;
- overflow-x: auto;
- width: 100%;
- &:focus {
- box-shadow: 0 0 0 3px var(--ons-color-page-light),
- 0 0 0 5px var(--ons-color-text-link-focus),
- 0 0 0 8px var(--ons-color-focus);
- outline: 3px solid transparent; // Add transparent outline because Windows High Contrast Mode doesn't show box-shadows
- outline-offset: 1px;
- }
- .ons-table__header,
- .ons-table__cell {
- @include mq(xxs, m) {
- white-space: nowrap;
- }
- }
- .ons-table__right-shadow,
- .ons-table__left-shadow {
- height: 100%;
- padding: 2px;
- position: absolute;
- top: 0;
- width: 5px;
- z-index: 200;
-
- &.ons-with-transition {
- transition: box-shadow 0.4s ease-out;
- }
- }
- .ons-table__right-shadow {
- right: 0;
- &.ons-visible {
- box-shadow: inset -1px 0 0 0 #bfc1c3,
- inset -5px 0 0 0 rgb(191 193 195 / 40%);
- }
- }
- .ons-table__left-shadow {
- left: 0;
- &.ons-visible {
- box-shadow: inset 1px 0 0 0 #bfc1c3,
- inset -5px 0 0 0 rgb(191 193 195 / 40%);
+
+ &:not(.ons-table--responsive) .ons-table__body .ons-table__row:last-child {
+ .ons-table__cell,
+ .ons-table__header--row {
+ border: 0;
}
- }
}
- }
- &--sortable {
- [aria-sort='descending'].ons-table__header {
- .ons-icon {
- .ons-topTriangle {
- fill: var(--ons-color-grey-15);
+ &__foot .ons-table__cell {
+ border-bottom: 0;
+ border-top: 1px solid var(--ons-color-borders);
+ }
+
+ &--compact {
+ .ons-table__head,
+ .ons-table__body,
+ .ons-table__foot {
+ font-size: 81.25%;
}
- .ons-bottomTriangle {
- fill: var(--ons-color-text);
+ }
+
+ &--row-hover {
+ .ons-table__body .ons-table__row:hover {
+ background: var(--ons-color-highlight);
}
- }
- .ons-table__sort-button:focus {
- .ons-icon {
- .ons-topTriangle {
- fill: #e3ba02;
- }
+ }
+
+ &--responsive {
+ @include mq(xxs, s) {
+ .ons-table__header {
+ display: none;
+ }
+
+ .ons-table__head {
+ border-bottom: 0;
+ }
+
+ .ons-table__body .ons-table__row {
+ border-bottom: 2px solid var(--ons-color-grey-75);
+ display: block;
+ margin-bottom: 1rem;
+ }
+
+ .ons-table__cell {
+ display: block;
+ padding-left: 0;
+ text-align: right;
+ &:last-child {
+ border: 0;
+ }
+ &::before {
+ content: attr(data-th);
+ float: left;
+ font-weight: $font-weight-bold;
+ padding-right: 1rem;
+ }
+ }
}
- }
}
- [aria-sort='ascending'].ons-table__header {
- .ons-icon {
- .ons-topTriangle {
- fill: var(--ons-color-text);
+ &-scrollable {
+ position: relative;
+ ::-webkit-scrollbar {
+ height: 7px;
}
- .ons-bottomTriangle {
- fill: var(--ons-color-grey-15);
+ ::-webkit-scrollbar-thumb {
+ background: var(--ons-color-grey-75);
+ border-radius: 20px;
}
- }
- .ons-table__sort-button:focus {
- .ons-icon {
- .ons-bottomTriangle {
- fill: #e3ba02;
- }
+ &--on {
+ .ons-table__header,
+ .ons-table__cell {
+ white-space: nowrap;
+ }
+ }
+ &__content {
+ overflow: visible;
+ overflow-x: auto;
+ width: 100%;
+ &:focus {
+ box-shadow:
+ 0 0 0 3px var(--ons-color-page-light),
+ 0 0 0 5px var(--ons-color-text-link-focus),
+ 0 0 0 8px var(--ons-color-focus);
+ outline: 3px solid transparent; // Add transparent outline because Windows High Contrast Mode doesn't show box-shadows
+ outline-offset: 1px;
+ }
+ .ons-table__header,
+ .ons-table__cell {
+ @include mq(xxs, m) {
+ white-space: nowrap;
+ }
+ }
+ .ons-table__right-shadow,
+ .ons-table__left-shadow {
+ height: 100%;
+ padding: 2px;
+ position: absolute;
+ top: 0;
+ width: 5px;
+ z-index: 200;
+
+ &.ons-with-transition {
+ transition: box-shadow 0.4s ease-out;
+ }
+ }
+ .ons-table__right-shadow {
+ right: 0;
+ &.ons-visible {
+ box-shadow:
+ inset -1px 0 0 0 #bfc1c3,
+ inset -5px 0 0 0 rgb(191 193 195 / 40%);
+ }
+ }
+ .ons-table__left-shadow {
+ left: 0;
+ &.ons-visible {
+ box-shadow:
+ inset 1px 0 0 0 #bfc1c3,
+ inset -5px 0 0 0 rgb(191 193 195 / 40%);
+ }
+ }
}
- }
}
- .ons-table__header {
- .ons-table__sort-button {
- background-color: transparent;
- border: 0;
- box-shadow: none;
- color: var(--ons-color-text-link);
- cursor: pointer;
- display: inline-block;
- font-family: $font-sans;
- font-weight: $font-weight-bold;
- line-height: 1rem;
- padding: 0 0 0.2rem;
- white-space: nowrap;
-
- &:hover:not(:focus) {
- color: var(--ons-color-text-link-hover);
- text-decoration: underline solid var(--ons-color-text-link-hover) 2px;
- text-underline-position: under;
+ &--sortable {
+ [aria-sort='descending'].ons-table__header {
+ .ons-icon {
+ .ons-topTriangle {
+ fill: var(--ons-color-grey-15);
+ }
+ .ons-bottomTriangle {
+ fill: var(--ons-color-text);
+ }
+ }
+ .ons-table__sort-button:focus {
+ .ons-icon {
+ .ons-topTriangle {
+ fill: #e3ba02;
+ }
+ }
+ }
}
- .ons-icon {
- fill: var(--ons-color-grey-75);
- height: 16px;
- margin-left: 0.2rem;
- padding-bottom: 0.1rem;
- width: 16px;
+ [aria-sort='ascending'].ons-table__header {
+ .ons-icon {
+ .ons-topTriangle {
+ fill: var(--ons-color-text);
+ }
+ .ons-bottomTriangle {
+ fill: var(--ons-color-grey-15);
+ }
+ }
+ .ons-table__sort-button:focus {
+ .ons-icon {
+ .ons-bottomTriangle {
+ fill: #e3ba02;
+ }
+ }
+ }
}
- &:focus {
- @extend %a-focus;
- .ons-icon {
- fill: var(--ons-color-black);
- }
+ .ons-table__header {
+ .ons-table__sort-button {
+ background-color: transparent;
+ border: 0;
+ box-shadow: none;
+ color: var(--ons-color-text-link);
+ cursor: pointer;
+ display: inline-block;
+ font-family: $font-sans;
+ font-weight: $font-weight-bold;
+ line-height: 1rem;
+ padding: 0 0 0.2rem;
+ white-space: nowrap;
+
+ &:hover:not(:focus) {
+ color: var(--ons-color-text-link-hover);
+ text-decoration: underline solid var(--ons-color-text-link-hover) 2px;
+ text-underline-position: under;
+ }
+
+ .ons-icon {
+ fill: var(--ons-color-grey-75);
+ height: 16px;
+ margin-left: 0.2rem;
+ padding-bottom: 0.1rem;
+ width: 16px;
+ }
+
+ &:focus {
+ @extend %a-focus;
+ .ons-icon {
+ fill: var(--ons-color-black);
+ }
+ }
+ }
}
- }
}
- }
}
diff --git a/src/components/table/scrollable-table.dom.js b/src/components/table/scrollable-table.dom.js
index 04bbe4de20..3a9dbaa2d3 100644
--- a/src/components/table/scrollable-table.dom.js
+++ b/src/components/table/scrollable-table.dom.js
@@ -3,13 +3,13 @@ import domready from '../../js/domready';
export const classTableScrollable = 'ons-table-scrollable';
async function tableScroller() {
- const tables = [...document.querySelectorAll(`.${classTableScrollable}`)];
+ const tables = [...document.querySelectorAll(`.${classTableScrollable}`)];
- if (tables.length) {
- const TableScroll = (await import('./scrollable-table')).default;
+ if (tables.length) {
+ const TableScroll = (await import('./scrollable-table')).default;
- tables.forEach((table) => new TableScroll(table));
- }
+ tables.forEach((table) => new TableScroll(table));
+ }
}
domready(tableScroller);
diff --git a/src/components/table/scrollable-table.js b/src/components/table/scrollable-table.js
index 34ecc1a8a7..fa3462e808 100644
--- a/src/components/table/scrollable-table.js
+++ b/src/components/table/scrollable-table.js
@@ -2,78 +2,78 @@ export const classTableScrollableContent = 'ons-table-scrollable__content';
export const classTable = 'ons-table';
export default class TableScroll {
- constructor(table) {
- this.table = table;
- this.tableScroll = this.table.getElementsByClassName(classTableScrollableContent);
- this.tableEl = this.tableScroll[0].getElementsByClassName(classTable);
+ constructor(table) {
+ this.table = table;
+ this.tableScroll = this.table.getElementsByClassName(classTableScrollableContent);
+ this.tableEl = this.tableScroll[0].getElementsByClassName(classTable);
- this.activeTable = false;
+ this.activeTable = false;
- this.tableSizeCheck();
- this.viewportCheck();
- }
+ this.tableSizeCheck();
+ this.viewportCheck();
+ }
- tableSizeCheck() {
- this.tableWidth = this.tableEl[0].offsetWidth;
- this.tableContainerWidth = this.tableScroll[0].offsetWidth;
- if (this.tableWidth > this.tableContainerWidth && this.activeTable === false) {
- this.activeTable = true;
- this.insertShadows();
- this.registerScroll();
- } else if (this.tableWidth <= this.tableContainerWidth && this.activeTable === true) {
- this.removeShadows();
- this.activeTable = false;
+ tableSizeCheck() {
+ this.tableWidth = this.tableEl[0].offsetWidth;
+ this.tableContainerWidth = this.tableScroll[0].offsetWidth;
+ if (this.tableWidth > this.tableContainerWidth && this.activeTable === false) {
+ this.activeTable = true;
+ this.insertShadows();
+ this.registerScroll();
+ } else if (this.tableWidth <= this.tableContainerWidth && this.activeTable === true) {
+ this.removeShadows();
+ this.activeTable = false;
+ }
}
- }
- viewportCheck() {
- window.addEventListener('resize', () => this.tableSizeCheck(), true);
- }
+ viewportCheck() {
+ window.addEventListener('resize', () => this.tableSizeCheck(), true);
+ }
- registerScroll() {
- this.tableScroll[0].addEventListener('scroll', this.toggleShadows.bind(this));
- }
+ registerScroll() {
+ this.tableScroll[0].addEventListener('scroll', this.toggleShadows.bind(this));
+ }
- insertShadows() {
- const rightShadowEl = document.createElement('div');
- const leftShadowEl = document.createElement('div');
+ insertShadows() {
+ const rightShadowEl = document.createElement('div');
+ const leftShadowEl = document.createElement('div');
- rightShadowEl.className = 'ons-table__right-shadow ons-visible';
- leftShadowEl.className = 'ons-table__left-shadow';
- this.tableScroll[0].appendChild(rightShadowEl);
- this.tableScroll[0].insertBefore(leftShadowEl, this.tableScroll[0].firstChild);
- }
+ rightShadowEl.className = 'ons-table__right-shadow ons-visible';
+ leftShadowEl.className = 'ons-table__left-shadow';
+ this.tableScroll[0].appendChild(rightShadowEl);
+ this.tableScroll[0].insertBefore(leftShadowEl, this.tableScroll[0].firstChild);
+ }
- removeShadows() {
- const rightShadow = this.tableScroll[0].querySelector('.ons-table__right-shadow');
- const leftShadow = this.tableScroll[0].querySelector('.ons-table__left-shadow');
- this.tableScroll[0].removeChild(rightShadow);
- this.tableScroll[0].removeChild(leftShadow);
- }
+ removeShadows() {
+ const rightShadow = this.tableScroll[0].querySelector('.ons-table__right-shadow');
+ const leftShadow = this.tableScroll[0].querySelector('.ons-table__left-shadow');
+ this.tableScroll[0].removeChild(rightShadow);
+ this.tableScroll[0].removeChild(leftShadow);
+ }
- toggleShadows() {
- const rightShadow = this.tableScroll[0].querySelector('.ons-table__right-shadow');
- const leftShadow = this.tableScroll[0].querySelector('.ons-table__left-shadow');
- const tableScrollPos = this.getOffset(this.tableScroll[0]).left;
- const tablePos = this.getOffset(this.tableEl[0]).left;
+ toggleShadows() {
+ const rightShadow = this.tableScroll[0].querySelector('.ons-table__right-shadow');
+ const leftShadow = this.tableScroll[0].querySelector('.ons-table__left-shadow');
+ const tableScrollPos = this.getOffset(this.tableScroll[0]).left;
+ const tablePos = this.getOffset(this.tableEl[0]).left;
- this.tableWidth = this.tableEl[0].offsetWidth;
- this.tableContainerWidth = this.tableScroll[0].offsetWidth;
- tablePos === tableScrollPos ? leftShadow.classList.remove('ons-visible') : leftShadow.classList.add('ons-visible');
+ this.tableWidth = this.tableEl[0].offsetWidth;
+ this.tableContainerWidth = this.tableScroll[0].offsetWidth;
+ tablePos === tableScrollPos ? leftShadow.classList.remove('ons-visible') : leftShadow.classList.add('ons-visible');
- -tableScrollPos === this.tableContainerWidth - this.tableWidth - tablePos
- ? rightShadow.classList.remove('ons-visible')
- : rightShadow.classList.add('ons-visible');
+ -tableScrollPos === this.tableContainerWidth - this.tableWidth - tablePos
+ ? rightShadow.classList.remove('ons-visible')
+ : rightShadow.classList.add('ons-visible');
- setTimeout(function () {
- return leftShadow.classList.add('ons-with-transition'), rightShadow.classList.add('ons-with-transition');
- }, 200);
- }
+ setTimeout(function () {
+ return leftShadow.classList.add('ons-with-transition'), rightShadow.classList.add('ons-with-transition');
+ }, 200);
+ }
- getOffset(el) {
- const rect = el.getBoundingClientRect();
- return {
- left: rect.left + window.scrollX,
- };
- }
+ getOffset(el) {
+ const rect = el.getBoundingClientRect();
+ return {
+ left: rect.left + window.scrollX,
+ };
+ }
}
diff --git a/src/components/table/sortable-table.dom.js b/src/components/table/sortable-table.dom.js
index ddeb399a58..9d65b09458 100644
--- a/src/components/table/sortable-table.dom.js
+++ b/src/components/table/sortable-table.dom.js
@@ -3,13 +3,13 @@ import domready from '../../js/domready';
export const classTableSortable = 'ons-table--sortable';
async function tableSorter() {
- const tables = [...document.querySelectorAll(`.${classTableSortable}`)];
+ const tables = [...document.querySelectorAll(`.${classTableSortable}`)];
- if (tables.length) {
- const TableSort = (await import('./sortable-table')).default;
+ if (tables.length) {
+ const TableSort = (await import('./sortable-table')).default;
- tables.forEach((table, index) => new TableSort(table, index));
- }
+ tables.forEach((table, index) => new TableSort(table, index));
+ }
}
domready(tableSorter);
diff --git a/src/components/table/sortable-table.js b/src/components/table/sortable-table.js
index 3eb8fd5199..603a7a67bf 100644
--- a/src/components/table/sortable-table.js
+++ b/src/components/table/sortable-table.js
@@ -3,154 +3,154 @@ export const classTableBody = 'ons-table__body';
export let status;
export default class TableSort {
- constructor(table, index) {
- this.table = table;
- this.options = {};
- this.options.statusMessage = table.getAttribute('data-aria-sort');
- this.options.ascendingText = table.getAttribute('data-aria-asc');
- this.options.descendingText = table.getAttribute('data-aria-desc');
- this.index = index;
- this.init();
- }
-
- init() {
- this.registerHeadings();
- this.createStatusBox();
- }
-
- registerHeadings() {
- this.sortableHeadings = this.table.querySelectorAll(jsSortableHeadings);
- const sortableHeadings = [...this.sortableHeadings];
- sortableHeadings.forEach((heading, i) => {
- this.createHeadingButtons(heading, i);
- });
- }
-
- createHeadingButtons(heading, i) {
- const text = heading.textContent.trim();
- heading.querySelector('.ons-table__header-text').remove();
- heading.querySelector('.ons-icon').classList.remove('ons-u-d-no');
- const button = document.createElement('button');
- button.setAttribute('aria-label', this.table.getAttribute('data-aria-sort') + ' ' + text);
- button.setAttribute('type', 'button');
- button.setAttribute('data-index', i);
- button.setAttribute('class', 'ons-table__sort-button');
- button.textContent = text;
- const sortSpriteIdText = text.replace(/\s+/g, '-').toLowerCase();
- const sortSprite = document.getElementById('sort-sprite-' + sortSpriteIdText);
- sortSprite.id = 'sort-sprite-' + sortSpriteIdText + '-' + this.index;
- const sortSpriteParent = sortSprite.parentNode;
- sortSpriteParent.replaceChild(button, sortSprite);
- button.appendChild(sortSprite);
- button.addEventListener('click', this.sortButtonClicked.bind(this));
- }
-
- sortButtonClicked(event) {
- const columnNumber = event.target.closest('[data-index]').getAttribute('data-index');
- const sortDirection = event.target.closest('[aria-sort]').getAttribute('aria-sort');
- let newSortDirection;
-
- if (sortDirection === 'none' || sortDirection === 'ascending') {
- newSortDirection = this.options.descendingText;
- } else {
- newSortDirection = this.options.ascendingText;
+ constructor(table, index) {
+ this.table = table;
+ this.options = {};
+ this.options.statusMessage = table.getAttribute('data-aria-sort');
+ this.options.ascendingText = table.getAttribute('data-aria-asc');
+ this.options.descendingText = table.getAttribute('data-aria-desc');
+ this.index = index;
+ this.init();
}
- this.tableBody = this.table.getElementsByClassName(classTableBody);
- const tableBody = [...this.tableBody];
+ init() {
+ this.registerHeadings();
+ this.createStatusBox();
+ }
- tableBody.forEach((tbody) => {
- const rows = this.getTableRowsArray(tbody);
- const sortedRows = this.sort(rows, columnNumber, newSortDirection);
- this.addRows(tbody, sortedRows);
- });
+ registerHeadings() {
+ this.sortableHeadings = this.table.querySelectorAll(jsSortableHeadings);
+ const sortableHeadings = [...this.sortableHeadings];
+ sortableHeadings.forEach((heading, i) => {
+ this.createHeadingButtons(heading, i);
+ });
+ }
- this.removeButtonStates();
- this.updateButtonState(event.target.closest('[aria-sort]'), newSortDirection);
- }
+ createHeadingButtons(heading, i) {
+ const text = heading.textContent.trim();
+ heading.querySelector('.ons-table__header-text').remove();
+ heading.querySelector('.ons-icon').classList.remove('ons-u-d-no');
+ const button = document.createElement('button');
+ button.setAttribute('aria-label', this.table.getAttribute('data-aria-sort') + ' ' + text);
+ button.setAttribute('type', 'button');
+ button.setAttribute('data-index', i);
+ button.setAttribute('class', 'ons-table__sort-button');
+ button.textContent = text;
+ const sortSpriteIdText = text.replace(/\s+/g, '-').toLowerCase();
+ const sortSprite = document.getElementById('sort-sprite-' + sortSpriteIdText);
+ sortSprite.id = 'sort-sprite-' + sortSpriteIdText + '-' + this.index;
+ const sortSpriteParent = sortSprite.parentNode;
+ sortSpriteParent.replaceChild(button, sortSprite);
+ button.appendChild(sortSprite);
+ button.addEventListener('click', this.sortButtonClicked.bind(this));
+ }
- getTableRowsArray(tbody) {
- let rows = [];
- this.trs = tbody.querySelectorAll('tr');
- const trs = [...this.trs];
+ sortButtonClicked(event) {
+ const columnNumber = event.target.closest('[data-index]').getAttribute('data-index');
+ const sortDirection = event.target.closest('[aria-sort]').getAttribute('aria-sort');
+ let newSortDirection;
- trs.forEach((tr) => {
- rows.push(tr);
- });
+ if (sortDirection === 'none' || sortDirection === 'ascending') {
+ newSortDirection = this.options.descendingText;
+ } else {
+ newSortDirection = this.options.ascendingText;
+ }
- return rows;
- }
+ this.tableBody = this.table.getElementsByClassName(classTableBody);
+ const tableBody = [...this.tableBody];
- sort(rows, columnNumber, sortDirection) {
- const newRows = rows.sort((rowA, rowB) => {
- const tdA = rowA.querySelectorAll('td, th')[columnNumber];
- const tdB = rowB.querySelectorAll('td, th')[columnNumber];
+ tableBody.forEach((tbody) => {
+ const rows = this.getTableRowsArray(tbody);
+ const sortedRows = this.sort(rows, columnNumber, newSortDirection);
+ this.addRows(tbody, sortedRows);
+ });
- const valueA = this.getCellValue(tdA);
- const valueB = this.getCellValue(tdB);
+ this.removeButtonStates();
+ this.updateButtonState(event.target.closest('[aria-sort]'), newSortDirection);
+ }
- if (sortDirection === 'ascending') {
- if (valueA < valueB) {
- return -1;
- }
- if (valueA > valueB) {
- return 1;
- }
- return 0;
- } else {
- if (valueB < valueA) {
- return -1;
- }
- if (valueB > valueA) {
- return 1;
+ getTableRowsArray(tbody) {
+ let rows = [];
+ this.trs = tbody.querySelectorAll('tr');
+ const trs = [...this.trs];
+
+ trs.forEach((tr) => {
+ rows.push(tr);
+ });
+
+ return rows;
+ }
+
+ sort(rows, columnNumber, sortDirection) {
+ const newRows = rows.sort((rowA, rowB) => {
+ const tdA = rowA.querySelectorAll('td, th')[columnNumber];
+ const tdB = rowB.querySelectorAll('td, th')[columnNumber];
+
+ const valueA = this.getCellValue(tdA);
+ const valueB = this.getCellValue(tdB);
+
+ if (sortDirection === 'ascending') {
+ if (valueA < valueB) {
+ return -1;
+ }
+ if (valueA > valueB) {
+ return 1;
+ }
+ return 0;
+ } else {
+ if (valueB < valueA) {
+ return -1;
+ }
+ if (valueB > valueA) {
+ return 1;
+ }
+ return 0;
+ }
+ });
+
+ return newRows;
+ }
+
+ getCellValue(cell) {
+ if (!cell) {
+ return undefined;
}
- return 0;
- }
- });
- return newRows;
- }
+ let cellValue = cell.getAttribute('data-sort-value') || cell.textContent;
+ cellValue = parseFloat(cellValue) || cellValue;
- getCellValue(cell) {
- if (!cell) {
- return undefined;
+ return cellValue;
}
- let cellValue = cell.getAttribute('data-sort-value') || cell.textContent;
- cellValue = parseFloat(cellValue) || cellValue;
-
- return cellValue;
- }
-
- addRows(body, rows) {
- rows.forEach((row) => {
- body.append(row);
- });
- }
-
- removeButtonStates() {
- this.sortableHeadings = this.table.querySelectorAll(jsSortableHeadings);
- const sortableHeadings = [...this.sortableHeadings];
- sortableHeadings.forEach((heading) => {
- heading.setAttribute('aria-sort', 'none');
- });
- }
-
- updateButtonState(button, direction) {
- button.setAttribute('aria-sort', direction);
- let message = this.options.statusMessage;
- message = message + ' ' + button.textContent.replace(/^\s+|\s+$/g, '');
- message = message + ' (' + direction + ')';
- status.textContent = message;
- }
-
- createStatusBox() {
- status = document.createElement('div');
- status.setAttribute('aria-live', 'polite');
- status.setAttribute('role', 'status');
- status.setAttribute('aria-atomic', 'true');
- status.setAttribute('class', 'ons-sortable-table-status ons-u-vh');
-
- this.table.parentElement.insertBefore(status, this.table.nextSibling);
- }
+ addRows(body, rows) {
+ rows.forEach((row) => {
+ body.append(row);
+ });
+ }
+
+ removeButtonStates() {
+ this.sortableHeadings = this.table.querySelectorAll(jsSortableHeadings);
+ const sortableHeadings = [...this.sortableHeadings];
+ sortableHeadings.forEach((heading) => {
+ heading.setAttribute('aria-sort', 'none');
+ });
+ }
+
+ updateButtonState(button, direction) {
+ button.setAttribute('aria-sort', direction);
+ let message = this.options.statusMessage;
+ message = message + ' ' + button.textContent.replace(/^\s+|\s+$/g, '');
+ message = message + ' (' + direction + ')';
+ status.textContent = message;
+ }
+
+ createStatusBox() {
+ status = document.createElement('div');
+ status.setAttribute('aria-live', 'polite');
+ status.setAttribute('role', 'status');
+ status.setAttribute('aria-atomic', 'true');
+ status.setAttribute('class', 'ons-sortable-table-status ons-u-vh');
+
+ this.table.parentElement.insertBefore(status, this.table.nextSibling);
+ }
}
diff --git a/src/components/table/table.spec.js b/src/components/table/table.spec.js
index fcbf2d4a8c..bfadc29c6b 100644
--- a/src/components/table/table.spec.js
+++ b/src/components/table/table.spec.js
@@ -1,159 +1,163 @@
import { renderComponent, setTestPage } from '../../tests/helpers/rendering';
describe('script: table', () => {
- describe('variant: scrollable', () => {
- // Construct a table with 15 columns and 15 rows with long labels.
- const params = {
- variants: ['scrollable'],
- ths: Array.from({ length: 15 }, (_, i) => ({ value: `Column ${i + 1}` })),
- trs: [
- {
- tds: Array.from({ length: 15 }, (_, i) => ({ value: `Business Register and Employment Survey ${i + 1}` })),
- },
- ],
- };
-
- beforeEach(async () => {
- await setTestPage('/test', renderComponent('table', params));
- });
+ describe('variant: scrollable', () => {
+ // Construct a table with 15 columns and 15 rows with long labels.
+ const params = {
+ variants: ['scrollable'],
+ ths: Array.from({ length: 15 }, (_, i) => ({ value: `Column ${i + 1}` })),
+ trs: [
+ {
+ tds: Array.from({ length: 15 }, (_, i) => ({ value: `Business Register and Employment Survey ${i + 1}` })),
+ },
+ ],
+ };
- it('should add shadow elements', async () => {
- const leftShadowCount = await page.$$eval('.ons-table__left-shadow', (nodes) => nodes.length);
- expect(leftShadowCount).not.toBe(0);
- const rightShadowCount = await page.$$eval('.ons-table__right-shadow', (nodes) => nodes.length);
- expect(rightShadowCount).not.toBe(0);
- });
+ beforeEach(async () => {
+ await setTestPage('/test', renderComponent('table', params));
+ });
- describe('When the table component is scrolled,', () => {
- beforeEach(async () => {
- await page.focus('.ons-table-scrollable__content');
- await page.keyboard.press('ArrowRight');
- });
+ it('should add shadow elements', async () => {
+ const leftShadowCount = await page.$$eval('.ons-table__left-shadow', (nodes) => nodes.length);
+ expect(leftShadowCount).not.toBe(0);
+ const rightShadowCount = await page.$$eval('.ons-table__right-shadow', (nodes) => nodes.length);
+ expect(rightShadowCount).not.toBe(0);
+ });
- it('should show both shadow elements', async () => {
- await page.waitForTimeout(200);
+ describe('When the table component is scrolled,', () => {
+ beforeEach(async () => {
+ await page.focus('.ons-table-scrollable__content');
+ await page.keyboard.press('ArrowRight');
+ });
- const leftShadowVisibleCount = await page.$$eval('.ons-table__left-shadow.ons-visible', (nodes) => nodes.length);
- expect(leftShadowVisibleCount).not.toBe(0);
- const rightShadowVisibleCount = await page.$$eval('.ons-table__right-shadow.ons-visible', (nodes) => nodes.length);
- expect(rightShadowVisibleCount).not.toBe(0);
- });
- });
- });
-
- describe('variant: sortable', () => {
- const params = {
- variants: ['sortable'],
- sortBy: 'Sort by',
- ariaAsc: 'ascending',
- ariaDesc: 'descending',
- ths: [
- { value: 'Column 1', ariaSort: 'none' },
- { value: 'Column 2', ariaSort: 'none' },
- { value: 'Column 3', ariaSort: 'none' },
- { value: 'Column 4', ariaSort: 'none' },
- ],
- trs: [
- {
- tds: [
- { value: 'A', dataSort: '1' },
- { value: 'A', dataSort: '4' },
- { value: 'A', dataSort: '0' },
- { value: 'A', dataSort: '2' },
- ],
- },
- {
- tds: [
- { value: 'B', dataSort: '2' },
- { value: 'B', dataSort: '4' },
- { value: 'B', dataSort: '0' },
- { value: 'B', dataSort: '2' },
- ],
- },
- {
- tds: [
- { value: 'C', dataSort: '3' },
- { value: 'C', dataSort: '4' },
- { value: 'C', dataSort: '0' },
- { value: 'C', dataSort: '2' },
- ],
- },
- ],
- };
-
- beforeEach(async () => {
- await setTestPage('/test', renderComponent('table', params));
- });
+ it('should show both shadow elements', async () => {
+ await page.waitForTimeout(200);
- it('should create a button element in each TH', async () => {
- const buttonCount = await page.$$eval('.ons-table__header .ons-table__sort-button', (nodes) => nodes.length);
- expect(buttonCount).toBe(4);
- });
-
- it('should create a status element with aria attributes', async () => {
- const ariaLiveAttribute = await page.$eval('.ons-sortable-table-status', (node) => node.getAttribute('aria-live'));
- expect(ariaLiveAttribute).toBe('polite');
- const roleAttribute = await page.$eval('.ons-sortable-table-status', (node) => node.getAttribute('role'));
- expect(roleAttribute).toBe('status');
- const ariaAtomicAttribute = await page.$eval('.ons-sortable-table-status', (node) => node.getAttribute('aria-atomic'));
- expect(ariaAtomicAttribute).toBe('true');
+ const leftShadowVisibleCount = await page.$$eval('.ons-table__left-shadow.ons-visible', (nodes) => nodes.length);
+ expect(leftShadowVisibleCount).not.toBe(0);
+ const rightShadowVisibleCount = await page.$$eval('.ons-table__right-shadow.ons-visible', (nodes) => nodes.length);
+ expect(rightShadowVisibleCount).not.toBe(0);
+ });
+ });
});
- describe('Each sort button element', () => {
- it('should contain an aria-label attribute', async () => {
- const ariaLabelValues = await page.$$eval('.ons-table__sort-button', (nodes) =>
- nodes.map((node) => node.getAttribute('aria-label')),
- );
- expect(ariaLabelValues).toEqual(['Sort by Column 1', 'Sort by Column 2', 'Sort by Column 3', 'Sort by Column 4']);
- });
-
- it('should contain a data-index attribute', async () => {
- const dataIndexValues = await page.$$eval('.ons-table__sort-button', (nodes) =>
- nodes.map((node) => node.getAttribute('data-index')),
- );
- expect(dataIndexValues).toEqual(['0', '1', '2', '3']);
- });
- });
+ describe('variant: sortable', () => {
+ const params = {
+ variants: ['sortable'],
+ sortBy: 'Sort by',
+ ariaAsc: 'ascending',
+ ariaDesc: 'descending',
+ ths: [
+ { value: 'Column 1', ariaSort: 'none' },
+ { value: 'Column 2', ariaSort: 'none' },
+ { value: 'Column 3', ariaSort: 'none' },
+ { value: 'Column 4', ariaSort: 'none' },
+ ],
+ trs: [
+ {
+ tds: [
+ { value: 'A', dataSort: '1' },
+ { value: 'A', dataSort: '4' },
+ { value: 'A', dataSort: '0' },
+ { value: 'A', dataSort: '2' },
+ ],
+ },
+ {
+ tds: [
+ { value: 'B', dataSort: '2' },
+ { value: 'B', dataSort: '4' },
+ { value: 'B', dataSort: '0' },
+ { value: 'B', dataSort: '2' },
+ ],
+ },
+ {
+ tds: [
+ { value: 'C', dataSort: '3' },
+ { value: 'C', dataSort: '4' },
+ { value: 'C', dataSort: '0' },
+ { value: 'C', dataSort: '2' },
+ ],
+ },
+ ],
+ };
- describe('When a sort button is clicked', () => {
- beforeEach(async () => {
- await page.click('.ons-table__header:nth-child(1) .ons-table__sort-button');
- });
-
- it('should update aria-sort value for each column header', async () => {
- const ariaSortValues = await page.$$eval('.ons-table__header', (nodes) => nodes.map((node) => node.getAttribute('aria-sort')));
- expect(ariaSortValues).toEqual(['descending', 'none', 'none', 'none']);
- });
-
- it('should sort the column into descending order', async () => {
- const firstColumnValues = await page.$$eval('.ons-table__row .ons-table__cell:first-child', (nodes) =>
- nodes.map((node) => node.textContent.trim()),
- );
- expect(firstColumnValues).toEqual(['C', 'B', 'A']);
- });
-
- it('should update the aria-live status', async () => {
- const statusText = await page.$eval('.ons-sortable-table-status', (node) => node.textContent);
- expect(statusText).toBe('Sort by Column 1 (descending)');
- });
-
- describe('When a sort button is clicked again', () => {
beforeEach(async () => {
- await page.click('.ons-table__header:nth-child(1) .ons-table__sort-button');
+ await setTestPage('/test', renderComponent('table', params));
+ });
+
+ it('should create a button element in each TH', async () => {
+ const buttonCount = await page.$$eval('.ons-table__header .ons-table__sort-button', (nodes) => nodes.length);
+ expect(buttonCount).toBe(4);
+ });
+
+ it('should create a status element with aria attributes', async () => {
+ const ariaLiveAttribute = await page.$eval('.ons-sortable-table-status', (node) => node.getAttribute('aria-live'));
+ expect(ariaLiveAttribute).toBe('polite');
+ const roleAttribute = await page.$eval('.ons-sortable-table-status', (node) => node.getAttribute('role'));
+ expect(roleAttribute).toBe('status');
+ const ariaAtomicAttribute = await page.$eval('.ons-sortable-table-status', (node) => node.getAttribute('aria-atomic'));
+ expect(ariaAtomicAttribute).toBe('true');
});
- it('should update aria-sort value for each column header', async () => {
- const ariaSortValues = await page.$$eval('.ons-table__header', (nodes) => nodes.map((node) => node.getAttribute('aria-sort')));
- expect(ariaSortValues).toEqual(['ascending', 'none', 'none', 'none']);
+ describe('Each sort button element', () => {
+ it('should contain an aria-label attribute', async () => {
+ const ariaLabelValues = await page.$$eval('.ons-table__sort-button', (nodes) =>
+ nodes.map((node) => node.getAttribute('aria-label')),
+ );
+ expect(ariaLabelValues).toEqual(['Sort by Column 1', 'Sort by Column 2', 'Sort by Column 3', 'Sort by Column 4']);
+ });
+
+ it('should contain a data-index attribute', async () => {
+ const dataIndexValues = await page.$$eval('.ons-table__sort-button', (nodes) =>
+ nodes.map((node) => node.getAttribute('data-index')),
+ );
+ expect(dataIndexValues).toEqual(['0', '1', '2', '3']);
+ });
});
- it('should sort the column into ascending order', async () => {
- const firstColumnValues = await page.$$eval('.ons-table__row .ons-table__cell:first-child', (nodes) =>
- nodes.map((node) => node.textContent.trim()),
- );
- expect(firstColumnValues).toEqual(['A', 'B', 'C']);
+ describe('When a sort button is clicked', () => {
+ beforeEach(async () => {
+ await page.click('.ons-table__header:nth-child(1) .ons-table__sort-button');
+ });
+
+ it('should update aria-sort value for each column header', async () => {
+ const ariaSortValues = await page.$$eval('.ons-table__header', (nodes) =>
+ nodes.map((node) => node.getAttribute('aria-sort')),
+ );
+ expect(ariaSortValues).toEqual(['descending', 'none', 'none', 'none']);
+ });
+
+ it('should sort the column into descending order', async () => {
+ const firstColumnValues = await page.$$eval('.ons-table__row .ons-table__cell:first-child', (nodes) =>
+ nodes.map((node) => node.textContent.trim()),
+ );
+ expect(firstColumnValues).toEqual(['C', 'B', 'A']);
+ });
+
+ it('should update the aria-live status', async () => {
+ const statusText = await page.$eval('.ons-sortable-table-status', (node) => node.textContent);
+ expect(statusText).toBe('Sort by Column 1 (descending)');
+ });
+
+ describe('When a sort button is clicked again', () => {
+ beforeEach(async () => {
+ await page.click('.ons-table__header:nth-child(1) .ons-table__sort-button');
+ });
+
+ it('should update aria-sort value for each column header', async () => {
+ const ariaSortValues = await page.$$eval('.ons-table__header', (nodes) =>
+ nodes.map((node) => node.getAttribute('aria-sort')),
+ );
+ expect(ariaSortValues).toEqual(['ascending', 'none', 'none', 'none']);
+ });
+
+ it('should sort the column into ascending order', async () => {
+ const firstColumnValues = await page.$$eval('.ons-table__row .ons-table__cell:first-child', (nodes) =>
+ nodes.map((node) => node.textContent.trim()),
+ );
+ expect(firstColumnValues).toEqual(['A', 'B', 'C']);
+ });
+ });
});
- });
});
- });
});
diff --git a/src/components/tabs/_macro.spec.js b/src/components/tabs/_macro.spec.js
index ff2b97035e..4b3dd4cbdf 100644
--- a/src/components/tabs/_macro.spec.js
+++ b/src/components/tabs/_macro.spec.js
@@ -6,122 +6,122 @@ import axe from '../../tests/helpers/axe';
import { renderComponent } from '../../tests/helpers/rendering';
const EXAMPLE_TABS = {
- title: 'Example tabs',
- tabs: [
- {
- id: 'first-tab',
- title: 'Tab 1',
- content: 'Example content...',
- hiddenSpan: 'for Example',
- },
- {
- id: 'second-tab',
- title: 'Tab 2',
- content: 'Some nested
strong element ...',
- },
- ],
+ title: 'Example tabs',
+ tabs: [
+ {
+ id: 'first-tab',
+ title: 'Tab 1',
+ content: 'Example content...',
+ hiddenSpan: 'for Example',
+ },
+ {
+ id: 'second-tab',
+ title: 'Tab 2',
+ content: 'Some nested
strong element ...',
+ },
+ ],
};
const EXAMPLE_TABS_WITH_SHOWTITLE = {
- title: 'Example tabs',
- tabs: [
- {
- id: 'first-tab',
- title: 'Tab 1',
- showTitle: true,
- content: 'Example content...',
- },
- {
- id: 'second-tab',
- title: 'Tab 2',
- content: 'Some nested
strong element ...',
- },
- ],
+ title: 'Example tabs',
+ tabs: [
+ {
+ id: 'first-tab',
+ title: 'Tab 1',
+ showTitle: true,
+ content: 'Example content...',
+ },
+ {
+ id: 'second-tab',
+ title: 'Tab 2',
+ content: 'Some nested
strong element ...',
+ },
+ ],
};
const EXAMPLE_TABS_WITHOUT_TAB_IDS = {
- title: 'Example tabs',
- tabs: [
- {
- title: 'Tab 1',
- content: 'Example content...',
- },
- {
- title: 'Tab 2',
- content: 'Some nested
strong element ...',
- },
- ],
+ title: 'Example tabs',
+ tabs: [
+ {
+ title: 'Tab 1',
+ content: 'Example content...',
+ },
+ {
+ title: 'Tab 2',
+ content: 'Some nested
strong element ...',
+ },
+ ],
};
describe('macro: tabs', () => {
- it('passes jest-axe checks', async () => {
- const $ = cheerio.load(renderComponent('tabs', EXAMPLE_TABS));
+ it('passes jest-axe checks', async () => {
+ const $ = cheerio.load(renderComponent('tabs', EXAMPLE_TABS));
- const results = await axe($.html());
- expect(results).toHaveNoViolations();
- });
+ const results = await axe($.html());
+ expect(results).toHaveNoViolations();
+ });
- it('has the provided variant classes', () => {
- const $ = cheerio.load(
- renderComponent('tabs', {
- ...EXAMPLE_TABS,
- variants: ['details', 'example-variant'],
- }),
- );
+ it('has the provided variant classes', () => {
+ const $ = cheerio.load(
+ renderComponent('tabs', {
+ ...EXAMPLE_TABS,
+ variants: ['details', 'example-variant'],
+ }),
+ );
- expect($('.ons-tabs').hasClass('ons-tabs--details')).toBe(true);
- expect($('.ons-tabs').hasClass('ons-tabs--example-variant')).toBe(true);
- });
+ expect($('.ons-tabs').hasClass('ons-tabs--details')).toBe(true);
+ expect($('.ons-tabs').hasClass('ons-tabs--example-variant')).toBe(true);
+ });
- it('has the provided `title`', () => {
- const $ = cheerio.load(renderComponent('tabs', EXAMPLE_TABS));
+ it('has the provided `title`', () => {
+ const $ = cheerio.load(renderComponent('tabs', EXAMPLE_TABS));
- expect($('.ons-tabs__title').text().trim()).toBe('Example tabs');
- });
+ expect($('.ons-tabs__title').text().trim()).toBe('Example tabs');
+ });
- it('has title with provided tag override', () => {
- const $ = cheerio.load(
- renderComponent('tabs', {
- ...EXAMPLE_TABS,
- titleTag: 'h4',
- }),
- );
+ it('has title with provided tag override', () => {
+ const $ = cheerio.load(
+ renderComponent('tabs', {
+ ...EXAMPLE_TABS,
+ titleTag: 'h4',
+ }),
+ );
- const titleTag = $('.ons-tabs__title')[0].tagName;
- expect(titleTag).toBe('h4');
- });
+ const titleTag = $('.ons-tabs__title')[0].tagName;
+ expect(titleTag).toBe('h4');
+ });
- it('has the provided tab id attributes', () => {
- const $ = cheerio.load(renderComponent('tabs', EXAMPLE_TABS));
+ it('has the provided tab id attributes', () => {
+ const $ = cheerio.load(renderComponent('tabs', EXAMPLE_TABS));
- expect($('.ons-tabs__panel:first').attr('id')).toBe('first-tab');
- expect($('.ons-tabs__panel:last').attr('id')).toBe('second-tab');
- });
+ expect($('.ons-tabs__panel:first').attr('id')).toBe('first-tab');
+ expect($('.ons-tabs__panel:last').attr('id')).toBe('second-tab');
+ });
- it('has default tab id attribute values when identifiers are not provided', () => {
- const $ = cheerio.load(renderComponent('tabs', EXAMPLE_TABS_WITHOUT_TAB_IDS));
+ it('has default tab id attribute values when identifiers are not provided', () => {
+ const $ = cheerio.load(renderComponent('tabs', EXAMPLE_TABS_WITHOUT_TAB_IDS));
- expect($('.ons-tabs__panel:first').attr('id')).toBe('tab-1');
- expect($('.ons-tabs__panel:last').attr('id')).toBe('tab-2');
- });
+ expect($('.ons-tabs__panel:first').attr('id')).toBe('tab-1');
+ expect($('.ons-tabs__panel:last').attr('id')).toBe('tab-2');
+ });
- it('has expected label text in tab links and visually hidden span in tab 1', () => {
- const $ = cheerio.load(renderComponent('tabs', EXAMPLE_TABS));
+ it('has expected label text in tab links and visually hidden span in tab 1', () => {
+ const $ = cheerio.load(renderComponent('tabs', EXAMPLE_TABS));
- expect($('.ons-tab:first').html()).toBe('Tab 1
for Example ');
- expect($('.ons-tab:last').text().trim()).toBe('Tab 2');
- });
+ expect($('.ons-tab:first').html()).toBe('Tab 1
for Example ');
+ expect($('.ons-tab:last').text().trim()).toBe('Tab 2');
+ });
- it('has expected content in tab panels', () => {
- const $ = cheerio.load(renderComponent('tabs', EXAMPLE_TABS));
+ it('has expected content in tab panels', () => {
+ const $ = cheerio.load(renderComponent('tabs', EXAMPLE_TABS));
- expect($('.ons-tabs__panel:first').html().trim()).toBe('Example content...');
- expect($('.ons-tabs__panel:last').html().trim()).toBe('Some nested
strong element ...');
- });
+ expect($('.ons-tabs__panel:first').html().trim()).toBe('Example content...');
+ expect($('.ons-tabs__panel:last').html().trim()).toBe('Some nested
strong element ...');
+ });
- it('displays a h2 when showTitle set to true', () => {
- const $ = cheerio.load(renderComponent('tabs', EXAMPLE_TABS_WITH_SHOWTITLE));
+ it('displays a h2 when showTitle set to true', () => {
+ const $ = cheerio.load(renderComponent('tabs', EXAMPLE_TABS_WITH_SHOWTITLE));
- expect($('.ons-tabs__panel:first').find('h2').length).toBe(1);
- });
+ expect($('.ons-tabs__panel:first').find('h2').length).toBe(1);
+ });
});
diff --git a/src/components/tabs/_tabs.scss b/src/components/tabs/_tabs.scss
index 2c013b944b..385dfcd30e 100644
--- a/src/components/tabs/_tabs.scss
+++ b/src/components/tabs/_tabs.scss
@@ -1,144 +1,149 @@
.ons-tabs {
- margin-bottom: 1rem;
+ margin-bottom: 1rem;
- &__title {
- @extend .ons-u-fs-r--b;
- }
+ &__title {
+ @extend .ons-u-fs-r--b;
+ }
- // Anchor links list
- &__list {
- border-bottom: 0;
- margin: 0 0 1rem;
- overflow: visible;
- padding: 0;
- width: max-content;
- // Tabs
- &--row {
- margin: 0;
- position: relative;
+ // Anchor links list
+ &__list {
+ border-bottom: 0;
+ margin: 0 0 1rem;
+ overflow: visible;
+ padding: 0;
+ width: max-content;
+ // Tabs
+ &--row {
+ margin: 0;
+ position: relative;
+ }
+ }
+ &__container {
+ border-bottom: 1px solid var(--ons-color-borders);
+ margin: 0;
+ padding: 0;
+ z-index: -2;
}
- }
- &__container {
- border-bottom: 1px solid var(--ons-color-borders);
- margin: 0;
- padding: 0;
- z-index: -2;
- }
}
.ons-tab__list-item {
- box-sizing: border-box;
- display: list-item;
- list-style: none;
- margin: 0 0 0.3rem;
-
- &--row {
- display: inline;
- }
+ box-sizing: border-box;
+ display: list-item;
+ list-style: none;
+ margin: 0 0 0.3rem;
+
+ &--row {
+ display: inline;
+ }
}
.ons-tab--row {
- background: var(--ons-color-button-secondary);
- border: 1px solid var(--ons-color-button-secondary);
- border-radius: 3px 3px 0 0;
- color: var(--ons-color-text);
- display: inline-block;
- height: 2.55rem;
- line-height: 2.3rem;
- margin: 0 0.4rem 0 0;
- overflow: visible;
- padding: 0 1rem;
- position: relative;
- text-decoration: underline;
-
- &:hover:not(:focus) {
- background-color: var(--ons-color-button-secondary-hover);
- border-color: var(--ons-color-button-secondary-hover);
- color: var(--ons-color-text);
- text-decoration: underline solid var(--ons-color-text) 2px;
- }
-
- &:focus {
- background-color: var(--ons-color-focus);
- border-bottom: 1px solid var(--ons-color-borders);
- box-shadow: inset 0 0 0 9px var(--ons-color-button-secondary),
- inset 17px 0 0 0 var(--ons-color-button-secondary),
- inset -17px 0 0 0 var(--ons-color-button-secondary),
- inset 0 -13px 0 0 var(--ons-color-text-link-focus);
- color: var(--ons-color-text-link-focus);
-
- // Add transparent outline because Windows High Contrast Mode doesn't show box-shadows
- outline: 3px solid transparent;
- outline-offset: 1px;
- text-decoration: underline solid var(--ons-color-text) 2px;
- }
-
- // Tab when selected
- &[aria-selected='true'] {
- background-color: var(--ons-color-page-light);
- border-bottom: 2px white;
- border-color: var(--ons-color-borders);
+ background: var(--ons-color-button-secondary);
+ border: 1px solid var(--ons-color-button-secondary);
border-radius: 3px 3px 0 0;
+ color: var(--ons-color-text);
+ display: inline-block;
+ height: 2.55rem;
+ line-height: 2.3rem;
+ margin: 0 0.4rem 0 0;
+ overflow: visible;
+ padding: 0 1rem;
position: relative;
- text-decoration: none;
- top: 1px;
- z-index: 1;
+ text-decoration: underline;
+
+ &:hover:not(:focus) {
+ background-color: var(--ons-color-button-secondary-hover);
+ border-color: var(--ons-color-button-secondary-hover);
+ color: var(--ons-color-text);
+ text-decoration: underline solid var(--ons-color-text) 2px;
+ }
&:focus {
- background-color: var(--ons-color-focus);
- box-shadow: inset 0 0 0 9px var(--ons-color-page-light),
- inset 17px 0 0 0 var(--ons-color-page-light),
- inset -17px 0 0 0 var(--ons-color-page-light),
- inset 0 -13px 0 0 var(--ons-color-text-link-focus);
- text-decoration: none;
+ background-color: var(--ons-color-focus);
+ border-bottom: 1px solid var(--ons-color-borders);
+ box-shadow:
+ inset 0 0 0 9px var(--ons-color-button-secondary),
+ inset 17px 0 0 0 var(--ons-color-button-secondary),
+ inset -17px 0 0 0 var(--ons-color-button-secondary),
+ inset 0 -13px 0 0 var(--ons-color-text-link-focus);
+ color: var(--ons-color-text-link-focus);
+
+ // Add transparent outline because Windows High Contrast Mode doesn't show box-shadows
+ outline: 3px solid transparent;
+ outline-offset: 1px;
+ text-decoration: underline solid var(--ons-color-text) 2px;
+ }
+
+ // Tab when selected
+ &[aria-selected='true'] {
+ background-color: var(--ons-color-page-light);
+ border-bottom: 2px var(--ons-color-white);
+ border-color: var(--ons-color-borders);
+ border-radius: 3px 3px 0 0;
+ position: relative;
+ text-decoration: none;
+ top: 1px;
+ z-index: 1;
+
+ &:focus {
+ background-color: var(--ons-color-focus);
+ box-shadow:
+ inset 0 0 0 9px var(--ons-color-page-light),
+ inset 17px 0 0 0 var(--ons-color-page-light),
+ inset -17px 0 0 0 var(--ons-color-page-light),
+ inset 0 -13px 0 0 var(--ons-color-text-link-focus);
+ text-decoration: none;
+ }
}
- }
}
// Tabs - Panels
.ons-tabs__panel {
- padding-bottom: 1rem;
- padding-top: 1rem;
- position: relative;
-
- &--hidden {
- display: none;
- }
-
- &:focus {
- box-shadow: 0 0 0 0 var(--ons-color-page-light), 0 0 0 2px var(--ons-color-text-link-focus), 0 0 0 6px var(--ons-color-focus);
- outline: 3px solid transparent; // Add transparent outline because Windows High Contrast Mode doesn't show box-shadows
- z-index: 1;
- }
+ padding-bottom: 1rem;
+ padding-top: 1rem;
+ position: relative;
+
+ &--hidden {
+ display: none;
+ }
+
+ &:focus {
+ box-shadow:
+ 0 0 0 0 var(--ons-color-page-light),
+ 0 0 0 2px var(--ons-color-text-link-focus),
+ 0 0 0 6px var(--ons-color-focus);
+ outline: 3px solid transparent; // Add transparent outline because Windows High Contrast Mode doesn't show box-shadows
+ z-index: 1;
+ }
}
.ons-tabs--details {
- border-top: 1px solid var(--ons-color-borders);
- margin: 0;
+ border-top: 1px solid var(--ons-color-borders);
+ margin: 0;
- .ons-tab {
- border-radius: 0;
- border-top: none;
- }
+ .ons-tab {
+ border-radius: 0;
+ border-top: 0;
+ }
- .ons-tab__list-item:first-child .ons-tab {
- border-left: none;
- }
+ .ons-tab__list-item:first-child .ons-tab {
+ border-left: 0;
+ }
- .ons-tabs__title {
- padding: 1rem 1rem 0;
- }
+ .ons-tabs__title {
+ padding: 1rem 1rem 0;
+ }
- .ons-tabs__list {
- padding: 0 1rem;
- }
+ .ons-tabs__list {
+ padding: 0 1rem;
+ }
- .ons-tabs__list--row {
- padding: 0;
- }
+ .ons-tabs__list--row {
+ padding: 0;
+ }
- .ons-tabs__panel {
- margin-top: 1px;
- padding: 1rem;
- }
+ .ons-tabs__panel {
+ margin-top: 1px;
+ padding: 1rem;
+ }
}
diff --git a/src/components/tabs/tabs.dom.js b/src/components/tabs/tabs.dom.js
index 24e0daaa55..9455386769 100644
--- a/src/components/tabs/tabs.dom.js
+++ b/src/components/tabs/tabs.dom.js
@@ -1,13 +1,13 @@
import domready from '../../js/domready';
async function tabs() {
- const tabs = [...document.querySelectorAll('.ons-tabs')];
+ const tabs = [...document.querySelectorAll('.ons-tabs')];
- if (tabs.length) {
- const Tabs = (await import('./tabs')).default;
+ if (tabs.length) {
+ const Tabs = (await import('./tabs')).default;
- tabs.forEach((component) => new Tabs(component));
- }
+ tabs.forEach((component) => new Tabs(component));
+ }
}
domready(tabs);
diff --git a/src/components/tabs/tabs.js b/src/components/tabs/tabs.js
index 7ac82ca74f..0f198d8bbb 100644
--- a/src/components/tabs/tabs.js
+++ b/src/components/tabs/tabs.js
@@ -13,290 +13,290 @@ const classTabsPanel = 'ons-tabs__panel';
const matchMediaUtil = matchMedia;
export default class Tabs {
- constructor(component) {
- this.boundTabClick = this.onTabClick.bind(this);
- this.boundTabKeydown = this.onTabKeydown.bind(this);
-
- this.component = component;
- this.tabsTitle = component.querySelector(`.${classTabTitle}`);
- this.tabs = [...component.getElementsByClassName(classTab)];
- this.tabList = component.getElementsByClassName(classTabList);
- this.tabListContainer = this.tabList[0].parentElement;
- this.tabListItems = [...component.getElementsByClassName(classTabListItems)];
- this.tabPanels = [...component.getElementsByClassName(classTabsPanel)];
-
- this.jsHiddenClass = 'ons-tabs__panel--hidden';
- this.jsTabListAsRowClass = 'ons-tabs__list--row';
- this.jsTabItemAsRowClass = 'ons-tab__list-item--row';
- this.jsTabAsListClass = 'ons-tab--row';
-
- this.noInitialActiveTab = this.component.getAttribute('data-no-initial-active-tab');
- if (matchMediaUtil.hasMatchMedia()) {
- this.setupViewportChecks();
- } else {
- this.makeTabs();
+ constructor(component) {
+ this.boundTabClick = this.onTabClick.bind(this);
+ this.boundTabKeydown = this.onTabKeydown.bind(this);
+
+ this.component = component;
+ this.tabsTitle = component.querySelector(`.${classTabTitle}`);
+ this.tabs = [...component.getElementsByClassName(classTab)];
+ this.tabList = component.getElementsByClassName(classTabList);
+ this.tabListContainer = this.tabList[0].parentElement;
+ this.tabListItems = [...component.getElementsByClassName(classTabListItems)];
+ this.tabPanels = [...component.getElementsByClassName(classTabsPanel)];
+
+ this.jsHiddenClass = 'ons-tabs__panel--hidden';
+ this.jsTabListAsRowClass = 'ons-tabs__list--row';
+ this.jsTabItemAsRowClass = 'ons-tab__list-item--row';
+ this.jsTabAsListClass = 'ons-tab--row';
+
+ this.noInitialActiveTab = this.component.getAttribute('data-no-initial-active-tab');
+ if (matchMediaUtil.hasMatchMedia()) {
+ this.setupViewportChecks();
+ } else {
+ this.makeTabs();
+ }
}
- }
-
- // Set up checks for responsive functionality
- // The tabs will display as tabs up until this.breakpoint is reached
- // Tabs will display as a TOC list and show full content for
{
- let finalBreakpoint = 0;
- this.tabListItems.forEach((tab) => {
- finalBreakpoint += tab.offsetWidth;
- });
- if (finalBreakpoint < 450) {
- return (finalBreakpoint = 450);
- } else {
- return finalBreakpoint;
- }
- };
- this.viewport = matchMediaUtil(`(min-width: ${breakpoint()}px)`);
- this.viewport.addListener(this.checkViewport.bind(this));
- this.checkViewport();
- }
-
- checkViewport() {
- if (this.viewport.matches) {
- this.makeTabs();
- } else {
- this.makeList();
+
+ // Set up checks for responsive functionality
+ // The tabs will display as tabs up until this.breakpoint is reached
+ // Tabs will display as a TOC list and show full content for {
+ let finalBreakpoint = 0;
+ this.tabListItems.forEach((tab) => {
+ finalBreakpoint += tab.offsetWidth;
+ });
+ if (finalBreakpoint < 450) {
+ return (finalBreakpoint = 450);
+ } else {
+ return finalBreakpoint;
+ }
+ };
+ this.viewport = matchMediaUtil(`(min-width: ${breakpoint()}px)`);
+ this.viewport.addListener(this.checkViewport.bind(this));
+ this.checkViewport();
+ }
+
+ checkViewport() {
+ if (this.viewport.matches) {
+ this.makeTabs();
+ } else {
+ this.makeList();
+ }
+ }
+
+ makeTabs() {
+ this.tabList[0].setAttribute('role', 'tablist');
+ this.tabList[0].classList.add(this.jsTabListAsRowClass);
+
+ this.tabsTitle.classList.add('ons-u-vh');
+ this.tabListContainer.classList.add('ons-tabs__container');
+ this.tabPanels.forEach((panel) => {
+ panel.setAttribute('tabindex', '0');
+ if (panel.querySelector('[id*="content-title"]')) {
+ panel.firstElementChild.classList.add('ons-u-vh');
+ }
+ });
+
+ this.tabListItems.forEach((item) => {
+ item.setAttribute('role', 'presentation');
+ item.classList.add(this.jsTabItemAsRowClass);
+ });
+
+ this.tabs.forEach((tab) => {
+ this.setAttributes(tab);
+ tab.classList.add(this.jsTabAsListClass);
+
+ tab.addEventListener('click', this.boundTabClick, true);
+ tab.addEventListener('keydown', this.boundTabKeydown, true);
+ this.hideTab(tab);
+ });
+
+ const hashTab = this.getTab(window.location.hash);
+ if (!this.noInitialActiveTab || !!hashTab) {
+ const activeTab = hashTab || this.tabs[0];
+ this.showTab(activeTab);
+ }
+
+ this.ensureTabIndexExists();
+
+ this.component.boundOnHashChange = this.onHashChange.bind(this);
+ window.addEventListener('hashchange', this.component.boundOnHashChange, true);
+ }
+
+ makeList() {
+ this.tabList[0].removeAttribute('role');
+ this.tabList[0].classList.remove(this.jsTabListAsRowClass);
+ this.tabListContainer.classList.remove('ons-tabs__container');
+ this.tabsTitle.classList.remove('ons-u-vh');
+
+ this.tabPanels.forEach((panel) => {
+ panel.removeAttribute('tabindex', '0');
+ if (panel.firstElementChild.classList.contains('ons-u-vh')) {
+ panel.firstElementChild.classList.remove('ons-u-vh');
+ }
+ });
+
+ this.tabListItems.forEach((item) => {
+ item.removeAttribute('role', 'presentation');
+ item.classList.remove(this.jsTabItemAsRowClass);
+ });
+
+ this.tabs.forEach((tab) => {
+ tab.removeEventListener('click', this.boundTabClick, true);
+ tab.removeEventListener('keydown', this.boundTabKeydown, true);
+ tab.classList.remove(this.jsTabAsListClass);
+ this.unsetAttributes(tab);
+ });
+
+ window.removeEventListener('hashchange', this.component.boundOnHashChange, true);
+ }
+
+ // Handle haschange so that the browser can cycle through the tab history
+ onHashChange() {
+ const hash = window.location.hash;
+ const tabWithHash = this.getTab(hash);
+ if (!tabWithHash) {
+ return;
+ }
+
+ if (this.changingHash) {
+ this.changingHash = false;
+ return;
+ }
+
+ const currentTab = this.getCurrentTab();
+ if (!!currentTab) {
+ this.hideTab(currentTab);
+ }
+ this.showTab(tabWithHash);
+ tabWithHash.focus();
+ }
+
+ hideTab(tab) {
+ this.unhighlightTab(tab);
+ this.hidePanel(tab);
+ }
+
+ showTab(tab) {
+ this.highlightTab(tab);
+ this.showPanel(tab);
+ }
+
+ getTab(hash) {
+ return this.component.querySelector('.ons-tab[href="' + hash + '"]');
+ }
+
+ // Set aria tags
+ setAttributes(tab) {
+ const panelId = this.getHref(tab).slice(1);
+ tab.setAttribute('id', 'tab_' + panelId);
+ tab.setAttribute('role', 'tab');
+ tab.setAttribute('aria-controls', panelId);
+ tab.setAttribute('tabindex', '-1');
+
+ const panel = this.getPanel(tab);
+ panel.setAttribute('role', 'tabpanel');
+ panel.setAttribute('aria-labelledby', tab.id);
+ panel.classList.add(this.jsHiddenClass);
+ }
+
+ // Remove aria tags for TOC view
+ unsetAttributes(tab) {
+ tab.removeAttribute('id');
+ tab.removeAttribute('role');
+ tab.removeAttribute('aria-controls');
+ tab.removeAttribute('tabindex');
+ tab.removeAttribute('aria-selected');
+
+ const panel = this.getPanel(tab);
+ panel.removeAttribute('role');
+ panel.removeAttribute('aria-labelledby');
+ panel.classList.remove(this.jsHiddenClass);
+ }
+
+ onTabClick(e) {
+ e.preventDefault();
+ const newTab = e.target;
+ const currentTab = this.getCurrentTab();
+
+ if (!!currentTab) {
+ this.hideTab(currentTab);
+ }
+
+ if (!this.noInitialActiveTab || newTab !== currentTab) {
+ this.showTab(newTab);
+ this.createHash(newTab);
+ }
+
+ this.ensureTabIndexExists();
}
- }
-
- makeTabs() {
- this.tabList[0].setAttribute('role', 'tablist');
- this.tabList[0].classList.add(this.jsTabListAsRowClass);
-
- this.tabsTitle.classList.add('ons-u-vh');
- this.tabListContainer.classList.add('ons-tabs__container');
- this.tabPanels.forEach((panel) => {
- panel.setAttribute('tabindex', '0');
- if (panel.querySelector('[id*="content-title"]')) {
- panel.firstElementChild.classList.add('ons-u-vh');
- }
- });
-
- this.tabListItems.forEach((item) => {
- item.setAttribute('role', 'presentation');
- item.classList.add(this.jsTabItemAsRowClass);
- });
-
- this.tabs.forEach((tab) => {
- this.setAttributes(tab);
- tab.classList.add(this.jsTabAsListClass);
-
- tab.addEventListener('click', this.boundTabClick, true);
- tab.addEventListener('keydown', this.boundTabKeydown, true);
- this.hideTab(tab);
- });
-
- const hashTab = this.getTab(window.location.hash);
- if (!this.noInitialActiveTab || !!hashTab) {
- const activeTab = hashTab || this.tabs[0];
- this.showTab(activeTab);
+
+ ensureTabIndexExists() {
+ // Ensure that at least the first tab has a tab index when all tabs are hidden.
+ if (!this.tabs.find((tab) => tab.getAttribute('tabindex') === '0')) {
+ this.tabs[0].setAttribute('tabindex', '0');
+ }
+ }
+
+ createHash(tab) {
+ const panel = this.getPanel(tab);
+ const id = panel.id;
+ panel.id = '';
+ this.changingHash = true;
+ window.location.hash = this.getHref(tab).slice(1);
+ panel.id = id;
+ }
+
+ onTabKeydown(event) {
+ if (event.which === 37) {
+ this.focusPreviousTab();
+ event.preventDefault();
+ } else if (event.which === 39) {
+ this.focusNextTab();
+ event.preventDefault();
+ } else if (event.which === 32) {
+ this.onTabClick(event);
+ }
}
- this.ensureTabIndexExists();
-
- this.component.boundOnHashChange = this.onHashChange.bind(this);
- window.addEventListener('hashchange', this.component.boundOnHashChange, true);
- }
-
- makeList() {
- this.tabList[0].removeAttribute('role');
- this.tabList[0].classList.remove(this.jsTabListAsRowClass);
- this.tabListContainer.classList.remove('ons-tabs__container');
- this.tabsTitle.classList.remove('ons-u-vh');
-
- this.tabPanels.forEach((panel) => {
- panel.removeAttribute('tabindex', '0');
- if (panel.firstElementChild.classList.contains('ons-u-vh')) {
- panel.firstElementChild.classList.remove('ons-u-vh');
- }
- });
-
- this.tabListItems.forEach((item) => {
- item.removeAttribute('role', 'presentation');
- item.classList.remove(this.jsTabItemAsRowClass);
- });
-
- this.tabs.forEach((tab) => {
- tab.removeEventListener('click', this.boundTabClick, true);
- tab.removeEventListener('keydown', this.boundTabKeydown, true);
- tab.classList.remove(this.jsTabAsListClass);
- this.unsetAttributes(tab);
- });
-
- window.removeEventListener('hashchange', this.component.boundOnHashChange, true);
- }
-
- // Handle haschange so that the browser can cycle through the tab history
- onHashChange() {
- const hash = window.location.hash;
- const tabWithHash = this.getTab(hash);
- if (!tabWithHash) {
- return;
+ focusNextTab() {
+ const focusedTab = this.getFocusedTab();
+ const nextTabListItem = focusedTab.nextElementSibling;
+ if (nextTabListItem) {
+ nextTabListItem.firstElementChild.focus();
+ }
}
- if (this.changingHash) {
- this.changingHash = false;
- return;
+ focusPreviousTab() {
+ const focusedTab = this.getFocusedTab();
+ const previousTabListItem = focusedTab.previousElementSibling;
+ if (previousTabListItem) {
+ previousTabListItem.firstElementChild.focus();
+ }
}
- const currentTab = this.getCurrentTab();
- if (!!currentTab) {
- this.hideTab(currentTab);
+ getPanel(tab) {
+ const panelSelector = this.getHref(tab).replace(/\./g, '\\.');
+ const panel = this.component.querySelector(panelSelector);
+ return panel;
}
- this.showTab(tabWithHash);
- tabWithHash.focus();
- }
-
- hideTab(tab) {
- this.unhighlightTab(tab);
- this.hidePanel(tab);
- }
-
- showTab(tab) {
- this.highlightTab(tab);
- this.showPanel(tab);
- }
-
- getTab(hash) {
- return this.component.querySelector('.ons-tab[href="' + hash + '"]');
- }
-
- // Set aria tags
- setAttributes(tab) {
- const panelId = this.getHref(tab).slice(1);
- tab.setAttribute('id', 'tab_' + panelId);
- tab.setAttribute('role', 'tab');
- tab.setAttribute('aria-controls', panelId);
- tab.setAttribute('tabindex', '-1');
-
- const panel = this.getPanel(tab);
- panel.setAttribute('role', 'tabpanel');
- panel.setAttribute('aria-labelledby', tab.id);
- panel.classList.add(this.jsHiddenClass);
- }
-
- // Remove aria tags for TOC view
- unsetAttributes(tab) {
- tab.removeAttribute('id');
- tab.removeAttribute('role');
- tab.removeAttribute('aria-controls');
- tab.removeAttribute('tabindex');
- tab.removeAttribute('aria-selected');
-
- const panel = this.getPanel(tab);
- panel.removeAttribute('role');
- panel.removeAttribute('aria-labelledby');
- panel.classList.remove(this.jsHiddenClass);
- }
-
- onTabClick(e) {
- e.preventDefault();
- const newTab = e.target;
- const currentTab = this.getCurrentTab();
-
- if (!!currentTab) {
- this.hideTab(currentTab);
+
+ showPanel(tab) {
+ const panel = this.getPanel(tab);
+ panel.classList.remove(this.jsHiddenClass);
}
- if (!this.noInitialActiveTab || newTab !== currentTab) {
- this.showTab(newTab);
- this.createHash(newTab);
+ hidePanel(tab) {
+ const panel = this.getPanel(tab);
+ panel.classList.add(this.jsHiddenClass);
}
- this.ensureTabIndexExists();
- }
+ unhighlightTab(tab) {
+ tab.setAttribute('aria-selected', 'false');
+ tab.classList.remove('ons-tab--selected');
+ tab.setAttribute('tabindex', '-1');
+ }
- ensureTabIndexExists() {
- // Ensure that at least the first tab has a tab index when all tabs are hidden.
- if (!this.tabs.find((tab) => tab.getAttribute('tabindex') === '0')) {
- this.tabs[0].setAttribute('tabindex', '0');
+ highlightTab(tab) {
+ tab.setAttribute('aria-selected', 'true');
+ tab.classList.add('ons-tab--selected');
+ tab.setAttribute('tabindex', '0');
}
- }
-
- createHash(tab) {
- const panel = this.getPanel(tab);
- const id = panel.id;
- panel.id = '';
- this.changingHash = true;
- window.location.hash = this.getHref(tab).slice(1);
- panel.id = id;
- }
-
- onTabKeydown(event) {
- if (event.which === 37) {
- this.focusPreviousTab();
- event.preventDefault();
- } else if (event.which === 39) {
- this.focusNextTab();
- event.preventDefault();
- } else if (event.which === 32) {
- this.onTabClick(event);
+
+ getFocusedTab() {
+ return document.activeElement.parentNode;
}
- }
- focusNextTab() {
- const focusedTab = this.getFocusedTab();
- const nextTabListItem = focusedTab.nextElementSibling;
- if (nextTabListItem) {
- nextTabListItem.firstElementChild.focus();
+ getCurrentTab() {
+ return this.component.querySelector('.ons-tab--selected');
}
- }
- focusPreviousTab() {
- const focusedTab = this.getFocusedTab();
- const previousTabListItem = focusedTab.previousElementSibling;
- if (previousTabListItem) {
- previousTabListItem.firstElementChild.focus();
+ getHref(tab) {
+ const href = tab.getAttribute('href');
+ const hash = href.slice(href.indexOf('#'), href.length);
+ return hash;
}
- }
-
- getPanel(tab) {
- const panelSelector = this.getHref(tab).replace(/\./g, '\\.');
- const panel = this.component.querySelector(panelSelector);
- return panel;
- }
-
- showPanel(tab) {
- const panel = this.getPanel(tab);
- panel.classList.remove(this.jsHiddenClass);
- }
-
- hidePanel(tab) {
- const panel = this.getPanel(tab);
- panel.classList.add(this.jsHiddenClass);
- }
-
- unhighlightTab(tab) {
- tab.setAttribute('aria-selected', 'false');
- tab.classList.remove('ons-tab--selected');
- tab.setAttribute('tabindex', '-1');
- }
-
- highlightTab(tab) {
- tab.setAttribute('aria-selected', 'true');
- tab.classList.add('ons-tab--selected');
- tab.setAttribute('tabindex', '0');
- }
-
- getFocusedTab() {
- return document.activeElement.parentNode;
- }
-
- getCurrentTab() {
- return this.component.querySelector('.ons-tab--selected');
- }
-
- getHref(tab) {
- const href = tab.getAttribute('href');
- const hash = href.slice(href.indexOf('#'), href.length);
- return hash;
- }
}
diff --git a/src/components/tabs/tabs.spec.js b/src/components/tabs/tabs.spec.js
index 0c4b41f0b3..53f20c48be 100644
--- a/src/components/tabs/tabs.spec.js
+++ b/src/components/tabs/tabs.spec.js
@@ -5,364 +5,364 @@ import { renderComponent, setTestPage } from '../../tests/helpers/rendering';
const iPhoneX = KnownDevices['iPhone X'];
const EXAMPLE_TABS = {
- title: 'Example tabs',
- titleClasses: 'ons-u-fs-m',
- tabs: [
- {
- id: 'tab.id.1',
- title: 'Tab 1',
- showTitle: true,
- content: 'First content...',
- },
- {
- id: 'tab.id.2',
- title: 'Tab 2',
- content: 'Second content...',
- },
- {
- id: 'tab.id.3',
- title: 'Tab 3',
- content: 'Third content...',
- },
- ],
+ title: 'Example tabs',
+ titleClasses: 'ons-u-fs-m',
+ tabs: [
+ {
+ id: 'tab.id.1',
+ title: 'Tab 1',
+ showTitle: true,
+ content: 'First content...',
+ },
+ {
+ id: 'tab.id.2',
+ title: 'Tab 2',
+ content: 'Second content...',
+ },
+ {
+ id: 'tab.id.3',
+ title: 'Tab 3',
+ content: 'Third content...',
+ },
+ ],
};
const EXAMPLE_TABS_LONGER = {
- title: 'Example tabs',
- tabs: [
- {
- id: 'tab.id.1',
- title: 'Tab 1',
- showTitle: true,
- content: 'First content...',
- },
- {
- id: 'tab.id.2',
- title: 'Tab 2',
- content: 'Second content...',
- },
- {
- id: 'tab.id.3',
- title: 'Tab 3',
- content: 'Third content...',
- },
- {
- id: 'tab.id.4',
- title: 'Tab 4',
- content: 'Fourth content...',
- },
- {
- id: 'tab.id.5',
- title: 'Tab 5',
- content: 'Fifth content...',
- },
- ],
+ title: 'Example tabs',
+ tabs: [
+ {
+ id: 'tab.id.1',
+ title: 'Tab 1',
+ showTitle: true,
+ content: 'First content...',
+ },
+ {
+ id: 'tab.id.2',
+ title: 'Tab 2',
+ content: 'Second content...',
+ },
+ {
+ id: 'tab.id.3',
+ title: 'Tab 3',
+ content: 'Third content...',
+ },
+ {
+ id: 'tab.id.4',
+ title: 'Tab 4',
+ content: 'Fourth content...',
+ },
+ {
+ id: 'tab.id.5',
+ title: 'Tab 5',
+ content: 'Fifth content...',
+ },
+ ],
};
const EXAMPLE_TABS_WITH_NO_INITIAL_ACTIVE_TAB = {
- ...EXAMPLE_TABS,
- noInitialActiveTab: true,
+ ...EXAMPLE_TABS,
+ noInitialActiveTab: true,
};
describe('script: tabs', () => {
- afterEach(async () => {
- // Clear viewport size and browser emulation after each test.
- await jestPuppeteer.resetPage();
- });
-
- describe('when the viewport is large', () => {
- beforeEach(async () => {
- await setViewport(page, { width: 1650, height: 1050 });
- await setTestPage('/test', renderComponent('tabs', EXAMPLE_TABS));
+ afterEach(async () => {
+ // Clear viewport size and browser emulation after each test.
+ await jestPuppeteer.resetPage();
});
- it('has additionally provided `titleClasses`', async () => {
- const hasClass = await page.$eval('.ons-tabs__title', (node) => node.classList.contains('ons-u-fs-m'));
+ describe('when the viewport is large', () => {
+ beforeEach(async () => {
+ await setViewport(page, { width: 1650, height: 1050 });
+ await setTestPage('/test', renderComponent('tabs', EXAMPLE_TABS));
+ });
- expect(hasClass).toBe(true);
- });
+ it('has additionally provided `titleClasses`', async () => {
+ const hasClass = await page.$eval('.ons-tabs__title', (node) => node.classList.contains('ons-u-fs-m'));
- it('has the "presentation" role assigned to tab list items', async () => {
- const role = await page.$eval('.ons-tab__list-item', (node) => node.getAttribute('role'));
- expect(role).toBe('presentation');
- });
+ expect(hasClass).toBe(true);
+ });
- it('has the "tab" role assigned to each tab', async () => {
- const tabRoleValues = await page.$$eval('.ons-tab', (nodes) => nodes.map((node) => node.getAttribute('role')));
+ it('has the "presentation" role assigned to tab list items', async () => {
+ const role = await page.$eval('.ons-tab__list-item', (node) => node.getAttribute('role'));
+ expect(role).toBe('presentation');
+ });
- expect(tabRoleValues).toEqual(['tab', 'tab', 'tab']);
- });
+ it('has the "tab" role assigned to each tab', async () => {
+ const tabRoleValues = await page.$$eval('.ons-tab', (nodes) => nodes.map((node) => node.getAttribute('role')));
- it('has "aria-controls" assigned to each tab with the corresponding panel id', async () => {
- const ariaControlsValues = await page.$$eval('.ons-tab', (nodes) => nodes.map((node) => node.getAttribute('aria-controls')));
+ expect(tabRoleValues).toEqual(['tab', 'tab', 'tab']);
+ });
- expect(ariaControlsValues).toEqual(['tab.id.1', 'tab.id.2', 'tab.id.3']);
- });
+ it('has "aria-controls" assigned to each tab with the corresponding panel id', async () => {
+ const ariaControlsValues = await page.$$eval('.ons-tab', (nodes) => nodes.map((node) => node.getAttribute('aria-controls')));
- it('has "aria-selected" assigned to the first tab', async () => {
- const ariaSelectedValue = await page.$eval('.ons-tab', (node) => node.getAttribute('aria-selected'));
+ expect(ariaControlsValues).toEqual(['tab.id.1', 'tab.id.2', 'tab.id.3']);
+ });
- expect(ariaSelectedValue).toBe('true');
- });
+ it('has "aria-selected" assigned to the first tab', async () => {
+ const ariaSelectedValue = await page.$eval('.ons-tab', (node) => node.getAttribute('aria-selected'));
- it('has the "ons-tab--selected" class assigned to the first tab', async () => {
- const hasClass = await page.$eval('.ons-tab', (node) => node.classList.contains('ons-tab--selected'));
+ expect(ariaSelectedValue).toBe('true');
+ });
- expect(hasClass).toBe(true);
- });
+ it('has the "ons-tab--selected" class assigned to the first tab', async () => {
+ const hasClass = await page.$eval('.ons-tab', (node) => node.classList.contains('ons-tab--selected'));
- it('has "tabindex" assigned to each tab', async () => {
- const tabIndexValues = await page.$$eval('.ons-tab', (nodes) => nodes.map((node) => node.getAttribute('tabindex')));
+ expect(hasClass).toBe(true);
+ });
- expect(tabIndexValues).toEqual(['0', '-1', '-1']);
- });
+ it('has "tabindex" assigned to each tab', async () => {
+ const tabIndexValues = await page.$$eval('.ons-tab', (nodes) => nodes.map((node) => node.getAttribute('tabindex')));
- it('has only one visible tab panel', async () => {
- const panelHiddenStates = await page.$$eval('.ons-tabs__panel', (nodes) =>
- nodes.map((node) => node.classList.contains('ons-tabs__panel--hidden')),
- );
+ expect(tabIndexValues).toEqual(['0', '-1', '-1']);
+ });
- expect(panelHiddenStates).toEqual([false, true, true]);
- });
+ it('has only one visible tab panel', async () => {
+ const panelHiddenStates = await page.$$eval('.ons-tabs__panel', (nodes) =>
+ nodes.map((node) => node.classList.contains('ons-tabs__panel--hidden')),
+ );
- describe('when a tab is clicked', () => {
- beforeEach(async () => {
- await page.focus('a[href="#tab.id.2"]');
- await page.keyboard.press('Enter');
- });
+ expect(panelHiddenStates).toEqual([false, true, true]);
+ });
- it('is assigned a "tabindex" value', async () => {
- const tabIndexValues = await page.$$eval('.ons-tab', (nodes) => nodes.map((node) => node.getAttribute('tabindex')));
+ describe('when a tab is clicked', () => {
+ beforeEach(async () => {
+ await page.focus('a[href="#tab.id.2"]');
+ await page.keyboard.press('Enter');
+ });
- expect(tabIndexValues).toEqual(['-1', '0', '-1']);
- });
+ it('is assigned a "tabindex" value', async () => {
+ const tabIndexValues = await page.$$eval('.ons-tab', (nodes) => nodes.map((node) => node.getAttribute('tabindex')));
- it('has the "aria-selected" attribute', async () => {
- const ariaSelectedValue = await page.$eval('a[href="#tab.id.2"]', (node) => node.getAttribute('aria-selected'));
+ expect(tabIndexValues).toEqual(['-1', '0', '-1']);
+ });
- expect(ariaSelectedValue).toBe('true');
- });
+ it('has the "aria-selected" attribute', async () => {
+ const ariaSelectedValue = await page.$eval('a[href="#tab.id.2"]', (node) => node.getAttribute('aria-selected'));
- it('has the "ons-tab--selected" class assigned', async () => {
- const hasClass = await page.$eval('a[href="#tab.id.2"]', (node) => node.classList.contains('ons-tab--selected'));
+ expect(ariaSelectedValue).toBe('true');
+ });
- expect(hasClass).toBe(true);
- });
+ it('has the "ons-tab--selected" class assigned', async () => {
+ const hasClass = await page.$eval('a[href="#tab.id.2"]', (node) => node.classList.contains('ons-tab--selected'));
- it('shows the corresponding panel', async () => {
- const panelHiddenStates = await page.$$eval('.ons-tabs__panel', (nodes) =>
- nodes.map((node) => node.classList.contains('ons-tabs__panel--hidden')),
- );
+ expect(hasClass).toBe(true);
+ });
- expect(panelHiddenStates).toEqual([true, false, true]);
- });
- });
+ it('shows the corresponding panel', async () => {
+ const panelHiddenStates = await page.$$eval('.ons-tabs__panel', (nodes) =>
+ nodes.map((node) => node.classList.contains('ons-tabs__panel--hidden')),
+ );
- describe('when the right arrow key is pressed', () => {
- it('focuses the next tab', async () => {
- await page.focus('a[href="#tab.id.2"]');
- await page.keyboard.press('ArrowRight');
+ expect(panelHiddenStates).toEqual([true, false, true]);
+ });
+ });
- const activeElement = await page.evaluate(() => document.activeElement.innerText);
- expect(activeElement).toBe('Tab 3');
- });
- });
+ describe('when the right arrow key is pressed', () => {
+ it('focuses the next tab', async () => {
+ await page.focus('a[href="#tab.id.2"]');
+ await page.keyboard.press('ArrowRight');
- describe('when the left arrow key is pressed', () => {
- it('focuses the previous tab', async () => {
- await page.focus('a[href="#tab.id.2"]');
- await page.keyboard.press('ArrowLeft');
+ const activeElement = await page.evaluate(() => document.activeElement.innerText);
+ expect(activeElement).toBe('Tab 3');
+ });
+ });
- const activeElement = await page.evaluate(() => document.activeElement.innerText);
- expect(activeElement).toBe('Tab 1');
- });
- });
- });
+ describe('when the left arrow key is pressed', () => {
+ it('focuses the previous tab', async () => {
+ await page.focus('a[href="#tab.id.2"]');
+ await page.keyboard.press('ArrowLeft');
- describe('when a hash for a tab is in the url', () => {
- beforeEach(async () => {
- await setViewport(page, { width: 1650, height: 1050 });
- await setTestPage('/test#tab.id.2', renderComponent('tabs', EXAMPLE_TABS));
+ const activeElement = await page.evaluate(() => document.activeElement.innerText);
+ expect(activeElement).toBe('Tab 1');
+ });
+ });
});
- it('is assigned a "tabindex" value', async () => {
- const tabIndexValues = await page.$$eval('.ons-tab', (nodes) => nodes.map((node) => node.getAttribute('tabindex')));
+ describe('when a hash for a tab is in the url', () => {
+ beforeEach(async () => {
+ await setViewport(page, { width: 1650, height: 1050 });
+ await setTestPage('/test#tab.id.2', renderComponent('tabs', EXAMPLE_TABS));
+ });
- expect(tabIndexValues).toEqual(['-1', '0', '-1']);
- });
+ it('is assigned a "tabindex" value', async () => {
+ const tabIndexValues = await page.$$eval('.ons-tab', (nodes) => nodes.map((node) => node.getAttribute('tabindex')));
- it('has the "aria-selected" attribute', async () => {
- const ariaSelectedValue = await page.$eval('a[href="#tab.id.2"]', (node) => node.getAttribute('aria-selected'));
+ expect(tabIndexValues).toEqual(['-1', '0', '-1']);
+ });
- expect(ariaSelectedValue).toBe('true');
- });
+ it('has the "aria-selected" attribute', async () => {
+ const ariaSelectedValue = await page.$eval('a[href="#tab.id.2"]', (node) => node.getAttribute('aria-selected'));
- it('has the "ons-tab--selected" class assigned', async () => {
- const hasClass = await page.$eval('a[href="#tab.id.2"]', (node) => node.classList.contains('ons-tab--selected'));
+ expect(ariaSelectedValue).toBe('true');
+ });
- expect(hasClass).toBe(true);
- });
+ it('has the "ons-tab--selected" class assigned', async () => {
+ const hasClass = await page.$eval('a[href="#tab.id.2"]', (node) => node.classList.contains('ons-tab--selected'));
+
+ expect(hasClass).toBe(true);
+ });
- it('shows the corresponding panel', async () => {
- const panelHiddenStates = await page.$$eval('.ons-tabs__panel', (nodes) =>
- nodes.map((node) => node.classList.contains('ons-tabs__panel--hidden')),
- );
+ it('shows the corresponding panel', async () => {
+ const panelHiddenStates = await page.$$eval('.ons-tabs__panel', (nodes) =>
+ nodes.map((node) => node.classList.contains('ons-tabs__panel--hidden')),
+ );
- expect(panelHiddenStates).toEqual([true, false, true]);
+ expect(panelHiddenStates).toEqual([true, false, true]);
+ });
});
- });
- describe('when the viewport is small', () => {
- beforeEach(async () => {
- await page.emulate(iPhoneX);
+ describe('when the viewport is small', () => {
+ beforeEach(async () => {
+ await page.emulate(iPhoneX);
- await setTestPage('/test', renderComponent('tabs', EXAMPLE_TABS_LONGER));
- });
+ await setTestPage('/test', renderComponent('tabs', EXAMPLE_TABS_LONGER));
+ });
- it('has no aria attributes on tabs', async () => {
- const tabElements = await page.$$('.ons-tab');
- for (let i = 0; i < 3; ++i) {
- const hasRoleAttribute = await tabElements[i].evaluate((node) => node.getAttribute('role') !== null);
- expect(hasRoleAttribute).toBe(false);
+ it('has no aria attributes on tabs', async () => {
+ const tabElements = await page.$$('.ons-tab');
+ for (let i = 0; i < 3; ++i) {
+ const hasRoleAttribute = await tabElements[i].evaluate((node) => node.getAttribute('role') !== null);
+ expect(hasRoleAttribute).toBe(false);
- const hasAriaControlsAttribute = await tabElements[i].evaluate((node) => node.getAttribute('aria-controls') !== null);
- expect(hasAriaControlsAttribute).toBe(false);
+ const hasAriaControlsAttribute = await tabElements[i].evaluate((node) => node.getAttribute('aria-controls') !== null);
+ expect(hasAriaControlsAttribute).toBe(false);
- const hasAriaSelectedAttribute = await tabElements[i].evaluate((node) => node.getAttribute('aria-selected') !== null);
- expect(hasAriaSelectedAttribute).toBe(false);
- }
- });
+ const hasAriaSelectedAttribute = await tabElements[i].evaluate((node) => node.getAttribute('aria-selected') !== null);
+ expect(hasAriaSelectedAttribute).toBe(false);
+ }
+ });
- it('has no hidden tab panels', async () => {
- const panelCount = await page.$$eval('.ons-tabs__panel', (nodes) => nodes.length);
- expect(panelCount).toBe(5);
+ it('has no hidden tab panels', async () => {
+ const panelCount = await page.$$eval('.ons-tabs__panel', (nodes) => nodes.length);
+ expect(panelCount).toBe(5);
- const hiddenPanelCount = await page.$$eval('.ons-tabs__panel--hidden', (nodes) => nodes.length);
- expect(hiddenPanelCount).toBe(0);
- });
+ const hiddenPanelCount = await page.$$eval('.ons-tabs__panel--hidden', (nodes) => nodes.length);
+ expect(hiddenPanelCount).toBe(0);
+ });
- it('displays a h2 element with a unique id', async () => {
- const panelCount = await page.$$eval('#tab-1-content-title', (nodes) => nodes.length);
- expect(panelCount).toBe(1);
+ it('displays a h2 element with a unique id', async () => {
+ const panelCount = await page.$$eval('#tab-1-content-title', (nodes) => nodes.length);
+ expect(panelCount).toBe(1);
+ });
});
- });
- describe('when `data-no-initial-active-tab` is present', () => {
- beforeEach(async () => {
- await setViewport(page, { width: 1650, height: 1050 });
- await setTestPage('/test', renderComponent('tabs', EXAMPLE_TABS_WITH_NO_INITIAL_ACTIVE_TAB));
- });
+ describe('when `data-no-initial-active-tab` is present', () => {
+ beforeEach(async () => {
+ await setViewport(page, { width: 1650, height: 1050 });
+ await setTestPage('/test', renderComponent('tabs', EXAMPLE_TABS_WITH_NO_INITIAL_ACTIVE_TAB));
+ });
- it('does not assign "aria-selected" to the first tab', async () => {
- const ariaSelectedValue = await page.$eval('.ons-tab', (node) => node.getAttribute('aria-selected'));
+ it('does not assign "aria-selected" to the first tab', async () => {
+ const ariaSelectedValue = await page.$eval('.ons-tab', (node) => node.getAttribute('aria-selected'));
- expect(ariaSelectedValue).not.toBe('true');
- });
+ expect(ariaSelectedValue).not.toBe('true');
+ });
- it('does not assign the "ons-tab--selected" class to the first tab', async () => {
- const hasClass = await page.$eval('.ons-tab', (node) => node.classList.contains('ons-tab--selected'));
+ it('does not assign the "ons-tab--selected" class to the first tab', async () => {
+ const hasClass = await page.$eval('.ons-tab', (node) => node.classList.contains('ons-tab--selected'));
- expect(hasClass).toBe(false);
- });
+ expect(hasClass).toBe(false);
+ });
- describe('when a tab is clicked', () => {
- beforeEach(async () => {
- await page.focus('a[href="#tab.id.1"]');
- await page.keyboard.press('Enter');
- });
+ describe('when a tab is clicked', () => {
+ beforeEach(async () => {
+ await page.focus('a[href="#tab.id.1"]');
+ await page.keyboard.press('Enter');
+ });
- it('is assigned a "tabindex" value', async () => {
- const tabIndexValues = await page.$$eval('.ons-tab', (nodes) => nodes.map((node) => node.getAttribute('tabindex')));
+ it('is assigned a "tabindex" value', async () => {
+ const tabIndexValues = await page.$$eval('.ons-tab', (nodes) => nodes.map((node) => node.getAttribute('tabindex')));
- expect(tabIndexValues).toEqual(['0', '-1', '-1']);
- });
+ expect(tabIndexValues).toEqual(['0', '-1', '-1']);
+ });
- it('has the "aria-selected" attribute', async () => {
- const ariaSelectedValue = await page.$eval('a[href="#tab.id.1"]', (node) => node.getAttribute('aria-selected'));
+ it('has the "aria-selected" attribute', async () => {
+ const ariaSelectedValue = await page.$eval('a[href="#tab.id.1"]', (node) => node.getAttribute('aria-selected'));
- expect(ariaSelectedValue).toBe('true');
- });
+ expect(ariaSelectedValue).toBe('true');
+ });
- it('has the "ons-tab--selected" class assigned', async () => {
- const hasClass = await page.$eval('a[href="#tab.id.1"]', (node) => node.classList.contains('ons-tab--selected'));
+ it('has the "ons-tab--selected" class assigned', async () => {
+ const hasClass = await page.$eval('a[href="#tab.id.1"]', (node) => node.classList.contains('ons-tab--selected'));
- expect(hasClass).toBe(true);
- });
+ expect(hasClass).toBe(true);
+ });
- it('shows the corresponding panel', async () => {
- const panelHiddenStates = await page.$$eval('.ons-tabs__panel', (nodes) =>
- nodes.map((node) => node.classList.contains('ons-tabs__panel--hidden')),
- );
+ it('shows the corresponding panel', async () => {
+ const panelHiddenStates = await page.$$eval('.ons-tabs__panel', (nodes) =>
+ nodes.map((node) => node.classList.contains('ons-tabs__panel--hidden')),
+ );
- expect(panelHiddenStates).toEqual([false, true, true]);
- });
- });
+ expect(panelHiddenStates).toEqual([false, true, true]);
+ });
+ });
- describe('when a tab is clicked twice', () => {
- beforeEach(async () => {
- await page.focus('a[href="#tab.id.2"]');
- await page.keyboard.press('Enter');
- await page.keyboard.press('Enter');
- });
+ describe('when a tab is clicked twice', () => {
+ beforeEach(async () => {
+ await page.focus('a[href="#tab.id.2"]');
+ await page.keyboard.press('Enter');
+ await page.keyboard.press('Enter');
+ });
- it('is assigned a "tabindex" value', async () => {
- const tabIndexValues = await page.$$eval('.ons-tab', (nodes) => nodes.map((node) => node.getAttribute('tabindex')));
+ it('is assigned a "tabindex" value', async () => {
+ const tabIndexValues = await page.$$eval('.ons-tab', (nodes) => nodes.map((node) => node.getAttribute('tabindex')));
- expect(tabIndexValues).toEqual(['0', '-1', '-1']);
- });
+ expect(tabIndexValues).toEqual(['0', '-1', '-1']);
+ });
- it('does not have the "aria-selected" attribute', async () => {
- const ariaSelectedValue = await page.$eval('a[href="#tab.id.2"]', (node) => node.getAttribute('aria-selected'));
+ it('does not have the "aria-selected" attribute', async () => {
+ const ariaSelectedValue = await page.$eval('a[href="#tab.id.2"]', (node) => node.getAttribute('aria-selected'));
- expect(ariaSelectedValue).not.toBe('true');
- });
+ expect(ariaSelectedValue).not.toBe('true');
+ });
- it('does not have the "ons-tab--selected" class assigned', async () => {
- const hasClass = await page.$eval('a[href="#tab.id.2"]', (node) => node.classList.contains('ons-tab--selected'));
+ it('does not have the "ons-tab--selected" class assigned', async () => {
+ const hasClass = await page.$eval('a[href="#tab.id.2"]', (node) => node.classList.contains('ons-tab--selected'));
- expect(hasClass).toBe(false);
- });
+ expect(hasClass).toBe(false);
+ });
- it('hides the corresponding panel', async () => {
- const panelHiddenStates = await page.$$eval('.ons-tabs__panel', (nodes) =>
- nodes.map((node) => node.classList.contains('ons-tabs__panel--hidden')),
- );
+ it('hides the corresponding panel', async () => {
+ const panelHiddenStates = await page.$$eval('.ons-tabs__panel', (nodes) =>
+ nodes.map((node) => node.classList.contains('ons-tabs__panel--hidden')),
+ );
- expect(panelHiddenStates).toEqual([true, true, true]);
- });
+ expect(panelHiddenStates).toEqual([true, true, true]);
+ });
+ });
});
- });
- describe('when a hash for a tab is in the url and `data-no-initial-active-tab` is present', () => {
- beforeEach(async () => {
- await setViewport(page, { width: 1650, height: 1050 });
- await setTestPage('/test#tab.id.2', renderComponent('tabs', EXAMPLE_TABS_WITH_NO_INITIAL_ACTIVE_TAB));
- });
+ describe('when a hash for a tab is in the url and `data-no-initial-active-tab` is present', () => {
+ beforeEach(async () => {
+ await setViewport(page, { width: 1650, height: 1050 });
+ await setTestPage('/test#tab.id.2', renderComponent('tabs', EXAMPLE_TABS_WITH_NO_INITIAL_ACTIVE_TAB));
+ });
- it('is assigned a "tabindex" value', async () => {
- const tabIndexValues = await page.$$eval('.ons-tab', (nodes) => nodes.map((node) => node.getAttribute('tabindex')));
+ it('is assigned a "tabindex" value', async () => {
+ const tabIndexValues = await page.$$eval('.ons-tab', (nodes) => nodes.map((node) => node.getAttribute('tabindex')));
- expect(tabIndexValues).toEqual(['-1', '0', '-1']);
- });
+ expect(tabIndexValues).toEqual(['-1', '0', '-1']);
+ });
- it('has the "aria-selected" attribute', async () => {
- const ariaSelectedValue = await page.$eval('a[href="#tab.id.2"]', (node) => node.getAttribute('aria-selected'));
+ it('has the "aria-selected" attribute', async () => {
+ const ariaSelectedValue = await page.$eval('a[href="#tab.id.2"]', (node) => node.getAttribute('aria-selected'));
- expect(ariaSelectedValue).toBe('true');
- });
+ expect(ariaSelectedValue).toBe('true');
+ });
- it('has the "ons-tab--selected" class assigned', async () => {
- const hasClass = await page.$eval('a[href="#tab.id.2"]', (node) => node.classList.contains('ons-tab--selected'));
+ it('has the "ons-tab--selected" class assigned', async () => {
+ const hasClass = await page.$eval('a[href="#tab.id.2"]', (node) => node.classList.contains('ons-tab--selected'));
- expect(hasClass).toBe(true);
- });
+ expect(hasClass).toBe(true);
+ });
- it('shows the corresponding panel', async () => {
- const panelHiddenStates = await page.$$eval('.ons-tabs__panel', (nodes) =>
- nodes.map((node) => node.classList.contains('ons-tabs__panel--hidden')),
- );
+ it('shows the corresponding panel', async () => {
+ const panelHiddenStates = await page.$$eval('.ons-tabs__panel', (nodes) =>
+ nodes.map((node) => node.classList.contains('ons-tabs__panel--hidden')),
+ );
- expect(panelHiddenStates).toEqual([true, false, true]);
+ expect(panelHiddenStates).toEqual([true, false, true]);
+ });
});
- });
});
diff --git a/src/components/text-indent/_macro.spec.js b/src/components/text-indent/_macro.spec.js
index ef579063b6..a8157d5f57 100644
--- a/src/components/text-indent/_macro.spec.js
+++ b/src/components/text-indent/_macro.spec.js
@@ -6,43 +6,43 @@ import axe from '../../tests/helpers/axe';
import { renderComponent } from '../../tests/helpers/rendering';
describe('macro: text-indent', () => {
- describe('mode: text parameter', () => {
- it('passes jest-axe checks', async () => {
- const $ = cheerio.load(
- renderComponent('text-indent', {
- text: 'Example text...',
- }),
- );
-
- const results = await axe($.html());
- expect(results).toHaveNoViolations();
+ describe('mode: text parameter', () => {
+ it('passes jest-axe checks', async () => {
+ const $ = cheerio.load(
+ renderComponent('text-indent', {
+ text: 'Example text...',
+ }),
+ );
+
+ const results = await axe($.html());
+ expect(results).toHaveNoViolations();
+ });
+
+ it('has expected text', async () => {
+ const $ = cheerio.load(
+ renderComponent('text-indent', {
+ text: 'Example text...',
+ }),
+ );
+
+ const content = $('.ons-text-indent').text().trim();
+ expect(content).toBe('Example text...');
+ });
});
- it('has expected text', async () => {
- const $ = cheerio.load(
- renderComponent('text-indent', {
- text: 'Example text...',
- }),
- );
+ describe('mode: called with content', () => {
+ it('passes jest-axe checks', async () => {
+ const $ = cheerio.load(renderComponent('text-indent', {}, 'Example content...'));
- const content = $('.ons-text-indent').text().trim();
- expect(content).toBe('Example text...');
- });
- });
-
- describe('mode: called with content', () => {
- it('passes jest-axe checks', async () => {
- const $ = cheerio.load(renderComponent('text-indent', {}, 'Example content...'));
-
- const results = await axe($.html());
- expect(results).toHaveNoViolations();
- });
+ const results = await axe($.html());
+ expect(results).toHaveNoViolations();
+ });
- it('has expected text', async () => {
- const $ = cheerio.load(renderComponent('text-indent', {}, 'Example content...'));
+ it('has expected text', async () => {
+ const $ = cheerio.load(renderComponent('text-indent', {}, 'Example content...'));
- const content = $('.ons-text-indent').text().trim();
- expect(content).toBe('Example content...');
+ const content = $('.ons-text-indent').text().trim();
+ expect(content).toBe('Example content...');
+ });
});
- });
});
diff --git a/src/components/text-indent/_text-indent.scss b/src/components/text-indent/_text-indent.scss
index 2e477f4201..d96305f498 100644
--- a/src/components/text-indent/_text-indent.scss
+++ b/src/components/text-indent/_text-indent.scss
@@ -1,5 +1,5 @@
.ons-text-indent {
- border-left: 4px solid var(--ons-color-borders-indent);
- margin: 0 0 1rem;
- padding: 0 0 0 1.3em;
+ border-left: 4px solid var(--ons-color-borders-indent);
+ margin: 0 0 1rem;
+ padding: 0 0 0 1.3em;
}
diff --git a/src/components/textarea/_macro.spec.js b/src/components/textarea/_macro.spec.js
index 6671b602fe..882d8929b6 100644
--- a/src/components/textarea/_macro.spec.js
+++ b/src/components/textarea/_macro.spec.js
@@ -6,295 +6,295 @@ import axe from '../../tests/helpers/axe';
import { renderComponent, templateFaker } from '../../tests/helpers/rendering';
const EXAMPLE_TEXTAREA = {
- id: 'example-id',
- name: 'feedback',
- label: {
- classes: 'extra-label-class',
- text: 'Please provide some feedback',
- description: 'For example, describe any difficulties you experienced in the use of this service',
- },
+ id: 'example-id',
+ name: 'feedback',
+ label: {
+ classes: 'extra-label-class',
+ text: 'Please provide some feedback',
+ description: 'For example, describe any difficulties you experienced in the use of this service',
+ },
};
const EXAMPLE_TEXTAREA_WITH_CHARACTER_LIMIT = {
- ...EXAMPLE_TEXTAREA,
- width: 30,
- charCheckLimit: {
- limit: 200,
- charCountSingular: 'You have {x} character remaining',
- charCountPlural: 'You have {x} characters remaining',
- },
+ ...EXAMPLE_TEXTAREA,
+ width: 30,
+ charCheckLimit: {
+ limit: 200,
+ charCountSingular: 'You have {x} character remaining',
+ charCountPlural: 'You have {x} characters remaining',
+ },
};
const EXAMPLE_TEXTAREA_WITH_ERROR = {
- ...EXAMPLE_TEXTAREA,
- error: {
- id: 'feedback-error',
- text: 'Enter your feedback',
- },
+ ...EXAMPLE_TEXTAREA,
+ error: {
+ id: 'feedback-error',
+ text: 'Enter your feedback',
+ },
};
const EXAMPLE_TEXTAREA_WITH_MUTUALLY_EXCLUSIVE_WITH_ERROR = {
- ...EXAMPLE_TEXTAREA_WITH_ERROR,
- dontWrap: true,
- mutuallyExclusive: {
- or: 'Or',
- deselectMessage: 'Selecting this will clear your feedback',
- deselectGroupAdjective: 'cleared',
- deselectExclusiveOptionAdjective: 'deselected',
- exclusiveOptions: [
- {
- id: 'feedback-exclusive-option',
- name: 'no-feedback',
- value: 'no-feedback',
- label: {
- text: 'I dont want to provide feedback',
- },
- },
- ],
- },
+ ...EXAMPLE_TEXTAREA_WITH_ERROR,
+ dontWrap: true,
+ mutuallyExclusive: {
+ or: 'Or',
+ deselectMessage: 'Selecting this will clear your feedback',
+ deselectGroupAdjective: 'cleared',
+ deselectExclusiveOptionAdjective: 'deselected',
+ exclusiveOptions: [
+ {
+ id: 'feedback-exclusive-option',
+ name: 'no-feedback',
+ value: 'no-feedback',
+ label: {
+ text: 'I dont want to provide feedback',
+ },
+ },
+ ],
+ },
};
describe('macro: textarea', () => {
- it('passes jest-axe checks', async () => {
- const $ = cheerio.load(renderComponent('textarea', EXAMPLE_TEXTAREA));
-
- const results = await axe($.html());
- expect(results).toHaveNoViolations();
- });
-
- it('has the provided `id` attribute', () => {
- const $ = cheerio.load(renderComponent('textarea', EXAMPLE_TEXTAREA));
-
- expect($('.ons-input--textarea').attr('id')).toBe('example-id');
- });
-
- it('has additionally provided classes', () => {
- const $ = cheerio.load(
- renderComponent('textarea', {
- ...EXAMPLE_TEXTAREA,
- classes: 'extra-class another-extra-class',
- }),
- );
-
- expect($('.ons-input--textarea').hasClass('extra-class')).toBe(true);
- expect($('.ons-input--textarea').hasClass('another-extra-class')).toBe(true);
- });
-
- it('has additionally provided `attributes`', () => {
- const $ = cheerio.load(
- renderComponent('textarea', {
- ...EXAMPLE_TEXTAREA,
- attributes: {
- a: 123,
- b: 456,
- },
- }),
- );
-
- expect($('.ons-input--textarea').attr('a')).toBe('123');
- expect($('.ons-input--textarea').attr('b')).toBe('456');
- });
-
- it('has the provided `name` attribute', () => {
- const $ = cheerio.load(renderComponent('textarea', EXAMPLE_TEXTAREA));
-
- expect($('.ons-input--textarea').attr('name')).toBe('feedback');
- });
-
- it('defaults to having 8 rows of text', () => {
- const $ = cheerio.load(renderComponent('textarea', EXAMPLE_TEXTAREA));
-
- expect($('.ons-input--textarea').attr('rows')).toBe('8');
- });
-
- it('has the provided number of rows', () => {
- const $ = cheerio.load(
- renderComponent('textarea', {
- ...EXAMPLE_TEXTAREA,
- rows: 12,
- }),
- );
-
- expect($('.ons-input--textarea').attr('rows')).toBe('12');
- });
-
- it('has a class representing the provided `width`', () => {
- const $ = cheerio.load(
- renderComponent('textarea', {
- ...EXAMPLE_TEXTAREA,
- width: 10,
- }),
- );
-
- expect($('.ons-input--textarea').hasClass('ons-input--w-10')).toBe(true);
- });
-
- it('has no value initially', () => {
- const $ = cheerio.load(renderComponent('textarea', EXAMPLE_TEXTAREA));
-
- expect($('.ons-input--textarea').text()).toBe('');
- });
-
- it('has the provided initial value', () => {
- const $ = cheerio.load(
- renderComponent('textarea', {
- ...EXAMPLE_TEXTAREA,
- value: 'Initial value',
- }),
- );
-
- expect($('.ons-input--textarea').text()).toBe('Initial value');
- });
-
- it('does not render label component when not specified', () => {
- const faker = templateFaker();
- const labelSpy = faker.spy('label');
-
- faker.renderComponent('textarea', {
- ...EXAMPLE_TEXTAREA,
- label: undefined,
+ it('passes jest-axe checks', async () => {
+ const $ = cheerio.load(renderComponent('textarea', EXAMPLE_TEXTAREA));
+
+ const results = await axe($.html());
+ expect(results).toHaveNoViolations();
});
- expect(labelSpy.occurrences.length).toBe(0);
- });
+ it('has the provided `id` attribute', () => {
+ const $ = cheerio.load(renderComponent('textarea', EXAMPLE_TEXTAREA));
- it('renders label using the label component', () => {
- const faker = templateFaker();
- const labelSpy = faker.spy('label');
+ expect($('.ons-input--textarea').attr('id')).toBe('example-id');
+ });
- faker.renderComponent('textarea', EXAMPLE_TEXTAREA);
+ it('has additionally provided classes', () => {
+ const $ = cheerio.load(
+ renderComponent('textarea', {
+ ...EXAMPLE_TEXTAREA,
+ classes: 'extra-class another-extra-class',
+ }),
+ );
- expect(labelSpy.occurrences).toContainEqual({
- for: 'example-id',
- text: 'Please provide some feedback',
- description: 'For example, describe any difficulties you experienced in the use of this service',
- classes: 'extra-label-class',
+ expect($('.ons-input--textarea').hasClass('extra-class')).toBe(true);
+ expect($('.ons-input--textarea').hasClass('another-extra-class')).toBe(true);
});
- });
- it('does not render character limit component when not specified', () => {
- const faker = templateFaker();
- const charCheckLimitSpy = faker.spy('char-check-limit');
+ it('has additionally provided `attributes`', () => {
+ const $ = cheerio.load(
+ renderComponent('textarea', {
+ ...EXAMPLE_TEXTAREA,
+ attributes: {
+ a: 123,
+ b: 456,
+ },
+ }),
+ );
+
+ expect($('.ons-input--textarea').attr('a')).toBe('123');
+ expect($('.ons-input--textarea').attr('b')).toBe('456');
+ });
- faker.renderComponent('textarea', EXAMPLE_TEXTAREA);
+ it('has the provided `name` attribute', () => {
+ const $ = cheerio.load(renderComponent('textarea', EXAMPLE_TEXTAREA));
- expect(charCheckLimitSpy.occurrences.length).toBe(0);
- });
+ expect($('.ons-input--textarea').attr('name')).toBe('feedback');
+ });
- it('renders field component', () => {
- const faker = templateFaker();
- const fieldSpy = faker.spy('field');
+ it('defaults to having 8 rows of text', () => {
+ const $ = cheerio.load(renderComponent('textarea', EXAMPLE_TEXTAREA));
- faker.renderComponent('textarea', {
- ...EXAMPLE_TEXTAREA_WITH_ERROR,
- dontWrap: true,
- fieldId: 'example-field-id',
- fieldClasses: 'extra-field-class',
+ expect($('.ons-input--textarea').attr('rows')).toBe('8');
});
- expect(fieldSpy.occurrences).toContainEqual({
- id: 'example-field-id',
- classes: 'extra-field-class',
- dontWrap: true,
- error: EXAMPLE_TEXTAREA_WITH_ERROR.error,
+ it('has the provided number of rows', () => {
+ const $ = cheerio.load(
+ renderComponent('textarea', {
+ ...EXAMPLE_TEXTAREA,
+ rows: 12,
+ }),
+ );
+
+ expect($('.ons-input--textarea').attr('rows')).toBe('12');
});
- });
- describe('with character limit', () => {
- it('has the `ons-js-char-limit-input` class', () => {
- const $ = cheerio.load(renderComponent('textarea', EXAMPLE_TEXTAREA_WITH_CHARACTER_LIMIT));
+ it('has a class representing the provided `width`', () => {
+ const $ = cheerio.load(
+ renderComponent('textarea', {
+ ...EXAMPLE_TEXTAREA,
+ width: 10,
+ }),
+ );
- expect($('.ons-input--textarea').hasClass('ons-js-char-limit-input')).toBe(true);
+ expect($('.ons-input--textarea').hasClass('ons-input--w-10')).toBe(true);
});
- it('has the provided maximum length', () => {
- const $ = cheerio.load(renderComponent('textarea', EXAMPLE_TEXTAREA_WITH_CHARACTER_LIMIT));
+ it('has no value initially', () => {
+ const $ = cheerio.load(renderComponent('textarea', EXAMPLE_TEXTAREA));
- expect($('.ons-input--textarea').attr('maxlength')).toBe('200');
+ expect($('.ons-input--textarea').text()).toBe('');
});
- it('has data attribute which references the character limit component', () => {
- const $ = cheerio.load(renderComponent('textarea', EXAMPLE_TEXTAREA_WITH_CHARACTER_LIMIT));
+ it('has the provided initial value', () => {
+ const $ = cheerio.load(
+ renderComponent('textarea', {
+ ...EXAMPLE_TEXTAREA,
+ value: 'Initial value',
+ }),
+ );
- expect($('.ons-input--textarea').attr('data-char-limit-ref')).toBe('example-id-lim');
+ expect($('.ons-input--textarea').text()).toBe('Initial value');
});
- it('has `aria-describedby` attribute which references the character limit component', () => {
- const $ = cheerio.load(renderComponent('textarea', EXAMPLE_TEXTAREA_WITH_CHARACTER_LIMIT));
+ it('does not render label component when not specified', () => {
+ const faker = templateFaker();
+ const labelSpy = faker.spy('label');
+
+ faker.renderComponent('textarea', {
+ ...EXAMPLE_TEXTAREA,
+ label: undefined,
+ });
- expect($('.ons-input--textarea').attr('aria-describedby')).toBe('example-id-lim');
+ expect(labelSpy.occurrences.length).toBe(0);
});
- it('renders character limit component', () => {
- const faker = templateFaker();
- const charCheckLimitSpy = faker.spy('char-check-limit');
+ it('renders label using the label component', () => {
+ const faker = templateFaker();
+ const labelSpy = faker.spy('label');
- faker.renderComponent('textarea', EXAMPLE_TEXTAREA_WITH_CHARACTER_LIMIT);
+ faker.renderComponent('textarea', EXAMPLE_TEXTAREA);
- expect(charCheckLimitSpy.occurrences).toContainEqual({
- id: 'example-id-lim',
- limit: 200,
- charCountSingular: 'You have {x} character remaining',
- charCountPlural: 'You have {x} characters remaining',
- });
+ expect(labelSpy.occurrences).toContainEqual({
+ for: 'example-id',
+ text: 'Please provide some feedback',
+ description: 'For example, describe any difficulties you experienced in the use of this service',
+ classes: 'extra-label-class',
+ });
});
- });
- describe('with error', () => {
- it('has the `error` modifier class', () => {
- const $ = cheerio.load(renderComponent('textarea', EXAMPLE_TEXTAREA_WITH_ERROR));
+ it('does not render character limit component when not specified', () => {
+ const faker = templateFaker();
+ const charCheckLimitSpy = faker.spy('char-check-limit');
- expect($('.ons-input--textarea').hasClass('ons-input--error')).toBe(true);
- });
- });
+ faker.renderComponent('textarea', EXAMPLE_TEXTAREA);
- describe('mutually exclusive', () => {
- it('has the `ons-js-exclusive-group-item` class', () => {
- const $ = cheerio.load(renderComponent('textarea', EXAMPLE_TEXTAREA_WITH_MUTUALLY_EXCLUSIVE_WITH_ERROR));
+ expect(charCheckLimitSpy.occurrences.length).toBe(0);
+ });
- expect($('.ons-input--textarea').hasClass('ons-js-exclusive-group-item')).toBe(true);
+ it('renders field component', () => {
+ const faker = templateFaker();
+ const fieldSpy = faker.spy('field');
+
+ faker.renderComponent('textarea', {
+ ...EXAMPLE_TEXTAREA_WITH_ERROR,
+ dontWrap: true,
+ fieldId: 'example-field-id',
+ fieldClasses: 'extra-field-class',
+ });
+
+ expect(fieldSpy.occurrences).toContainEqual({
+ id: 'example-field-id',
+ classes: 'extra-field-class',
+ dontWrap: true,
+ error: EXAMPLE_TEXTAREA_WITH_ERROR.error,
+ });
});
- it('renders mutually exclusive component', () => {
- const faker = templateFaker();
- const mutuallyExclusiveSpy = faker.spy('mutually-exclusive');
-
- faker.renderComponent('textarea', {
- ...EXAMPLE_TEXTAREA_WITH_MUTUALLY_EXCLUSIVE_WITH_ERROR,
- fieldId: 'example-field-id',
- fieldClasses: 'extra-field-class',
- legend: 'Legend text',
- legendClasses: 'extra-legend-class',
- description: 'Example description text',
- legendIsQuestionTitle: true,
- });
-
- expect(mutuallyExclusiveSpy.occurrences).toContainEqual({
- id: 'example-field-id',
- classes: 'extra-field-class',
- legend: 'Legend text',
- legendClasses: 'extra-legend-class',
- description: 'Example description text',
- dontWrap: true,
- legendIsQuestionTitle: true,
- exclusiveOptions: EXAMPLE_TEXTAREA_WITH_MUTUALLY_EXCLUSIVE_WITH_ERROR.mutuallyExclusive.exclusiveOptions,
- or: 'Or',
- deselectMessage: 'Selecting this will clear your feedback',
- deselectGroupAdjective: 'cleared',
- deselectExclusiveOptionAdjective: 'deselected',
- error: EXAMPLE_TEXTAREA_WITH_MUTUALLY_EXCLUSIVE_WITH_ERROR.error,
- });
+ describe('with character limit', () => {
+ it('has the `ons-js-char-limit-input` class', () => {
+ const $ = cheerio.load(renderComponent('textarea', EXAMPLE_TEXTAREA_WITH_CHARACTER_LIMIT));
+
+ expect($('.ons-input--textarea').hasClass('ons-js-char-limit-input')).toBe(true);
+ });
+
+ it('has the provided maximum length', () => {
+ const $ = cheerio.load(renderComponent('textarea', EXAMPLE_TEXTAREA_WITH_CHARACTER_LIMIT));
+
+ expect($('.ons-input--textarea').attr('maxlength')).toBe('200');
+ });
+
+ it('has data attribute which references the character limit component', () => {
+ const $ = cheerio.load(renderComponent('textarea', EXAMPLE_TEXTAREA_WITH_CHARACTER_LIMIT));
+
+ expect($('.ons-input--textarea').attr('data-char-limit-ref')).toBe('example-id-lim');
+ });
+
+ it('has `aria-describedby` attribute which references the character limit component', () => {
+ const $ = cheerio.load(renderComponent('textarea', EXAMPLE_TEXTAREA_WITH_CHARACTER_LIMIT));
+
+ expect($('.ons-input--textarea').attr('aria-describedby')).toBe('example-id-lim');
+ });
+
+ it('renders character limit component', () => {
+ const faker = templateFaker();
+ const charCheckLimitSpy = faker.spy('char-check-limit');
+
+ faker.renderComponent('textarea', EXAMPLE_TEXTAREA_WITH_CHARACTER_LIMIT);
+
+ expect(charCheckLimitSpy.occurrences).toContainEqual({
+ id: 'example-id-lim',
+ limit: 200,
+ charCountSingular: 'You have {x} character remaining',
+ charCountPlural: 'You have {x} characters remaining',
+ });
+ });
});
- it('still renders field component', () => {
- const faker = templateFaker();
- const fieldSpy = faker.spy('field');
+ describe('with error', () => {
+ it('has the `error` modifier class', () => {
+ const $ = cheerio.load(renderComponent('textarea', EXAMPLE_TEXTAREA_WITH_ERROR));
- faker.renderComponent('textarea', EXAMPLE_TEXTAREA_WITH_MUTUALLY_EXCLUSIVE_WITH_ERROR);
+ expect($('.ons-input--textarea').hasClass('ons-input--error')).toBe(true);
+ });
+ });
- expect(fieldSpy.occurrences).toContainEqual({
- error: EXAMPLE_TEXTAREA_WITH_MUTUALLY_EXCLUSIVE_WITH_ERROR.error,
- });
+ describe('mutually exclusive', () => {
+ it('has the `ons-js-exclusive-group-item` class', () => {
+ const $ = cheerio.load(renderComponent('textarea', EXAMPLE_TEXTAREA_WITH_MUTUALLY_EXCLUSIVE_WITH_ERROR));
+
+ expect($('.ons-input--textarea').hasClass('ons-js-exclusive-group-item')).toBe(true);
+ });
+
+ it('renders mutually exclusive component', () => {
+ const faker = templateFaker();
+ const mutuallyExclusiveSpy = faker.spy('mutually-exclusive');
+
+ faker.renderComponent('textarea', {
+ ...EXAMPLE_TEXTAREA_WITH_MUTUALLY_EXCLUSIVE_WITH_ERROR,
+ fieldId: 'example-field-id',
+ fieldClasses: 'extra-field-class',
+ legend: 'Legend text',
+ legendClasses: 'extra-legend-class',
+ description: 'Example description text',
+ legendIsQuestionTitle: true,
+ });
+
+ expect(mutuallyExclusiveSpy.occurrences).toContainEqual({
+ id: 'example-field-id',
+ classes: 'extra-field-class',
+ legend: 'Legend text',
+ legendClasses: 'extra-legend-class',
+ description: 'Example description text',
+ dontWrap: true,
+ legendIsQuestionTitle: true,
+ exclusiveOptions: EXAMPLE_TEXTAREA_WITH_MUTUALLY_EXCLUSIVE_WITH_ERROR.mutuallyExclusive.exclusiveOptions,
+ or: 'Or',
+ deselectMessage: 'Selecting this will clear your feedback',
+ deselectGroupAdjective: 'cleared',
+ deselectExclusiveOptionAdjective: 'deselected',
+ error: EXAMPLE_TEXTAREA_WITH_MUTUALLY_EXCLUSIVE_WITH_ERROR.error,
+ });
+ });
+
+ it('still renders field component', () => {
+ const faker = templateFaker();
+ const fieldSpy = faker.spy('field');
+
+ faker.renderComponent('textarea', EXAMPLE_TEXTAREA_WITH_MUTUALLY_EXCLUSIVE_WITH_ERROR);
+
+ expect(fieldSpy.occurrences).toContainEqual({
+ error: EXAMPLE_TEXTAREA_WITH_MUTUALLY_EXCLUSIVE_WITH_ERROR.error,
+ });
+ });
});
- });
});
diff --git a/src/components/textarea/textarea.dom.js b/src/components/textarea/textarea.dom.js
index c351e64702..06fc8b225e 100644
--- a/src/components/textarea/textarea.dom.js
+++ b/src/components/textarea/textarea.dom.js
@@ -1,12 +1,12 @@
import domready from '../../js/domready';
async function initialise() {
- const limitedInputs = [...document.querySelectorAll('.ons-js-char-limit-input')];
- if (limitedInputs.length) {
- const CharLimit = (await import('../char-check-limit/character-limit')).default;
+ const limitedInputs = [...document.querySelectorAll('.ons-js-char-limit-input')];
+ if (limitedInputs.length) {
+ const CharLimit = (await import('../char-check-limit/character-limit')).default;
- limitedInputs.forEach((input) => new CharLimit(input));
- }
+ limitedInputs.forEach((input) => new CharLimit(input));
+ }
}
domready(initialise);
diff --git a/src/components/textarea/textarea.spec.js b/src/components/textarea/textarea.spec.js
index 646c92d9d5..d618404360 100644
--- a/src/components/textarea/textarea.spec.js
+++ b/src/components/textarea/textarea.spec.js
@@ -1,94 +1,98 @@
import { renderComponent, setTestPage } from '../../tests/helpers/rendering';
describe('script: textarea', () => {
- describe('character limit', () => {
- beforeEach(async () => {
- await setTestPage(
- '/test',
- renderComponent('textarea', {
- id: 'example-textarea',
- name: 'feedback-limited',
- width: '30',
- label: {
- text: 'Please provide some feedback',
- description: 'For example describe any difficulties you experienced in the use of this service',
- },
- charCheckLimit: {
- limit: 50,
- charCountSingular: 'You have {x} character remaining',
- charCountPlural: 'You have {x} characters remaining',
- },
- }),
- );
- });
-
- describe('Given that the char limit helper has initialised correctly', () => {
- it('the char limit readout should be visible', async () => {
- const hasClass = await page.$eval('#example-textarea-lim', (node) => node.classList.contains('ons-u-d-no'));
- expect(hasClass).toBe(false);
- });
- });
-
- describe('Given that the user has not typed into the textarea', () => {
- describe('when the user types into the textarea', () => {
+ describe('character limit', () => {
beforeEach(async () => {
- await page.type('#example-textarea', 'Lorem ipsum dolor.\nMorbi rhoncus amet.');
+ await setTestPage(
+ '/test',
+ renderComponent('textarea', {
+ id: 'example-textarea',
+ name: 'feedback-limited',
+ width: '30',
+ label: {
+ text: 'Please provide some feedback',
+ description: 'For example describe any difficulties you experienced in the use of this service',
+ },
+ charCheckLimit: {
+ limit: 50,
+ charCountSingular: 'You have {x} character remaining',
+ charCountPlural: 'You have {x} characters remaining',
+ },
+ }),
+ );
});
- it('then the characters remaining readout reflect the number of characters remaining', async () => {
- const readout = await page.$eval('#example-textarea-lim', (node) => node.textContent);
- expect(readout).toBe('You have 12 characters remaining');
+ describe('Given that the char limit helper has initialised correctly', () => {
+ it('the char limit readout should be visible', async () => {
+ const hasClass = await page.$eval('#example-textarea-lim', (node) => node.classList.contains('ons-u-d-no'));
+ expect(hasClass).toBe(false);
+ });
});
- });
- describe('when the user reaches/exceeds the maxlength of the textarea', () => {
- beforeEach(async () => {
- await page.type('#example-textarea', 'Lorem ipsum dolor sit amet, consectetur porttitor.');
- });
+ describe('Given that the user has not typed into the textarea', () => {
+ describe('when the user types into the textarea', () => {
+ beforeEach(async () => {
+ await page.type('#example-textarea', 'Lorem ipsum dolor.\nMorbi rhoncus amet.');
+ });
- it('then the characters remaining readout reflect the number of characters remaining', async () => {
- const readout = await page.$eval('#example-textarea-lim', (node) => node.textContent);
- expect(readout).toBe('You have 0 characters remaining');
- });
+ it('then the characters remaining readout reflect the number of characters remaining', async () => {
+ const readout = await page.$eval('#example-textarea-lim', (node) => node.textContent);
+ expect(readout).toBe('You have 12 characters remaining');
+ });
+ });
- it('then the textarea should be given limit reached classes', async () => {
- const hasClass = await page.$eval('#example-textarea', (node) => node.classList.contains('ons-input--limit-reached'));
- expect(hasClass).toBe(true);
- });
+ describe('when the user reaches/exceeds the maxlength of the textarea', () => {
+ beforeEach(async () => {
+ await page.type('#example-textarea', 'Lorem ipsum dolor sit amet, consectetur porttitor.');
+ });
- it('then the readout should be given limit reached classes', async () => {
- const hasClass = await page.$eval('#example-textarea-lim', (node) => node.classList.contains('ons-input__limit--reached'));
- expect(hasClass).toBe(true);
- });
- });
- });
+ it('then the characters remaining readout reflect the number of characters remaining', async () => {
+ const readout = await page.$eval('#example-textarea-lim', (node) => node.textContent);
+ expect(readout).toBe('You have 0 characters remaining');
+ });
- describe('Given that the user has reached/exceeded the maxlength of the textarea', () => {
- beforeEach(async () => {
- await page.type('#example-textarea', 'Lorem ipsum dolor sit amet, consectetur porttitor.');
- });
+ it('then the textarea should be given limit reached classes', async () => {
+ const hasClass = await page.$eval('#example-textarea', (node) => node.classList.contains('ons-input--limit-reached'));
+ expect(hasClass).toBe(true);
+ });
- describe('when the user removes a character', () => {
- beforeEach(async () => {
- await page.focus('#example-textarea');
- await page.keyboard.press('Backspace');
+ it('then the readout should be given limit reached classes', async () => {
+ const hasClass = await page.$eval('#example-textarea-lim', (node) =>
+ node.classList.contains('ons-input__limit--reached'),
+ );
+ expect(hasClass).toBe(true);
+ });
+ });
});
- it('then the characters remaining readout reflect the number of characters remaining', async () => {
- const readout = await page.$eval('#example-textarea-lim', (node) => node.textContent);
- expect(readout).toBe('You have 1 character remaining');
- });
+ describe('Given that the user has reached/exceeded the maxlength of the textarea', () => {
+ beforeEach(async () => {
+ await page.type('#example-textarea', 'Lorem ipsum dolor sit amet, consectetur porttitor.');
+ });
- it('then the textarea should not be given limit reached classes', async () => {
- const hasClass = await page.$eval('#example-textarea', (node) => node.classList.contains('ons-input--limit-reached'));
- expect(hasClass).toBe(false);
- });
+ describe('when the user removes a character', () => {
+ beforeEach(async () => {
+ await page.focus('#example-textarea');
+ await page.keyboard.press('Backspace');
+ });
+
+ it('then the characters remaining readout reflect the number of characters remaining', async () => {
+ const readout = await page.$eval('#example-textarea-lim', (node) => node.textContent);
+ expect(readout).toBe('You have 1 character remaining');
+ });
+
+ it('then the textarea should not be given limit reached classes', async () => {
+ const hasClass = await page.$eval('#example-textarea', (node) => node.classList.contains('ons-input--limit-reached'));
+ expect(hasClass).toBe(false);
+ });
- it('then the readout should not be given limit reached classes', async () => {
- const hasClass = await page.$eval('#example-textarea-lim', (node) => node.classList.contains('ons-input__limit--reached'));
- expect(hasClass).toBe(false);
+ it('then the readout should not be given limit reached classes', async () => {
+ const hasClass = await page.$eval('#example-textarea-lim', (node) =>
+ node.classList.contains('ons-input__limit--reached'),
+ );
+ expect(hasClass).toBe(false);
+ });
+ });
});
- });
});
- });
});
diff --git a/src/components/timeline/_macro.spec.js b/src/components/timeline/_macro.spec.js
index 1660bc29b1..63f46bd9e4 100644
--- a/src/components/timeline/_macro.spec.js
+++ b/src/components/timeline/_macro.spec.js
@@ -6,94 +6,94 @@ import axe from '../../tests/helpers/axe';
import { renderComponent, templateFaker } from '../../tests/helpers/rendering';
const EXAMPLE_TIMELINE = {
- items: [
- {
- heading: 'January 2020',
- content: 'Timeline entry 1',
- },
- {
- heading: 'March 2020',
- content: 'Timeline entry 2',
- },
- {
- heading: 'December 2020',
- itemsList: [
+ items: [
{
- text: 'Timeline entry 3 item 1',
+ heading: 'January 2020',
+ content: 'Timeline entry 1',
},
{
- text: 'Timeline entry 3 item 2',
+ heading: 'March 2020',
+ content: 'Timeline entry 2',
},
- ],
- },
- ],
+ {
+ heading: 'December 2020',
+ itemsList: [
+ {
+ text: 'Timeline entry 3 item 1',
+ },
+ {
+ text: 'Timeline entry 3 item 2',
+ },
+ ],
+ },
+ ],
};
describe('macro: timeline', () => {
- it('passes jest-axe checks', async () => {
- const $ = cheerio.load(renderComponent('timeline', EXAMPLE_TIMELINE));
-
- const results = await axe($.html());
- expect(results).toHaveNoViolations();
- });
-
- it('has additionally provided style classes', () => {
- const $ = cheerio.load(
- renderComponent('timeline', {
- ...EXAMPLE_TIMELINE,
- classes: 'extra-class another-extra-class',
- }),
- );
-
- expect($('.ons-timeline').hasClass('extra-class')).toBe(true);
- expect($('.ons-timeline').hasClass('another-extra-class')).toBe(true);
- });
-
- it('has the provided timeline items', () => {
- const $ = cheerio.load(renderComponent('timeline', EXAMPLE_TIMELINE));
-
- const $firstItem = $('.ons-timeline__item:nth-child(1)');
- const $firstItemHeading = $firstItem.find('.ons-timeline__heading');
- const $secondItem = $('.ons-timeline__item:nth-child(2)');
- const $secondItemHeading = $secondItem.find('.ons-timeline__heading');
-
- expect($firstItemHeading.text().trim()).toBe('January 2020');
- expect($firstItem.text()).toContain('Timeline entry 1');
- expect($secondItemHeading.text().trim()).toBe('March 2020');
- expect($secondItem.text()).toContain('Timeline entry 2');
- });
-
- it('renders a heading based upon titleTag parameter', () => {
- const EXAMPLE_TIMELINE_WITH_TITLE_TAG = {
- ...EXAMPLE_TIMELINE,
- titleTag: 'h3',
- };
- const $ = cheerio.load(renderComponent('timeline', EXAMPLE_TIMELINE_WITH_TITLE_TAG));
- const $firstItem = $('.ons-timeline__item:nth-child(1)');
- expect($firstItem.html().includes('h3')).toBe(true);
- });
-
- it('has a provided ons-timeline__content class, wrapping the content', () => {
- const $ = cheerio.load(renderComponent('timeline', EXAMPLE_TIMELINE));
-
- const $content = $('.ons-timeline__content');
-
- expect($content.length).toBe(3);
- });
-
- it('has the provided inner item list', () => {
- const faker = templateFaker();
- const listSpy = faker.spy('list');
-
- faker.renderComponent('timeline', EXAMPLE_TIMELINE);
-
- expect(listSpy.occurrences[0].itemsList).toEqual([
- {
- text: 'Timeline entry 3 item 1',
- },
- {
- text: 'Timeline entry 3 item 2',
- },
- ]);
- });
+ it('passes jest-axe checks', async () => {
+ const $ = cheerio.load(renderComponent('timeline', EXAMPLE_TIMELINE));
+
+ const results = await axe($.html());
+ expect(results).toHaveNoViolations();
+ });
+
+ it('has additionally provided style classes', () => {
+ const $ = cheerio.load(
+ renderComponent('timeline', {
+ ...EXAMPLE_TIMELINE,
+ classes: 'extra-class another-extra-class',
+ }),
+ );
+
+ expect($('.ons-timeline').hasClass('extra-class')).toBe(true);
+ expect($('.ons-timeline').hasClass('another-extra-class')).toBe(true);
+ });
+
+ it('has the provided timeline items', () => {
+ const $ = cheerio.load(renderComponent('timeline', EXAMPLE_TIMELINE));
+
+ const $firstItem = $('.ons-timeline__item:nth-child(1)');
+ const $firstItemHeading = $firstItem.find('.ons-timeline__heading');
+ const $secondItem = $('.ons-timeline__item:nth-child(2)');
+ const $secondItemHeading = $secondItem.find('.ons-timeline__heading');
+
+ expect($firstItemHeading.text().trim()).toBe('January 2020');
+ expect($firstItem.text()).toContain('Timeline entry 1');
+ expect($secondItemHeading.text().trim()).toBe('March 2020');
+ expect($secondItem.text()).toContain('Timeline entry 2');
+ });
+
+ it('renders a heading based upon titleTag parameter', () => {
+ const EXAMPLE_TIMELINE_WITH_TITLE_TAG = {
+ ...EXAMPLE_TIMELINE,
+ titleTag: 'h3',
+ };
+ const $ = cheerio.load(renderComponent('timeline', EXAMPLE_TIMELINE_WITH_TITLE_TAG));
+ const $firstItem = $('.ons-timeline__item:nth-child(1)');
+ expect($firstItem.html().includes('h3')).toBe(true);
+ });
+
+ it('has a provided ons-timeline__content class, wrapping the content', () => {
+ const $ = cheerio.load(renderComponent('timeline', EXAMPLE_TIMELINE));
+
+ const $content = $('.ons-timeline__content');
+
+ expect($content.length).toBe(3);
+ });
+
+ it('has the provided inner item list', () => {
+ const faker = templateFaker();
+ const listSpy = faker.spy('list');
+
+ faker.renderComponent('timeline', EXAMPLE_TIMELINE);
+
+ expect(listSpy.occurrences[0].itemsList).toEqual([
+ {
+ text: 'Timeline entry 3 item 1',
+ },
+ {
+ text: 'Timeline entry 3 item 2',
+ },
+ ]);
+ });
});
diff --git a/src/components/timeline/_timeline.scss b/src/components/timeline/_timeline.scss
index 7c2c8b0f64..38192f93a9 100644
--- a/src/components/timeline/_timeline.scss
+++ b/src/components/timeline/_timeline.scss
@@ -1,37 +1,37 @@
.ons-timeline {
- padding-left: 1.5rem;
- position: relative;
+ padding-left: 1.5rem;
+ position: relative;
- &::before {
- border-left: 4px solid var(--ons-color-black);
- content: '';
- height: 100%;
- left: 0;
- position: absolute;
- top: 10px;
- }
+ &::before {
+ border-left: 4px solid var(--ons-color-black);
+ content: '';
+ height: 100%;
+ left: 0;
+ position: absolute;
+ top: 10px;
+ }
}
.ons-timeline__item {
- margin-bottom: 1.5rem;
- position: relative;
+ margin-bottom: 1.5rem;
+ position: relative;
- &::before {
- background-color: var(--ons-color-black);
- content: '';
- height: 4px;
- left: -1.5rem;
- position: absolute;
- top: 10px;
- width: 12px;
- }
+ &::before {
+ background-color: var(--ons-color-black);
+ content: '';
+ height: 4px;
+ left: -1.5rem;
+ position: absolute;
+ top: 10px;
+ width: 12px;
+ }
- :last-child {
- margin-bottom: 0;
- }
+ :last-child {
+ margin-bottom: 0;
+ }
}
.ons-timeline__heading {
- font-size: 1rem !important;
- margin-bottom: 0.5rem;
+ font-size: 1rem !important;
+ margin-bottom: 0.5rem;
}
diff --git a/src/components/timeout-modal/_macro.spec.js b/src/components/timeout-modal/_macro.spec.js
index 0fb98a12f2..e595917cde 100644
--- a/src/components/timeout-modal/_macro.spec.js
+++ b/src/components/timeout-modal/_macro.spec.js
@@ -6,61 +6,61 @@ import axe from '../../tests/helpers/axe';
import { renderComponent, templateFaker } from '../../tests/helpers/rendering';
const EXAMPLE_TIMEOUT_MODAL_BASIC = {
- showModalTimeInSeconds: 60,
- redirectUrl: '#!',
- title: 'You will be signed out soon',
- serverSessionExpiryEndpoint: '/some-endpoint',
- sessionExpiresAt: '000-000-000',
- textFirstLine: 'It appears you have been inactive for a while.',
- countdownText: 'To protect your information, your progress will be saved and you will be signed out in',
- countdownExpiredText: 'You are being signed out.',
- btnText: 'Continue survey',
- minutesTextSingular: 'minute',
- minutesTextPlural: 'minutes',
- secondsTextSingular: 'second',
- secondsTextPlural: 'seconds',
- endWithFullStop: true,
+ showModalTimeInSeconds: 60,
+ redirectUrl: '#!',
+ title: 'You will be signed out soon',
+ serverSessionExpiryEndpoint: '/some-endpoint',
+ sessionExpiresAt: '000-000-000',
+ textFirstLine: 'It appears you have been inactive for a while.',
+ countdownText: 'To protect your information, your progress will be saved and you will be signed out in',
+ countdownExpiredText: 'You are being signed out.',
+ btnText: 'Continue survey',
+ minutesTextSingular: 'minute',
+ minutesTextPlural: 'minutes',
+ secondsTextSingular: 'second',
+ secondsTextPlural: 'seconds',
+ endWithFullStop: true,
};
describe('macro: timeout modal', () => {
- it('passes jest-axe checks with', async () => {
- const $ = cheerio.load(renderComponent('timeout-modal', EXAMPLE_TIMEOUT_MODAL_BASIC));
+ it('passes jest-axe checks with', async () => {
+ const $ = cheerio.load(renderComponent('timeout-modal', EXAMPLE_TIMEOUT_MODAL_BASIC));
- const results = await axe($.html());
- expect(results).toHaveNoViolations();
- });
+ const results = await axe($.html());
+ expect(results).toHaveNoViolations();
+ });
- it('provides expected parameters to the inner `modal` component', () => {
- const faker = templateFaker();
- const modalSpy = faker.spy('modal');
+ it('provides expected parameters to the inner `modal` component', () => {
+ const faker = templateFaker();
+ const modalSpy = faker.spy('modal');
- cheerio.load(faker.renderComponent('timeout-modal', EXAMPLE_TIMEOUT_MODAL_BASIC));
+ cheerio.load(faker.renderComponent('timeout-modal', EXAMPLE_TIMEOUT_MODAL_BASIC));
- expect(modalSpy.occurrences[0]).toEqual({
- title: 'You will be signed out soon',
- btnText: 'Continue survey',
- classes: 'ons-js-timeout-modal',
- attributes: {
- 'data-redirect-url': '#!',
- 'data-server-session-expires-at': '000-000-000',
- 'data-show-modal-time': 60,
- 'data-server-session-expiry-endpoint': '/some-endpoint',
- 'data-countdown-text': 'To protect your information, your progress will be saved and you will be signed out in',
- 'data-countdown-expired-text': 'You are being signed out.',
- 'data-minutes-text-singular': 'minute',
- 'data-minutes-text-plural': 'minutes',
- 'data-seconds-text-singular': 'second',
- 'data-seconds-text-plural': 'seconds',
- 'data-full-stop': true,
- 'aria-describedby': 'timeout-time-remaining',
- },
+ expect(modalSpy.occurrences[0]).toEqual({
+ title: 'You will be signed out soon',
+ btnText: 'Continue survey',
+ classes: 'ons-js-timeout-modal',
+ attributes: {
+ 'data-redirect-url': '#!',
+ 'data-server-session-expires-at': '000-000-000',
+ 'data-show-modal-time': 60,
+ 'data-server-session-expiry-endpoint': '/some-endpoint',
+ 'data-countdown-text': 'To protect your information, your progress will be saved and you will be signed out in',
+ 'data-countdown-expired-text': 'You are being signed out.',
+ 'data-minutes-text-singular': 'minute',
+ 'data-minutes-text-plural': 'minutes',
+ 'data-seconds-text-singular': 'second',
+ 'data-seconds-text-plural': 'seconds',
+ 'data-full-stop': true,
+ 'aria-describedby': 'timeout-time-remaining',
+ },
+ });
});
- });
- it('has expected `textFirstLine`', () => {
- const $ = cheerio.load(renderComponent('timeout-modal', EXAMPLE_TIMEOUT_MODAL_BASIC));
+ it('has expected `textFirstLine`', () => {
+ const $ = cheerio.load(renderComponent('timeout-modal', EXAMPLE_TIMEOUT_MODAL_BASIC));
- const title = $('.ons-modal__body > p').html().trim();
- expect(title).toBe('It appears you have been inactive for a while.');
- });
+ const title = $('.ons-modal__body > p').html().trim();
+ expect(title).toBe('It appears you have been inactive for a while.');
+ });
});
diff --git a/src/components/timeout-modal/timeout-modal.dom.js b/src/components/timeout-modal/timeout-modal.dom.js
index 73f1a8cebd..545ab01e39 100644
--- a/src/components/timeout-modal/timeout-modal.dom.js
+++ b/src/components/timeout-modal/timeout-modal.dom.js
@@ -1,17 +1,17 @@
import domready from '../../js/domready';
async function modals() {
- const timeouts = [...document.querySelectorAll('.ons-js-timeout-modal')];
+ const timeouts = [...document.querySelectorAll('.ons-js-timeout-modal')];
- if (timeouts.length) {
- const TimeoutModal = (await import('./timeout-modal')).default;
+ if (timeouts.length) {
+ const TimeoutModal = (await import('./timeout-modal')).default;
- timeouts.forEach((context) => {
- let url = context.getAttribute('data-server-session-expiry-endpoint');
- let time = context.getAttribute('data-server-session-expires-at');
- new TimeoutModal(context, url, time);
- });
- }
+ timeouts.forEach((context) => {
+ let url = context.getAttribute('data-server-session-expiry-endpoint');
+ let time = context.getAttribute('data-server-session-expires-at');
+ new TimeoutModal(context, url, time);
+ });
+ }
}
domready(modals);
diff --git a/src/components/timeout-modal/timeout-modal.js b/src/components/timeout-modal/timeout-modal.js
index 5415973cd6..999b096308 100644
--- a/src/components/timeout-modal/timeout-modal.js
+++ b/src/components/timeout-modal/timeout-modal.js
@@ -2,86 +2,86 @@ import Modal from '../modal/modal';
import Timeout from '../../js/timeout';
export default class TimeoutModal {
- constructor(context, sessionExpiryEndpoint, initialExpiryTime) {
- this.context = context;
- this.sessionExpiryEndpoint = sessionExpiryEndpoint;
- this.initialExpiryTime = initialExpiryTime;
- this.continueButton = context.querySelector('.ons-js-modal-btn');
- this.modalVisibleInMilliseconds = context.getAttribute('data-show-modal-time') * 1000;
- this.expiryTime = '';
- this.expiryTimeInMilliseconds = 0;
- this.shouldRestartCheck = false;
+ constructor(context, sessionExpiryEndpoint, initialExpiryTime) {
+ this.context = context;
+ this.sessionExpiryEndpoint = sessionExpiryEndpoint;
+ this.initialExpiryTime = initialExpiryTime;
+ this.continueButton = context.querySelector('.ons-js-modal-btn');
+ this.modalVisibleInMilliseconds = context.getAttribute('data-show-modal-time') * 1000;
+ this.expiryTime = '';
+ this.expiryTimeInMilliseconds = 0;
+ this.shouldRestartCheck = false;
- // Create modal instance
- this.modal = new Modal(this.context);
+ // Create modal instance
+ this.modal = new Modal(this.context);
- // Create timeout instance
- this.timeout = new Timeout(this.context, this.sessionExpiryEndpoint, this.initialExpiryTime, true);
+ // Create timeout instance
+ this.timeout = new Timeout(this.context, this.sessionExpiryEndpoint, this.initialExpiryTime, true);
- this.bindEventListeners();
- }
+ this.bindEventListeners();
+ }
- bindEventListeners() {
- window.onload = this.startTimeout();
- window.addEventListener('keydown', this.escToClose.bind(this));
- this.continueButton.addEventListener('click', this.closeModalAndRestartTimeout.bind(this));
- }
+ bindEventListeners() {
+ window.onload = this.startTimeout();
+ window.addEventListener('keydown', this.escToClose.bind(this));
+ this.continueButton.addEventListener('click', this.closeModalAndRestartTimeout.bind(this));
+ }
- startTimeout() {
- clearTimeout(this.showModalTimeout);
- if (this.initialExpiryTime) {
- this.expiryTime = this.timeout.expiryTime;
- this.expiryTimeInMilliseconds = this.timeout.convertTimeToMilliSeconds(this.expiryTime);
- } else {
- // For demo purposes
- this.expiryTimeInMilliseconds = 60000;
+ startTimeout() {
+ clearTimeout(this.showModalTimeout);
+ if (this.initialExpiryTime) {
+ this.expiryTime = this.timeout.expiryTime;
+ this.expiryTimeInMilliseconds = this.timeout.convertTimeToMilliSeconds(this.expiryTime);
+ } else {
+ // For demo purposes
+ this.expiryTimeInMilliseconds = 60000;
+ }
+ this.showModalTimeout = setTimeout(
+ this.openModalAndStartCountdown.bind(this),
+ this.expiryTimeInMilliseconds - this.modalVisibleInMilliseconds,
+ );
}
- this.showModalTimeout = setTimeout(
- this.openModalAndStartCountdown.bind(this),
- this.expiryTimeInMilliseconds - this.modalVisibleInMilliseconds,
- );
- }
- async openModalAndStartCountdown() {
- const modalWillOpen = await this.hasExpiryTimeResetInAnotherTab();
- if (modalWillOpen && !this.modal.isDialogOpen()) {
- this.modal.openDialog();
- this.timeout.startUiCountdown();
+ async openModalAndStartCountdown() {
+ const modalWillOpen = await this.hasExpiryTimeResetInAnotherTab();
+ if (modalWillOpen && !this.modal.isDialogOpen()) {
+ this.modal.openDialog();
+ this.timeout.startUiCountdown();
- this.shouldRestartCheck = setInterval(async () => {
- await this.hasExpiryTimeResetInAnotherTab();
- }, 20000);
+ this.shouldRestartCheck = setInterval(async () => {
+ await this.hasExpiryTimeResetInAnotherTab();
+ }, 20000);
+ }
}
- }
- async hasExpiryTimeResetInAnotherTab() {
- const checkExpiryTime = await this.timeout.getExpiryTime();
- if (checkExpiryTime.substring(0, 19) != this.timeout.expiryTime.substring(0, 19)) {
- // Substring is required as endpoint can at random return milliseconds with expiry time
- this.expiryTime = checkExpiryTime;
- this.expiryTimeInMilliseconds = this.timeout.convertTimeToMilliSeconds(checkExpiryTime);
- this.closeModalAndRestartTimeout(this.expiryTimeInMilliseconds);
- } else {
- return true;
+ async hasExpiryTimeResetInAnotherTab() {
+ const checkExpiryTime = await this.timeout.getExpiryTime();
+ if (checkExpiryTime.substring(0, 19) != this.timeout.expiryTime.substring(0, 19)) {
+ // Substring is required as endpoint can at random return milliseconds with expiry time
+ this.expiryTime = checkExpiryTime;
+ this.expiryTimeInMilliseconds = this.timeout.convertTimeToMilliSeconds(checkExpiryTime);
+ this.closeModalAndRestartTimeout(this.expiryTimeInMilliseconds);
+ } else {
+ return true;
+ }
}
- }
- async closeModalAndRestartTimeout(time) {
- clearInterval(this.shouldRestartCheck);
+ async closeModalAndRestartTimeout(time) {
+ clearInterval(this.shouldRestartCheck);
- if (typeof timeInMilliSeconds !== 'string') {
- time = false;
- }
- if (this.modal.isDialogOpen()) {
- this.modal.closeDialog(event);
+ if (typeof timeInMilliSeconds !== 'string') {
+ time = false;
+ }
+ if (this.modal.isDialogOpen()) {
+ this.modal.closeDialog(event);
+ }
+ await this.timeout.restartTimeout(time);
+ this.startTimeout();
}
- await this.timeout.restartTimeout(time);
- this.startTimeout();
- }
- escToClose(event) {
- if (this.modal.isDialogOpen() && event.keyCode === 27) {
- this.closeModalAndRestartTimeout();
+ escToClose(event) {
+ if (this.modal.isDialogOpen() && event.keyCode === 27) {
+ this.closeModalAndRestartTimeout();
+ }
}
- }
}
diff --git a/src/components/timeout-modal/timeout-modal.spec.js b/src/components/timeout-modal/timeout-modal.spec.js
index 15cbb211a8..8ed4c6e28d 100644
--- a/src/components/timeout-modal/timeout-modal.spec.js
+++ b/src/components/timeout-modal/timeout-modal.spec.js
@@ -1,226 +1,226 @@
import { renderComponent, setTestPage } from '../../tests/helpers/rendering';
const EXAMPLE_TIMEOUT_MODAL_BASIC = {
- redirectUrl: '#!',
- title: 'You will be signed out soon',
- textFirstLine: 'It appears you have been inactive for a while.',
- countdownText: 'To protect your information, your progress will be saved and you will be signed out in',
- countdownExpiredText: 'You are being signed out.',
- btnText: 'Continue survey',
- minutesTextSingular: 'minute',
- minutesTextPlural: 'minutes',
- secondsTextSingular: 'second',
- secondsTextPlural: 'seconds',
- endWithFullStop: true,
+ redirectUrl: '#!',
+ title: 'You will be signed out soon',
+ textFirstLine: 'It appears you have been inactive for a while.',
+ countdownText: 'To protect your information, your progress will be saved and you will be signed out in',
+ countdownExpiredText: 'You are being signed out.',
+ btnText: 'Continue survey',
+ minutesTextSingular: 'minute',
+ minutesTextPlural: 'minutes',
+ secondsTextSingular: 'second',
+ secondsTextPlural: 'seconds',
+ endWithFullStop: true,
};
describe('script: timeout modal', () => {
- describe('when the page loads', () => {
- beforeEach(async () => {
- const component = renderComponent('timeout-modal', { ...EXAMPLE_TIMEOUT_MODAL_BASIC, showModalTimeInSeconds: 58 });
- const template = `
+ describe('when the page loads', () => {
+ beforeEach(async () => {
+ const component = renderComponent('timeout-modal', { ...EXAMPLE_TIMEOUT_MODAL_BASIC, showModalTimeInSeconds: 58 });
+ const template = `
${component}
`;
- await setTestPage('/test', template);
- });
+ await setTestPage('/test', template);
+ });
- it('displays the modal after the correct number of seconds', async () => {
- await page.waitForTimeout(2000);
- const modalIsVisible = await page.$eval('.ons-modal', (node) => node.classList.contains('ons-u-db'));
- expect(modalIsVisible).toBe(true);
- });
- });
-
- describe('when the modal first opens', () => {
- describe('when the countdown starts', () => {
- beforeEach(async () => {
- const component = renderComponent('timeout-modal', {
- ...EXAMPLE_TIMEOUT_MODAL_BASIC,
- showModalTimeInSeconds: 60,
+ it('displays the modal after the correct number of seconds', async () => {
+ await page.waitForTimeout(2000);
+ const modalIsVisible = await page.$eval('.ons-modal', (node) => node.classList.contains('ons-u-db'));
+ expect(modalIsVisible).toBe(true);
});
+ });
+
+ describe('when the modal first opens', () => {
+ describe('when the countdown starts', () => {
+ beforeEach(async () => {
+ const component = renderComponent('timeout-modal', {
+ ...EXAMPLE_TIMEOUT_MODAL_BASIC,
+ showModalTimeInSeconds: 60,
+ });
- const template = `
+ const template = `
${component}
`;
- await setTestPage('/test', template);
- });
+ await setTestPage('/test', template);
+ });
- it('shows the time counting down', async () => {
- const timeAtStart = await page.$eval('.ons-js-timeout-timer span', (element) => element.innerHTML);
- await page.waitForTimeout(1000);
- const timeAfterOneSecond = await page.$eval('.ons-js-timeout-timer span', (element) => element.innerHTML);
- expect(timeAfterOneSecond).not.toEqual(timeAtStart);
- });
- });
+ it('shows the time counting down', async () => {
+ const timeAtStart = await page.$eval('.ons-js-timeout-timer span', (element) => element.innerHTML);
+ await page.waitForTimeout(1000);
+ const timeAfterOneSecond = await page.$eval('.ons-js-timeout-timer span', (element) => element.innerHTML);
+ expect(timeAfterOneSecond).not.toEqual(timeAtStart);
+ });
+ });
- describe('when there are two minutes or more remaining', () => {
- beforeEach(async () => {
- const expiryTime = new Date(Date.now() + 60 * 3000);
- const expiryTimeInISOFormat = new Date(expiryTime).toISOString();
+ describe('when there are two minutes or more remaining', () => {
+ beforeEach(async () => {
+ const expiryTime = new Date(Date.now() + 60 * 3000);
+ const expiryTimeInISOFormat = new Date(expiryTime).toISOString();
- const component = renderComponent('timeout-modal', {
- ...EXAMPLE_TIMEOUT_MODAL_BASIC,
- showModalTimeInSeconds: 180,
- sessionExpiresAt: expiryTimeInISOFormat,
- });
+ const component = renderComponent('timeout-modal', {
+ ...EXAMPLE_TIMEOUT_MODAL_BASIC,
+ showModalTimeInSeconds: 180,
+ sessionExpiresAt: expiryTimeInISOFormat,
+ });
- const template = `
+ const template = `
${component}
`;
- await setTestPage('/test', template);
- });
+ await setTestPage('/test', template);
+ });
- it('displays the `minutes` (plural) string', async () => {
- const timeString = await page.$eval('.ons-js-timeout-timer span', (element) => element.innerHTML);
- expect(timeString).toEqual(expect.stringContaining('minutes'));
- });
- });
+ it('displays the `minutes` (plural) string', async () => {
+ const timeString = await page.$eval('.ons-js-timeout-timer span', (element) => element.innerHTML);
+ expect(timeString).toEqual(expect.stringContaining('minutes'));
+ });
+ });
- describe('when there are two seconds or more remaining', () => {
- beforeEach(async () => {
- const expiryTime = new Date(Date.now() + 60 * 1000);
- const expiryTimeInISOFormat = new Date(expiryTime).toISOString();
+ describe('when there are two seconds or more remaining', () => {
+ beforeEach(async () => {
+ const expiryTime = new Date(Date.now() + 60 * 1000);
+ const expiryTimeInISOFormat = new Date(expiryTime).toISOString();
- const component = renderComponent('timeout-modal', {
- ...EXAMPLE_TIMEOUT_MODAL_BASIC,
- showModalTimeInSeconds: 60,
- sessionExpiresAt: expiryTimeInISOFormat,
- });
+ const component = renderComponent('timeout-modal', {
+ ...EXAMPLE_TIMEOUT_MODAL_BASIC,
+ showModalTimeInSeconds: 60,
+ sessionExpiresAt: expiryTimeInISOFormat,
+ });
- const template = `
+ const template = `
${component}
`;
- await setTestPage('/test', template);
- });
+ await setTestPage('/test', template);
+ });
- it('displays the `seconds` (plural) string', async () => {
- const timeString = await page.$eval('.ons-js-timeout-timer span', (element) => element.innerHTML);
- expect(timeString).toEqual(expect.stringContaining('seconds'));
- });
- });
+ it('displays the `seconds` (plural) string', async () => {
+ const timeString = await page.$eval('.ons-js-timeout-timer span', (element) => element.innerHTML);
+ expect(timeString).toEqual(expect.stringContaining('seconds'));
+ });
+ });
- describe('when there is one minute remaining', () => {
- beforeEach(async () => {
- const expiryTime = new Date(Date.now() + 60 * 1500);
- const expiryTimeInISOFormat = new Date(expiryTime).toISOString();
+ describe('when there is one minute remaining', () => {
+ beforeEach(async () => {
+ const expiryTime = new Date(Date.now() + 60 * 1500);
+ const expiryTimeInISOFormat = new Date(expiryTime).toISOString();
- const component = renderComponent('timeout-modal', {
- ...EXAMPLE_TIMEOUT_MODAL_BASIC,
- showModalTimeInSeconds: 90,
- sessionExpiresAt: expiryTimeInISOFormat,
- });
+ const component = renderComponent('timeout-modal', {
+ ...EXAMPLE_TIMEOUT_MODAL_BASIC,
+ showModalTimeInSeconds: 90,
+ sessionExpiresAt: expiryTimeInISOFormat,
+ });
- const template = `
+ const template = `
${component}
`;
- await setTestPage('/test', template);
- });
+ await setTestPage('/test', template);
+ });
- it('displays the `minute` (singular) string', async () => {
- const timeString = await page.$eval('.ons-js-timeout-timer span', (element) => element.innerHTML);
- expect(timeString).toEqual(expect.stringContaining('minute'));
- });
- });
+ it('displays the `minute` (singular) string', async () => {
+ const timeString = await page.$eval('.ons-js-timeout-timer span', (element) => element.innerHTML);
+ expect(timeString).toEqual(expect.stringContaining('minute'));
+ });
+ });
- describe('when there is one second remaining', () => {
- beforeEach(async () => {
- const expiryTime = new Date(Date.now() + 3 * 1000);
- const expiryTimeInISOFormat = new Date(expiryTime).toISOString();
+ describe('when there is one second remaining', () => {
+ beforeEach(async () => {
+ const expiryTime = new Date(Date.now() + 3 * 1000);
+ const expiryTimeInISOFormat = new Date(expiryTime).toISOString();
- const component = renderComponent('timeout-modal', {
- ...EXAMPLE_TIMEOUT_MODAL_BASIC,
- showModalTimeInSeconds: 3,
- sessionExpiresAt: expiryTimeInISOFormat,
- });
+ const component = renderComponent('timeout-modal', {
+ ...EXAMPLE_TIMEOUT_MODAL_BASIC,
+ showModalTimeInSeconds: 3,
+ sessionExpiresAt: expiryTimeInISOFormat,
+ });
- const template = `
+ const template = `
${component}
`;
- await setTestPage('/test', template);
- });
+ await setTestPage('/test', template);
+ });
- it('displays the `second` (singular) string', async () => {
- const timeString = await page.$eval('.ons-js-timeout-timer span', (element) => element.innerHTML);
- expect(timeString).toEqual(expect.stringContaining('second'));
- });
- });
+ it('displays the `second` (singular) string', async () => {
+ const timeString = await page.$eval('.ons-js-timeout-timer span', (element) => element.innerHTML);
+ expect(timeString).toEqual(expect.stringContaining('second'));
+ });
+ });
- describe('when the timer runs to zero', () => {
- beforeEach(async () => {
- const expiryTime = new Date(Date.now() + 1 * 1000);
- const expiryTimeInISOFormat = new Date(expiryTime).toISOString();
+ describe('when the timer runs to zero', () => {
+ beforeEach(async () => {
+ const expiryTime = new Date(Date.now() + 1 * 1000);
+ const expiryTimeInISOFormat = new Date(expiryTime).toISOString();
- const component = renderComponent('timeout-modal', {
- ...EXAMPLE_TIMEOUT_MODAL_BASIC,
- showModalTimeInSeconds: 1,
- sessionExpiresAt: expiryTimeInISOFormat,
- });
+ const component = renderComponent('timeout-modal', {
+ ...EXAMPLE_TIMEOUT_MODAL_BASIC,
+ showModalTimeInSeconds: 1,
+ sessionExpiresAt: expiryTimeInISOFormat,
+ });
- const template = `
+ const template = `
${component}
`;
- await setTestPage('/test', template);
- });
-
- it('displays the `countdownExpiredText` text', async () => {
- const timeString = await page.$eval('.ons-js-timeout-timer span', (element) => element.innerHTML);
- expect(timeString).toEqual(expect.stringContaining('You are being signed out'));
- });
-
- it('then redirects to the provided `redirectUrl`', async () => {
- await page.waitForTimeout(2000);
- expect(page.url()).toContain('#!');
- });
+ await setTestPage('/test', template);
+ });
+
+ it('displays the `countdownExpiredText` text', async () => {
+ const timeString = await page.$eval('.ons-js-timeout-timer span', (element) => element.innerHTML);
+ expect(timeString).toEqual(expect.stringContaining('You are being signed out'));
+ });
+
+ it('then redirects to the provided `redirectUrl`', async () => {
+ await page.waitForTimeout(2000);
+ expect(page.url()).toContain('#!');
+ });
+ });
});
- });
- describe('when the modal is open', () => {
- beforeEach(async () => {
- const component = renderComponent('timeout-modal', {
- ...EXAMPLE_TIMEOUT_MODAL_BASIC,
- showModalTimeInSeconds: 59,
- });
+ describe('when the modal is open', () => {
+ beforeEach(async () => {
+ const component = renderComponent('timeout-modal', {
+ ...EXAMPLE_TIMEOUT_MODAL_BASIC,
+ showModalTimeInSeconds: 59,
+ });
- const template = `
+ const template = `
${component}
`;
- await setTestPage('/test', template);
- });
+ await setTestPage('/test', template);
+ });
- describe('when the `esc` key is pressed', () => {
- beforeEach(async () => {
- await page.waitForSelector('.ons-modal');
- await page.keyboard.press('Escape');
- });
-
- it('closes the modal', async () => {
- const modalIsVisible = await page.$eval('.ons-modal', (node) => node.classList.contains('ons-u-db'));
- expect(modalIsVisible).toBe(false);
- });
-
- it('restarts the timer and displays the modal after the correct number of seconds', async () => {
- await page.waitForTimeout(2000);
- const modalIsVisible = await page.$eval('.ons-modal', (node) => node.classList.contains('ons-u-db'));
- expect(modalIsVisible).toBe(true);
- });
+ describe('when the `esc` key is pressed', () => {
+ beforeEach(async () => {
+ await page.waitForSelector('.ons-modal');
+ await page.keyboard.press('Escape');
+ });
+
+ it('closes the modal', async () => {
+ const modalIsVisible = await page.$eval('.ons-modal', (node) => node.classList.contains('ons-u-db'));
+ expect(modalIsVisible).toBe(false);
+ });
+
+ it('restarts the timer and displays the modal after the correct number of seconds', async () => {
+ await page.waitForTimeout(2000);
+ const modalIsVisible = await page.$eval('.ons-modal', (node) => node.classList.contains('ons-u-db'));
+ expect(modalIsVisible).toBe(true);
+ });
+ });
});
- });
});
diff --git a/src/components/timeout-panel/_macro.spec.js b/src/components/timeout-panel/_macro.spec.js
index 9e93814088..bfee0caaa0 100644
--- a/src/components/timeout-panel/_macro.spec.js
+++ b/src/components/timeout-panel/_macro.spec.js
@@ -6,49 +6,49 @@ import axe from '../../tests/helpers/axe';
import { renderComponent, templateFaker } from '../../tests/helpers/rendering';
const EXAMPLE_TIMEOUT_PANEL_BASIC = {
- id: 'countdown',
- sessionExpiresAt: '000-000-000',
- redirectUrl: '#!',
- minutesTextSingular: 'minute',
- minutesTextPlural: 'minutes',
- secondsTextSingular: 'second',
- secondsTextPlural: 'seconds',
- countdownText: 'For security, your answers will only be available to view for another',
- nojsText: 'For security, your answers will only be available to view for another 1 minute',
- countdownExpiredText: 'You are being signed out',
- endWithFullStop: true,
+ id: 'countdown',
+ sessionExpiresAt: '000-000-000',
+ redirectUrl: '#!',
+ minutesTextSingular: 'minute',
+ minutesTextPlural: 'minutes',
+ secondsTextSingular: 'second',
+ secondsTextPlural: 'seconds',
+ countdownText: 'For security, your answers will only be available to view for another',
+ nojsText: 'For security, your answers will only be available to view for another 1 minute',
+ countdownExpiredText: 'You are being signed out',
+ endWithFullStop: true,
};
describe('macro: timeout panel', () => {
- it('passes jest-axe checks with', async () => {
- const $ = cheerio.load(renderComponent('timeout-panel', EXAMPLE_TIMEOUT_PANEL_BASIC));
-
- const results = await axe($.html());
- expect(results).toHaveNoViolations();
- });
-
- it('provides expected parameters to the inner `panel` component', () => {
- const faker = templateFaker();
- const panelSpy = faker.spy('panel');
-
- cheerio.load(faker.renderComponent('timeout-panel', EXAMPLE_TIMEOUT_PANEL_BASIC));
-
- expect(panelSpy.occurrences[0]).toEqual({
- id: 'countdown',
- classes: 'ons-js-panel-with-countdown',
- variant: 'warn',
- attributes: {
- 'data-redirect-url': '#!',
- 'data-server-session-expires-at': '000-000-000',
- 'data-countdown-text': 'For security, your answers will only be available to view for another',
- 'data-countdown-expired-text': 'You are being signed out',
- 'data-minutes-text-singular': 'minute',
- 'data-minutes-text-plural': 'minutes',
- 'data-seconds-text-singular': 'second',
- 'data-seconds-text-plural': 'seconds',
- 'aria-describedby': 'timeout-time-remaining',
- 'data-full-stop': true,
- },
+ it('passes jest-axe checks with', async () => {
+ const $ = cheerio.load(renderComponent('timeout-panel', EXAMPLE_TIMEOUT_PANEL_BASIC));
+
+ const results = await axe($.html());
+ expect(results).toHaveNoViolations();
+ });
+
+ it('provides expected parameters to the inner `panel` component', () => {
+ const faker = templateFaker();
+ const panelSpy = faker.spy('panel');
+
+ cheerio.load(faker.renderComponent('timeout-panel', EXAMPLE_TIMEOUT_PANEL_BASIC));
+
+ expect(panelSpy.occurrences[0]).toEqual({
+ id: 'countdown',
+ classes: 'ons-js-panel-with-countdown',
+ variant: 'warn',
+ attributes: {
+ 'data-redirect-url': '#!',
+ 'data-server-session-expires-at': '000-000-000',
+ 'data-countdown-text': 'For security, your answers will only be available to view for another',
+ 'data-countdown-expired-text': 'You are being signed out',
+ 'data-minutes-text-singular': 'minute',
+ 'data-minutes-text-plural': 'minutes',
+ 'data-seconds-text-singular': 'second',
+ 'data-seconds-text-plural': 'seconds',
+ 'aria-describedby': 'timeout-time-remaining',
+ 'data-full-stop': true,
+ },
+ });
});
- });
});
diff --git a/src/components/timeout-panel/timeout-panel.dom.js b/src/components/timeout-panel/timeout-panel.dom.js
index 3f153ef4e8..47ccb8e395 100644
--- a/src/components/timeout-panel/timeout-panel.dom.js
+++ b/src/components/timeout-panel/timeout-panel.dom.js
@@ -1,16 +1,16 @@
import domready from '../../js/domready';
async function timeoutPanels() {
- const panels = [...document.querySelectorAll('.ons-js-panel-with-countdown')];
+ const panels = [...document.querySelectorAll('.ons-js-panel-with-countdown')];
- if (panels.length) {
- const Timeout = (await import('../../js/timeout')).default;
+ if (panels.length) {
+ const Timeout = (await import('../../js/timeout')).default;
- panels.forEach((context) => {
- let time = context.getAttribute('data-server-session-expires-at');
- new Timeout(context, null, time, false, true);
- });
- }
+ panels.forEach((context) => {
+ let time = context.getAttribute('data-server-session-expires-at');
+ new Timeout(context, null, time, false, true);
+ });
+ }
}
domready(timeoutPanels);
diff --git a/src/components/timeout-panel/timeout-panel.spec.js b/src/components/timeout-panel/timeout-panel.spec.js
index 240b8173fe..c2b8b5541d 100644
--- a/src/components/timeout-panel/timeout-panel.spec.js
+++ b/src/components/timeout-panel/timeout-panel.spec.js
@@ -1,161 +1,161 @@
import { renderComponent, setTestPage } from '../../tests/helpers/rendering';
const EXAMPLE_TIMEOUT_PANEL_BASIC = {
- id: 'countdown',
- redirectUrl: '#!',
- minutesTextSingular: 'minute',
- minutesTextPlural: 'minutes',
- secondsTextSingular: 'second',
- secondsTextPlural: 'seconds',
- countdownText: 'For security, your answers will only be available to view for another',
- nojsText: 'For security, your answers will only be available to view for another 1 minute',
- countdownExpiredText: 'You are being signed out',
- endWithFullStop: true,
+ id: 'countdown',
+ redirectUrl: '#!',
+ minutesTextSingular: 'minute',
+ minutesTextPlural: 'minutes',
+ secondsTextSingular: 'second',
+ secondsTextPlural: 'seconds',
+ countdownText: 'For security, your answers will only be available to view for another',
+ nojsText: 'For security, your answers will only be available to view for another 1 minute',
+ countdownExpiredText: 'You are being signed out',
+ endWithFullStop: true,
};
describe('script: timeout panel', () => {
- describe('when the page loads', () => {
- beforeEach(async () => {
- const expiryTime = new Date(Date.now() + 60 * 3000);
- const expiryTimeInISOFormat = new Date(expiryTime).toISOString();
+ describe('when the page loads', () => {
+ beforeEach(async () => {
+ const expiryTime = new Date(Date.now() + 60 * 3000);
+ const expiryTimeInISOFormat = new Date(expiryTime).toISOString();
- const component = renderComponent('timeout-panel', {
- ...EXAMPLE_TIMEOUT_PANEL_BASIC,
- sessionExpiresAt: expiryTimeInISOFormat,
- });
+ const component = renderComponent('timeout-panel', {
+ ...EXAMPLE_TIMEOUT_PANEL_BASIC,
+ sessionExpiresAt: expiryTimeInISOFormat,
+ });
- await setTestPage('/test', component);
- });
+ await setTestPage('/test', component);
+ });
- it('shows the time counting down', async () => {
- const timeAtStart = await page.$eval('.ons-js-timeout-timer', (element) => element.innerHTML);
- await page.waitForTimeout(1000);
- const timeAfterOneSecond = await page.$eval('.ons-js-timeout-timer', (element) => element.innerHTML);
- expect(timeAfterOneSecond).not.toEqual(timeAtStart);
+ it('shows the time counting down', async () => {
+ const timeAtStart = await page.$eval('.ons-js-timeout-timer', (element) => element.innerHTML);
+ await page.waitForTimeout(1000);
+ const timeAfterOneSecond = await page.$eval('.ons-js-timeout-timer', (element) => element.innerHTML);
+ expect(timeAfterOneSecond).not.toEqual(timeAtStart);
+ });
});
- });
- describe('when there are two minutes or more remaining', () => {
- beforeEach(async () => {
- const expiryTime = new Date(Date.now() + 60 * 3000);
- const expiryTimeInISOFormat = new Date(expiryTime).toISOString();
+ describe('when there are two minutes or more remaining', () => {
+ beforeEach(async () => {
+ const expiryTime = new Date(Date.now() + 60 * 3000);
+ const expiryTimeInISOFormat = new Date(expiryTime).toISOString();
- const component = renderComponent('timeout-panel', {
- ...EXAMPLE_TIMEOUT_PANEL_BASIC,
- sessionExpiresAt: expiryTimeInISOFormat,
- });
+ const component = renderComponent('timeout-panel', {
+ ...EXAMPLE_TIMEOUT_PANEL_BASIC,
+ sessionExpiresAt: expiryTimeInISOFormat,
+ });
- await setTestPage('/test', component);
- });
+ await setTestPage('/test', component);
+ });
- it('displays the `minutes` (plural) string', async () => {
- const timeString = await page.$eval('.ons-js-timeout-timer', (element) => element.innerHTML);
- expect(timeString).toEqual(expect.stringContaining('minutes'));
+ it('displays the `minutes` (plural) string', async () => {
+ const timeString = await page.$eval('.ons-js-timeout-timer', (element) => element.innerHTML);
+ expect(timeString).toEqual(expect.stringContaining('minutes'));
+ });
});
- });
- describe('when there are two seconds or more remaining', () => {
- beforeEach(async () => {
- const expiryTime = new Date(Date.now() + 60 * 1000);
- const expiryTimeInISOFormat = new Date(expiryTime).toISOString();
+ describe('when there are two seconds or more remaining', () => {
+ beforeEach(async () => {
+ const expiryTime = new Date(Date.now() + 60 * 1000);
+ const expiryTimeInISOFormat = new Date(expiryTime).toISOString();
- const component = renderComponent('timeout-panel', {
- ...EXAMPLE_TIMEOUT_PANEL_BASIC,
- sessionExpiresAt: expiryTimeInISOFormat,
- });
+ const component = renderComponent('timeout-panel', {
+ ...EXAMPLE_TIMEOUT_PANEL_BASIC,
+ sessionExpiresAt: expiryTimeInISOFormat,
+ });
- await setTestPage('/test', component);
- });
+ await setTestPage('/test', component);
+ });
- it('displays the `seconds` (plural) string', async () => {
- const timeString = await page.$eval('.ons-js-timeout-timer span', (element) => element.innerHTML);
- expect(timeString).toEqual(expect.stringContaining('seconds'));
+ it('displays the `seconds` (plural) string', async () => {
+ const timeString = await page.$eval('.ons-js-timeout-timer span', (element) => element.innerHTML);
+ expect(timeString).toEqual(expect.stringContaining('seconds'));
+ });
});
- });
- describe('when there is one minute remaining', () => {
- beforeEach(async () => {
- const expiryTime = new Date(Date.now() + 60 * 1500);
- const expiryTimeInISOFormat = new Date(expiryTime).toISOString();
+ describe('when there is one minute remaining', () => {
+ beforeEach(async () => {
+ const expiryTime = new Date(Date.now() + 60 * 1500);
+ const expiryTimeInISOFormat = new Date(expiryTime).toISOString();
- const component = renderComponent('timeout-panel', {
- ...EXAMPLE_TIMEOUT_PANEL_BASIC,
- sessionExpiresAt: expiryTimeInISOFormat,
- });
+ const component = renderComponent('timeout-panel', {
+ ...EXAMPLE_TIMEOUT_PANEL_BASIC,
+ sessionExpiresAt: expiryTimeInISOFormat,
+ });
- await setTestPage('/test', component);
- });
+ await setTestPage('/test', component);
+ });
- it('displays the `minute` (singular) string', async () => {
- const timeString = await page.$eval('.ons-js-timeout-timer', (element) => element.innerHTML);
- expect(timeString).toEqual(expect.stringContaining('minute'));
+ it('displays the `minute` (singular) string', async () => {
+ const timeString = await page.$eval('.ons-js-timeout-timer', (element) => element.innerHTML);
+ expect(timeString).toEqual(expect.stringContaining('minute'));
+ });
});
- });
- describe('when there is one second remaining', () => {
- beforeEach(async () => {
- const expiryTime = new Date(Date.now() + 3 * 1000);
- const expiryTimeInISOFormat = new Date(expiryTime).toISOString();
+ describe('when there is one second remaining', () => {
+ beforeEach(async () => {
+ const expiryTime = new Date(Date.now() + 3 * 1000);
+ const expiryTimeInISOFormat = new Date(expiryTime).toISOString();
- const component = renderComponent('timeout-panel', {
- ...EXAMPLE_TIMEOUT_PANEL_BASIC,
- sessionExpiresAt: expiryTimeInISOFormat,
- });
+ const component = renderComponent('timeout-panel', {
+ ...EXAMPLE_TIMEOUT_PANEL_BASIC,
+ sessionExpiresAt: expiryTimeInISOFormat,
+ });
- await setTestPage('/test', component);
- });
+ await setTestPage('/test', component);
+ });
- it('displays the `second` (singular) string', async () => {
- const timeString = await page.$eval('.ons-js-timeout-timer span', (element) => element.innerHTML);
- expect(timeString).toEqual(expect.stringContaining('second'));
+ it('displays the `second` (singular) string', async () => {
+ const timeString = await page.$eval('.ons-js-timeout-timer span', (element) => element.innerHTML);
+ expect(timeString).toEqual(expect.stringContaining('second'));
+ });
});
- });
- describe('when the timer runs to zero', () => {
- beforeEach(async () => {
- const expiryTime = new Date(Date.now() + 1 * 1000);
- const expiryTimeInISOFormat = new Date(expiryTime).toISOString();
+ describe('when the timer runs to zero', () => {
+ beforeEach(async () => {
+ const expiryTime = new Date(Date.now() + 1 * 1000);
+ const expiryTimeInISOFormat = new Date(expiryTime).toISOString();
- const component = renderComponent('timeout-panel', {
- ...EXAMPLE_TIMEOUT_PANEL_BASIC,
- sessionExpiresAt: expiryTimeInISOFormat,
- });
+ const component = renderComponent('timeout-panel', {
+ ...EXAMPLE_TIMEOUT_PANEL_BASIC,
+ sessionExpiresAt: expiryTimeInISOFormat,
+ });
- await setTestPage('/test', component);
- });
+ await setTestPage('/test', component);
+ });
- it('displays the `countdownExpiredText` text', async () => {
- const timeString = await page.$eval('.ons-js-timeout-timer', (element) => element.innerHTML);
- expect(timeString).toEqual(expect.stringContaining('You are being signed out'));
- });
+ it('displays the `countdownExpiredText` text', async () => {
+ const timeString = await page.$eval('.ons-js-timeout-timer', (element) => element.innerHTML);
+ expect(timeString).toEqual(expect.stringContaining('You are being signed out'));
+ });
- it('then redirects to the provided `redirectUrl`', async () => {
- await page.waitForTimeout(2000);
- expect(page.url()).toContain('#!');
+ it('then redirects to the provided `redirectUrl`', async () => {
+ await page.waitForTimeout(2000);
+ expect(page.url()).toContain('#!');
+ });
});
- });
- describe('when Javascript is disabled', () => {
- beforeEach(async () => {
- const expiryTime = new Date(Date.now() + 1 * 1000);
- const expiryTimeInISOFormat = new Date(expiryTime).toISOString();
+ describe('when Javascript is disabled', () => {
+ beforeEach(async () => {
+ const expiryTime = new Date(Date.now() + 1 * 1000);
+ const expiryTimeInISOFormat = new Date(expiryTime).toISOString();
- const component = renderComponent('timeout-panel', {
- ...EXAMPLE_TIMEOUT_PANEL_BASIC,
- sessionExpiresAt: expiryTimeInISOFormat,
- });
+ const component = renderComponent('timeout-panel', {
+ ...EXAMPLE_TIMEOUT_PANEL_BASIC,
+ sessionExpiresAt: expiryTimeInISOFormat,
+ });
- await page.setJavaScriptEnabled(false);
- await setTestPage('/test', component);
- });
+ await page.setJavaScriptEnabled(false);
+ await setTestPage('/test', component);
+ });
- afterEach(async () => {
- await page.setJavaScriptEnabled(true);
- });
+ afterEach(async () => {
+ await page.setJavaScriptEnabled(true);
+ });
- it('displays the `nojsText` text', async () => {
- const nojsText = await page.$eval('.ons-js-nojs-text', (element) => element.innerHTML);
- expect(nojsText.trim()).toBe('For security, your answers will only be available to view for another 1 minute');
+ it('displays the `nojsText` text', async () => {
+ const nojsText = await page.$eval('.ons-js-nojs-text', (element) => element.innerHTML);
+ expect(nojsText.trim()).toBe('For security, your answers will only be available to view for another 1 minute');
+ });
});
- });
});
diff --git a/src/components/upload/_macro.spec.js b/src/components/upload/_macro.spec.js
index 11cf86b8fd..6381d66206 100644
--- a/src/components/upload/_macro.spec.js
+++ b/src/components/upload/_macro.spec.js
@@ -6,70 +6,70 @@ import axe from '../../tests/helpers/axe';
import { renderComponent, templateFaker } from '../../tests/helpers/rendering';
const EXAMPLE_UPLOAD = {
- id: 'example-upload',
- accept: '.xls,.xlsx,.pdf',
- name: 'example-upload-name',
- label: {
- description: 'File types accepted are XLS and XLSX or PDF',
- text: 'Upload a file',
- },
- dontWrap: true,
- fieldId: 'example-upload-field',
- fieldClasses: 'extra-field-class',
- attributes: { a: 42 },
- listeners: { test: 'console.log(42)' },
+ id: 'example-upload',
+ accept: '.xls,.xlsx,.pdf',
+ name: 'example-upload-name',
+ label: {
+ description: 'File types accepted are XLS and XLSX or PDF',
+ text: 'Upload a file',
+ },
+ dontWrap: true,
+ fieldId: 'example-upload-field',
+ fieldClasses: 'extra-field-class',
+ attributes: { a: 42 },
+ listeners: { test: 'console.log(42)' },
};
const EXAMPLE_UPLOAD_WITH_ERROR = {
- ...EXAMPLE_UPLOAD,
- error: {
- id: 'file-error',
- text: 'Select a file that is an XLS, XLSX or PDF',
- },
+ ...EXAMPLE_UPLOAD,
+ error: {
+ id: 'file-error',
+ text: 'Select a file that is an XLS, XLSX or PDF',
+ },
};
describe('macro: upload', () => {
- describe.each([
- ['without error', EXAMPLE_UPLOAD, 'ons-input--upload'],
- ['with error', EXAMPLE_UPLOAD_WITH_ERROR, 'ons-input--upload ons-input--error'],
- ])('%s', (_, params, expectedInputClasses) => {
- it('passes jest-axe checks', async () => {
- const $ = cheerio.load(renderComponent('upload', params));
+ describe.each([
+ ['without error', EXAMPLE_UPLOAD, 'ons-input--upload'],
+ ['with error', EXAMPLE_UPLOAD_WITH_ERROR, 'ons-input--upload ons-input--error'],
+ ])('%s', (_, params, expectedInputClasses) => {
+ it('passes jest-axe checks', async () => {
+ const $ = cheerio.load(renderComponent('upload', params));
- const results = await axe($.html());
- expect(results).toHaveNoViolations();
- });
+ const results = await axe($.html());
+ expect(results).toHaveNoViolations();
+ });
- it('renders `field` component with the expected parameters', () => {
- const faker = templateFaker();
- const fieldSpy = faker.spy('field');
+ it('renders `field` component with the expected parameters', () => {
+ const faker = templateFaker();
+ const fieldSpy = faker.spy('field');
- faker.renderComponent('upload', params);
+ faker.renderComponent('upload', params);
- expect(fieldSpy.occurrences).toContainEqual({
- id: 'example-upload-field',
- classes: 'extra-field-class',
- dontWrap: true,
- error: params.error,
- });
- });
+ expect(fieldSpy.occurrences).toContainEqual({
+ id: 'example-upload-field',
+ classes: 'extra-field-class',
+ dontWrap: true,
+ error: params.error,
+ });
+ });
- it('renders `input` component with the expected parameters', () => {
- const faker = templateFaker();
- const inputSpy = faker.spy('input');
+ it('renders `input` component with the expected parameters', () => {
+ const faker = templateFaker();
+ const inputSpy = faker.spy('input');
- faker.renderComponent('upload', params);
+ faker.renderComponent('upload', params);
- expect(inputSpy.occurrences).toContainEqual({
- id: 'example-upload',
- type: 'file',
- label: params.label,
- classes: expectedInputClasses,
- accept: '.xls,.xlsx,.pdf',
- name: 'example-upload-name',
- attributes: { a: 42 },
- listeners: params.listeners,
- });
+ expect(inputSpy.occurrences).toContainEqual({
+ id: 'example-upload',
+ type: 'file',
+ label: params.label,
+ classes: expectedInputClasses,
+ accept: '.xls,.xlsx,.pdf',
+ name: 'example-upload-name',
+ attributes: { a: 42 },
+ listeners: params.listeners,
+ });
+ });
});
- });
});
diff --git a/src/components/upload/_upload.scss b/src/components/upload/_upload.scss
index 93ee21554c..76c2beca35 100644
--- a/src/components/upload/_upload.scss
+++ b/src/components/upload/_upload.scss
@@ -1,35 +1,35 @@
.ons-input--upload {
- background: var(--ons-color-white);
- border: 1px solid var(--ons-color-input-border);
- border-radius: $input-radius;
- font-size: 1rem;
- padding: 0;
- width: 100%;
- &::-webkit-file-upload-button {
- background: var(--ons-color-button-secondary);
- border: 0;
- border-bottom-right-radius: 0;
- border-right: 1px solid var(--ons-color-input-border);
- border-top-right-radius: 0;
- color: var(--ons-color-text);
+ background: var(--ons-color-white);
+ border: 1px solid var(--ons-color-input-border);
+ border-radius: $input-radius;
font-size: 1rem;
- outline: none;
- padding: 0.5rem 1rem;
- transition: border-color 200ms ease-in;
- }
- &:hover {
- cursor: pointer;
+ padding: 0;
+ width: 100%;
&::-webkit-file-upload-button {
- border-right-color: var(--ons-color-text-link-hover);
- cursor: pointer;
+ background: var(--ons-color-button-secondary);
+ border: 0;
+ border-bottom-right-radius: 0;
+ border-right: 1px solid var(--ons-color-input-border);
+ border-top-right-radius: 0;
+ color: var(--ons-color-text);
+ font-size: 1rem;
+ outline: none;
+ padding: 0.5rem 1rem;
+ transition: border-color 200ms ease-in;
+ }
+ &:hover {
+ cursor: pointer;
+ &::-webkit-file-upload-button {
+ border-right-color: var(--ons-color-text-link-hover);
+ cursor: pointer;
+ }
}
- }
- @include mq(m) {
- width: 80%;
- }
+ @include mq(m) {
+ width: 80%;
+ }
- @include mq(l) {
- width: 70%;
- }
+ @include mq(l) {
+ width: 70%;
+ }
}
diff --git a/src/components/video/_macro.spec.js b/src/components/video/_macro.spec.js
index a57feef8ac..d81b2dbbee 100644
--- a/src/components/video/_macro.spec.js
+++ b/src/components/video/_macro.spec.js
@@ -6,64 +6,64 @@ import axe from '../../tests/helpers/axe';
import { renderComponent } from '../../tests/helpers/rendering';
const EXAMPLE_VIDEO_YOUTUBE = {
- videoEmbedUrl: 'https://www.youtube.com/embed/_EGJlvkgbPo',
- videoLinkURL: 'https://www.youtube.com/watch?v=_EGJlvkgbPo',
- title: 'Census 2021 promotional TV advert',
- linkText: 'Example link text',
- image: {
- smallSrc: 'example-small.png',
- largeSrc: 'example-large.png',
- alt: 'Example alt text',
- },
+ videoEmbedUrl: 'https://www.youtube.com/embed/_EGJlvkgbPo',
+ videoLinkURL: 'https://www.youtube.com/watch?v=_EGJlvkgbPo',
+ title: 'Census 2021 promotional TV advert',
+ linkText: 'Example link text',
+ image: {
+ smallSrc: 'example-small.png',
+ largeSrc: 'example-large.png',
+ alt: 'Example alt text',
+ },
};
describe('macro: video', () => {
- it('passes jest-axe checks', async () => {
- const $ = cheerio.load(renderComponent('video', EXAMPLE_VIDEO_YOUTUBE));
+ it('passes jest-axe checks', async () => {
+ const $ = cheerio.load(renderComponent('video', EXAMPLE_VIDEO_YOUTUBE));
- const results = await axe($.html());
- expect(results).toHaveNoViolations();
- });
+ const results = await axe($.html());
+ expect(results).toHaveNoViolations();
+ });
- it('outputs an `img` element with the expected `srcset`', () => {
- const $ = cheerio.load(renderComponent('video', EXAMPLE_VIDEO_YOUTUBE));
+ it('outputs an `img` element with the expected `srcset`', () => {
+ const $ = cheerio.load(renderComponent('video', EXAMPLE_VIDEO_YOUTUBE));
- expect($('.ons-video__img').attr('srcset')).toBe('example-small.png 1x, example-large.png 2x');
- });
+ expect($('.ons-video__img').attr('srcset')).toBe('example-small.png 1x, example-large.png 2x');
+ });
- it('outputs an `img` element with the expected `src`', () => {
- const $ = cheerio.load(renderComponent('video', EXAMPLE_VIDEO_YOUTUBE));
+ it('outputs an `img` element with the expected `src`', () => {
+ const $ = cheerio.load(renderComponent('video', EXAMPLE_VIDEO_YOUTUBE));
- expect($('.ons-video__img').attr('src')).toBe('example-small.png');
- });
+ expect($('.ons-video__img').attr('src')).toBe('example-small.png');
+ });
- it('outputs an `img` element with the expected alt text', () => {
- const $ = cheerio.load(renderComponent('video', EXAMPLE_VIDEO_YOUTUBE));
+ it('outputs an `img` element with the expected alt text', () => {
+ const $ = cheerio.load(renderComponent('video', EXAMPLE_VIDEO_YOUTUBE));
- expect($('.ons-video__img').attr('alt')).toBe('Example alt text');
- });
+ expect($('.ons-video__img').attr('alt')).toBe('Example alt text');
+ });
- it('outputs the provided link text', () => {
- const $ = cheerio.load(renderComponent('video', EXAMPLE_VIDEO_YOUTUBE));
+ it('outputs the provided link text', () => {
+ const $ = cheerio.load(renderComponent('video', EXAMPLE_VIDEO_YOUTUBE));
- expect($('.ons-video__link-text').text().trim()).toBe('Example link text');
- });
+ expect($('.ons-video__link-text').text().trim()).toBe('Example link text');
+ });
- it('outputs a hyperlink with the provided `url`', () => {
- const $ = cheerio.load(renderComponent('video', EXAMPLE_VIDEO_YOUTUBE));
+ it('outputs a hyperlink with the provided `url`', () => {
+ const $ = cheerio.load(renderComponent('video', EXAMPLE_VIDEO_YOUTUBE));
- expect($('.ons-video__link').attr('href')).toBe('https://www.youtube.com/watch?v=_EGJlvkgbPo');
- });
+ expect($('.ons-video__link').attr('href')).toBe('https://www.youtube.com/watch?v=_EGJlvkgbPo');
+ });
- it('outputs an