// Component tests for usa-side-navigation
import './index.ts';
describe('USA Side Navigation Component Tests', () => {
const basicNavItems = [
{ label: 'Home', href: '/', current: true },
{ label: 'About', href: '/about' },
{
label: 'Services',
href: '/services',
subnav: [
{ label: 'Health Services', href: '/services/health' },
{ label: 'Education Services', href: '/services/education' },
],
},
{ label: 'Contact', href: '/contact' },
];
it('should render side navigation with default properties', () => {
cy.mount(``);
cy.get('usa-side-navigation').should('exist');
cy.get('.usa-sidenav').should('exist');
});
it('should render navigation items when provided', () => {
cy.mount(``);
cy.window().then((win) => {
const sidenav = win.document.getElementById('test-sidenav') as any;
sidenav.items = basicNavItems;
});
cy.get('.usa-sidenav__item').should('have.length', 4);
cy.get('.usa-sidenav__link').should('contain.text', 'Home');
cy.get('.usa-sidenav__link').should('contain.text', 'About');
});
it('should handle navigation item clicks', () => {
cy.mount(``);
cy.window().then((win) => {
const sidenav = win.document.getElementById('test-sidenav') as any;
sidenav.items = basicNavItems;
});
// Click on a navigation item
cy.get('.usa-sidenav__link').first().click();
// Should maintain accessibility attributes
cy.get('.usa-sidenav__link.usa-current').should('exist');
});
it('should handle subnav expansion', () => {
cy.mount(``);
cy.window().then((win) => {
const sidenav = win.document.getElementById('test-sidenav') as any;
sidenav.items = basicNavItems;
});
// Should show subnav when expanded
cy.get('.usa-sidenav__sublist').should('exist');
cy.get('.usa-sidenav__sublist .usa-sidenav__item').should('have.length', 2);
});
it('should be accessible', () => {
cy.mount(``);
cy.window().then((win) => {
const sidenav = win.document.getElementById('test-sidenav') as any;
sidenav.items = basicNavItems;
});
// Check ARIA attributes
cy.get('nav').should('have.attr', 'aria-label');
cy.get('.usa-current').should('have.attr', 'aria-current', 'page');
// Check keyboard navigation
cy.get('.usa-sidenav__link').first().focus().should('be.focused');
});
// Responsive Layout Testing (Critical Gap Fix)
describe('Responsive Layout Testing', () => {
const multiLevelNavItems = [
{ label: 'Dashboard', href: '/dashboard', current: true },
{ label: 'Profile', href: '/profile' },
{
label: 'Administration',
href: '/admin',
subnav: [
{ label: 'User Management', href: '/admin/users' },
{ label: 'System Settings', href: '/admin/settings' },
{
label: 'Reports',
href: '/admin/reports',
subnav: [
{ label: 'Monthly Reports', href: '/admin/reports/monthly' },
{ label: 'Annual Reports', href: '/admin/reports/annual' },
{ label: 'Custom Reports', href: '/admin/reports/custom' },
],
},
],
},
{
label: 'Resources',
href: '/resources',
subnav: [
{ label: 'Documentation', href: '/resources/docs' },
{ label: 'Help Center', href: '/resources/help' },
{ label: 'FAQ', href: '/resources/faq' },
],
},
{ label: 'Support', href: '/support' },
{ label: 'Contact', href: '/contact' },
];
const viewports = [
{ name: 'Mobile Portrait', width: 375, height: 667 },
{ name: 'Mobile Landscape', width: 667, height: 375 },
{ name: 'Tablet Portrait', width: 768, height: 1024 },
{ name: 'Tablet Landscape', width: 1024, height: 768 },
{ name: 'Desktop', width: 1200, height: 800 },
{ name: 'Large Desktop', width: 1920, height: 1080 },
];
describe('Basic Responsive Behavior', () => {
viewports.forEach((viewport) => {
it(`should render correctly on ${viewport.name} (${viewport.width}x${viewport.height})`, () => {
cy.viewport(viewport.width, viewport.height);
cy.mount(``);
cy.window().then((win) => {
const sidenav = win.document.getElementById('responsive-sidenav') as any;
sidenav.items = multiLevelNavItems;
});
// Basic visibility test
cy.get('usa-side-navigation').should('be.visible');
cy.get('.usa-sidenav').should('be.visible');
// Navigation should fit within viewport
cy.get('usa-side-navigation').should(($el) => {
expect($el[0].scrollWidth).to.be.at.most($el[0].clientWidth + 10);
});
// Accessibility at all sizes
cy.injectAxe();
cy.checkAccessibility();
});
});
it('should handle viewport orientation changes', () => {
cy.mount(``);
cy.window().then((win) => {
const sidenav = win.document.getElementById('orientation-sidenav') as any;
sidenav.items = multiLevelNavItems;
});
// Portrait tablet
cy.viewport(768, 1024);
cy.get('.usa-sidenav').should('be.visible');
cy.get('.usa-sidenav__link').should('be.visible');
// Landscape tablet
cy.viewport(1024, 768);
cy.get('.usa-sidenav').should('be.visible');
cy.get('.usa-sidenav__link').should('be.visible');
// Component should adapt without breaking
cy.injectAxe();
cy.checkAccessibility();
});
});
describe('Mobile Navigation Behavior', () => {
it('should show collapsible mobile navigation', () => {
cy.viewport(375, 667); // iPhone SE
cy.mount(
``
);
cy.window().then((win) => {
const sidenav = win.document.getElementById('mobile-sidenav') as any;
sidenav.items = multiLevelNavItems;
sidenav.mobileCollapsible = true;
});
// Should show mobile toggle button
cy.get('.usa-sidenav__toggle').should('be.visible');
// Navigation should be hidden initially on mobile
cy.get('.usa-sidenav__nav').should('not.have.class', 'is-visible');
// Click toggle should show navigation
cy.get('.usa-sidenav__toggle').click();
cy.get('.usa-sidenav__nav').should('have.class', 'is-visible');
// Click toggle again should hide navigation
cy.get('.usa-sidenav__toggle').click();
cy.get('.usa-sidenav__nav').should('not.have.class', 'is-visible');
});
it('should handle mobile subnav expansion', () => {
cy.viewport(375, 667);
cy.mount(``);
cy.window().then((win) => {
const sidenav = win.document.getElementById('mobile-subnav') as any;
sidenav.items = multiLevelNavItems;
sidenav.mobileCollapsible = true;
});
// Open mobile menu
cy.get('.usa-sidenav__toggle').click();
cy.get('.usa-sidenav__nav').should('have.class', 'is-visible');
// Test subnav expansion on mobile
cy.get('.usa-sidenav__item')
.contains('Administration')
.within(() => {
cy.get('button, .usa-sidenav__link').click();
});
// Subnavigation should be visible
cy.get('.usa-sidenav__sublist').should('be.visible');
cy.get('.usa-sidenav__sublist .usa-sidenav__item').should('be.visible');
});
it('should handle touch interactions on mobile', () => {
cy.viewport(375, 667);
cy.mount(``);
cy.window().then((win) => {
const sidenav = win.document.getElementById('mobile-touch') as any;
sidenav.items = multiLevelNavItems;
sidenav.mobileCollapsible = true;
});
// Test touch targets are at least 44px
cy.get('.usa-sidenav__toggle').should(($el) => {
const rect = $el[0].getBoundingClientRect();
expect(Math.min(rect.width, rect.height)).to.be.at.least(44);
});
// Simulate touch on toggle button
cy.get('.usa-sidenav__toggle')
.trigger('touchstart', { touches: [{ clientX: 100, clientY: 100 }] })
.trigger('touchend', { changedTouches: [{ clientX: 100, clientY: 100 }] });
cy.get('.usa-sidenav__nav').should('have.class', 'is-visible');
// Test touch on navigation links
cy.get('.usa-sidenav__link')
.first()
.trigger('touchstart', { touches: [{ clientX: 150, clientY: 150 }] })
.trigger('touchend', { changedTouches: [{ clientX: 150, clientY: 150 }] });
cy.get('.usa-sidenav').should('be.visible');
});
it('should handle mobile navigation with long labels', () => {
const longLabelItems = [
{
label: 'Very Long Navigation Item Label That Should Wrap Properly on Mobile Devices',
href: '/long1',
},
{
label: 'Another Extremely Long Navigation Label for Testing Mobile Layout',
href: '/long2',
},
{
label: 'Administration and Management Section',
href: '/admin',
subnav: [
{ label: 'Comprehensive User Management and Access Control', href: '/admin/users' },
{ label: 'Advanced System Configuration and Settings', href: '/admin/settings' },
],
},
];
cy.viewport(320, 568); // Very narrow mobile
cy.mount(``);
cy.window().then((win) => {
const sidenav = win.document.getElementById('mobile-long-labels') as any;
sidenav.items = longLabelItems;
sidenav.mobileCollapsible = true;
});
// Open mobile menu
cy.get('.usa-sidenav__toggle').click();
// Long labels should wrap and be readable
cy.get('.usa-sidenav__link').should('be.visible');
cy.get('.usa-sidenav__link').each(($link) => {
expect($link.text().trim()).to.not.be.empty;
});
// Should not cause horizontal overflow
cy.get('usa-side-navigation').should(($el) => {
expect($el[0].scrollWidth).to.be.at.most($el[0].clientWidth + 20);
});
});
it('should close mobile navigation when clicking outside', () => {
cy.viewport(375, 667);
cy.mount(`
Main page content
`);
cy.window().then((win) => {
const sidenav = win.document.getElementById('mobile-outside-click') as any;
sidenav.items = multiLevelNavItems;
sidenav.mobileCollapsible = true;
});
// Open mobile menu
cy.get('.usa-sidenav__toggle').click();
cy.get('.usa-sidenav__nav').should('have.class', 'is-visible');
// Click outside navigation
cy.get('#main-content').click();
// Navigation should close
cy.get('.usa-sidenav__nav').should('not.have.class', 'is-visible');
});
});
describe('Tablet Navigation Behavior', () => {
it('should show full navigation on tablet portrait', () => {
cy.viewport(768, 1024);
cy.mount(``);
cy.window().then((win) => {
const sidenav = win.document.getElementById('tablet-portrait') as any;
sidenav.items = multiLevelNavItems;
});
// Should show full navigation without toggle
cy.get('.usa-sidenav__nav').should('be.visible');
cy.get('.usa-sidenav__toggle').should('not.be.visible');
// All navigation items should be visible
cy.get('.usa-sidenav__item').should('have.length', multiLevelNavItems.length);
cy.get('.usa-sidenav__link').should('be.visible');
});
it('should handle tablet subnav interactions', () => {
cy.viewport(768, 1024);
cy.mount(``);
cy.window().then((win) => {
const sidenav = win.document.getElementById('tablet-subnav') as any;
sidenav.items = multiLevelNavItems;
});
// Test subnav expansion with mouse
cy.get('.usa-sidenav__item')
.contains('Administration')
.within(() => {
cy.get('button, .usa-sidenav__link').click();
});
cy.get('.usa-sidenav__sublist').should('be.visible');
// Test nested subnav
cy.get('.usa-sidenav__sublist .usa-sidenav__item')
.contains('Reports')
.within(() => {
cy.get('button, .usa-sidenav__link').click();
});
cy.get('.usa-sidenav__sublist .usa-sidenav__sublist').should('be.visible');
});
it('should adapt to tablet landscape orientation', () => {
cy.viewport(1024, 768);
cy.mount(``);
cy.window().then((win) => {
const sidenav = win.document.getElementById('tablet-landscape') as any;
sidenav.items = multiLevelNavItems;
});
// Should maintain full navigation functionality
cy.get('.usa-sidenav__nav').should('be.visible');
cy.get('.usa-sidenav__item').should('have.length', multiLevelNavItems.length);
// Navigation should fit well in landscape
cy.get('.usa-sidenav').should('be.visible');
});
});
describe('Desktop Navigation Behavior', () => {
it('should show full featured navigation on desktop', () => {
cy.viewport(1200, 800);
cy.mount(``);
cy.window().then((win) => {
const sidenav = win.document.getElementById('desktop-full') as any;
sidenav.items = multiLevelNavItems;
});
// Should show full navigation
cy.get('.usa-sidenav__nav').should('be.visible');
cy.get('.usa-sidenav__toggle').should('not.be.visible');
// All navigation features should be available
cy.get('.usa-sidenav__item').should('have.length', multiLevelNavItems.length);
cy.get('.usa-sidenav__link').should('be.visible');
// Should support complex navigation hierarchy
cy.get('.usa-sidenav__sublist').should('exist');
});
it('should handle hover states on desktop', () => {
cy.viewport(1200, 800);
cy.mount(``);
cy.window().then((win) => {
const sidenav = win.document.getElementById('desktop-hover') as any;
sidenav.items = multiLevelNavItems;
});
// Test hover interactions
cy.get('.usa-sidenav__link').first().trigger('mouseover');
cy.get('.usa-sidenav__link').first().should('be.visible');
// Test subnav hover
cy.get('.usa-sidenav__item').contains('Administration').trigger('mouseover');
cy.get('.usa-sidenav__sublist').should('be.visible');
});
it('should handle keyboard navigation on desktop', () => {
cy.viewport(1200, 800);
cy.mount(``);
cy.window().then((win) => {
const sidenav = win.document.getElementById('desktop-keyboard') as any;
sidenav.items = multiLevelNavItems;
});
// Focus first navigation item
cy.get('.usa-sidenav__link').first().focus();
cy.focused().should('contain.text', 'Dashboard');
// Tab to next item
cy.focused().tab();
cy.focused().should('contain.text', 'Profile');
// Test arrow key navigation
cy.focused().type('{downarrow}');
cy.focused().should('contain.text', 'Administration');
// Test enter to expand subnav
cy.focused().type('{enter}');
cy.get('.usa-sidenav__sublist').should('be.visible');
});
it('should handle large navigation sets efficiently', () => {
const largeNavSet = Array.from({ length: 20 }, (_, i) => ({
label: `Navigation Item ${i + 1}`,
href: `/item-${i + 1}`,
subnav:
i % 3 === 0
? [
{ label: `Subitem ${i + 1}.1`, href: `/item-${i + 1}/sub1` },
{ label: `Subitem ${i + 1}.2`, href: `/item-${i + 1}/sub2` },
]
: undefined,
}));
cy.viewport(1920, 1080); // Large desktop
cy.mount(``);
cy.window().then((win) => {
const sidenav = win.document.getElementById('desktop-large') as any;
sidenav.items = largeNavSet;
});
// Should render all items efficiently
cy.get('.usa-sidenav__item').should('have.length', 20);
// Should be scrollable if needed
cy.get('.usa-sidenav').scrollTo('bottom');
cy.get('.usa-sidenav__item').last().should('be.visible');
// Performance should be maintained
cy.get('.usa-sidenav__link').should('be.visible');
});
});
describe('Responsive Navigation Features', () => {
it('should handle sticky navigation on all viewport sizes', () => {
viewports.slice(2).forEach((viewport) => {
// Test tablet and desktop
cy.viewport(viewport.width, viewport.height);
cy.mount(`
`);
cy.window().then((win) => {
const sidenav = win.document.getElementById(`sticky-nav-${viewport.width}`) as any;
sidenav.items = multiLevelNavItems;
sidenav.sticky = true;
});
// Should stick during scroll
cy.scrollTo(0, 500);
cy.get('.usa-sidenav').should('have.css', 'position', 'sticky');
cy.get('.usa-sidenav__nav').should('be.visible');
});
});
it('should maintain state across viewport changes', () => {
cy.mount(``);
cy.window().then((win) => {
const sidenav = win.document.getElementById('state-persistence') as any;
sidenav.items = multiLevelNavItems;
});
// Start on desktop and expand subnav
cy.viewport(1200, 800);
cy.get('.usa-sidenav__item')
.contains('Administration')
.within(() => {
cy.get('button, .usa-sidenav__link').click();
});
cy.get('.usa-sidenav__sublist').should('be.visible');
// Switch to mobile
cy.viewport(375, 667);
cy.get('.usa-sidenav__toggle').should('be.visible');
// Open mobile menu
cy.get('.usa-sidenav__toggle').click();
// Expanded state should be preserved
cy.get('.usa-sidenav__sublist').should('be.visible');
// Switch back to desktop
cy.viewport(1200, 800);
cy.get('.usa-sidenav__sublist').should('be.visible');
});
it('should handle responsive search integration', () => {
cy.viewport(375, 667); // Mobile
cy.mount(``);
cy.window().then((win) => {
const sidenav = win.document.getElementById('responsive-search') as any;
sidenav.items = multiLevelNavItems;
sidenav.searchable = true;
});
// Should show search functionality on mobile
cy.get('.usa-sidenav__toggle').click();
cy.get('.usa-sidenav__search').should('be.visible');
// Search should filter navigation
cy.get('.usa-sidenav__search input').type('Admin');
cy.get('.usa-sidenav__item').should('contain.text', 'Administration');
// Change to desktop
cy.viewport(1200, 800);
// Search should persist and be visible
cy.get('.usa-sidenav__search').should('be.visible');
cy.get('.usa-sidenav__item').should('contain.text', 'Administration');
});
});
describe('Responsive Edge Cases', () => {
it('should handle very narrow mobile screens', () => {
cy.viewport(240, 320); // Very narrow mobile
cy.mount(``);
cy.window().then((win) => {
const sidenav = win.document.getElementById('narrow-mobile') as any;
sidenav.items = multiLevelNavItems;
sidenav.mobileCollapsible = true;
});
// Should still function on very narrow screens
cy.get('.usa-sidenav__toggle').should('be.visible');
cy.get('.usa-sidenav__toggle').click();
// Navigation should open without layout issues
cy.get('.usa-sidenav__nav').should('have.class', 'is-visible');
// Should not cause horizontal overflow
cy.get('usa-side-navigation').should(($el) => {
expect($el[0].scrollWidth).to.be.at.most($el[0].clientWidth + 30);
});
});
it('should handle empty navigation state responsively', () => {
viewports.forEach((viewport) => {
cy.viewport(viewport.width, viewport.height);
cy.mount(``);
cy.window().then((win) => {
const sidenav = win.document.getElementById(`empty-nav-${viewport.width}`) as any;
sidenav.items = [];
});
// Should handle empty state gracefully
cy.get('usa-side-navigation').should('be.visible');
cy.get('.usa-sidenav').should('be.visible');
// No layout issues with empty state
cy.get('usa-side-navigation').should(($el) => {
expect($el[0].scrollWidth).to.be.at.most($el[0].clientWidth + 10);
});
});
});
it('should handle dynamic navigation updates during viewport changes', () => {
cy.mount(``);
cy.window().then((win) => {
const sidenav = win.document.getElementById('dynamic-nav') as any;
sidenav.items = multiLevelNavItems;
});
// Start on desktop
cy.viewport(1200, 800);
cy.get('.usa-sidenav__item').should('have.length', multiLevelNavItems.length);
// Add more navigation items while switching to mobile
cy.window().then((win) => {
const sidenav = win.document.getElementById('dynamic-nav') as any;
const additionalItems = [
{ label: 'New Section', href: '/new' },
{ label: 'Another Section', href: '/another' },
];
sidenav.items = [...multiLevelNavItems, ...additionalItems];
});
cy.viewport(375, 667); // Switch to mobile
cy.get('.usa-sidenav__toggle').click();
cy.get('.usa-sidenav__item').should('have.length', multiLevelNavItems.length + 2);
// Navigation should adapt to new content and viewport
cy.get('.usa-sidenav__nav').should('have.class', 'is-visible');
});
it('should maintain accessibility across all responsive states', () => {
const testViewports = [
{ width: 375, height: 667 },
{ width: 768, height: 1024 },
{ width: 1200, height: 800 },
];
testViewports.forEach((viewport) => {
cy.viewport(viewport.width, viewport.height);
cy.mount(``);
cy.window().then((win) => {
const sidenav = win.document.getElementById(`a11y-nav-${viewport.width}`) as any;
sidenav.items = multiLevelNavItems;
if (viewport.width <= 768) {
sidenav.mobileCollapsible = true;
}
});
// Verify ARIA attributes at all sizes
cy.get('nav').should('have.attr', 'aria-label');
if (viewport.width <= 768) {
// Mobile accessibility
cy.get('.usa-sidenav__toggle').should('have.attr', 'aria-expanded');
cy.get('.usa-sidenav__toggle').click();
}
cy.get('.usa-sidenav__link').should('be.visible');
cy.get('.usa-current').should('have.attr', 'aria-current', 'page');
// Check keyboard navigation works
cy.get('.usa-sidenav__link').first().focus();
cy.focused().tab();
// Run accessibility test
cy.injectAxe();
cy.checkAccessibility();
});
});
});
});
});