// Component tests for usa-in-page-navigation import './index.ts'; import { testRapidClicking, testRapidKeyboardInteraction, COMMON_BUG_PATTERNS, } from '../../cypress/support/rapid-interaction-tests.ts'; describe('InPageNavigation Component Tests', () => { beforeEach(() => { // Set up console error tracking cy.window().then((win) => { cy.stub(win.console, 'error').as('consoleError'); }); }); it('should render component with default properties', () => { cy.mount(''); cy.get('usa-in-page-navigation').should('exist'); cy.get('usa-in-page-navigation').should('be.visible'); }); it('should handle rapid clicking without visual glitches', () => { cy.mount(''); // Rapid clicking without waiting - simulates real user behavior cy.get('usa-in-page-navigation').as('component'); // Multiple rapid clicks cy.get('@component').click().click().click().click().click(); cy.wait(500); // Let events settle // Component should remain functional cy.get('@component').should('exist'); cy.get('@component').should('be.visible'); }); it('should handle interaction during CSS transitions', () => { cy.mount(''); // Click during potential transitions cy.get('usa-in-page-navigation').click().click(); // Immediate second click cy.wait(1000); // Wait for animations // Should be in consistent state cy.get('usa-in-page-navigation').should('exist'); }); // Stress tests using utility functions describe('Stress Testing', () => { it('should handle event listener duplication pattern', () => { cy.mount(''); // Test for event listener duplication testRapidClicking({ selector: 'usa-in-page-navigation', clickCount: 15, description: 'event listener duplication', }); }); it('should handle race condition patterns', () => { cy.mount(''); // Test for race conditions during state changes cy.get('usa-in-page-navigation').as('component'); // Rapid interactions that might cause race conditions cy.get('@component').click().click().trigger('focus').trigger('blur').click(); cy.wait(1000); // Wait for all async operations // Component should still be functional cy.get('@component').should('exist'); cy.get('@component').should('be.visible'); }); }); // Accessibility testing - critical for government components it('should be accessible', () => { cy.mount(''); cy.injectAxe(); cy.checkAccessibility(); }); // Test that component maintains accessibility after interactions it('should maintain accessibility after rapid interactions', () => { cy.mount(''); // Perform various rapid interactions cy.get('usa-in-page-navigation').click().focus().blur().click().click(); cy.wait(500); // Accessibility should still be intact cy.injectAxe(); cy.checkAccessibility(); }); // Performance regression test it('should not cause memory leaks with rapid mounting/unmounting', () => { // This catches memory leaks and cleanup issues for (let i = 0; i < 5; i++) { cy.mount(''); cy.get('usa-in-page-navigation').should('exist'); // Cypress automatically cleans up between mounts } }); // Console error test - should not generate any JavaScript errors it('should not generate console errors during interactions', () => { cy.mount(''); // Various interactions that might cause errors cy.get('usa-in-page-navigation') .click() .trigger('mouseenter') .trigger('mouseleave') .focus() .blur(); cy.wait(500); // No console errors should have occurred cy.get('@consoleError').should('not.have.been.called'); }); // Event Propagation Control Testing (Critical Gap Fix) describe('Event Propagation Control', () => { it('should handle smooth scroll vs page navigation properly', () => { let smoothScrollInitiated = false; let pageNavigated = false; const navigationItems = [ { id: 'section1', text: 'Section 1', href: '#section-1' }, { id: 'section2', text: 'Section 2', href: '#section-2' }, { id: 'section3', text: 'Section 3', href: '#section-3' }, ]; cy.mount(`
Section 1 Content
Section 2 Content
Section 3 Content
`); cy.window().then((win) => { const nav = win.document.getElementById('test-nav') as any; nav.items = navigationItems; // Listen for smooth scroll behavior const originalScrollTo = win.scrollTo; win.scrollTo = function (x: number, y: number) { smoothScrollInitiated = true; originalScrollTo.call(this, x, y); }; // Listen for hash changes (page navigation) win.addEventListener('hashchange', () => { pageNavigated = true; }); }); // Click navigation link should initiate smooth scroll, not page navigation cy.get('usa-in-page-navigation a[href="#section-2"]') .click() .then(() => { // Should prevent default navigation behavior expect(pageNavigated).to.be.false; // URL should still update for accessibility/bookmarking cy.url().should('include', '#section-2'); }); }); it('should handle anchor link behavior with proper event propagation', () => { let anchorClicked = false; let parentClicked = false; let preventDefault = false; const navigationItems = [ { id: 'intro', text: 'Introduction', href: '#introduction' }, { id: 'content', text: 'Main Content', href: '#main-content' }, { id: 'conclusion', text: 'Conclusion', href: '#conclusion' }, ]; cy.mount(`
Introduction section
Main content section
Conclusion section
`); cy.window().then((win) => { const nav = win.document.getElementById('anchor-test') as any; const parent = win.document.getElementById('parent-container'); nav.items = navigationItems; // Listen for anchor clicks nav.addEventListener('click', (e: Event) => { anchorClicked = true; if (e.defaultPrevented) { preventDefault = true; } }); // Listen for parent clicks parent?.addEventListener('click', () => { parentClicked = true; }); }); // Click anchor link cy.get('usa-in-page-navigation a[href="#main-content"]') .click() .then(() => { expect(anchorClicked).to.be.true; // Should control event propagation appropriately // (Component should decide whether to prevent default or allow bubbling) }); }); it('should prevent click events during scroll animations', () => { let firstClickProcessed = false; let secondClickBlocked = false; let scrollAnimationActive = false; const navigationItems = [ { id: 'top', text: 'Top', href: '#top' }, { id: 'middle', text: 'Middle', href: '#middle' }, { id: 'bottom', text: 'Bottom', href: '#bottom' }, ]; cy.mount(`
Top section
Middle section
Bottom section
`); cy.window().then((win) => { const nav = win.document.getElementById('scroll-nav') as any; nav.items = navigationItems; // Mock scroll behavior to simulate animation const originalScrollTo = win.scrollTo; win.scrollTo = function (x: number, y: number) { scrollAnimationActive = true; setTimeout(() => { scrollAnimationActive = false; }, 500); originalScrollTo.call(this, x, y); }; // Listen for navigation clicks nav.addEventListener('click', (e: Event) => { if (!firstClickProcessed) { firstClickProcessed = true; } else if (scrollAnimationActive) { secondClickBlocked = true; e.preventDefault(); } }); }); // First click should work normally cy.get('usa-in-page-navigation a[href="#middle"]').click(); // Rapid second click during animation should be prevented cy.get('usa-in-page-navigation a[href="#bottom"]') .click() .then(() => { expect(firstClickProcessed).to.be.true; // Test demonstrates that component can implement click protection during animations }); }); it('should handle navigation within forms without triggering submission', () => { let formSubmitted = false; let navigationTriggered = false; const navigationItems = [ { id: 'step1', text: 'Step 1', href: '#form-step-1' }, { id: 'step2', text: 'Step 2', href: '#form-step-2' }, { id: 'step3', text: 'Step 3', href: '#form-step-3' }, ]; cy.mount(`
`); cy.window().then((win) => { const nav = win.document.getElementById('form-nav') as any; const form = win.document.getElementById('multi-step-form') as HTMLFormElement; nav.items = navigationItems; form.addEventListener('submit', (e: Event) => { e.preventDefault(); formSubmitted = true; }); nav.addEventListener('navigate', () => { navigationTriggered = true; }); }); // Navigation within form should not trigger form submission cy.get('usa-in-page-navigation a[href="#form-step-2"]') .click() .then(() => { expect(formSubmitted).to.be.false; }); // But submit button should still work cy.get('button[type="submit"]') .click() .then(() => { expect(formSubmitted).to.be.true; }); }); it('should handle nested navigation contexts properly', () => { let outerNavClicked = false; let innerNavClicked = false; let containerClicked = false; const outerNavItems = [ { id: 'overview', text: 'Overview', href: '#overview' }, { id: 'details', text: 'Details', href: '#details' }, ]; const innerNavItems = [ { id: 'detail1', text: 'Detail 1', href: '#detail-1' }, { id: 'detail2', text: 'Detail 2', href: '#detail-2' }, ]; cy.mount(`
Overview content
Detail 1 content
Detail 2 content
`); cy.window().then((win) => { const outerNav = win.document.getElementById('outer-nav') as any; const innerNav = win.document.getElementById('inner-nav') as any; const container = win.document.getElementById('nested-container'); outerNav.items = outerNavItems; innerNav.items = innerNavItems; outerNav.addEventListener('click', () => { outerNavClicked = true; }); innerNav.addEventListener('click', () => { innerNavClicked = true; }); container?.addEventListener('click', () => { containerClicked = true; }); }); // Inner navigation should not trigger outer navigation cy.get('#inner-nav a[href="#detail-1"]') .click() .then(() => { expect(innerNavClicked).to.be.true; // Test event isolation between nested navigations }); // Reset flags cy.window().then(() => { outerNavClicked = false; innerNavClicked = false; }); // Outer navigation should work independently cy.get('#outer-nav a[href="#details"]') .click() .then(() => { expect(outerNavClicked).to.be.true; expect(innerNavClicked).to.be.false; }); }); it('should handle keyboard navigation with proper event control', () => { let keyboardNavigationTriggered = false; let mouseNavigationTriggered = false; const navigationItems = [ { id: 'keyboard1', text: 'Section A', href: '#section-a' }, { id: 'keyboard2', text: 'Section B', href: '#section-b' }, { id: 'keyboard3', text: 'Section C', href: '#section-c' }, ]; cy.mount(`
Section A content
Section B content
Section C content
`); cy.window().then((win) => { const nav = win.document.getElementById('keyboard-nav') as any; nav.items = navigationItems; nav.addEventListener('keydown', (e: KeyboardEvent) => { if (e.key === 'Enter' || e.key === ' ') { keyboardNavigationTriggered = true; } }); nav.addEventListener('click', () => { mouseNavigationTriggered = true; }); }); // Test keyboard navigation cy.get('usa-in-page-navigation a[href="#section-b"]') .focus() .type('{enter}') .then(() => { expect(keyboardNavigationTriggered).to.be.true; }); // Reset flags cy.window().then(() => { keyboardNavigationTriggered = false; mouseNavigationTriggered = false; }); // Test mouse navigation cy.get('usa-in-page-navigation a[href="#section-c"]') .click() .then(() => { expect(mouseNavigationTriggered).to.be.true; }); }); it('should prevent double navigation when links are clicked rapidly', () => { let navigationCount = 0; let lastNavigationTime = 0; const navigationItems = [ { id: 'rapid1', text: 'Target A', href: '#target-a' }, { id: 'rapid2', text: 'Target B', href: '#target-b' }, ]; cy.mount(`
Target A content
Target B content
`); cy.window().then((win) => { const nav = win.document.getElementById('rapid-nav') as any; nav.items = navigationItems; nav.addEventListener('click', () => { const currentTime = Date.now(); if (currentTime - lastNavigationTime > 100) { // Debounce rapid clicks navigationCount++; lastNavigationTime = currentTime; } }); }); // Rapid clicks should be debounced cy.get('usa-in-page-navigation a[href="#target-a"]').click().click().click(); cy.wait(200).then(() => { // Should only register reasonable number of navigations expect(navigationCount).to.be.lessThan(3); }); }); }); });