import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import '../../../__tests__/test-utils.js'; import { testComponentAccessibility, USWDS_A11Y_CONFIG, } from '../../../__tests__/accessibility-utils.js'; import { quickUSWDSComplianceTest } from '../../../__tests__/uswds-compliance-utils.js'; import { testARIAAccessibility, testARIARoles, testAccessibleName, testARIARelationships, } from '../../../__tests__/aria-screen-reader-utils.js'; import { testTextResize, testReflow, testTextSpacing, testMobileAccessibility, } from '../../../__tests__/responsive-accessibility-utils.js'; import './usa-accordion.js'; import type { USAAccordion, AccordionItem } from './usa-accordion.js'; import { assertHTMLIsRendered, cleanupAfterTest } from '../../../__tests__/test-utils.js'; describe('USAAccordion', () => { let element: USAAccordion; let container: HTMLDivElement; beforeEach(() => { container = document.createElement('div'); document.body.appendChild(container); }); afterEach(() => { // Clear all timers and async operations before removing DOM cleanupAfterTest(); container?.remove(); }); describe('Component Initialization', () => { beforeEach(() => { element = document.createElement('usa-accordion') as USAAccordion; container.appendChild(element); }); it('should create accordion element', () => { expect(element).toBeInstanceOf(HTMLElement); expect(element.tagName).toBe('USA-ACCORDION'); }); it('should have default properties', () => { expect(element.items).toEqual([]); expect(element.multiselectable).toBe(false); expect(element.bordered).toBe(false); }); it('should render light DOM for USWDS compatibility', () => { expect(element.shadowRoot).toBeNull(); }); it('should render slot when no items provided', async () => { await element.updateComplete; const slot = element.querySelector('slot'); expect(slot).toBeTruthy(); }); }); describe('USWDS HTML Structure', () => { beforeEach(async () => { element = document.createElement('usa-accordion') as USAAccordion; element.items = [ { id: 'item-1', title: 'First Item', content: 'Content 1' }, { id: 'item-2', title: 'Second Item', content: 'Content 2' }, ]; container.appendChild(element); await element.updateComplete; }); it('should render with correct USWDS accordion class', () => { const accordion = element.querySelector('.usa-accordion'); expect(accordion).toBeTruthy(); expect(accordion?.classList.contains('usa-accordion')).toBe(true); }); it('should apply bordered class when bordered property is true', async () => { element.bordered = true; await element.updateComplete; const accordion = element.querySelector('.usa-accordion'); expect(accordion?.classList.contains('usa-accordion--bordered')).toBe(true); }); // REMOVED: USWDS doesn't use a multiselectable CSS class, it uses data-allow-multiple attribute // See next test for correct validation it('should add data-allow-multiple attribute when multiselectable is true', async () => { element.multiselectable = true; await element.updateComplete; const accordion = element.querySelector('.usa-accordion'); expect(accordion?.hasAttribute('data-allow-multiple')).toBe(true); }); it('should not add multiselectable class and data-allow-multiple when multiselectable is false', async () => { element.multiselectable = false; await element.updateComplete; const accordion = element.querySelector('.usa-accordion'); expect(accordion?.classList.contains('usa-accordion--multiselectable')).toBe(false); expect(accordion?.hasAttribute('data-allow-multiple')).toBe(false); }); it('should render accordion headings with correct USWDS class', () => { const headings = element.querySelectorAll('.usa-accordion__heading'); expect(headings).toHaveLength(2); headings.forEach((heading) => { expect(heading.tagName).toBe('H4'); }); }); it('should render buttons with correct USWDS classes', () => { const buttons = element.querySelectorAll('.usa-accordion__button'); expect(buttons).toHaveLength(2); buttons.forEach((button) => { expect(button.getAttribute('type')).toBe('button'); }); }); it('should render content divs with correct USWDS classes', () => { const contents = element.querySelectorAll('.usa-accordion__content'); expect(contents).toHaveLength(2); contents.forEach((content) => { expect(content.classList.contains('usa-prose')).toBe(true); }); }); it('should maintain slot for additional content', () => { const slot = element.querySelector('slot'); expect(slot).toBeTruthy(); }); }); describe('Item Rendering', () => { beforeEach(async () => { element = document.createElement('usa-accordion') as USAAccordion; container.appendChild(element); }); it('should render items with titles and content', async () => { const items: AccordionItem[] = [ { id: 'test-1', title: 'Test Title 1', content: 'Test Content 1' }, { id: 'test-2', title: 'Test Title 2', content: 'Test Content 2' }, ]; element.items = items; await element.updateComplete; const buttons = element.querySelectorAll('.usa-accordion__button'); expect(buttons[0]?.textContent?.trim()).toBe('Test Title 1'); expect(buttons[1]?.textContent?.trim()).toBe('Test Title 2'); const contents = element.querySelectorAll('.usa-accordion__content'); expect(contents[0]?.textContent?.trim()).toBe('Test Content 1'); expect(contents[1]?.textContent?.trim()).toBe('Test Content 2'); }); it('should auto-generate IDs if not provided', async () => { // Set items before adding to DOM const testElement = document.createElement('usa-accordion') as USAAccordion; testElement.items = [ { title: 'No ID 1', content: 'Content 1' } as any, { title: 'No ID 2', content: 'Content 2' } as any, ]; // Now add to DOM - this triggers connectedCallback which generates IDs container.appendChild(testElement); await testElement.updateComplete; const contents = testElement.querySelectorAll('.usa-accordion__content'); // IDs are generated in connectedCallback for items expect(testElement.items[0].id).toBe('accordion-item-0'); expect(testElement.items[1].id).toBe('accordion-item-1'); // Content elements get IDs (for USWDS toggle targets) expect(contents[0]?.getAttribute('id')).toBe('accordion-item-0-content'); expect(contents[1]?.getAttribute('id')).toBe('accordion-item-1-content'); }); it('should support HTML content via unsafeHTML', async () => { element.items = [ { id: 'html-test', title: 'HTML Test', content: 'Bold text and italic text', }, ]; await element.updateComplete; const content = element.querySelector('.usa-accordion__content'); const strong = content?.querySelector('strong'); const em = content?.querySelector('em'); expect(strong?.textContent).toBe('Bold text'); expect(em?.textContent).toBe('italic text'); }); it('should NOT display raw HTML tags as text (regression test)', async () => { const htmlContent = '

This is a paragraph

'; element.items = [ { id: 'raw-html-test', title: 'Raw HTML Detection Test', content: htmlContent, }, ]; await element.updateComplete; const content = element.querySelector('.usa-accordion__content') as HTMLElement; // Should render as actual HTML elements, not raw text const paragraph = content.querySelector('p'); const list = content.querySelector('ul'); const listItems = content.querySelectorAll('li'); expect(paragraph).toBeTruthy(); expect(paragraph?.textContent).toBe('This is a paragraph'); expect(list).toBeTruthy(); expect(listItems.length).toBe(2); expect(listItems[0]?.textContent).toBe('List item 1'); expect(listItems[1]?.textContent).toBe('List item 2'); // Critical: innerHTML should contain actual HTML tags expect(content.innerHTML).toContain('

This is a paragraph

'); expect(content.innerHTML).toContain('