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 CustomEvent; eventFired = true; eventDetail = customEvent.detail; }); element.content = '

New 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 CustomEvent; eventDetail = customEvent.detail; }); element.variant = 'condensed'; element.content = '

Test

'; (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 = `

Heading

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('

Heading

'); expect(contentDiv?.innerHTML).toContain('