import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import './usa-process-list.ts';
import type { USAProcessList, ProcessItem } from './usa-process-list.js';
import {
testComponentAccessibility,
USWDS_A11Y_CONFIG,
} from '../../../__tests__/accessibility-utils.js';
import {
setupTestEnvironment,
validateComponentJavaScript,
} from '../../../__tests__/test-utils.js';
describe('USAProcessList', () => {
let element: USAProcessList;
let cleanup: () => void;
beforeEach(() => {
cleanup = setupTestEnvironment();
element = document.createElement('usa-process-list') as USAProcessList;
document.body.appendChild(element);
});
afterEach(() => {
element.remove();
cleanup?.();
});
describe('Default Properties', () => {
it('should have correct default properties', async () => {
await element.updateComplete;
expect(element.items).toEqual([]);
expect(element.headingLevel).toBe('h4');
});
});
describe('Basic Rendering', () => {
it('should render slot when items array is empty', async () => {
await element.updateComplete;
// In light DOM pattern, slot elements are replaced with slotted content
// When no slotted content exists, the slot is removed
const slot = element.querySelector('slot');
expect(slot).toBeFalsy(); // Slot should be replaced/removed by moveSlottedContent()
// But the container should always exist
const list = element.querySelector('.usa-process-list');
expect(list).toBeTruthy();
});
it('should render ordered list when items are provided', async () => {
element.items = [
{ heading: 'Step 1', content: 'First step content' },
{ heading: 'Step 2', content: 'Second step content' },
];
await element.updateComplete;
const list = element.querySelector('ol.usa-process-list');
expect(list).toBeTruthy();
});
it('should render correct number of list items', async () => {
const testItems: ProcessItem[] = [
{ heading: 'Step 1', content: 'Content 1' },
{ heading: 'Step 2', content: 'Content 2' },
{ heading: 'Step 3', content: 'Content 3' },
];
element.items = testItems;
await element.updateComplete;
const listItems = element.querySelectorAll('.usa-process-list__item');
expect(listItems.length).toBe(3);
});
it('should apply USWDS process list classes', async () => {
element.items = [{ heading: 'Test', content: 'Content' }];
await element.updateComplete;
const list = element.querySelector('ol');
expect(list?.classList.contains('usa-process-list')).toBe(true);
const item = element.querySelector('li');
expect(item?.classList.contains('usa-process-list__item')).toBe(true);
});
});
describe('Process Item Content', () => {
it('should render item headings', async () => {
element.items = [
{ heading: 'Apply Online', content: 'Visit our website' },
{ heading: 'Submit Documents', content: 'Upload required files' },
{ heading: 'Await Review', content: 'Processing takes 5-7 days' },
];
await element.updateComplete;
const headings = element.querySelectorAll('.usa-process-list__heading');
expect(headings.length).toBe(3);
expect(headings[0].textContent).toBe('Apply Online');
expect(headings[1].textContent).toBe('Submit Documents');
expect(headings[2].textContent).toBe('Await Review');
});
it('should render item content', async () => {
element.items = [
{ heading: 'Step 1', content: 'First step description' },
{ heading: 'Step 2', content: 'Second step description' },
];
await element.updateComplete;
const contents = element.querySelectorAll('.usa-process-list__item');
expect(contents.length).toBe(2);
// Check that each item contains both heading and content
expect(contents[0].textContent?.trim()).toContain('Step 1');
expect(contents[0].textContent?.trim()).toContain('First step description');
expect(contents[1].textContent?.trim()).toContain('Step 2');
expect(contents[1].textContent?.trim()).toContain('Second step description');
});
it('should render HTML content correctly', async () => {
element.items = [
{
heading: 'Step 1',
content: 'Important: Bring your ID',
},
];
await element.updateComplete;
const listItem = element.querySelector('.usa-process-list__item');
expect(listItem?.querySelector('strong')).toBeTruthy();
expect(listItem?.querySelector('em')).toBeTruthy();
expect(listItem?.querySelector('strong')?.textContent).toBe('Important:');
expect(listItem?.querySelector('em')?.textContent).toBe('ID');
});
it('should handle complex HTML content', async () => {
element.items = [
{
heading: 'Documentation',
content: `
Required documents:
- Valid ID
- Proof of residence
- Application form
`,
},
];
await element.updateComplete;
const listItem = element.querySelector('.usa-process-list__item');
const paragraph = listItem?.querySelector('p');
const list = listItem?.querySelector('ul');
const listItems = listItem?.querySelectorAll('li');
expect(paragraph?.textContent).toBe('Required documents:');
expect(list).toBeTruthy();
expect(listItems?.length).toBe(3);
});
});
describe('Heading Level Customization', () => {
it('should use default h4 heading level', async () => {
element.items = [{ heading: 'Default Heading', content: 'Content' }];
await element.updateComplete;
const heading = element.querySelector('.usa-process-list__heading');
expect(heading?.tagName.toLowerCase()).toBe('h4');
});
it('should render h1 headings when specified', async () => {
element.headingLevel = 'h1';
element.items = [{ heading: 'H1 Heading', content: 'Content' }];
await element.updateComplete;
const heading = element.querySelector('.usa-process-list__heading');
expect(heading?.tagName.toLowerCase()).toBe('h1');
});
it('should render h2 headings when specified', async () => {
element.headingLevel = 'h2';
element.items = [{ heading: 'H2 Heading', content: 'Content' }];
await element.updateComplete;
const heading = element.querySelector('.usa-process-list__heading');
expect(heading?.tagName.toLowerCase()).toBe('h2');
});
it('should render h3 headings when specified', async () => {
element.headingLevel = 'h3';
element.items = [{ heading: 'H3 Heading', content: 'Content' }];
await element.updateComplete;
const heading = element.querySelector('.usa-process-list__heading');
expect(heading?.tagName.toLowerCase()).toBe('h3');
});
it('should render h5 headings when specified', async () => {
element.headingLevel = 'h5';
element.items = [{ heading: 'H5 Heading', content: 'Content' }];
await element.updateComplete;
const heading = element.querySelector('.usa-process-list__heading');
expect(heading?.tagName.toLowerCase()).toBe('h5');
});
it('should render h6 headings when specified', async () => {
element.headingLevel = 'h6';
element.items = [{ heading: 'H6 Heading', content: 'Content' }];
await element.updateComplete;
const heading = element.querySelector('.usa-process-list__heading');
expect(heading?.tagName.toLowerCase()).toBe('h6');
});
it('should fall back to h4 for invalid heading level', async () => {
element.headingLevel = 'h7' as any; // Invalid level
element.items = [{ heading: 'Fallback Heading', content: 'Content' }];
await element.updateComplete;
const heading = element.querySelector('.usa-process-list__heading');
expect(heading?.tagName.toLowerCase()).toBe('h4');
});
it('should update heading level dynamically', async () => {
element.items = [{ heading: 'Dynamic Heading', content: 'Content' }];
element.headingLevel = 'h3';
await element.updateComplete;
let heading = element.querySelector('.usa-process-list__heading');
expect(heading?.tagName.toLowerCase()).toBe('h3');
element.headingLevel = 'h5';
await element.updateComplete;
heading = element.querySelector('.usa-process-list__heading');
expect(heading?.tagName.toLowerCase()).toBe('h5');
});
});
describe('Slot Content', () => {
it('should render slot content when no items provided', async () => {
// Create new element with slotted content from the start
const testElement = document.createElement('usa-process-list') as USAProcessList;
const slotContent = document.createElement('ol');
slotContent.className = 'usa-process-list';
slotContent.innerHTML = `
Custom Step
Custom content
`;
testElement.appendChild(slotContent);
document.body.appendChild(testElement);
await testElement.updateComplete;
// In light DOM pattern, slot is replaced with actual slotted content
const slot = testElement.querySelector('slot');
expect(slot).toBeFalsy(); // Slot should be replaced by moveSlottedContent()
// But the slotted content should be visible
expect(testElement.textContent).toContain('Custom Step');
expect(testElement.textContent).toContain('Custom content');
testElement.remove();
});
it('should override slot content when items are provided', async () => {
// Create new element with slotted content from the start
const testElement = document.createElement('usa-process-list') as USAProcessList;
const slotContent = document.createElement('div');
slotContent.textContent = 'Slot content';
testElement.appendChild(slotContent);
document.body.appendChild(testElement);
// Items property takes precedence over slot
testElement.items = [{ heading: 'Item Heading', content: 'Item content' }];
await testElement.updateComplete;
const list = testElement.querySelector('ol.usa-process-list');
expect(list).toBeTruthy();
expect(testElement.textContent).toContain('Item Heading');
// When items are provided, slot is not rendered (items.length > 0)
// So slot content might still be in DOM but hidden by CSS
testElement.remove();
});
});
describe('Dynamic Updates', () => {
it('should update when items are added', async () => {
element.items = [{ heading: 'Initial Step', content: 'Initial content' }];
await element.updateComplete;
let listItems = element.querySelectorAll('.usa-process-list__item');
expect(listItems.length).toBe(1);
element.items = [...element.items, { heading: 'Added Step', content: 'Added content' }];
await element.updateComplete;
listItems = element.querySelectorAll('.usa-process-list__item');
expect(listItems.length).toBe(2);
expect(listItems[1].textContent).toContain('Added Step');
});
it('should update when items are removed', async () => {
element.items = [
{ heading: 'Step 1', content: 'Content 1' },
{ heading: 'Step 2', content: 'Content 2' },
{ heading: 'Step 3', content: 'Content 3' },
];
await element.updateComplete;
let listItems = element.querySelectorAll('.usa-process-list__item');
expect(listItems.length).toBe(3);
element.items = element.items.slice(0, 2);
await element.updateComplete;
listItems = element.querySelectorAll('.usa-process-list__item');
expect(listItems.length).toBe(2);
});
it('should update when item content changes', async () => {
element.items = [{ heading: 'Original Heading', content: 'Original content' }];
await element.updateComplete;
let heading = element.querySelector('.usa-process-list__heading');
expect(heading?.textContent).toBe('Original Heading');
element.items = [{ heading: 'Updated Heading', content: 'Updated content' }];
await element.updateComplete;
heading = element.querySelector('.usa-process-list__heading');
expect(heading?.textContent).toBe('Updated Heading');
});
it('should clear list when items are emptied', async () => {
element.items = [{ heading: 'Step', content: 'Content' }];
await element.updateComplete;
expect(element.querySelector('.usa-process-list')).toBeTruthy();
element.items = [];
await element.updateComplete;
// Container always exists now, but should be empty or show slot
expect(element.querySelector('.usa-process-list')).toBeTruthy();
// Slot would have been removed by moveSlottedContent() in firstUpdated
// When items are emptied, slot is rendered but then removed
const listItems = element.querySelectorAll('.usa-process-list__item');
expect(listItems.length).toBe(0);
});
});
describe('Accessibility Features', () => {
it('should use semantic ordered list', async () => {
element.items = [
{ heading: 'Step 1', content: 'Content 1' },
{ heading: 'Step 2', content: 'Content 2' },
];
await element.updateComplete;
const list = element.querySelector('ol');
expect(list).toBeTruthy();
expect(list?.tagName.toLowerCase()).toBe('ol');
});
it('should maintain proper heading hierarchy', async () => {
element.headingLevel = 'h3';
element.items = [{ heading: 'Process Step', content: 'Step description' }];
await element.updateComplete;
const heading = element.querySelector('h3.usa-process-list__heading');
expect(heading).toBeTruthy();
expect(heading?.textContent).toBe('Process Step');
});
it('should structure content semantically', async () => {
element.items = [{ heading: 'Apply', content: 'Application instructions
' }];
await element.updateComplete;
const item = element.querySelector('.usa-process-list__item');
const heading = item?.querySelector('.usa-process-list__heading');
expect(heading).toBeTruthy();
expect(item?.querySelector('p')).toBeTruthy();
});
});
describe('Edge Cases', () => {
it('should handle empty headings', async () => {
element.items = [{ heading: '', content: 'Content with no heading' }];
await element.updateComplete;
const heading = element.querySelector('.usa-process-list__heading');
expect(heading).toBeTruthy();
expect(heading?.textContent).toBe('');
});
it('should handle empty content', async () => {
element.items = [{ heading: 'Heading with no content', content: '' }];
await element.updateComplete;
// For empty content, just verify the item exists and has only the heading
const item = element.querySelector('.usa-process-list__item');
const heading = item?.querySelector('.usa-process-list__heading');
expect(item).toBeTruthy();
expect(heading).toBeTruthy();
expect(heading?.textContent?.trim()).toBe('Heading with no content');
// The item should not contain any content beyond the heading
const itemText = item?.textContent?.trim() || '';
const headingText = heading?.textContent?.trim() || '';
expect(itemText).toBe(headingText);
});
it('should handle special characters in heading', async () => {
element.items = [{ heading: 'Step & Process "quotes"', content: 'Content' }];
await element.updateComplete;
const heading = element.querySelector('.usa-process-list__heading');
expect(heading?.textContent).toBe('Step & Process "quotes"');
});
it('should handle script tags in content safely', async () => {
element.items = [
{
heading: 'Test',
content: 'Safe content
',
},
];
await element.updateComplete;
// Note: unsafeHTML does render script tags, but they don't execute in this context
const listItem = element.querySelector('.usa-process-list__item');
expect(listItem?.querySelector('p')?.textContent).toBe('Safe content');
});
it('should handle very long content', async () => {
const longContent = 'A'.repeat(1000);
element.items = [{ heading: 'Long Step', content: longContent }];
await element.updateComplete;
const item = element.querySelector('.usa-process-list__item');
expect(item).toBeTruthy();
expect(item?.textContent?.trim()).toContain(longContent);
});
it('should handle unicode characters', async () => {
element.items = [{ heading: '步骤 1 🚀', content: 'مرحبا العالم' }];
await element.updateComplete;
const heading = element.querySelector('.usa-process-list__heading');
const item = element.querySelector('.usa-process-list__item');
expect(heading?.textContent).toBe('步骤 1 🚀');
expect(item?.textContent?.trim()).toContain('مرحبا العالم');
});
});
describe('Light DOM Rendering', () => {
it('should render in light DOM for USWDS compatibility', async () => {
element.items = [{ heading: 'Test', content: 'Content' }];
await element.updateComplete;
expect(element.shadowRoot).toBeNull();
expect(element.querySelector('.usa-process-list')).toBeTruthy();
});
});
describe('USWDS CSS Classes', () => {
it('should apply all required USWDS classes', async () => {
element.items = [{ heading: 'Step', content: 'Description' }];
await element.updateComplete;
const list = element.querySelector('ol');
const item = element.querySelector('li');
const heading = element.querySelector('[class*="heading"]');
expect(list?.classList.contains('usa-process-list')).toBe(true);
expect(item?.classList.contains('usa-process-list__item')).toBe(true);
expect(heading?.classList.contains('usa-process-list__heading')).toBe(true);
// Verify the item contains the content text (no specific content wrapper class)
expect(item?.textContent?.trim()).toContain('Description');
});
});
describe('Performance Considerations', () => {
it('should handle large lists efficiently', async () => {
const largeList = Array.from({ length: 100 }, (_, i) => ({
heading: `Step ${i + 1}`,
content: `Description for step ${i + 1}`,
}));
const startTime = performance.now();
element.items = largeList;
await element.updateComplete;
const endTime = performance.now();
const listItems = element.querySelectorAll('.usa-process-list__item');
expect(listItems.length).toBe(100);
// Should render within reasonable time (500ms for 100 items)
expect(endTime - startTime).toBeLessThan(500);
});
it('should handle rapid updates efficiently', async () => {
for (let i = 0; i < 10; i++) {
element.items = [{ heading: `Heading ${i}`, content: `Content ${i}` }];
}
await element.updateComplete;
const heading = element.querySelector('.usa-process-list__heading');
expect(heading?.textContent).toBe('Heading 9');
});
});
describe('CRITICAL: Component Lifecycle Stability', () => {
beforeEach(() => {
element = document.createElement('usa-process-list') as USAProcessList;
document.body.appendChild(element);
});
it('should remain in DOM after property changes', async () => {
await element.updateComplete;
expect(document.body.contains(element)).toBe(true);
expect(element.isConnected).toBe(true);
element.headingLevel = 'h2';
await element.updateComplete;
expect(document.body.contains(element)).toBe(true);
expect(element.isConnected).toBe(true);
element.items = [
{ heading: 'Test Step 1', content: 'Test content 1' },
{ heading: 'Test Step 2', content: 'Test content 2' },
];
await element.updateComplete;
expect(document.body.contains(element)).toBe(true);
expect(element.isConnected).toBe(true);
element.headingLevel = 'h5';
await element.updateComplete;
expect(document.body.contains(element)).toBe(true);
expect(element.isConnected).toBe(true);
});
it('should maintain element stability during process item updates', async () => {
const originalElement = element;
const processItems = [
{ heading: 'Apply', content: 'Submit your application online' },
{ heading: 'Review', content: 'Wait for processing review' },
{ heading: 'Approve', content: 'Receive approval notification' },
{ heading: 'Complete', content: 'Process completed successfully' },
];
for (const item of processItems) {
element.items = [...element.items, item];
await element.updateComplete;
expect(element).toBe(originalElement);
expect(document.body.contains(element)).toBe(true);
expect(element.isConnected).toBe(true);
}
});
it('should preserve DOM connection through heading level changes', async () => {
element.items = [{ heading: 'Process Step', content: 'Step description' }];
const headingLevels = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'] as const;
for (const level of headingLevels) {
element.headingLevel = level;
await element.updateComplete;
expect(document.body.contains(element)).toBe(true);
expect(element.isConnected).toBe(true);
}
});
it('should maintain stability during complex content updates', async () => {
const complexItems = [
{
heading: 'Step 1',
content: 'Important: Required documentation',
},
{
heading: 'Step 2',
content: '',
},
{
heading: 'Step 3',
content: 'Final verification step
',
},
];
for (const item of complexItems) {
element.items = [item];
await element.updateComplete;
expect(document.body.contains(element)).toBe(true);
expect(element.isConnected).toBe(true);
}
});
});
describe('CRITICAL: Event System Stability', () => {
beforeEach(async () => {
element = document.createElement('usa-process-list') as USAProcessList;
element.items = [
{ heading: 'Event Step 1', content: 'Event content 1' },
{ heading: 'Event Step 2', content: 'Event content 2' },
];
document.body.appendChild(element);
await element.updateComplete;
});
it('should not pollute global event handling', async () => {
const globalClickSpy = vi.fn();
document.addEventListener('click', globalClickSpy);
// Test clicking on process list content
const content = element.querySelector('div');
if (content) {
content.dispatchEvent(new MouseEvent('click', { bubbles: true }));
await element.updateComplete;
}
expect(document.body.contains(element)).toBe(true);
expect(element.isConnected).toBe(true);
document.removeEventListener('click', globalClickSpy);
});
it('should maintain stability during rapid property changes', async () => {
const headingLevels = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
for (let i = 0; i < 5; i++) {
element.headingLevel = headingLevels[i % headingLevels.length] as any;
await new Promise((resolve) => setTimeout(resolve, 10));
expect(document.body.contains(element)).toBe(true);
expect(element.isConnected).toBe(true);
}
});
it('should maintain stability during rapid item array changes', async () => {
const itemSets = [
[{ heading: 'Set 1 Step 1', content: 'Content 1' }],
[
{ heading: 'Set 2 Step 1', content: 'Content 1' },
{ heading: 'Set 2 Step 2', content: 'Content 2' },
],
[
{ heading: 'Set 3 Step 1', content: 'Content 1' },
{ heading: 'Set 3 Step 2', content: 'Content 2' },
{ heading: 'Set 3 Step 3', content: 'Content 3' },
],
[],
];
for (const itemSet of itemSets) {
element.items = itemSet;
await element.updateComplete;
expect(document.body.contains(element)).toBe(true);
expect(element.isConnected).toBe(true);
}
});
});
describe('CRITICAL: Process List State Management Stability', () => {
beforeEach(async () => {
element = document.createElement('usa-process-list') as USAProcessList;
document.body.appendChild(element);
await element.updateComplete;
});
it('should maintain DOM connection during slot to items transition', async () => {
// In light DOM pattern, slot is replaced/removed by moveSlottedContent()
// Container should always exist
expect(element.querySelector('.usa-process-list')).toBeTruthy();
expect(document.body.contains(element)).toBe(true);
expect(element.isConnected).toBe(true);
// Switch to items
element.items = [{ heading: 'Transition Step', content: 'Transition content' }];
await element.updateComplete;
expect(document.body.contains(element)).toBe(true);
expect(element.isConnected).toBe(true);
// Switch back to empty (renders slot, but it gets removed by firstUpdated logic)
element.items = [];
await element.updateComplete;
expect(document.body.contains(element)).toBe(true);
expect(element.isConnected).toBe(true);
});
it('should preserve element stability during content rendering', async () => {
const originalElement = element;
// Test various content types
const contentTypes = [
'Plain text content',
'Bold content',
'Italic content',
'Paragraph with link
',
'',
'Unicode content 🚀 步骤 مرحبا',
];
for (const content of contentTypes) {
element.items = [{ heading: 'Test Heading', content }];
await element.updateComplete;
expect(element).toBe(originalElement);
expect(document.body.contains(element)).toBe(true);
expect(element.isConnected).toBe(true);
}
});
it('should maintain stability during large list operations', async () => {
// Test with large list
const largeList = Array.from({ length: 50 }, (_, i) => ({
heading: `Large Step ${i + 1}`,
content: `Large content ${i + 1}`,
}));
element.items = largeList;
await element.updateComplete;
expect(document.body.contains(element)).toBe(true);
expect(element.isConnected).toBe(true);
// Clear the large list
element.items = [];
await element.updateComplete;
expect(document.body.contains(element)).toBe(true);
expect(element.isConnected).toBe(true);
});
});
describe('CRITICAL: Storybook Integration', () => {
it('should render in Storybook-like environment without auto-dismiss', async () => {
const storyContainer = document.createElement('div');
storyContainer.id = 'storybook-root';
document.body.appendChild(storyContainer);
element = document.createElement('usa-process-list') as USAProcessList;
element.headingLevel = 'h3';
element.items = [
{ heading: 'Storybook Step 1', content: 'Storybook content 1' },
{ heading: 'Storybook Step 2', content: 'Storybook content 2' },
];
storyContainer.appendChild(element);
await element.updateComplete;
// Simulate Storybook control updates
element.headingLevel = 'h2';
element.items = [
{ heading: 'Updated Step 1', content: 'Updated content 1' },
{ heading: 'Updated Step 2', content: 'Updated content 2' },
{ heading: 'New Step 3', content: 'New content 3' },
];
await element.updateComplete;
expect(storyContainer.contains(element)).toBe(true);
expect(element.isConnected).toBe(true);
expect(element.headingLevel).toBe('h2');
storyContainer.remove();
});
it('should handle Storybook args updates without component removal', async () => {
element = document.createElement('usa-process-list') as USAProcessList;
document.body.appendChild(element);
await element.updateComplete;
const storyArgs = [
{
headingLevel: 'h1',
items: [{ heading: 'H1 Process', content: 'H1 content' }],
},
{
headingLevel: 'h3',
items: [
{ heading: 'H3 Step 1', content: 'H3 content 1' },
{ heading: 'H3 Step 2', content: 'H3 content 2' },
],
},
{
headingLevel: 'h5',
items: [],
},
];
for (const args of storyArgs) {
Object.assign(element, args);
await element.updateComplete;
expect(document.body.contains(element)).toBe(true);
expect(element.isConnected).toBe(true);
}
});
it('should maintain stability during complex Storybook interactions', async () => {
element = document.createElement('usa-process-list') as USAProcessList;
document.body.appendChild(element);
// Simulate complex Storybook scenario
const interactions = [
() => {
element.headingLevel = 'h2';
},
() => {
element.items = [{ heading: 'Interactive Step', content: 'Interactive content' }];
},
() => {
element.headingLevel = 'h4';
},
() => {
element.items = [];
},
() => {
element.items = [
{ heading: 'Final Step 1', content: 'Final content 1' },
{ heading: 'Final Step 2', content: 'Final content 2' },
];
},
];
for (const interaction of interactions) {
interaction();
await element.updateComplete;
expect(document.body.contains(element)).toBe(true);
expect(element.isConnected).toBe(true);
}
});
describe('JavaScript Implementation Validation', () => {
it('should pass JavaScript implementation validation', async () => {
// Validate USWDS JavaScript implementation patterns
const componentPath =
`${process.cwd()}/src/components/process-list/usa-process-list.ts`;
const validation = validateComponentJavaScript(componentPath, 'process-list');
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 process list (slot-based)
await element.updateComplete;
await testComponentAccessibility(element, USWDS_A11Y_CONFIG.FULL_COMPLIANCE);
// Test simple process list with items
element.items = [
{
heading: 'Apply for Benefits',
content:
'Complete the online application form with your personal information and required documentation.',
},
{
heading: 'Document Review',
content:
'Our team will review your application and supporting documents within 5-7 business days.',
},
{
heading: 'Approval Decision',
content: 'You will receive notification of our decision via email and postal mail.',
},
];
await element.updateComplete;
await testComponentAccessibility(element, USWDS_A11Y_CONFIG.FULL_COMPLIANCE);
// Test with different heading level
element.headingLevel = 'h3';
element.items = [
{
heading: 'Register for Services',
content:
'Create your government services account to access federal programs and benefits.',
},
{
heading: 'Verify Identity',
content: 'Upload required identification documents for verification.',
},
{
heading: 'Select Programs',
content: 'Choose which federal programs and services you wish to enroll in.',
},
{
heading: 'Complete Enrollment',
content: 'Review and submit your enrollment information to finalize registration.',
},
];
await element.updateComplete;
await testComponentAccessibility(element, USWDS_A11Y_CONFIG.FULL_COMPLIANCE);
// Test with rich content including HTML
element.headingLevel = 'h2';
element.items = [
{
heading: 'Gather Required Documents',
content:
'You will need the following documents:- Social Security card
- Photo identification
- Proof of income
',
},
{
heading: 'Submit Application Online',
content:
'Visit our secure application portal to submit your application electronically.',
},
{
heading: 'Track Application Status',
content:
'Use your confirmation number to track your application status and receive updates.',
},
];
await element.updateComplete;
await testComponentAccessibility(element, USWDS_A11Y_CONFIG.FULL_COMPLIANCE);
});
it('should maintain accessibility during dynamic updates', async () => {
// Set initial accessible state
element.items = [
{ heading: 'Initial Step', content: 'Initial content for accessibility testing.' },
];
element.headingLevel = 'h4';
await element.updateComplete;
await testComponentAccessibility(element, USWDS_A11Y_CONFIG.FULL_COMPLIANCE);
// Update heading level
element.headingLevel = 'h2';
await element.updateComplete;
await testComponentAccessibility(element, USWDS_A11Y_CONFIG.FULL_COMPLIANCE);
// Add more items
element.items = [
{ heading: 'Step 1', content: 'First step in the process.' },
{ heading: 'Step 2', content: 'Second step with additional details.' },
{ heading: 'Step 3', content: 'Final step to complete the process.' },
];
await element.updateComplete;
await testComponentAccessibility(element, USWDS_A11Y_CONFIG.FULL_COMPLIANCE);
// Change heading level again
element.headingLevel = 'h5';
await element.updateComplete;
await testComponentAccessibility(element, USWDS_A11Y_CONFIG.FULL_COMPLIANCE);
// Clear items (back to slot mode)
element.items = [];
await element.updateComplete;
await testComponentAccessibility(element, USWDS_A11Y_CONFIG.FULL_COMPLIANCE);
});
it('should pass accessibility with government use cases', async () => {
// Federal tax filing process
element.headingLevel = 'h3';
element.items = [
{
heading: 'Prepare Your Tax Documents',
content:
'Gather all necessary tax documents including W-2 forms, 1099 forms, receipts for deductions, and your prior year tax return.',
},
{
heading: 'Choose Your Filing Method',
content:
'You can file your taxes online through IRS Free File, use tax preparation software, hire a professional tax preparer, or file paper forms by mail.',
},
{
heading: 'Complete Your Tax Return',
content:
'Fill out all required forms accurately, including Form 1040 and any necessary schedules. Double-check all information before submission.',
},
{
heading: 'Submit Your Return',
content:
'File your completed tax return electronically for faster processing, or mail paper forms to the appropriate IRS processing center by the deadline.',
},
{
heading: 'Track Your Refund',
content:
'If you are expecting a refund, you can track its status using the IRS "Where\'s My Refund?" tool online or by calling the automated phone system.',
},
];
await element.updateComplete;
await testComponentAccessibility(element, USWDS_A11Y_CONFIG.FULL_COMPLIANCE);
// Medicare enrollment process
element.headingLevel = 'h4';
element.items = [
{
heading: 'Determine Your Eligibility',
content:
'You are eligible for Medicare if you are 65 or older, have certain disabilities, or have End-Stage Renal Disease (ESRD).',
},
{
heading: 'Learn About Medicare Parts',
content:
'Understand the different parts of Medicare: Part A (hospital insurance), Part B (medical insurance), Part C (Medicare Advantage), and Part D (prescription drugs).',
},
{
heading: 'Compare Plan Options',
content:
'Use the Medicare Plan Finder tool at Medicare.gov to compare available plans in your area based on your healthcare needs and budget.',
},
{
heading: 'Enroll in Medicare',
content:
'Enroll during your Initial Enrollment Period (7-month window around your 65th birthday) or during Open Enrollment (October 15 - December 7 annually).',
},
{
heading: 'Receive Your Medicare Card',
content:
'Your Medicare card will arrive by mail, typically 1-3 months before your coverage begins. Keep this card with you whenever you receive healthcare services.',
},
];
await element.updateComplete;
await testComponentAccessibility(element, USWDS_A11Y_CONFIG.FULL_COMPLIANCE);
});
});
});