Skip to content

Commit

Permalink
Merge pull request #3265 from citizensadvice/greedy-nav-refactor
Browse files Browse the repository at this point in the history
Refactor greedy navigation
  • Loading branch information
davidrapson authored Jan 22, 2025
2 parents 27ce00c + 6579fef commit 4d5ecb1
Show file tree
Hide file tree
Showing 43 changed files with 931 additions and 1,712 deletions.
16 changes: 15 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,26 @@
- Accounts for paragraph elements within rows
- Rails interface accepts a new `hide_empty_rows` option to control whether rows with blank values are shown
- Add the ability to have text in a footer column
- New greedy navigation implementation

For backwards compatibility the old greedy navigation entrypoint will continue to work but will be removed in a future version. The existing code should look something like this:

```js
import greedyNav from '@citizensadvice/design-system/lib/greedy-nav/index';
greedyNav.init();
```

To switch to the new entrypoint update your application to use the following code:

```js
import { initNavigation } from "@citizensadvice/design-system"
initNavigation();
```

## v6.3.0

### 5 December 2024


**New**
- Add support for Rails 8
- Drop support for Rails 6.1
Expand Down
4 changes: 2 additions & 2 deletions demo/app/assets/javascript/application.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import {
initHeader,
initGreedyNav,
initNavigation,
initTargetedContent,
initOnThisPage,
initDisclosure,
initErrorSummary,
} from '@citizensadvice/design-system';

initHeader();
initGreedyNav();
initNavigation();
initTargetedContent();
initOnThisPage();
initDisclosure();
Expand Down
3 changes: 2 additions & 1 deletion demo/app/assets/stylesheets/previews.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
}

.component-preview--breadcrumbs,
.component-preview--navigation {
.component-preview--navigation,
.component-preview--header {
#content {
margin: 0;
}
Expand Down
14 changes: 7 additions & 7 deletions demo/backstop-common.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ module.exports = function backstopCommon(baseUrl) {
{
label: `${labelPrefix}`,
url: `${baseUrl}/${url}`,
readySelector: '.cads-greedy-nav-has-dropdown',
readySelector: '.cads-has-greedy-nav',
},
];
}
Expand Down Expand Up @@ -112,7 +112,7 @@ module.exports = function backstopCommon(baseUrl) {
viewports: [{ label: 'desktop', width: 800, height: 200 }],
},
{
label: 'Components/Header',
label: 'Components/Header (search open)',
url: componentUrlFor('header/full_example'),
clickSelector: '.js-cads-search-reveal',
viewports: [{ label: 'small', width: 320, height: 480 }],
Expand Down Expand Up @@ -276,14 +276,14 @@ module.exports = function backstopCommon(baseUrl) {
{
label: ' Components/Navigation',
url: componentUrlFor('navigation/example'),
readySelector: '.cads-greedy-nav-has-dropdown',
clickSelector: '.cads-greedy-nav__dropdown-toggle',
readySelector: '.cads-has-greedy-nav',
clickSelector: '#cads-greedy-nav-toggle',
viewports: [{ label: 'desktop', width: 800, height: 200 }],
},
{
label: 'Components/Navigation (focus)',
url: componentUrlFor('navigation/example'),
readySelector: '.cads-greedy-nav-has-dropdown',
readySelector: '.cads-has-greedy-nav',
keyPressSelectors: [
{ selector: '.cads-navigation__link', keyPress: 'Tab' },
],
Expand All @@ -292,8 +292,8 @@ module.exports = function backstopCommon(baseUrl) {
{
label: 'Components/Navigation mobile',
url: componentUrlFor('navigation/example'),
readySelector: '.cads-greedy-nav-has-dropdown',
clickSelector: '.cads-greedy-nav__dropdown-toggle',
readySelector: '.cads-has-greedy-nav',
clickSelector: '#cads-greedy-nav-toggle',
viewports: [{ label: 'mobile', width: 320, height: 400 }],
},
{
Expand Down
184 changes: 146 additions & 38 deletions demo/cypress/e2e/navigation.cy.js
Original file line number Diff line number Diff line change
@@ -1,55 +1,147 @@
describe('Navigation', () => {
beforeEach(() => {
cy.visitComponentUrl('header/with_navigation');
cy.viewport('iphone-6');
context('when on a large screen', () => {
beforeEach(() => {
loadComponentExample();
cy.viewport(1440, 860);
});

it('renders navigation with no greedy navigation', () => {
cy.findByRole('button', { name: /More/i }).should('not.exist');
cy.get('.cads-greedy-nav').should('have.attr', 'aria-haspopup', 'false');
});
});

it('navigation links can be viewed by opening/closing the dropdown', () => {
openNavigation();
assertNavigationOpen();
closeNavigation();
assertNavigationClosed();
context('when greedy navigation is first triggered', () => {
beforeEach(() => {
loadComponentExample();
cy.viewport(800, 600);
});

it('has expected aria attributes', () => {
cy.get('.cads-greedy-nav').should('have.attr', 'aria-haspopup', 'true');
cy.findByRole('button', { name: /More/i }).should(
'have.attr',
'aria-controls',
'cads-greedy-nav-dropdown',
);
});

it('moves navigation items into the greedy navigation', () => {
cy.findByRole('button', { name: /More/i }).click();
assertItemsInMainNavigation([
'Benefits',
'Work',
'Debt and money',
'Consumer',
'Housing',
'Family',
'Law and courts',
]);
assertItemsInGreedyNavigation(['Immigration', 'Health', 'More from us']);
});
});

it('tabbing into the dropdown toggle automatically opens the dropdown menu', () => {
tabIntoNavigation();
assertNavigationOpen();
context('when on a small screen', () => {
beforeEach(() => {
loadComponentExample();
cy.viewport(375, 667);
});

it('moves navigation items into the greedy navigation including header links', () => {
cy.findByRole('button', { name: /More/i }).click();
assertItemsInMainNavigation(['Benefits', 'Work']);

assertItemsInGreedyNavigation([
'Debt and money',
'Consumer',
'Housing',
'Family',
'Law and courts',
'Immigration',
'Health',
'More from us',
'AdviserNet',
'Cymraeg',
'Sign in',
]);
});
});

// NP-1755
it.skip('tabbing out of the dropdown menu automatically closes it');
context('when interacting with a mouse', () => {
beforeEach(() => {
loadComponentExample();
cy.viewport(375, 667);
});

it('can close the dropdown menu using your keyboard', () => {
openNavigation().type('{esc}');
assertNavigationClosed();
it('allows opening and closing the greedy navigation', () => {
cy.findByRole('button', { name: /More/i }).click();
assertNavigationOpen();
cy.findByRole('button', { name: /Close/i }).click();
assertNavigationClosed();
});

it('closes the greedy navigation when clicking outside it', () => {
cy.findByRole('button', { name: /More/i }).click();
assertNavigationOpen();
cy.get('body').click();
assertNavigationClosed();
});
});

it('can close the dropdown menu by clicking outside it', () => {
openNavigation();
assertNavigationOpen();
cy.get('body').click();
assertNavigationClosed();
// Note: Tab interaction is a bit fraught to test in Cypress
// We use https://github.com/dmtrKovalenko/cypress-real-events
// to approximate this but full keyboard testing should still be
// done manually when changing this behaviour.
context('when interacting with a keyboard', () => {
beforeEach(() => {
loadComponentExample();
cy.viewport(375, 667);
});

it('closes the greedy navigation when tabbing out', () => {
cy.findByRole('button', { name: /More/i }).click();
cy.findByTestId('cads-greedy-nav-dropdown').within(() => {
cy.findByText('Sign in').focus().realPress('Tab');
assertNavigationClosed();
});
});

it('closes the greedy navigation when pressing the escape key', () => {
cy.findByRole('button', { name: /More/i }).click().type('{esc}');
assertNavigationClosed();
});
});

function openNavigation() {
return cy
.findByText('More')
.should('be.visible')
.click()
.should('have.attr', 'aria-expanded', 'true')
.should('have.attr', 'aria-controls', 'greedy-nav-dropdown');
}
context('when in english', () => {
beforeEach(() => {
loadComponentExample('en');
cy.viewport(375, 667);
});

function closeNavigation() {
return cy
.findByText('Close')
.should('be.visible')
.click()
.should('have.attr', 'aria-expanded', 'false');
}
it('has translated labels when viewing in english locale', () => {
cy.findByRole('button', { name: /More/i })
.should('have.attr', 'aria-label', 'More navigation items')
.click();
cy.findByRole('button', { name: /Close/i }).click();
});
});

context('when in welsh', () => {
beforeEach(() => {
loadComponentExample('cy');
cy.viewport(375, 667);
});

it('has translated labels when viewing in welsh locale', () => {
cy.findByRole('button', { name: /Mwy/i }).click();
assertNavigationOpen();
cy.findByRole('button', { name: /Cau/i }).click();
assertNavigationClosed();
});
});

function tabIntoNavigation() {
cy.findByText('More').should('be.visible').focus().tab().tab();
function loadComponentExample(locale = 'en') {
cy.visitComponentUrl('header/with_navigation', locale);
}

function assertNavigationOpen() {
Expand All @@ -59,4 +151,20 @@ describe('Navigation', () => {
function assertNavigationClosed() {
cy.findByText('More from us').should('not.be.visible');
}

function assertItemsInMainNavigation(expected) {
const items = [];
cy.get('.cads-navigation > ul a:visible').each(($el) =>
items.push($el.text()),
);
cy.wrap(items).should('deep.equal', expected);
}

function assertItemsInGreedyNavigation(expected) {
const items = [];
cy.get('.cads-greedy-nav > ul a:visible').each(($el) =>
items.push($el.text()),
);
cy.wrap(items).should('deep.equal', expected);
}
});
10 changes: 7 additions & 3 deletions demo/cypress/support/commands.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import '@testing-library/cypress/add-commands';
import 'cypress-axe';
import 'cypress-plugin-tab';
import 'cypress-real-events';

Cypress.Commands.add('visitComponentUrl', (urlPart) => {
cy.visit(`/rails/view_components/${urlPart}`);
Cypress.Commands.add('visitComponentUrl', (urlPart, locale = 'en') => {
if (locale === 'cy') {
cy.visit(`/rails/view_components/${urlPart}?locale=cy`);
} else {
cy.visit(`/rails/view_components/${urlPart}`);
}
});

Cypress.Commands.add('scrolledIntoView', (selector) => {
Expand Down
31 changes: 7 additions & 24 deletions demo/package-lock.json

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

Loading

0 comments on commit 4d5ecb1

Please sign in to comment.