Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
297 changes: 297 additions & 0 deletions __tests__/mobile-navigation-section.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,297 @@
const { goTo, getAttribute } = require('./helpers/puppeteer.js')

describe('MobileNavigationSection', () => {
beforeEach(async () => {
await page.setViewport({ width: 375, height: 667 })
})

describe('when JavaScript is unavailable or fails', () => {
beforeEach(async () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to repeat this for each test or is it enough to use beforeAll and do it once at the start of the describe block?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will need to investigate when Jest tears down pages between tests, but if it doesn't we could use beforeAll.

await page.setJavaScriptEnabled(false)

await goTo(page, '/')
})

it('does not render the `<button>`s that toggle sections', async () => {
await goTo(page, '/')

await expect(
page.$$('.govuk-service-navigation__link button')
).resolves.toStrictEqual([])
})

it('does not render the sub navigation items', async () => {
await expect(
page.$$('.govuk-service-navigation__link ul')
).resolves.toStrictEqual([])
})
})

describe('when JavaScript is available', () => {
beforeEach(async () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to repeat this for each test or is it enough to use beforeAll and do it once at the start of the describe block?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably do need this to be beforeEach because some of the nested describe blocks take us to other pages.

But the way the tests are structured makes this hard to follow – it's not particuarly clear for example which page the tests in the User interactions describe block are running on.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll have a think at how to clarify that. Could always move the goTo at the start of the tests rather than in beforeEach/All to make it clearer what they run on 😊

await page.setJavaScriptEnabled(true)

await goTo(page, '/')
})

describe('Initial state', () => {
it('adds a `<button>` to each Service Navigation items with the same label as its link', async () => {
const $serviceNavigationItems = await page.$$(
'.govuk-service-navigation__item'
)

for (const $serviceNavigationItem of $serviceNavigationItems) {
const [linkLabel, buttonLabel] =
await $serviceNavigationItem.evaluate((el) => {
return [
el.querySelector('a').textContent,
el.querySelector('button')?.textContent
]
})

expect(buttonLabel.trim()).toBe(linkLabel.trim())
}
})

it('adds the sub navigation to each Service Navigation items', async () => {
const $serviceNavigationItems = await page.$$(
'.govuk-service-navigation__item'
)

for (const $serviceNavigationItem of $serviceNavigationItems) {
await expect($serviceNavigationItem.$('ul')).resolves.not.toBeNull()
}
})
})

describe('User interactions', () => {
it("toggles the sub navigation when clicking on an item's button", async () => {
// Open the service navigation so Puppeteer can click on elements inside it
const $navigationToggler = await page.$(
'.govuk-js-service-navigation-toggle'
)
await $navigationToggler.click()

const $serviceNavigationItem = await page.$(
'.govuk-service-navigation__item'
)

const $subNavigation = await $serviceNavigationItem.$('ul')
const $button = await $serviceNavigationItem.$('button')

await $button.click()

await expect(getAttribute($button, 'aria-expanded')).resolves.toBe(
'true'
)
await expect(getAttribute($subNavigation, 'hidden')).resolves.toBeNull()

await $button.click()

await expect(getAttribute($button, 'aria-expanded')).resolves.toBe(
'false'
)
await expect(getAttribute($subNavigation, 'hidden')).resolves.toBe('')
})
})

describe('Responsiveness', () => {
it('hides the link from the service navigation when the viewport is narrow', async () => {
const $serviceNavigationItems = await page.$$(
'.govuk-service-navigation__item'
)

// await jestPuppeteer.debug();

for (const $serviceNavigationItem of $serviceNavigationItems) {
const [
linkHiddenAttribute,
buttonHiddenAttribute,
subnavHiddenAttribute
] = await $serviceNavigationItem.evaluate((el) => [
el
.querySelector('.govuk-service-navigation__link')
.getAttribute('hidden'),
el.querySelector('button').getAttribute('hidden'),
el.querySelector('ul').getAttribute('hidden')
])

expect(linkHiddenAttribute).toBe('')
expect(buttonHiddenAttribute).toBeNull()
expect(subnavHiddenAttribute).toBe('')
}
})

it('hides the sub navigation and its toggle when the viewport becomes large enough', async () => {
await page.setViewport({ width: 1024, height: 768 })
// Wait a little bit that the Media Query List reacting to the change of viewport
// has triggered its 'change' event before we look at the page
await page.waitForSelector(
'.govuk-service-navigation__link:not([hidden])'
)

const $serviceNavigationItems = await page.$$(
'.govuk-service-navigation__item'
)

for (const $serviceNavigationItem of $serviceNavigationItems) {
const [
linkHiddenAttribute,
buttonHiddenAttribute,
subnavHiddenAttribute
] = await $serviceNavigationItem.evaluate((el) => [
el
.querySelector('.govuk-service-navigation__link')
.getAttribute('hidden'),
el.querySelector('button').getAttribute('hidden'),
el.querySelector('ul').getAttribute('hidden')
])

expect(linkHiddenAttribute).toBeNull()
expect(buttonHiddenAttribute).toBe('')
expect(subnavHiddenAttribute).toBe('')
}

await page.setViewport({ width: 375, height: 667 })
// Wait a little bit that the Media Query List reacting to the change of viewport
// has triggered its 'change' event before we look at the page
await page.waitForSelector('.govuk-service-navigation__link[hidden]')

for (const $serviceNavigationItem of $serviceNavigationItems) {
const [
linkHiddenAttribute,
buttonHiddenAttribute,
subnavHiddenAttribute
] = await $serviceNavigationItem.evaluate((el) => [
el
.querySelector('.govuk-service-navigation__link')
.getAttribute('hidden'),
el.querySelector('button').getAttribute('hidden'),
el.querySelector('ul').getAttribute('hidden')
])

expect(linkHiddenAttribute).toBe('')
expect(buttonHiddenAttribute).toBeNull()
expect(subnavHiddenAttribute).toBe('')
}
})

it('keeps the open sections open when the viewport is resized', async () => {
// Navigate to a page where a section will be open
await goTo(page, '/components/')

const $buttonOfOpenSection = await page.$(
'.govuk-service-navigation__item--active button'
)
const $subnavOfOpenSection = await page.$(
'.govuk-service-navigation__item--active ul'
)

await expect(
getAttribute($subnavOfOpenSection, 'hidden')
).resolves.toBeNull()
await expect(
getAttribute($buttonOfOpenSection, 'aria-expanded')
).resolves.toBe('true')

await page.setViewport({ width: 1024, height: 768 })
// Wait a little bit that the Media Query List reacting to the change of viewport
// has triggered its 'change' event before we look at the page
await page.waitForSelector(
'[data-module="app-mobile-navigation-section"]:not([hidden])'
)

await expect(
getAttribute($subnavOfOpenSection, 'hidden')
).resolves.toBe('')
// Button remains expanded as it's hidden anyways
await expect(
getAttribute($buttonOfOpenSection, 'aria-expanded')
).resolves.toBe('true')

await page.setViewport({ width: 375, height: 667 })
// Wait a little bit that the Media Query List reacting to the change of viewport
// has triggered its 'change' event before we look at the page
await page.waitForSelector(
'[data-module="app-mobile-navigation-section"][hidden]'
)

// When viewport is narrow again, the subnav is visible like it was before the viewport got larger
await expect(
getAttribute($subnavOfOpenSection, 'hidden')
).resolves.toBeNull()
await expect(
getAttribute($buttonOfOpenSection, 'aria-expanded')
).resolves.toBe('true')
})
})

describe('On a section root', () => {
beforeEach(async () => {
await goTo(page, '/components/')
})

it('keeps sub-navigation in the active section open', async () => {
const $serviceNavigationItem = await page.$(
'.govuk-service-navigation__item--active'
)

const $button = await $serviceNavigationItem.$('button')
const $subNavigation = await $serviceNavigationItem.$('ul')

await expect(getAttribute($button, 'aria-expanded')).resolves.toBe(
'true'
)
await expect(getAttribute($subNavigation, 'hidden')).resolves.toBeNull()
})

it('marks the overview as the current link', async () => {
const $serviceNavigationItem = await page.$(
'.govuk-service-navigation__item--active'
)

const $subNavigationOverviewLink = await $serviceNavigationItem.$(
'ul a[href="/components/"]'
)

await expect(
getAttribute($subNavigationOverviewLink, 'aria-current')
).resolves.toBe('page')
})
})

describe('In a page within a section', () => {
beforeEach(async () => {
await goTo(page, '/components/button/')
})

it('keeps the active section open', async () => {
const $serviceNavigationItem = await page.$(
'.govuk-service-navigation__item--active'
)

const $button = await $serviceNavigationItem.$('button')
const $subNavigation = await $serviceNavigationItem.$('ul')

await expect(getAttribute($button, 'aria-expanded')).resolves.toBe(
'true'
)
await expect(getAttribute($subNavigation, 'hidden')).resolves.toBeNull()
})

it('marks the overview as the current link', async () => {
const $serviceNavigationItem = await page.$(
'.govuk-service-navigation__item--active'
)

const $subNavigationLink = await $serviceNavigationItem.$(
'ul a[href="/components/button/"]'
)

await expect(
getAttribute($subNavigationLink, 'aria-current')
).resolves.toBe('page')
})
})
})
})
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
"glob": "^11.0.2",
"gray-matter": "^4.0.2",
"highlight.js": "^11.11.1",
"html-validate": "^9.5.3",
"html-validate": "^9.5.4",
"husky": "^9.1.7",
"hyperlink": "^5.0.4",
"jest": "^29.7.0",
Expand Down
4 changes: 4 additions & 0 deletions src/javascripts/application.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import CookiesPage from './components/cookies-page.mjs'
import Copy from './components/copy.mjs'
import EmbedCard from './components/embed-card.mjs'
import ExampleFrame from './components/example-frame.mjs'
import MobileNavigationSection from './components/mobile-navigation-section.mjs'
import OptionsTable from './components/options-table.mjs'
import ScrollContainer from './components/scroll-container.mjs'
import Search from './components/search.mjs'
Expand All @@ -31,6 +32,9 @@ createAll(NotificationBanner)
createAll(ServiceNavigation)
createAll(SkipLink)

// Mobile navigation
createAll(MobileNavigationSection)

// Cookies and analytics
createAll(CookieBanner)
createAll(CookiesPage)
Expand Down
Loading