import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import '../prose/usa-prose.ts'; import type { USAProse, ProseDetail } from '../prose/usa-prose.js'; import { waitForUpdate, testPropertyChanges, validateComponentJavaScript, } from '../../../__tests__/test-utils.js'; import { testComponentAccessibility, USWDS_A11Y_CONFIG, } from '../../../__tests__/accessibility-utils.js'; describe('USAProse', () => { let element: USAProse; beforeEach(() => { element = document.createElement('usa-prose') as USAProse; document.body.appendChild(element); }); afterEach(() => { element.remove(); }); describe('Properties', () => { it('should have default properties', () => { expect(element.variant).toBe('default'); expect(element.width).toBe('default'); expect(element.content).toBe(''); }); it('should update variant property', async () => { await testPropertyChanges( element, 'variant', ['condensed', 'expanded', 'default'], (el, value) => { expect(el.variant).toBe(value); } ); }); it('should update width property', async () => { await testPropertyChanges(element, 'width', ['narrow', 'wide', 'default'], (el, value) => { expect(el.width).toBe(value); }); }); it('should update content property', async () => { element.content = '
Test content
'; await waitForUpdate(element); expect(element.content).toBe('Test content
'); }); }); describe('Rendering', () => { it('should render prose container with correct classes', async () => { await waitForUpdate(element); const proseDiv = element.querySelector('.usa-prose'); expect(proseDiv).toBeTruthy(); expect(proseDiv?.classList.contains('usa-prose')).toBe(true); }); it('should apply variant classes correctly', async () => { element.variant = 'condensed'; await waitForUpdate(element); const proseDiv = element.querySelector('.usa-prose'); expect(proseDiv?.classList.contains('usa-prose--condensed')).toBe(true); element.variant = 'expanded'; await waitForUpdate(element); expect(proseDiv?.classList.contains('usa-prose--expanded')).toBe(true); }); it('should apply width classes correctly', async () => { element.width = 'narrow'; await waitForUpdate(element); const proseDiv = element.querySelector('.usa-prose'); expect(proseDiv?.classList.contains('usa-prose--narrow')).toBe(true); element.width = 'wide'; await waitForUpdate(element); expect(proseDiv?.classList.contains('usa-prose--wide')).toBe(true); }); it('should combine variant and width classes', async () => { element.variant = 'condensed'; element.width = 'narrow'; await waitForUpdate(element); const proseDiv = element.querySelector('.usa-prose'); expect(proseDiv?.classList.contains('usa-prose')).toBe(true); expect(proseDiv?.classList.contains('usa-prose--condensed')).toBe(true); expect(proseDiv?.classList.contains('usa-prose--narrow')).toBe(true); }); it('should render slot 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 render HTML content property', async () => { element.content = 'HTML content
'; await waitForUpdate(element); const contentDiv = element.querySelector('div:last-child'); expect(contentDiv?.innerHTML).toContain('HTML content
'); }); it('should render both slot and content property', async () => { // Set content first, then add slot content element.content = 'Property content
'; await waitForUpdate(element); // Add slot content using appendChild to avoid innerHTML conflicts const slotParagraph = document.createElement('p'); slotParagraph.textContent = 'Slot content'; element.appendChild(slotParagraph); await waitForUpdate(element); expect(element.textContent).toContain('Slot content'); expect(element.textContent).toContain('Property content'); }); }); describe('CSS Classes', () => { it('should generate correct prose classes for default state', () => { const classes = (element as any).getProseClasses(); expect(classes).toBe('usa-prose'); }); it('should generate correct classes for variant', () => { element.variant = 'condensed'; const classes = (element as any).getProseClasses(); expect(classes).toBe('usa-prose usa-prose--condensed'); }); it('should generate correct classes for width', () => { element.width = 'narrow'; const classes = (element as any).getProseClasses(); expect(classes).toBe('usa-prose usa-prose--narrow'); }); it('should generate correct classes for both variant and width', () => { element.variant = 'expanded'; element.width = 'wide'; const classes = (element as any).getProseClasses(); expect(classes).toBe('usa-prose usa-prose--expanded usa-prose--wide'); }); }); describe('Events', () => { it('should dispatch prose-change event when content changes', async () => { let eventFired = false; let eventDetail: ProseDetail | null = null; element.addEventListener('prose-change', (e: Event) => { const customEvent = e as CustomEventNew content
'; (element as any).handleContentChange(); expect(eventFired).toBe(true); expect((eventDetail as any)?.content).toBe('New content
'); expect((eventDetail as any)?.variant).toBe('default'); }); it('should include current variant in event detail', async () => { let eventDetail: ProseDetail | null = null; element.addEventListener('prose-change', (e: Event) => { const customEvent = e as CustomEventTest
'; (element as any).handleContentChange(); expect((eventDetail as any)?.variant).toBe('condensed'); }); }); describe('Public API Methods', () => { it('should set variant using setVariant method', () => { const spy = vi.spyOn(element as any, 'handleContentChange'); element.setVariant('condensed'); expect(element.variant).toBe('condensed'); expect(spy).toHaveBeenCalled(); }); it('should set width using setWidth method', () => { const spy = vi.spyOn(element as any, 'handleContentChange'); element.setWidth('narrow'); expect(element.width).toBe('narrow'); expect(spy).toHaveBeenCalled(); }); it('should set content using setContent method', () => { const spy = vi.spyOn(element as any, 'handleContentChange'); element.setContent('New content
'); expect(element.content).toBe('New content
'); expect(spy).toHaveBeenCalled(); }); it('should get content using getContent method', () => { element.content = 'Test content
'; expect(element.getContent()).toBe('Test content
'); }); it('should add content using addContent method', () => { const spy = vi.spyOn(element as any, 'handleContentChange'); element.content = 'Initial
'; element.addContent('Additional
'); expect(element.content).toBe('Initial
Additional
'); expect(spy).toHaveBeenCalled(); }); it('should clear content using clearContent method', () => { const spy = vi.spyOn(element as any, 'handleContentChange'); element.content = 'Some content
'; element.clearContent(); expect(element.content).toBe(''); expect(spy).toHaveBeenCalled(); }); }); describe('Content Handling', () => { it('should handle empty content gracefully', async () => { element.content = ''; await waitForUpdate(element); const proseDiv = element.querySelector('.usa-prose'); expect(proseDiv).toBeTruthy(); // Should only have slot content, no content div const contentDivs = element.querySelectorAll('div'); expect(contentDivs.length).toBe(1); // Just the prose container }); it('should handle complex HTML content', async () => { const complexContent = `Paragraph with bold and italic text.
Quote content`; element.content = complexContent; await waitForUpdate(element); const contentDiv = element.querySelector('div:last-child'); expect(contentDiv?.innerHTML).toContain('
'); }); it('should handle code content', async () => { element.content = ''; await waitForUpdate(element); const contentDiv = element.querySelector('div:last-child'); expect(contentDiv?.innerHTML).toContain('const x = 5;'); }); it('should handle table content', async () => { const tableContent = `const x = 5;`; element.content = tableContent; await waitForUpdate(element); const contentDiv = element.querySelector('div:last-child'); expect(contentDiv?.innerHTML).toContain('
Header 1 Header 2 Cell 1 Cell 2 '); expect(contentDiv?.innerHTML).toContain(''); expect(contentDiv?.innerHTML).toContain(''); }); }); 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 proseDiv = element.querySelector('.usa-prose'); expect(proseDiv).toBeTruthy(); expect(proseDiv?.parentElement).toBe(element); }); }); describe('Variant Behaviors', () => { it('should support all variant options', async () => { const variants = ['default', 'condensed', 'expanded'] as const; for (const variant of variants) { element.variant = variant; await waitForUpdate(element); expect(element.variant).toBe(variant); } }); it('should support all width options', async () => { const widths = ['default', 'narrow', 'wide'] as const; for (const width of widths) { element.width = width; await waitForUpdate(element); expect(element.width).toBe(width); } }); }); describe('Accessibility', () => { it('should maintain semantic HTML structure', async () => { element.content = '
Heading
Content
'; await waitForUpdate(element); const heading = element.querySelector('h2'); const paragraph = element.querySelector('p'); expect(heading).toBeTruthy(); expect(paragraph).toBeTruthy(); expect(heading?.textContent).toBe('Heading'); }); it('should preserve accessibility attributes in content', async () => { element.content = 'Link'; await waitForUpdate(element); const img = element.querySelector('img'); const link = element.querySelector('a'); expect(img?.getAttribute('alt')).toBe('Test image'); expect(link?.getAttribute('aria-label')).toBe('Test link'); }); }); describe('Edge Cases', () => { it('should handle special characters in content', async () => { const specialContent = '
<script>alert("test")</script> & special chars
'; element.content = specialContent; await waitForUpdate(element); const contentDiv = element.querySelector('div:last-child'); expect(contentDiv?.innerHTML).toContain('<script>'); expect(contentDiv?.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); expect(element.querySelector('p')).toBeTruthy(); }); it('should handle rapid property changes', async () => { element.variant = 'condensed'; element.width = 'narrow'; element.content = 'Test 1
'; element.variant = 'expanded'; element.width = 'wide'; element.content = 'Test 2
'; await waitForUpdate(element); expect(element.variant).toBe('expanded'); expect(element.width).toBe('wide'); expect(element.content).toBe('Test 2
'); }); }); describe('Performance', () => { it('should handle multiple rapid updates efficiently', async () => { const startTime = performance.now(); for (let i = 0; i < 50; i++) { element.content = `Content ${i}
`; element.variant = i % 2 === 0 ? 'condensed' : 'expanded'; } await waitForUpdate(element); const endTime = performance.now(); expect(endTime - startTime).toBeLessThan(1000); // Should complete in under 1 second }); describe('JavaScript Implementation Validation', () => { it('should pass JavaScript implementation validation', async () => { // Validate USWDS JavaScript implementation patterns const componentPath = `${process.cwd()}/src/components/prose/usa-prose.ts`; const validation = validateComponentJavaScript(componentPath, 'prose'); 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 () => { // Test default empty prose await element.updateComplete; await testComponentAccessibility(element, USWDS_A11Y_CONFIG.FULL_COMPLIANCE); // Test simple prose content element.content = `Federal Program Information
This section provides important information about federal programs available to citizens.
For more information, please contact your local representative.
`; await element.updateComplete; await testComponentAccessibility(element, USWDS_A11Y_CONFIG.FULL_COMPLIANCE); // Test prose with condensed variant element.variant = 'condensed'; element.content = `Application Requirements
Please ensure you have the following documents ready:
`; await element.updateComplete; await testComponentAccessibility(element, USWDS_A11Y_CONFIG.FULL_COMPLIANCE); // Test prose with expanded variant and narrow width element.variant = 'expanded'; element.width = 'narrow'; element.content = `
- Valid identification (passport or driver's license)
- Proof of income (tax returns or pay stubs)
- Social Security card
Government Services Portal
Welcome to the official government services portal. Here you can access various federal programs and services.
Available Services
Our services include healthcare enrollment, tax filing assistance, and social security benefits.
`; await element.updateComplete; await testComponentAccessibility(element, USWDS_A11Y_CONFIG.FULL_COMPLIANCE); // Test prose with wide width and rich content element.variant = 'default'; element.width = 'wide'; element.content = `"Government exists to serve the people, and we are committed to providing accessible, efficient services to all citizens."
Federal Benefits Overview
The federal government offers a wide range of benefits and services to eligible citizens.
`; await element.updateComplete; await testComponentAccessibility(element, USWDS_A11Y_CONFIG.FULL_COMPLIANCE); }); it('should maintain accessibility during dynamic updates', async () => { // Set initial accessible state element.content = `
Common Federal Benefits Benefit Type Eligibility How to Apply Social Security Age 62 or older, or disability Online at ssa.gov Medicare Age 65 or older, or disability Online or call 1-800-MEDICARE Initial Content
This is the initial content for accessibility testing.
`; await element.updateComplete; await testComponentAccessibility(element, USWDS_A11Y_CONFIG.FULL_COMPLIANCE); // Update variant element.variant = 'condensed'; await element.updateComplete; await testComponentAccessibility(element, USWDS_A11Y_CONFIG.FULL_COMPLIANCE); // Update width element.width = 'narrow'; await element.updateComplete; await testComponentAccessibility(element, USWDS_A11Y_CONFIG.FULL_COMPLIANCE); // Update content with headings and structure element.content = `Updated Document
Section 1
Updated content with proper heading structure.
Subsection
This ensures proper heading hierarchy for screen readers.
Section 2
Another section with important information.
`; await element.updateComplete; await testComponentAccessibility(element, USWDS_A11Y_CONFIG.FULL_COMPLIANCE); // Update to wide expanded content element.variant = 'expanded'; element.width = 'wide'; await element.updateComplete; await testComponentAccessibility(element, USWDS_A11Y_CONFIG.FULL_COMPLIANCE); }); it('should pass accessibility with complex government content', async () => { // Federal agency content with proper structure element.variant = 'default'; element.width = 'default'; element.content = `Department of Federal Services
Mission Statement
Our mission is to provide efficient, accessible government services to all citizens of the United States.
Contact Information
- Phone:
- 1-800-555-1234
- Email:
- info@federal.gov
- Address:
- 123 Federal Building
Washington, DC 20001Office Hours
Monday through Friday: 8:00 AM - 5:00 PM (EST)
Saturday: 9:00 AM - 1:00 PM (EST)
Sunday: ClosedEmergency Services
For emergency assistance outside of normal business hours, please call our 24/7 emergency hotline at 1-800-555-9999.
Accessibility Statement
This agency is committed to providing services that are accessible to all individuals. If you need assistance or have accessibility concerns, please contact us using the information above.
`; await element.updateComplete; await testComponentAccessibility(element, USWDS_A11Y_CONFIG.FULL_COMPLIANCE); }); }); });