import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import '../summary-box/usa-summary-box.ts'; import type { USASummaryBox } from '../summary-box/usa-summary-box.js'; import { testComponentAccessibility, USWDS_A11Y_CONFIG, } from '../../../__tests__/accessibility-utils.js'; import { waitForUpdate, testPropertyChanges, validateComponentJavaScript, } from '../../../__tests__/test-utils.js'; describe('USASummaryBox', () => { let element: USASummaryBox; beforeEach(() => { element = document.createElement('usa-summary-box') as USASummaryBox; document.body.appendChild(element); }); afterEach(() => { element.remove(); }); describe('Properties', () => { it('should have default properties', () => { expect(element.heading).toBe(''); expect(element.content).toBe(''); expect(element.headingLevel).toBe('h3'); }); it('should update heading property', async () => { await testPropertyChanges( element, 'heading', ['Summary', 'Important Information', 'Federal Guidelines'], (el, value) => { expect(el.heading).toBe(value); } ); }); it('should update content property', async () => { element.content = '
Test content
'; await waitForUpdate(element); expect(element.content).toBe('Test content
'); }); it('should update headingLevel property', async () => { await testPropertyChanges( element, 'headingLevel', ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'], (el, value) => { expect(el.headingLevel).toBe(value); } ); }); }); describe('Rendering', () => { it('should render summary box container with correct classes', async () => { await waitForUpdate(element); const summaryBox = element.querySelector('.usa-summary-box'); expect(summaryBox).toBeTruthy(); expect(summaryBox?.classList.contains('usa-summary-box')).toBe(true); const body = element.querySelector('.usa-summary-box__body'); expect(body).toBeTruthy(); expect(body?.classList.contains('usa-summary-box__body')).toBe(true); const text = element.querySelector('.usa-summary-box__text'); expect(text).toBeTruthy(); expect(text?.classList.contains('usa-summary-box__text')).toBe(true); }); it('should render heading with correct level and class', async () => { element.heading = 'Test Heading'; element.headingLevel = 'h2'; await waitForUpdate(element); const heading = element.querySelector('h2.usa-summary-box__heading'); expect(heading).toBeTruthy(); expect(heading?.textContent).toBe('Test Heading'); expect(heading?.classList.contains('usa-summary-box__heading')).toBe(true); }); it('should render all heading levels correctly', async () => { const headingLevels = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'] as const; for (const level of headingLevels) { element.heading = `Test ${level.toUpperCase()}`; element.headingLevel = level; await waitForUpdate(element); const heading = element.querySelector(`${level}.usa-summary-box__heading`); expect(heading).toBeTruthy(); expect(heading?.textContent).toBe(`Test ${level.toUpperCase()}`); } }); it('should default to h3 for invalid heading level', async () => { element.heading = 'Test Heading'; element.headingLevel = 'invalid' as any; await waitForUpdate(element); const heading = element.querySelector('h3.usa-summary-box__heading'); expect(heading).toBeTruthy(); expect(heading?.textContent).toBe('Test Heading'); }); it('should not render heading when heading property is empty', async () => { element.heading = ''; await waitForUpdate(element); const heading = element.querySelector('.usa-summary-box__heading'); expect(heading).toBeFalsy(); }); it('should render content via property', async () => { element.content = 'Property content
'; await waitForUpdate(element); const textContainer = element.querySelector('.usa-summary-box__text'); expect(textContainer?.innerHTML).toContain('Property content
'); }); it('should render slot content when no property content', async () => { const slotParagraph = document.createElement('p'); slotParagraph.textContent = 'Slot content'; element.appendChild(slotParagraph); await waitForUpdate(element); expect(element.textContent).toContain('Slot content'); }); it('should prioritize content property over slot content', async () => { // Set content property first element.content = 'Property content
'; // Add slot content const slotParagraph = document.createElement('p'); slotParagraph.textContent = 'Slot content'; element.appendChild(slotParagraph); await waitForUpdate(element); const textContainer = element.querySelector('.usa-summary-box__text'); expect(textContainer?.innerHTML).toContain('Property content'); // Slot content should not be rendered when property content exists expect(textContainer?.innerHTML).not.toContain('Slot content'); }); it('should handle complex HTML content', async () => { const complexContent = `Important: This is a complex summary.
Summary content
'; await waitForUpdate(element); // Check outer container const summaryBox = element.querySelector('.usa-summary-box'); expect(summaryBox).toBeTruthy(); // Check body container const body = summaryBox?.querySelector('.usa-summary-box__body'); expect(body).toBeTruthy(); // Check heading structure const heading = body?.querySelector('.usa-summary-box__heading'); expect(heading).toBeTruthy(); expect(heading?.tagName.toLowerCase()).toBe('h3'); // Check text container const text = body?.querySelector('.usa-summary-box__text'); expect(text).toBeTruthy(); }); it('should maintain proper DOM hierarchy', async () => { element.heading = 'Test'; element.content = 'Content
'; await waitForUpdate(element); const summaryBox = element.querySelector('.usa-summary-box'); const body = summaryBox?.querySelector('.usa-summary-box__body'); const heading = body?.querySelector('.usa-summary-box__heading'); const text = body?.querySelector('.usa-summary-box__text'); // Verify hierarchy expect(summaryBox?.parentElement).toBe(element); expect(body?.parentElement).toBe(summaryBox); expect(heading?.parentElement).toBe(body); expect(text?.parentElement).toBe(body); }); }); describe('Accessibility', () => { it('should use semantic heading elements', async () => { element.heading = 'Accessible Heading'; element.headingLevel = 'h2'; await waitForUpdate(element); const heading = element.querySelector('h2'); expect(heading).toBeTruthy(); expect(heading?.textContent).toBe('Accessible Heading'); }); it('should maintain heading hierarchy', async () => { // Test that different heading levels are rendered as proper semantic elements const testCases = [ { level: 'h1' as const, expectedTag: 'H1' }, { level: 'h2' as const, expectedTag: 'H2' }, { level: 'h3' as const, expectedTag: 'H3' }, { level: 'h4' as const, expectedTag: 'H4' }, { level: 'h5' as const, expectedTag: 'H5' }, { level: 'h6' as const, expectedTag: 'H6' }, ]; for (const testCase of testCases) { element.heading = `Level ${testCase.level}`; element.headingLevel = testCase.level; await waitForUpdate(element); const heading = element.querySelector('.usa-summary-box__heading'); expect(heading?.tagName).toBe(testCase.expectedTag); } }); it('should preserve accessibility attributes in content', async () => { element.content = ''; await waitForUpdate(element); const link = element.querySelector('a'); expect(link?.getAttribute('aria-label')).toBe('Test link'); }); it('should support slotted content with accessibility attributes', async () => { const button = document.createElement('button'); button.setAttribute('aria-label', 'Close summary'); button.textContent = 'Close'; element.appendChild(button); await waitForUpdate(element); const slottedButton = element.querySelector('button'); expect(slottedButton?.getAttribute('aria-label')).toBe('Close summary'); }); }); describe('Light DOM Rendering', () => { it('should use light DOM rendering', () => { expect(element.shadowRoot).toBe(null); expect(element.renderRoot).toBe(element); }); it('should apply USWDS classes directly to light DOM', async () => { await waitForUpdate(element); const summaryBox = element.querySelector('.usa-summary-box'); expect(summaryBox).toBeTruthy(); expect(summaryBox?.parentElement).toBe(element); }); }); describe('Content Handling', () => { it('should handle empty content gracefully', async () => { element.content = ''; await waitForUpdate(element); const textContainer = element.querySelector('.usa-summary-box__text'); expect(textContainer).toBeTruthy(); // In light DOM pattern, slot elements are replaced with slotted content // When no slotted content exists, the slot is removed leaving an empty container const slotElement = textContainer?.querySelector('slot'); expect(slotElement).toBeFalsy(); // Slot should be replaced/removed by moveSlottedContent() }); it('should handle whitespace-only content', async () => { element.content = ' \n\t '; await waitForUpdate(element); const textContainer = element.querySelector('.usa-summary-box__text'); // Whitespace-only content should still be rendered via property expect(textContainer?.innerHTML).toContain(' \n\t '); }); it('should handle special characters in content', async () => { const specialContent = '<script>alert("test")</script> & special chars
'; element.content = specialContent; await waitForUpdate(element); const textContainer = element.querySelector('.usa-summary-box__text'); expect(textContainer?.innerHTML).toContain('<script>'); expect(textContainer?.innerHTML).toContain('&'); }); it('should handle very long content', async () => { const longContent = '' + 'Very long content. '.repeat(1000) + '
'; element.content = longContent; await waitForUpdate(element); expect(element.content.length).toBeGreaterThan(10000); const textContainer = element.querySelector('.usa-summary-box__text'); expect(textContainer?.querySelector('p')).toBeTruthy(); }); // SKIP: jsdom limitation - MOVE TO CYPRESS // ✅ CYPRESS COVERAGE: cypress/e2e/summary-box-content.cy.ts // Tests slot/property content transitions in real browser with innerHTML updates }); describe('Edge Cases', () => { it('should handle rapid property changes', async () => { element.heading = 'First'; element.headingLevel = 'h1'; element.content = 'First content
'; element.heading = 'Second'; element.headingLevel = 'h2'; element.content = 'Second content
'; await waitForUpdate(element); expect(element.heading).toBe('Second'); expect(element.headingLevel).toBe('h2'); expect(element.content).toBe('Second content
'); const heading = element.querySelector('h2.usa-summary-box__heading'); expect(heading?.textContent).toBe('Second'); }); it('should handle null and undefined values', async () => { element.heading = null as any; element.content = undefined as any; await waitForUpdate(element); // Should handle gracefully without errors const heading = element.querySelector('.usa-summary-box__heading'); expect(heading).toBeFalsy(); const textContainer = element.querySelector('.usa-summary-box__text'); expect(textContainer).toBeTruthy(); }); it('should handle boolean values as strings', async () => { element.heading = true as any; element.content = false as any; await waitForUpdate(element); const heading = element.querySelector('.usa-summary-box__heading'); expect(heading?.textContent).toBe('true'); // Boolean false gets coerced to empty string, so slot is rendered initially // In light DOM pattern, slot elements are replaced with slotted content by moveSlottedContent() const textContainer = element.querySelector('.usa-summary-box__text'); const slotElement = textContainer?.querySelector('slot'); expect(slotElement).toBeFalsy(); // Slot should be replaced/removed by moveSlottedContent() }); }); describe('Performance', () => { it('should handle multiple rapid updates efficiently', async () => { const startTime = performance.now(); for (let i = 0; i < 50; i++) { element.heading = `Heading ${i}`; element.content = `Content ${i}
`; element.headingLevel = i % 2 === 0 ? 'h2' : 'h3'; } await waitForUpdate(element); const endTime = performance.now(); expect(endTime - startTime).toBeLessThan(1000); // Should complete in under 1 second // Verify final state expect(element.heading).toBe('Heading 49'); expect(element.content).toBe('Content 49
'); expect(element.headingLevel).toBe('h3'); }); // SKIP: jsdom limitation - MOVE TO CYPRESS // ✅ CYPRESS COVERAGE: cypress/e2e/summary-box-content.cy.ts // Tests memory leak prevention with multiple content changes in real browser }); describe('Component Lifecycle', () => { it('should render correctly after being moved in DOM', async () => { element.heading = 'Test Heading'; element.content = 'Test content
'; await waitForUpdate(element); // Move to different parent const newParent = document.createElement('div'); document.body.appendChild(newParent); newParent.appendChild(element); await waitForUpdate(element); const heading = element.querySelector('.usa-summary-box__heading'); expect(heading?.textContent).toBe('Test Heading'); newParent.remove(); }); it('should maintain state through property updates', async () => { element.heading = 'Initial'; await waitForUpdate(element); element.heading = 'Updated'; element.content = 'New content
'; await waitForUpdate(element); expect(element.heading).toBe('Updated'); expect(element.content).toBe('New content
'); const heading = element.querySelector('.usa-summary-box__heading'); expect(heading?.textContent).toBe('Updated'); }); }); describe('Application Use Cases', () => { it('should handle federal policy summary', async () => { element.heading = 'Executive Order Summary'; element.headingLevel = 'h2'; element.content = `Executive Order 14028 - Improving the Nation's Cybersecurity
URGENT: Federal offices in DC area closed due to severe weather.
Effective: Immediately until further notice
Essential personnel: Follow emergency protocols
`; await waitForUpdate(element); const heading = element.querySelector('h1.usa-summary-box__heading'); expect(heading?.textContent).toBe('Weather Emergency Alert'); const content = element.querySelector('.usa-summary-box__text'); expect(content?.innerHTML).toContain('URGENT'); expect(content?.innerHTML).toContain('Essential personnel'); }); it('should handle benefits program summary', async () => { element.heading = 'SNAP Benefits Eligibility'; element.content = `The Supplemental Nutrition Assistance Program (SNAP) helps eligible families purchase nutritious food.
Income Limits (2024):
Apply online: benefits.gov
`; await waitForUpdate(element); const heading = element.querySelector('h3.usa-summary-box__heading'); expect(heading?.textContent).toBe('SNAP Benefits Eligibility'); const content = element.querySelector('.usa-summary-box__text'); expect(content?.innerHTML).toContain('Supplemental Nutrition Assistance Program'); expect(content?.innerHTML).toContain('Income Limits'); expect(content?.innerHTML).toContain('benefits.gov'); }); describe('JavaScript Implementation Validation', () => { it('should pass JavaScript implementation validation', async () => { // Validate USWDS JavaScript implementation patterns const componentPath = `${process.cwd()}/src/components/summary-box/usa-summary-box.ts`; const validation = validateComponentJavaScript(componentPath, 'summary-box'); if (!validation.isValid) { console.warn('JavaScript validation issues:', validation.issues); } // JavaScript validation should pass for critical integration patterns expect(validation.score).toBeGreaterThan(50); // Allow some non-critical issues // Critical USWDS integration should be present const criticalIssues = validation.issues.filter((issue) => issue.includes('Missing USWDS JavaScript integration') ); expect(criticalIssues.length).toBe(0); }); }); }); describe('Accessibility Compliance (CRITICAL)', () => { it('should pass comprehensive accessibility tests (same as Storybook)', async () => { // Setup summary box with comprehensive government content element.heading = 'Important Program Information'; element.headingLevel = 'h2'; element.content = `This summary box contains important information about government benefits and services.
Key Benefits:
How to Apply: Visit USA.gov for more information.
`; await waitForUpdate(element); // Run comprehensive accessibility audit await testComponentAccessibility(element, USWDS_A11Y_CONFIG.FULL_COMPLIANCE); }); it('should pass accessibility tests with different heading levels', async () => { element.heading = 'Section Summary'; element.headingLevel = 'h3'; element.content = `This section provides key information about the topic.
Important Note: Please review all requirements before proceeding.
`; await waitForUpdate(element); await testComponentAccessibility(element, USWDS_A11Y_CONFIG.FULL_COMPLIANCE); }); it('should pass accessibility tests with complex content and links', async () => { element.heading = 'Federal Assistance Programs'; element.headingLevel = 'h4'; element.content = `The following federal programs provide assistance to qualified individuals and families:
For more information about eligibility requirements, visit the eligibility page or contact your local office.
Application Deadlines: Applications must be submitted by the 15th of each month.
`; await waitForUpdate(element); await testComponentAccessibility(element, USWDS_A11Y_CONFIG.FULL_COMPLIANCE); }); it('should pass accessibility tests with minimal content', async () => { element.heading = 'Simple Notice'; element.headingLevel = 'h2'; element.content = 'This is a brief informational notice.
'; await waitForUpdate(element); await testComponentAccessibility(element, USWDS_A11Y_CONFIG.FULL_COMPLIANCE); }); }); });