diff --git a/package-lock.json b/package-lock.json index c941c923..eb9c20dd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "hmrc-frontend", - "version": "1.12.0", + "version": "1.13.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1007,9 +1007,9 @@ } }, "graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", "dev": true }, "https-proxy-agent": { @@ -1032,9 +1032,9 @@ } }, "mime": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", - "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==", + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", + "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==", "dev": true }, "mime-db": { @@ -8861,9 +8861,9 @@ }, "dependencies": { "graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", "dev": true, "optional": true } @@ -11170,9 +11170,9 @@ "dev": true }, "portfinder": { - "version": "1.0.25", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.25.tgz", - "integrity": "sha512-6ElJnHBbxVA1XSLgBp7G1FiCkQdlqGzuF7DswL5tcea+E8UpuvPU7beVAjjRwCioTS9ZluNbu+ZyRvgTsmqEBg==", + "version": "1.0.26", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.26.tgz", + "integrity": "sha512-Xi7mKxJHHMI3rIUrnm/jjUgwhbYMkp/XKEcZX3aG4BrumLpq3nmoQMX+ClYnDZnZ/New7IatC1no5RX0zo1vXQ==", "dev": true, "requires": { "async": "^2.6.2", diff --git a/src/components/add-to-a-list/template.test.js b/src/components/add-to-a-list/template.test.js index 7a52bca0..ed459846 100644 --- a/src/components/add-to-a-list/template.test.js +++ b/src/components/add-to-a-list/template.test.js @@ -171,4 +171,19 @@ describe('Add to a list', () => { expect($form.attr('action')).toBe('#addItem') }) }) + + describe('Empty, null and missing itemLists', () => { + function overrideItemList (itemList) { + const params = {...examples.default} + params.itemList = itemList + return params + } + + it('should have the same output for all versions of no items being provided', () => { + const outputs = [undefined, null, []].map(itemList => render('add-to-a-list', overrideItemList(itemList)).html()) + + expect(outputs[0]).toEqual(outputs[1]) + expect(outputs[0]).toEqual(outputs[2]) + }) + }) }) diff --git a/src/components/header/header.yaml b/src/components/header/header.yaml index 887cf468..c697fd9b 100644 --- a/src/components/header/header.yaml +++ b/src/components/header/header.yaml @@ -10,11 +10,11 @@ params: - name: productName type: string required: false - description: Header title that is placed next to GOV.UK. Used for product names (i.e. Pay, Verify) + description: Product name, used when the product name follows on directly from ‘GOV.UK’. For example, GOV.UK Pay or GOV.UK Design System. In most circumstances, you should use serviceName. - name: serviceName type: string required: false - description: Header title that is placed next to GOV.UK. Used for product names (i.e. Pay, Verify) + description: The name of your service, included in the header. - name: serviceUrl type: string required: false @@ -27,7 +27,11 @@ params: - name: text type: string required: false - description: Text of the navigation item. + description: Text for the navigation item. If `html` is provided, the `text` argument will be ignored. + - name: html + type: string + required: false + description: HTML for the navigation item. If `html` is provided, the `text` argument will be ignored. - name: href type: string required: false @@ -84,17 +88,16 @@ accessibilityCriteria: | examples: - name: default description: The standard header as used on information pages on GOV.UK - language: en data: {} -- name: with-service-name +- name: with service name description: If your service is more than a few pages long, you can help users understand where they are by adding the service name. data: serviceName: Service Name serviceUrl: '/components/header' -- name: with-navigation +- name: with navigation data: navigation: - href: '#1' @@ -107,7 +110,7 @@ examples: - href: '#4' text: Navigation item 4 -- name: with-service-name-and-navigation +- name: with service name and navigation description: If you need to include basic navigation, contact or account management links. data: serviceName: Service Name @@ -123,7 +126,7 @@ examples: - href: '#4' text: Navigation item 4 -- name: with-large-navigation +- name: with large navigation description: An edge case example with a large number of navitation items with long names used to test wrapping data: navigation: @@ -160,13 +163,18 @@ examples: - href: '/browse/working' text: Working, jobs and pensions -- name: full-width +- name: with product name + data: + navigationClasses: govuk-header__navigation--end + productName: Product Name + +- name: full width data: containerClasses: govuk-header__container--full-width navigationClasses: govuk-header__navigation--end productName: Product Name -- name: full-width-with-navigation +- name: full width with navigation data: containerClasses: govuk-header__container--full-width navigationClasses: govuk-header__navigation--end @@ -180,19 +188,32 @@ examples: - href: '#3' text: Navigation item 3 -- name: sign-out-link +- name: navigation item with html + data: + serviceName: Service Name + serviceUrl: '/components/header' + navigation: + - href: '#1' + html: Navigation item 1 + active: true + - href: '#2' + html: Navigation item 2 + - href: '#3' + html: Navigation item 3 + +- name: sign out link data: serviceName: Example Service signOutHref: /sign-out -- name: sign-out-link-in-welsh +- name: sign out link in welsh description: This puts the signout link into the header and sets the whole header to be welsh. At the time of writing the language only affects the signout text. data: serviceName: Gwasanaeth enghreifftiol language: cy signOutHref: /sign-out -- name: with-language-toggle-english +- name: with language toggle english data: serviceName: Example Service language: en @@ -202,7 +223,7 @@ examples: cy: href: "/components/header/with-language-toggle-welsh/preview" -- name: with-language-toggle-welsh +- name: with language toggle welsh data: serviceName: Gwasanaeth enghreifftiol language: cy @@ -212,7 +233,7 @@ examples: cy: href: "/components/header/with-language-toggle-welsh/preview" -- name: with-hmrc-banner-english +- name: with hmrc banner english data: serviceName: Service with HMRC Banner language: en @@ -223,7 +244,7 @@ examples: cy: href: "/components/header/with-hmrc-banner-welsh/preview" -- name: with-hmrc-banner-welsh +- name: with hmrc banner welsh data: serviceName: Gwasanaeth gyda Banner CThEM language: cy diff --git a/src/components/header/template.njk b/src/components/header/template.njk index d3458112..f5973f10 100644 --- a/src/components/header/template.njk +++ b/src/components/header/template.njk @@ -2,12 +2,12 @@ {% from "../banner/macro.njk" import hmrcBanner %}
diff --git a/src/components/header/template.test.js b/src/components/header/template.test.js new file mode 100644 index 00000000..1bb50578 --- /dev/null +++ b/src/components/header/template.test.js @@ -0,0 +1,190 @@ +/** + * @jest-environment jsdom + */ +/* eslint-env jest */ + +const axe = require('../../../lib/axe-helper') + +const { render, getExamples } = require('../../../lib/jest-helpers') + +const examples = getExamples('header') + +describe('header', () => { + it('passes accessibility tests', async () => { + const $ = render('header', examples.default) + + const results = await axe($.html()) + expect(results).toHaveNoViolations() + }) + + it('has a role of `banner`', () => { + const $ = render('header', {}) + + const $component = $('.govuk-header') + expect($component.attr('role')).toEqual('banner') + }) + + it('renders attributes correctly', () => { + const $ = render('header', { + attributes: { + 'data-test-attribute': 'value', + 'data-test-attribute-2': 'value-2' + } + }) + + const $component = $('.govuk-header') + expect($component.attr('data-test-attribute')).toEqual('value') + expect($component.attr('data-test-attribute-2')).toEqual('value-2') + }) + + it('renders classes', () => { + const $ = render('header', { + classes: 'app-header--custom-modifier' + }) + + const $component = $('.govuk-header') + expect($component.hasClass('app-header--custom-modifier')).toBeTruthy() + }) + + it('renders custom container classes', () => { + const $ = render('header', { + containerClasses: 'app-width-container' + }) + + const $component = $('.govuk-header') + const $container = $component.find('.govuk-header__container') + + expect($container.hasClass('app-width-container')).toBeTruthy() + }) + + it('renders home page URL', () => { + const $ = render('header', { + homepageUrl: '/' + }) + + const $component = $('.govuk-header') + const $homepageLink = $component.find('.govuk-header__link--homepage') + expect($homepageLink.attr('href')).toEqual('/') + }) + + describe('with product name', () => { + it('renders product name', () => { + const $ = render('header', examples['full width']) + + const $component = $('.govuk-header') + const $productName = $component.find('.govuk-header__product-name') + expect($productName.text().trim()).toEqual('Product Name') + }) + }) + + describe('with service name', () => { + it('renders service name', () => { + const $ = render('header', examples['with service name']) + + const $component = $('.govuk-header') + const $serviceName = $component.find('.govuk-header__link--service-name') + expect($serviceName.text().trim()).toEqual('Service Name') + }) + }) + + describe('with navigation', () => { + it('passes accessibility tests', async () => { + const $ = render('header', examples['with navigation']) + + const results = await axe($.html()) + expect(results).toHaveNoViolations() + }) + + it('renders navigation', () => { + const $ = render('header', examples['with navigation']) + + const $component = $('.govuk-header') + const $list = $component.find('ul.govuk-header__navigation') + const $items = $list.find('li.govuk-header__navigation-item') + const $firstItem = $items.find('a.govuk-header__link:first-child') + expect($items.length).toEqual(4) + expect($firstItem.attr('href')).toEqual('#1') + expect($firstItem.text()).toContain('Navigation item 1') + }) + + it('renders navigation item with html', () => { + const $ = render('header', { + navigation: [ + { + href: '#1', + html: 'Nav item' + } + ] + }) + + const $navigationLink = $('.govuk-header__navigation-item a') + expect($navigationLink.html()).toContain('Nav item') + }) + + it('renders navigation item anchor with attributes', () => { + const $ = render('header', { + navigation: [ + { + text: 'Item', + href: '/link', + attributes: { + 'data-attribute': 'my-attribute', + 'data-attribute-2': 'my-attribute-2' + } + } + ] + }) + + const $navigationLink = $('.govuk-header__navigation-item a') + expect($navigationLink.attr('data-attribute')).toEqual('my-attribute') + expect($navigationLink.attr('data-attribute-2')).toEqual('my-attribute-2') + }) + it('renders navigation the same with different empty values', () => { + function overrideItemList (navigation) { + const params = {...examples.default} + params.navigation = navigation + return params + } + + const outputs = [undefined, null, []].map(itemList => render('header', overrideItemList(itemList)).html()) + + expect(outputs[0]).toEqual(outputs[1]) + expect(outputs[0]).toEqual(outputs[2]) + }) + describe('menu button', () => { + it('has an explicit type="button" so it does not act as a submit button', () => { + const $ = render('header', examples['with navigation']) + + const $button = $('.govuk-header__menu-button') + + expect($button.attr('type')).toEqual('button') + }) + }) + }) + + describe('SVG logo', () => { + const $ = render('header', {}) + const $svg = $('.govuk-header__logotype-crown') + + it('sets focusable="false" so that IE does not treat it as an interactive element', () => { + expect($svg.attr('focusable')).toEqual('false') + }) + + it('sets aria-hidden="true" so that it is ignored by assistive technologies', () => { + expect($svg.attr('aria-hidden')).toEqual('true') + }) + + describe('fallback PNG', () => { + const $fallbackImage = $('.govuk-header__logotype-crown-fallback-image') + + it('uses the tag which is a valid SVG element', () => { + expect($fallbackImage[0].tagName).toEqual('image') + }) + + it('sets a blank xlink:href to prevent IE from downloading both the SVG and the PNG', () => { + // Cheerio converts xhref to href - https://github.com/cheeriojs/cheerio/issues/1101 + expect($fallbackImage.attr('href')).toEqual('') + }) + }) + }) +})