import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import '../site-alert/usa-site-alert.ts'; import type { USASiteAlert } from '../site-alert/usa-site-alert.js'; import { waitForUpdate, testPropertyChanges, validateComponentJavaScript, } from '../../../__tests__/test-utils.js'; import { testComponentAccessibility, USWDS_A11Y_CONFIG, } from '../../../__tests__/accessibility-utils.js'; describe('USASiteAlert', () => { let element: USASiteAlert; beforeEach(() => { element = document.createElement('usa-site-alert') as USASiteAlert; document.body.appendChild(element); }); afterEach(() => { element.remove(); }); describe('Properties', () => { it('should have default properties', () => { expect(element.type).toBe('info'); expect(element.heading).toBe(''); expect(element.content).toBe(''); expect(element.slim).toBe(false); expect(element.noIcon).toBe(false); expect(element.closable).toBe(false); expect(element.visible).toBe(true); expect(element.closeLabel).toBe('Close'); }); it('should update type property', async () => { await testPropertyChanges(element, 'type', ['info', 'emergency'], (el, value) => { expect(el.type).toBe(value); }); }); it('should update heading property', async () => { await testPropertyChanges( element, 'heading', ['Alert Heading', 'Emergency Notice', 'System Update'], (el, value) => { expect(el.heading).toBe(value); } ); }); it('should update content property', async () => { element.content = '

Alert content

'; await waitForUpdate(element); expect(element.content).toBe('

Alert content

'); }); it('should update boolean properties', async () => { const booleanProps = ['slim', 'noIcon', 'closable', 'visible'] as const; for (const prop of booleanProps) { element[prop] = true; await waitForUpdate(element); expect(element[prop]).toBe(true); element[prop] = false; await waitForUpdate(element); expect(element[prop]).toBe(false); } }); it('should update closeLabel property', async () => { element.closeLabel = 'Dismiss Alert'; await waitForUpdate(element); expect(element.closeLabel).toBe('Dismiss Alert'); }); }); describe('Rendering', () => { it('should render site alert container with correct classes', async () => { await waitForUpdate(element); const siteAlert = element.querySelector('.usa-site-alert'); expect(siteAlert).toBeTruthy(); expect(siteAlert?.classList.contains('usa-site-alert')).toBe(true); expect(siteAlert?.classList.contains('usa-site-alert--info')).toBe(true); const alertBody = element.querySelector('.usa-alert__body'); expect(alertBody).toBeTruthy(); expect(alertBody?.classList.contains('usa-alert__body')).toBe(true); }); it('should apply type classes correctly', async () => { // Test info type (default) await waitForUpdate(element); let siteAlert = element.querySelector('.usa-site-alert'); expect(siteAlert?.classList.contains('usa-site-alert--info')).toBe(true); // Test emergency type element.type = 'emergency'; await waitForUpdate(element); siteAlert = element.querySelector('.usa-site-alert'); expect(siteAlert?.classList.contains('usa-site-alert--emergency')).toBe(true); expect(siteAlert?.classList.contains('usa-site-alert--info')).toBe(false); }); it('should apply slim modifier class', async () => { element.slim = true; await waitForUpdate(element); const siteAlert = element.querySelector('.usa-site-alert'); expect(siteAlert?.classList.contains('usa-site-alert--slim')).toBe(true); }); it('should apply no-icon modifier class', async () => { element.noIcon = true; await waitForUpdate(element); const siteAlert = element.querySelector('.usa-site-alert'); expect(siteAlert?.classList.contains('usa-site-alert--no-icon')).toBe(true); }); it('should combine multiple modifier classes', async () => { element.type = 'emergency'; element.slim = true; element.noIcon = true; await waitForUpdate(element); const siteAlert = element.querySelector('.usa-site-alert'); expect(siteAlert?.classList.contains('usa-site-alert')).toBe(true); expect(siteAlert?.classList.contains('usa-site-alert--emergency')).toBe(true); expect(siteAlert?.classList.contains('usa-site-alert--slim')).toBe(true); expect(siteAlert?.classList.contains('usa-site-alert--no-icon')).toBe(true); }); it('should render heading when provided', async () => { element.heading = 'Test Alert Heading'; await waitForUpdate(element); const heading = element.querySelector('.usa-alert__heading'); expect(heading).toBeTruthy(); expect(heading?.textContent?.trim()).toBe('Test Alert Heading'); expect(heading?.tagName.toLowerCase()).toBe('h3'); }); it('should render empty heading when not provided', async () => { element.heading = ''; await waitForUpdate(element); const heading = element.querySelector('.usa-alert__heading'); expect(heading).toBeTruthy(); expect(heading?.textContent?.trim()).toBe(''); }); it('should render content via slot', async () => { const slotContent = document.createElement('p'); slotContent.textContent = 'Alert content from slot'; element.appendChild(slotContent); await waitForUpdate(element); const textContainer = element.querySelector('.usa-alert__text'); expect(textContainer).toBeTruthy(); expect(element.textContent).toContain('Alert content from slot'); }); 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 slot content properly', async () => { // Add slot content const slotParagraph = document.createElement('p'); slotParagraph.textContent = 'Slot content'; element.appendChild(slotParagraph); await waitForUpdate(element); const textContainer = element.querySelector('.usa-alert__text'); expect(textContainer).toBeTruthy(); expect(element.textContent).toContain('Slot content'); }); it('should not render close button (closable functionality not implemented)', async () => { element.closable = true; await waitForUpdate(element); // Note: closable functionality is not implemented in the current component const closeButton = element.querySelector('.usa-site-alert__close'); expect(closeButton).toBeFalsy(); }); it('should not render close button when not closable', async () => { element.closable = false; await waitForUpdate(element); const closeButton = element.querySelector('.usa-site-alert__close'); expect(closeButton).toBeFalsy(); }); it('should store custom close label (functionality not implemented)', async () => { element.closable = true; element.closeLabel = 'Dismiss Alert'; await waitForUpdate(element); // Property is stored but close button is not implemented expect(element.closeLabel).toBe('Dismiss Alert'); const closeButton = element.querySelector('.usa-site-alert__close'); expect(closeButton).toBeFalsy(); }); it('should not render when not visible', async () => { element.visible = false; await waitForUpdate(element); expect(element.querySelector('.usa-site-alert')).toBeNull(); }); }); describe('USWDS HTML Structure', () => { it('should match USWDS site alert HTML structure', async () => { element.heading = 'Test Alert'; await waitForUpdate(element); // Check section container const section = element.querySelector('section.usa-site-alert'); expect(section).toBeTruthy(); expect(section?.getAttribute('aria-label')).toBe('Site alert'); // Check alert body const body = section?.querySelector('.usa-alert__body'); expect(body).toBeTruthy(); // Check heading const heading = body?.querySelector('.usa-alert__heading'); expect(heading).toBeTruthy(); expect(heading?.tagName.toLowerCase()).toBe('h3'); // Check text container const text = body?.querySelector('.usa-alert__text'); expect(text).toBeTruthy(); }); it('should maintain proper DOM hierarchy', async () => { element.heading = 'Test'; await waitForUpdate(element); const section = element.querySelector('section.usa-site-alert'); const body = section?.querySelector('.usa-alert__body'); const heading = body?.querySelector('.usa-alert__heading'); const text = body?.querySelector('.usa-alert__text'); // Verify hierarchy - updated for nested alert structure const alertContainer = section?.querySelector('.usa-alert'); expect(section?.parentElement).toBe(element); expect(alertContainer?.parentElement).toBe(section); expect(body?.parentElement).toBe(alertContainer); expect(heading?.parentElement).toBe(body); expect(text?.parentElement).toBe(body); }); }); describe('Event Handling', () => { it('should not dispatch close events (closable functionality not implemented)', async () => { element.closable = true; element.heading = 'Test Alert'; await waitForUpdate(element); let eventFired = false; element.addEventListener('site-alert-close', () => { eventFired = true; }); // No close button to click since functionality is not implemented const closeButton = element.querySelector('.usa-site-alert__close'); expect(closeButton).toBeFalsy(); expect(eventFired).toBe(false); }); it('should not hide alert when close button clicked (not implemented)', async () => { element.closable = true; await waitForUpdate(element); // No close button exists const closeButton = element.querySelector('.usa-site-alert__close'); expect(closeButton).toBeFalsy(); expect(element.visible).toBe(true); }); it('should not close alert on Escape key (not implemented)', async () => { element.closable = true; await waitForUpdate(element); const section = element.querySelector('section') as HTMLElement; const escapeEvent = new KeyboardEvent('keydown', { key: 'Escape' }); section.dispatchEvent(escapeEvent); expect(element.visible).toBe(true); }); it('should not close on Escape key when not closable', async () => { element.closable = false; await waitForUpdate(element); const section = element.querySelector('section') as HTMLElement; const escapeEvent = new KeyboardEvent('keydown', { key: 'Escape' }); section.dispatchEvent(escapeEvent); expect(element.visible).toBe(true); }); it('should not close on other key presses', async () => { element.closable = true; await waitForUpdate(element); const section = element.querySelector('section') as HTMLElement; const enterEvent = new KeyboardEvent('keydown', { key: 'Enter' }); section.dispatchEvent(enterEvent); expect(element.visible).toBe(true); }); }); describe('Public API Methods', () => { it('should show alert using show() method', async () => { element.visible = false; await waitForUpdate(element); expect(element.querySelector('.usa-site-alert')).toBeNull(); element.show(); await waitForUpdate(element); expect(element.visible).toBe(true); const siteAlert = element.querySelector('.usa-site-alert'); expect(siteAlert).toBeTruthy(); }); it('should hide alert using hide() method', async () => { element.visible = true; await waitForUpdate(element); const siteAlert = element.querySelector('.usa-site-alert'); expect(siteAlert).toBeTruthy(); element.hide(); await waitForUpdate(element); expect(element.visible).toBe(false); expect(element.querySelector('.usa-site-alert')).toBeNull(); }); it('should toggle alert visibility using toggle() method', async () => { // Start visible element.visible = true; await waitForUpdate(element); expect(element.querySelector('.usa-site-alert')).toBeTruthy(); // Toggle to hidden element.toggle(); await waitForUpdate(element); expect(element.visible).toBe(false); expect(element.querySelector('.usa-site-alert')).toBeNull(); // Toggle back to visible element.toggle(); await waitForUpdate(element); expect(element.visible).toBe(true); expect(element.querySelector('.usa-site-alert')).toBeTruthy(); }); }); describe('Accessibility', () => { it('should have proper ARIA attributes', async () => { await waitForUpdate(element); const section = element.querySelector('section'); expect(section?.getAttribute('aria-label')).toBe('Site alert'); }); it('should not have accessible close button (not implemented)', async () => { element.closable = true; element.closeLabel = 'Dismiss notification'; await waitForUpdate(element); const closeButton = element.querySelector('.usa-site-alert__close'); expect(closeButton).toBeFalsy(); }); it('should maintain semantic heading structure', async () => { element.heading = 'Emergency Alert'; await waitForUpdate(element); const heading = element.querySelector('.usa-alert__heading'); expect(heading?.tagName.toLowerCase()).toBe('h3'); expect(heading?.textContent?.trim()).toBe('Emergency Alert'); }); it('should not have keyboard navigation for close (not implemented)', async () => { element.closable = true; await waitForUpdate(element); const closeButton = element.querySelector('.usa-site-alert__close'); expect(closeButton).toBeFalsy(); }); it('should pass comprehensive accessibility tests (same as Storybook)', async () => { element.heading = 'Test Alert'; await waitForUpdate(element); await testComponentAccessibility(element, USWDS_A11Y_CONFIG.FULL_COMPLIANCE); }); }); 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 siteAlert = element.querySelector('.usa-site-alert'); expect(siteAlert).toBeTruthy(); expect(siteAlert?.parentElement).toBe(element); }); }); describe('Content Handling', () => { it('should handle empty content gracefully', async () => { await waitForUpdate(element); const textContainer = element.querySelector('.usa-alert__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 complex HTML content in slot', async () => { const complexContent = document.createElement('div'); complexContent.innerHTML = `

Alert: System maintenance in progress.

Check system status

`; element.appendChild(complexContent); await waitForUpdate(element); const textContainer = element.querySelector('.usa-alert__text'); expect(textContainer).toBeTruthy(); expect(element.innerHTML).toContain('Alert:'); expect(element.innerHTML).toContain('