Skip to content

Commit

Permalink
Merge branch 'master' into edit-3.9.0-release-note
Browse files Browse the repository at this point in the history
  • Loading branch information
m-green authored Sep 8, 2020
2 parents fd4eaf6 + 9aa0ca2 commit d618f15
Show file tree
Hide file tree
Showing 11 changed files with 644 additions and 118 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,14 @@ We’ve made fixes to GOV.UK Frontend in the following pull requests:
- [#1942: Set aria-expanded and aria-hidden attributes on header menu button and menu when page loads](https://github.com/alphagov/govuk-frontend/pull/1942)
- [#1947 Add print styles for the panel component](https://github.com/alphagov/govuk-frontend/pull/1947)

### Fixes

We’ve made fixes to GOV.UK Frontend in the following pull requests:

- [#1943: Change header menu button label](https://github.com/alphagov/govuk-frontend/pull/1943)
- [#1942: Set aria-expanded and aria-hidden attributes on header menu button and menu when page loads](https://github.com/alphagov/govuk-frontend/pull/1942)
- [#1947 Add print styles for the panel component](https://github.com/alphagov/govuk-frontend/pull/1947)

## 3.8.1 (Fix release)

### Fixes
Expand Down
72 changes: 34 additions & 38 deletions src/govuk/components/header/header.js
Original file line number Diff line number Diff line change
@@ -1,57 +1,53 @@
import '../../vendor/polyfills/Event'
import '../../vendor/polyfills/Element/prototype/classList'
import '../../vendor/polyfills/Function/prototype/bind'
import '../../vendor/polyfills/Event' // addEventListener and event.target normaliziation

function Header ($module) {
this.$module = $module
this.$menuButton = $module && $module.querySelector('.govuk-js-header-toggle')
this.$menu = this.$menuButton && $module.querySelector(
'#' + this.$menuButton.getAttribute('aria-controls')
)
}

/**
* Initialise header
*
* Check for the presence of the header, menu and menu button – if any are
* missing then there's nothing to do so return early.
*/
Header.prototype.init = function () {
// Check for module
var $module = this.$module
if (!$module) {
return
}

// Check for button
var $toggleButton = $module.querySelector('.govuk-js-header-toggle')
if (!$toggleButton) {
if (!this.$module || !this.$menuButton || !this.$menu) {
return
}

// Handle $toggleButton click events
$toggleButton.addEventListener('click', this.handleClick.bind(this))
this.syncState(this.$menu.classList.contains('govuk-header__navigation--open'))
this.$menuButton.addEventListener('click', this.handleMenuButtonClick.bind(this))
}

/**
* Toggle class
* @param {object} node element
* @param {string} className to toggle
*/
Header.prototype.toggleClass = function (node, className) {
if (node.className.indexOf(className) > 0) {
node.className = node.className.replace(' ' + className, '')
} else {
node.className += ' ' + className
}
* Sync menu state
*
* Sync the menu button class and the accessible state of the menu and the menu
* button with the visible state of the menu
*
* @param {boolean} isVisible Whether the menu is currently visible
*/
Header.prototype.syncState = function (isVisible) {
this.$menuButton.classList.toggle('govuk-header__menu-button--open', isVisible)
this.$menuButton.setAttribute('aria-expanded', isVisible)
this.$menu.setAttribute('aria-hidden', !isVisible)
}

/**
* An event handler for click event on $toggleButton
* @param {object} event event
*/
Header.prototype.handleClick = function (event) {
var $module = this.$module
var $toggleButton = event.target || event.srcElement
var $target = $module.querySelector('#' + $toggleButton.getAttribute('aria-controls'))

// If a button with aria-controls, handle click
if ($toggleButton && $target) {
this.toggleClass($target, 'govuk-header__navigation--open')
this.toggleClass($toggleButton, 'govuk-header__menu-button--open')

$toggleButton.setAttribute('aria-expanded', $toggleButton.getAttribute('aria-expanded') !== 'true')
$target.setAttribute('aria-hidden', $target.getAttribute('aria-hidden') === 'false')
}
* Handle menu button click
*
* When the menu button is clicked, change the visibility of the menu and then
* sync the accessibility state and menu button state
*/
Header.prototype.handleMenuButtonClick = function () {
var isVisible = this.$menu.classList.toggle('govuk-header__navigation--open')
this.syncState(isVisible)
}

export default Header
176 changes: 107 additions & 69 deletions src/govuk/components/header/header.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,108 +7,146 @@ const PORT = configPaths.ports.test

const baseUrl = 'http://localhost:' + PORT

beforeAll(async (done) => {
await page.emulate(iPhone)
done()
})
describe('Header navigation', () => {
beforeAll(async (done) => {
await page.emulate(iPhone)
done()
})

describe('/components/header', () => {
describe('/components/header/with-navigation/preview', () => {
describe('when JavaScript is unavailable or fails', () => {
beforeAll(async () => {
await page.setJavaScriptEnabled(false)
describe('when JavaScript is unavailable or fails', () => {
beforeAll(async () => {
await page.setJavaScriptEnabled(false)
await page.goto(`${baseUrl}/components/header/with-navigation/preview`, {
waitUntil: 'load'
})
})

afterAll(async () => {
await page.setJavaScriptEnabled(true)
afterAll(async () => {
await page.setJavaScriptEnabled(true)
})

it('shows the navigation', async () => {
await expect(page).toMatchElement('.govuk-header__navigation', {
visible: true,
timeout: 1000
})
})
})

it('falls back to making the navigation visible', async () => {
await page.goto(baseUrl + '/components/header/with-navigation/preview', { waitUntil: 'load' })
const isContentVisible = await page.waitForSelector('.govuk-header__navigation', { visible: true, timeout: 1000 })
expect(isContentVisible).toBeTruthy()
describe('when JavaScript is available', () => {
describe('when no navigation is present', () => {
it('exits gracefully with no errors', async () => {
// Errors logged to the console will cause this test to fail
await page.goto(`${baseUrl}/components/header/preview`, {
waitUntil: 'load'
})
})
})

describe('when JavaScript is available', () => {
describe('when menu button is pressed', () => {
it('should indicate the open state of the toggle button', async () => {
await page.goto(baseUrl + '/components/header/with-navigation/preview', { waitUntil: 'load' })
describe('on page load', () => {
beforeAll(async () => {
await page.goto(`${baseUrl}/components/header/with-navigation/preview`, {
waitUntil: 'load'
})
})

await page.click('.govuk-js-header-toggle')
it('exposes the hidden state of the menu using aria-hidden', async () => {
const ariaHidden = await page.$eval('.govuk-header__navigation',
el => el.getAttribute('aria-hidden')
)

const toggleButtonIsOpen = await page.evaluate(() => document.body.querySelector('.govuk-header__menu-button').classList.contains('govuk-header__menu-button--open'))
expect(toggleButtonIsOpen).toBeTruthy()
})
expect(ariaHidden).toBe('true')
})

it('should indicate the expanded state of the toggle button using aria-expanded', async () => {
await page.goto(baseUrl + '/components/header/with-navigation/preview', { waitUntil: 'load' })
it('exposes the collapsed state of the menu button using aria-expanded', async () => {
const ariaExpanded = await page.$eval('.govuk-header__menu-button',
el => el.getAttribute('aria-expanded')
)

await page.click('.govuk-js-header-toggle')
expect(ariaExpanded).toBe('false')
})
})

const toggleButtonAriaExpanded = await page.evaluate(() => document.body.querySelector('.govuk-header__menu-button').getAttribute('aria-expanded'))
expect(toggleButtonAriaExpanded).toBe('true')
describe('when menu button is pressed', () => {
beforeAll(async () => {
await page.goto(`${baseUrl}/components/header/with-navigation/preview`, {
waitUntil: 'load'
})
await page.click('.govuk-js-header-toggle')
})

it('should indicate the open state of the navigation', async () => {
await page.goto(baseUrl + '/components/header/with-navigation/preview', { waitUntil: 'load' })
it('adds the --open modifier class to the menu, making it visible', async () => {
const hasOpenClass = await page.$eval('.govuk-header__navigation',
el => el.classList.contains('govuk-header__navigation--open')
)

await page.click('.govuk-js-header-toggle')
expect(hasOpenClass).toBeTruthy()
})

const navigationIsOpen = await page.evaluate(() => document.body.querySelector('.govuk-header__navigation').classList.contains('govuk-header__navigation--open'))
expect(navigationIsOpen).toBeTruthy()
})
it('adds the --open modifier class to the menu button', async () => {
const hasOpenClass = await page.$eval('.govuk-header__menu-button',
el => el.classList.contains('govuk-header__menu-button--open')
)

it('should indicate the visible state of the navigation using aria-hidden', async () => {
await page.goto(baseUrl + '/components/header/with-navigation/preview', { waitUntil: 'load' })
expect(hasOpenClass).toBeTruthy()
})

await page.click('.govuk-js-header-toggle')
it('exposes the visible state of the menu using aria-hidden', async () => {
const ariaHidden = await page.$eval('.govuk-header__navigation',
el => el.getAttribute('aria-hidden')
)

const navigationAriaHidden = await page.evaluate(() => document.body.querySelector('.govuk-header__navigation').getAttribute('aria-hidden'))
expect(navigationAriaHidden).toBe('false')
})
expect(ariaHidden).toBe('false')
})

describe('when menu button is pressed twice', () => {
it('should indicate the open state of the toggle button', async () => {
await page.goto(baseUrl + '/components/header/with-navigation/preview', { waitUntil: 'load' })
it('exposes the expanded state of the menu button using aria-expanded', async () => {
const ariaExpanded = await page.$eval('.govuk-header__menu-button',
el => el.getAttribute('aria-expanded')
)

await page.click('.govuk-js-header-toggle')
await page.click('.govuk-js-header-toggle')
expect(ariaExpanded).toBe('true')
})
})

const toggleButtonIsOpen = await page.evaluate(() => document.body.querySelector('.govuk-header__menu-button').classList.contains('govuk-header__menu-button--open'))
expect(toggleButtonIsOpen).toBeFalsy()
describe('when menu button is pressed twice', () => {
beforeAll(async () => {
await page.goto(`${baseUrl}/components/header/with-navigation/preview`, {
waitUntil: 'load'
})
await page.click('.govuk-js-header-toggle')
await page.click('.govuk-js-header-toggle')
})

it('should indicate the expanded state of the toggle button using aria-expanded', async () => {
await page.goto(baseUrl + '/components/header/with-navigation/preview', { waitUntil: 'load' })

await page.click('.govuk-js-header-toggle')
await page.click('.govuk-js-header-toggle')
it('removes the --open modifier class from the menu, hiding it', async () => {
const hasOpenClass = await page.$eval('.govuk-header__navigation',
el => el.classList.contains('govuk-header__navigation--open')
)

const toggleButtonAriaExpanded = await page.evaluate(() => document.body.querySelector('.govuk-header__menu-button').getAttribute('aria-expanded'))
expect(toggleButtonAriaExpanded).toBe('false')
})
expect(hasOpenClass).toBeFalsy()
})

it('should indicate the open state of the navigation', async () => {
await page.goto(baseUrl + '/components/header/with-navigation/preview', { waitUntil: 'load' })
it('removes the --open modifier class from the menu button', async () => {
const hasOpenClass = await page.$eval('.govuk-header__menu-button',
el => el.classList.contains('govuk-header__menu-button--open')
)

await page.click('.govuk-js-header-toggle')
await page.click('.govuk-js-header-toggle')
expect(hasOpenClass).toBeFalsy()
})

const navigationIsOpen = await page.evaluate(() => document.body.querySelector('.govuk-header__navigation').classList.contains('govuk-header__navigation--open'))
expect(navigationIsOpen).toBeFalsy()
})
it('exposes the hidden state of the menu using aria-hidden', async () => {
const ariaHidden = await page.$eval('.govuk-header__navigation',
el => el.getAttribute('aria-hidden')
)

it('should indicate the visible state of the navigation using aria-hidden', async () => {
await page.goto(baseUrl + '/components/header/with-navigation/preview', { waitUntil: 'load' })
expect(ariaHidden).toBe('true')
})

await page.click('.govuk-js-header-toggle')
await page.click('.govuk-js-header-toggle')
it('exposes the collapsed state of the menu button using aria-expanded', async () => {
const ariaExpanded = await page.$eval('.govuk-header__menu-button',
el => el.getAttribute('aria-expanded')
)

const navigationAriaHidden = await page.evaluate(() => document.body.querySelector('.govuk-header__navigation').getAttribute('aria-hidden'))
expect(navigationAriaHidden).toBe('true')
})
expect(ariaExpanded).toBe('false')
})
})
})
Expand Down
38 changes: 35 additions & 3 deletions src/govuk/components/header/header.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ params:
- name: href
type: string
required: false
description: Url of the navigation item anchor. Both `href` and `text` attributes for navigation items need to be provided to create an item.
description: Url of the navigation item anchor.
- name: active
type: boolean
required: false
Expand All @@ -51,11 +51,11 @@ params:
- name: navigationLabel
type: string
required: false
description: Text for the `aria-label` attribute of the navigation. Defaults to "Top Level Navigation".
description: Text for the `aria-label` attribute of the navigation. Defaults to "Navigation menu".
- name: menuButtonLabel
type: string
required: false
description: Text for the `aria-label` attribute of the button that toggles the navigation. Defaults to "Show or hide Top Level Navigation".
description: Text for the `aria-label` attribute of the button that toggles the navigation. Defaults to "Show or hide navigation menu".
- name: containerClasses
type: string
required: false
Expand Down Expand Up @@ -233,6 +233,15 @@ examples:
- href: '#3'
html: <em>Navigation item 3</em>

- name: navigation item with text without link
data:
serviceName: Service Name
serviceUrl: '/components/header'
navigation:
- text: Navigation item 1
- text: Navigation item 2
- text: Navigation item 3

# Hidden examples are not shown in the review app, but are used for tests and HTML fixtures
- name: attributes
hidden: true
Expand All @@ -257,3 +266,26 @@ examples:
attributes:
data-attribute: my-attribute
data-attribute-2: my-attribute-2
- name: navigation item with html as text
hidden: true
data:
serviceName: Service Name
serviceUrl: '/components/header'
navigation:
- href: '#1'
text: <em>Navigation item 1</em>
active: true
- href: '#2'
text: <em>Navigation item 2</em>
- href: '#3'
text: <em>Navigation item 3</em>
- name: navigation item with html without link
hidden: true
data:
serviceName: Service Name
serviceUrl: '/components/header'
navigation:
- html: <em>Navigation item 1</em>
active: true
- html: <em>Navigation item 2</em>
- html: <em>Navigation item 3</em>
Loading

0 comments on commit d618f15

Please sign in to comment.