import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import '../../../__tests__/test-utils.js'; import './usa-list.ts'; import type { USAList } from './usa-list.js'; import { testComponentAccessibility, USWDS_A11Y_CONFIG, } from '../../../__tests__/accessibility-utils.js'; import { validateComponentJavaScript } from '../../../__tests__/test-utils.js'; import { testKeyboardNavigation, getFocusableElements, } from '../../../__tests__/keyboard-navigation-utils.js'; describe('USAList', () => { let element: USAList; beforeEach(() => { element = document.createElement('usa-list') as USAList; document.body.appendChild(element); }); afterEach(async () => { // Wait for any pending Lit updates before cleanup await element.updateComplete; element.remove(); }); describe('Component Initialization', () => { it('should create component with default properties', () => { expect(element).toBeInstanceOf(HTMLElement); expect(element.tagName).toBe('USA-LIST'); expect(element.type).toBe('unordered'); expect(element.unstyled).toBe(false); }); it('should use light DOM rendering', () => { expect(element.shadowRoot).toBeNull(); }); it('should have correct default values', () => { expect(element.type).toBe('unordered'); expect(element.unstyled).toBe(false); }); }); describe('List Type Property', () => { it('should handle type property changes', async () => { // Test unordered (default) element.type = 'unordered'; await element.updateComplete; const ulElement = element.querySelector('ul.usa-list'); expect(ulElement).not.toBeNull(); expect(element.querySelector('ol.usa-list')).toBeNull(); // Test ordered element.type = 'ordered'; await element.updateComplete; const olElement = element.querySelector('ol.usa-list'); expect(olElement).not.toBeNull(); expect(element.querySelector('ul.usa-list')).toBeNull(); }); it('should reflect type changes in DOM structure', async () => { // Start with unordered element.type = 'unordered'; await element.updateComplete; expect(element.querySelector('ul')).not.toBeNull(); // Change to ordered element.type = 'ordered'; await element.updateComplete; expect(element.querySelector('ol')).not.toBeNull(); expect(element.querySelector('ul')).toBeNull(); }); it('should accept valid type values', async () => { const validTypes: Array<'unordered' | 'ordered'> = ['unordered', 'ordered']; for (const type of validTypes) { element.type = type; await element.updateComplete; expect(element.type).toBe(type); } }); }); describe('Unstyled Property', () => { it('should handle unstyled property', async () => { // Test default (styled) element.unstyled = false; await element.updateComplete; const listElement = element.querySelector('.usa-list'); expect(listElement?.classList.contains('usa-list--unstyled')).toBe(false); // Test unstyled element.unstyled = true; await element.updateComplete; const unstyledElement = element.querySelector('.usa-list.usa-list--unstyled'); expect(unstyledElement).not.toBeNull(); }); it('should reflect unstyled attribute', async () => { element.unstyled = true; await element.updateComplete; expect(element.hasAttribute('unstyled')).toBe(true); element.unstyled = false; await element.updateComplete; expect(element.hasAttribute('unstyled')).toBe(false); }); }); describe('CSS Classes', () => { it('should apply correct base classes', async () => { await element.updateComplete; const listElement = element.querySelector('ul, ol'); expect(listElement?.classList.contains('usa-list')).toBe(true); }); it('should apply unstyled class when unstyled is true', async () => { element.unstyled = true; await element.updateComplete; const listElement = element.querySelector('.usa-list--unstyled'); expect(listElement).not.toBeNull(); }); it('should not apply unstyled class when unstyled is false', async () => { element.unstyled = false; await element.updateComplete; const listElement = element.querySelector('.usa-list'); expect(listElement?.classList.contains('usa-list--unstyled')).toBe(false); }); }); describe('ARIA and Accessibility', () => { it('should set role="list" for ordered lists', async () => { element.type = 'ordered'; await element.updateComplete; expect(element.getAttribute('role')).toBe('list'); }); it('should not set role for unordered lists', async () => { element.type = 'unordered'; await element.updateComplete; expect(element.hasAttribute('role')).toBe(false); }); it('should update role when type changes', async () => { // Start unordered (no role) element.type = 'unordered'; await element.updateComplete; expect(element.hasAttribute('role')).toBe(false); // Change to ordered (should have role) element.type = 'ordered'; await element.updateComplete; expect(element.getAttribute('role')).toBe('list'); // Change back to unordered (should remove role) element.type = 'unordered'; await element.updateComplete; expect(element.hasAttribute('role')).toBe(false); }); }); describe('Content and Slotting', () => { it('should render slot element for content', async () => { await element.updateComplete; const slot = element.querySelector('slot'); expect(slot).not.toBeNull(); }); it('should create list element structure', async () => { await element.updateComplete; const listElement = element.querySelector('ul, ol'); expect(listElement).not.toBeNull(); expect(listElement?.classList.contains('usa-list')).toBe(true); }); it('should handle empty content structure', async () => { await element.updateComplete; const listElement = element.querySelector('ul, ol'); expect(listElement).not.toBeNull(); const slot = element.querySelector('slot'); expect(slot).not.toBeNull(); }); }); describe('List Item Organization', () => { it('should have reorganization method available', () => { expect(typeof (element as any).reorganizeListItems).toBe('function'); }); it('should handle reorganization without errors', async () => { await element.updateComplete; expect(() => { (element as any).reorganizeListItems(); }).not.toThrow(); }); }); describe('Dynamic Content Updates', () => { it('should handle type changes in DOM structure', async () => { // Start with unordered element.type = 'unordered'; await element.updateComplete; expect(element.querySelector('ul')).not.toBeNull(); expect(element.querySelector('ol')).toBeNull(); // Change to ordered element.type = 'ordered'; await element.updateComplete; expect(element.querySelector('ol')).not.toBeNull(); expect(element.querySelector('ul')).toBeNull(); }); it('should handle property updates correctly', async () => { element.type = 'ordered'; element.unstyled = true; await element.updateComplete; const listElement = element.querySelector('ol.usa-list.usa-list--unstyled'); expect(listElement).not.toBeNull(); }); }); describe('Nested Lists', () => { it('should support nested list structure', async () => { element.type = 'ordered'; await element.updateComplete; // The component should create an ordered list element const orderList = element.querySelector('ol.usa-list'); expect(orderList).toBeTruthy(); expect(orderList?.classList.contains('usa-list')).toBe(true); // Test that the component can handle li children const li1 = document.createElement('li'); li1.textContent = 'First item'; const li2 = document.createElement('li'); li2.textContent = 'Second item'; element.appendChild(li1); element.appendChild(li2); // Wait for reorganization element.forceReorganize(); await element.updateComplete; // Verify items were moved to the list const listItems = orderList?.querySelectorAll('li'); expect(listItems?.length).toBe(2); }); it('should handle nested list DOM structure correctly', async () => { element.type = 'unordered'; await element.updateComplete; // The component should create an unordered list element const unorderList = element.querySelector('ul.usa-list'); expect(unorderList).toBeTruthy(); expect(unorderList?.classList.contains('usa-list')).toBe(true); // Test that nested lists can be added within list items const li = document.createElement('li'); li.innerHTML = 'Item with nested list'; element.appendChild(li); element.forceReorganize(); await element.updateComplete; // Verify the nested structure exists within the reorganized content const nestedUl = element.querySelector('ul.usa-list ul.usa-list'); expect(nestedUl).toBeTruthy(); expect(nestedUl?.classList.contains('usa-list')).toBe(true); }); }); describe('Error Handling', () => { it('should handle property changes gracefully', async () => { await element.updateComplete; expect(() => { element.type = 'ordered'; element.unstyled = true; }).not.toThrow(); }); it('should handle multiple updates gracefully', async () => { element.type = 'ordered'; await element.updateComplete; element.type = 'unordered'; await element.updateComplete; element.unstyled = true; await element.updateComplete; expect(element.querySelector('ul.usa-list.usa-list--unstyled')).not.toBeNull(); }); }); describe('Application Use Cases', () => { it('should support ordered lists for procedural steps', async () => { element.type = 'ordered'; await element.updateComplete; const orderedList = element.querySelector('ol.usa-list'); expect(orderedList).not.toBeNull(); expect(element.getAttribute('role')).toBe('list'); }); it('should support unordered lists for document requirements', async () => { element.type = 'unordered'; await element.updateComplete; const unorderedList = element.querySelector('ul.usa-list'); expect(unorderedList).not.toBeNull(); expect(element.hasAttribute('role')).toBe(false); }); it('should support unstyled lists for agency contacts', async () => { element.type = 'unordered'; element.unstyled = true; await element.updateComplete; const unstyledList = element.querySelector('ul.usa-list.usa-list--unstyled'); expect(unstyledList).not.toBeNull(); }); it('should provide accessible ordered lists for eligibility criteria', async () => { element.type = 'ordered'; await element.updateComplete; const criteriaList = element.querySelector('ol.usa-list'); expect(criteriaList).not.toBeNull(); expect(element.getAttribute('role')).toBe('list'); }); it('should handle benefits information as unordered list', async () => { element.type = 'unordered'; await element.updateComplete; const benefitsList = element.querySelector('ul.usa-list'); expect(benefitsList).not.toBeNull(); const slot = benefitsList?.querySelector('slot'); expect(slot).not.toBeNull(); }); }); describe('Component Lifecycle', () => { it('should properly initialize on connection', async () => { const newElement = document.createElement('usa-list') as USAList; newElement.type = 'ordered'; document.body.appendChild(newElement); await newElement.updateComplete; expect(newElement.querySelector('ol.usa-list')).not.toBeNull(); expect(newElement.getAttribute('role')).toBe('list'); newElement.remove(); }); it('should handle disconnection gracefully', async () => { element.type = 'ordered'; await element.updateComplete; expect(element.isConnected).toBe(true); // Test that disconnection doesn't throw errors await element.updateComplete; // Wait for any pending updates expect(() => { element.remove(); }).not.toThrow(); expect(element.isConnected).toBe(false); }); }); describe('Integration Scenarios', () => { it('should support complex government content structure', async () => { element.type = 'ordered'; await element.updateComplete; const mainList = element.querySelector('ol.usa-list'); expect(mainList).not.toBeNull(); expect(element.getAttribute('role')).toBe('list'); const slot = mainList?.querySelector('slot'); expect(slot).not.toBeNull(); }); it('should maintain accessibility with ordered content', async () => { element.type = 'ordered'; await element.updateComplete; expect(element.getAttribute('role')).toBe('list'); const orderedList = element.querySelector('ol.usa-list'); expect(orderedList).not.toBeNull(); // Verify the list has proper structure for slotted content const slot = orderedList?.querySelector('slot'); expect(slot).not.toBeNull(); }); }); describe('CSS Class Application', () => { it('should apply correct USWDS classes to list elements', async () => { await element.updateComplete; const listElement = element.querySelector('ul, ol'); expect(listElement).toBeTruthy(); expect(listElement?.classList.contains('usa-list')).toBe(true); }); it('should apply unstyled class when unstyled property is true', async () => { element.unstyled = true; await element.updateComplete; const listElement = element.querySelector('ul, ol'); expect(listElement?.classList.contains('usa-list--unstyled')).toBe(true); }); }); // CRITICAL TESTS - Auto-dismiss prevention and lifecycle stability describe('CRITICAL: Component Lifecycle Stability', () => { it('should remain in DOM after property changes', async () => { expect(document.body.contains(element)).toBe(true); expect(element.isConnected).toBe(true); // Test critical property combinations that could cause auto-dismiss const criticalPropertySets = [ { type: 'unordered', unstyled: false }, { type: 'ordered', unstyled: false }, { type: 'unordered', unstyled: true }, { type: 'ordered', unstyled: true }, ]; for (const properties of criticalPropertySets) { Object.assign(element, properties); await element.updateComplete; expect(document.body.contains(element)).toBe(true); expect(element.isConnected).toBe(true); } }); it('should maintain DOM connection during rapid property updates', async () => { const rapidUpdates = async () => { for (let i = 0; i < 10; i++) { element.type = i % 2 === 0 ? 'unordered' : 'ordered'; element.unstyled = i % 3 === 0; await element.updateComplete; expect(document.body.contains(element)).toBe(true); expect(element.isConnected).toBe(true); } }; await rapidUpdates(); }); it('should survive complete property reset cycles', async () => { element.type = 'ordered'; element.unstyled = true; await element.updateComplete; // Reset all properties element.type = 'unordered'; element.unstyled = false; await element.updateComplete; expect(document.body.contains(element)).toBe(true); expect(element.isConnected).toBe(true); // Set properties again element.type = 'ordered'; element.unstyled = false; await element.updateComplete; expect(document.body.contains(element)).toBe(true); expect(element.isConnected).toBe(true); }); }); describe('CRITICAL: Event System Stability', () => { it('should not pollute global event handlers', async () => { const originalAddEventListener = document.addEventListener; const originalRemoveEventListener = document.removeEventListener; const addEventListenerSpy = vi.fn(originalAddEventListener); const removeEventListenerSpy = vi.fn(originalRemoveEventListener); document.addEventListener = addEventListenerSpy; document.removeEventListener = removeEventListenerSpy; element.type = 'ordered'; await element.updateComplete; document.addEventListener = originalAddEventListener; document.removeEventListener = originalRemoveEventListener; expect(document.body.contains(element)).toBe(true); expect(element.isConnected).toBe(true); }); it('should handle custom events without side effects', async () => { const eventSpy = vi.fn(); element.addEventListener('custom-event', eventSpy); element.type = 'ordered'; await element.updateComplete; expect(document.body.contains(element)).toBe(true); expect(element.isConnected).toBe(true); element.removeEventListener('custom-event', eventSpy); }); it('should maintain DOM connection during event handling', async () => { const testEvent = () => { element.type = 'ordered'; element.unstyled = true; }; element.addEventListener('click', testEvent); element.click(); await element.updateComplete; expect(document.body.contains(element)).toBe(true); expect(element.isConnected).toBe(true); element.removeEventListener('click', testEvent); }); }); describe('CRITICAL: List State Management Stability', () => { it('should maintain DOM connection during list type transitions', async () => { // Start with unordered element.type = 'unordered'; await element.updateComplete; expect(document.body.contains(element)).toBe(true); expect(element.isConnected).toBe(true); // Change to ordered element.type = 'ordered'; await element.updateComplete; expect(document.body.contains(element)).toBe(true); expect(element.isConnected).toBe(true); // Back to unordered element.type = 'unordered'; await element.updateComplete; expect(document.body.contains(element)).toBe(true); expect(element.isConnected).toBe(true); }); it('should maintain DOM connection during styling changes', async () => { const stylingConfigs = [{ unstyled: false }, { unstyled: true }, { unstyled: false }]; for (const config of stylingConfigs) { Object.assign(element, config); await element.updateComplete; expect(document.body.contains(element)).toBe(true); expect(element.isConnected).toBe(true); } }); it('should handle list reorganization without DOM removal', async () => { element.type = 'ordered'; await element.updateComplete; // Call reorganization method if (typeof (element as any).reorganizeListItems === 'function') { (element as any).reorganizeListItems(); await element.updateComplete; expect(document.body.contains(element)).toBe(true); expect(element.isConnected).toBe(true); } }); }); describe('CRITICAL: Storybook Integration Stability', () => { it('should maintain DOM connection during args updates', async () => { const storybookArgs = [ { type: 'unordered', unstyled: false }, { type: 'ordered', unstyled: false }, { type: 'unordered', unstyled: true }, { type: 'ordered', unstyled: true }, ]; for (const args of storybookArgs) { Object.assign(element, args); await element.updateComplete; expect(document.body.contains(element)).toBe(true); expect(element.isConnected).toBe(true); } }); it('should survive Storybook control panel interactions', async () => { const interactions = [ () => { element.type = 'ordered'; }, () => { element.type = 'unordered'; }, () => { element.unstyled = true; }, () => { element.unstyled = false; }, () => { element.type = 'ordered'; element.unstyled = true; }, () => { element.type = 'unordered'; element.unstyled = false; }, ]; for (const interaction of interactions) { interaction(); await element.updateComplete; expect(document.body.contains(element)).toBe(true); expect(element.isConnected).toBe(true); } }); it('should handle Storybook story switching', async () => { // Simulate story 1 args - Unordered list element.type = 'unordered'; element.unstyled = false; await element.updateComplete; expect(document.body.contains(element)).toBe(true); // Simulate story 2 args - Ordered list element.type = 'ordered'; element.unstyled = false; await element.updateComplete; expect(document.body.contains(element)).toBe(true); // Simulate story 3 args - Unstyled list element.type = 'unordered'; element.unstyled = true; 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/list/usa-list.ts`; const validation = validateComponentJavaScript(componentPath, '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 () => { // Helper function that ensures proper DOM structure for accessibility testing const testListAccessibility = async (items: string[]) => { // Ensure component is rendered first await element.updateComplete; // Remove problematic role attribute for accessibility testing element.removeAttribute('role'); // Clear existing slotted content by removing li elements from the component // DO NOT use innerHTML = '' as it removes Lit's slot element const existingLis = element.querySelectorAll('li'); existingLis.forEach(li => li.remove()); // Add test items as children of the component (they'll be slotted into the list) items.forEach((item) => { const li = document.createElement('li'); li.textContent = item; element.appendChild(li); }); // Force reorganization to move items into the list element element.forceReorganize(); await element.updateComplete; await testComponentAccessibility(element, USWDS_A11Y_CONFIG.FULL_COMPLIANCE); }; // Test default unordered list element.type = 'unordered'; await testListAccessibility([ 'First benefit available to applicants', 'Second benefit for eligible recipients', 'Third program requirement', ]); // Test ordered list element.type = 'ordered'; await testListAccessibility([ 'Complete application form', 'Submit required documentation', 'Wait for approval notification', ]); // Test unstyled unordered list element.type = 'unordered'; element.unstyled = true; await testListAccessibility([ 'Contact Information: 1-800-555-0199', 'Office Hours: Monday-Friday 9am-5pm', 'Email: info@agency.gov', ]); // Test unstyled ordered list element.type = 'ordered'; element.unstyled = true; await testListAccessibility([ 'Review eligibility criteria', 'Gather required documents', 'Submit online application', ]); }); it('should maintain accessibility during dynamic updates', async () => { // Helper function that ensures proper DOM structure for accessibility testing const testListWithContent = async (items: string[]) => { await element.updateComplete; // Remove problematic role attribute for accessibility testing element.removeAttribute('role'); // Clear existing slotted content by removing li elements from the component // DO NOT use innerHTML = '' as it removes Lit's slot element const existingLis = element.querySelectorAll('li'); existingLis.forEach(li => li.remove()); // Add test items as children of the component (they'll be slotted into the list) items.forEach((item) => { const li = document.createElement('li'); li.textContent = item; element.appendChild(li); }); // Force reorganization to move items into the list element element.forceReorganize(); await element.updateComplete; await testComponentAccessibility(element, USWDS_A11Y_CONFIG.FULL_COMPLIANCE); }; // Set initial accessible state element.type = 'unordered'; await testListWithContent(['Initial list item']); // Update to ordered list element.type = 'ordered'; await testListWithContent(['Step 1: Initial application', 'Step 2: Document review']); // Update styling element.unstyled = true; await testListWithContent(['Unstyled item 1', 'Unstyled item 2']); // Back to styled element.unstyled = false; await testListWithContent(['Styled item 1', 'Styled item 2']); }); it('should pass accessibility with complex nested content', async () => { // Create nested list structure properly - nested lists should be inside li elements element.type = 'ordered'; await element.updateComplete; // Remove the role="list" attribute that causes issues with nested content element.removeAttribute('role'); // Clear existing slotted content by removing li elements from the component // DO NOT use innerHTML = '' as it removes Lit's slot element const existingLis = element.querySelectorAll('li'); existingLis.forEach(li => li.remove()); // First list item with nested unordered list const li1 = document.createElement('li'); li1.innerHTML = 'Primary application requirements'; const nestedUl = document.createElement('ul'); nestedUl.className = 'usa-list'; ['Completed Form SF-424', 'Budget narrative', 'Project timeline'].forEach((text) => { const nestedLi = document.createElement('li'); nestedLi.textContent = text; nestedUl.appendChild(nestedLi); }); li1.appendChild(nestedUl); element.appendChild(li1); // Second list item with nested ordered list const li2 = document.createElement('li'); li2.innerHTML = 'Supporting documentation'; const nestedOl = document.createElement('ol'); nestedOl.className = 'usa-list'; ['Financial statements', 'Organizational chart', 'Letters of support'].forEach((text) => { const nestedLi = document.createElement('li'); nestedLi.textContent = text; nestedOl.appendChild(nestedLi); }); li2.appendChild(nestedOl); element.appendChild(li2); // Third list item const li3 = document.createElement('li'); li3.textContent = 'Submission deadlines and contact information'; element.appendChild(li3); // Force reorganization to move items into the list element element.forceReorganize(); await element.updateComplete; await testComponentAccessibility(element, USWDS_A11Y_CONFIG.FULL_COMPLIANCE); // Test same nested content in unordered list // The items are already added to the component, just change the type element.type = 'unordered'; await element.updateComplete; // Force reorganization to move items into the new unordered list element.forceReorganize(); await element.updateComplete; await testComponentAccessibility(element, USWDS_A11Y_CONFIG.FULL_COMPLIANCE); }); }); describe('Keyboard Navigation (WCAG 2.1)', () => { it('should allow keyboard navigation to links in list items', async () => { // Create list items as DOM nodes const li1 = document.createElement('li'); li1.innerHTML = 'Item 1'; const li2 = document.createElement('li'); li2.innerHTML = 'Item 2'; const li3 = document.createElement('li'); li3.innerHTML = 'Item 3'; element.appendChild(li1); element.appendChild(li2); element.appendChild(li3); await element.updateComplete; const focusableElements = getFocusableElements(element); const links = focusableElements.filter((el) => el.tagName === 'A'); expect(links.length).toBeGreaterThanOrEqual(3); }); it('should pass keyboard navigation tests (presentational)', async () => { // Create list items as DOM nodes const li1 = document.createElement('li'); li1.textContent = 'Item 1'; const li2 = document.createElement('li'); li2.textContent = 'Item 2'; element.appendChild(li1); element.appendChild(li2); await element.updateComplete; const result = await testKeyboardNavigation(element, { shortcuts: [], testEscapeKey: false, testArrowKeys: false, allowNonFocusable: true, // List is presentational }); // List is presentational, no keyboard interaction expected expect(result.passed).toBe(true); }); it('should have no keyboard traps with interactive content', async () => { // Create list items as DOM nodes const li1 = document.createElement('li'); li1.innerHTML = 'Link 1'; const li2 = document.createElement('li'); li2.innerHTML = 'Link 2'; element.appendChild(li1); element.appendChild(li2); await element.updateComplete; const links = element.querySelectorAll('a'); expect(links.length).toBeGreaterThanOrEqual(2); const tabEvent = new KeyboardEvent('keydown', { key: 'Tab', code: 'Tab', keyCode: 9, bubbles: true, cancelable: true, }); links[0]?.dispatchEvent(tabEvent); expect(true).toBe(true); }); it('should maintain proper tab order through list items', async () => { // Create list items as DOM nodes const li1 = document.createElement('li'); li1.innerHTML = 'First'; const li2 = document.createElement('li'); li2.innerHTML = 'Second'; const li3 = document.createElement('li'); li3.innerHTML = 'Third'; element.appendChild(li1); element.appendChild(li2); element.appendChild(li3); await element.updateComplete; const focusableElements = getFocusableElements(element); focusableElements.forEach((el) => { expect((el as HTMLElement).tabIndex).toBeGreaterThanOrEqual(0); }); }); it('should not be focusable itself (presentational list)', async () => { // Create list items as DOM nodes const li1 = document.createElement('li'); li1.textContent = 'Item 1'; const li2 = document.createElement('li'); li2.textContent = 'Item 2'; element.appendChild(li1); element.appendChild(li2); await element.updateComplete; const listElement = element.querySelector('ul, ol'); expect(listElement).toBeTruthy(); // List container should not be focusable const focusableInList = getFocusableElements(element); const isListContainer = focusableInList.some((el) => el === listElement); expect(isListContainer).toBe(false); }); it('should handle ordered list keyboard navigation', async () => { element.type = 'ordered'; // Create list items as DOM nodes const li1 = document.createElement('li'); li1.innerHTML = 'One'; const li2 = document.createElement('li'); li2.innerHTML = 'Two'; element.appendChild(li1); element.appendChild(li2); await element.updateComplete; const ol = element.querySelector('ol'); expect(ol).toBeTruthy(); const links = element.querySelectorAll('a'); links.forEach((link) => { expect(link.tabIndex).toBeGreaterThanOrEqual(0); }); }); it('should handle unstyled list keyboard navigation', async () => { element.unstyled = true; // Create list item as DOM node const li = document.createElement('li'); li.innerHTML = 'Link'; element.appendChild(li); await element.updateComplete; const link = element.querySelector('a'); expect(link).toBeTruthy(); expect(link!.tabIndex).toBeGreaterThanOrEqual(0); }); it('should handle mixed interactive and non-interactive content', async () => { // Create list items as DOM nodes const li1 = document.createElement('li'); li1.textContent = 'Plain text item'; const li2 = document.createElement('li'); li2.innerHTML = 'Link item'; const li3 = document.createElement('li'); li3.innerHTML = ''; const li4 = document.createElement('li'); li4.textContent = 'Another plain item'; element.appendChild(li1); element.appendChild(li2); element.appendChild(li3); element.appendChild(li4); await element.updateComplete; const focusableElements = getFocusableElements(element); // Should have link + button expect(focusableElements.length).toBe(2); }); it('should handle empty list gracefully', async () => { // Component with no list items await element.updateComplete; const list = element.querySelector('ul, ol'); expect(list).toBeTruthy(); // Empty list should not have keyboard traps const focusableElements = getFocusableElements(element); expect(focusableElements.length).toBe(0); }); it('should not interfere with nested lists keyboard navigation', async () => { // Create nested list structure as DOM nodes const li1 = document.createElement('li'); const link1 = document.createElement('a'); link1.href = '/parent1'; link1.textContent = 'Parent 1'; li1.appendChild(link1); const nestedUl = document.createElement('ul'); const childLi1 = document.createElement('li'); const childLink1 = document.createElement('a'); childLink1.href = '/child1'; childLink1.textContent = 'Child 1'; childLi1.appendChild(childLink1); const childLi2 = document.createElement('li'); const childLink2 = document.createElement('a'); childLink2.href = '/child2'; childLink2.textContent = 'Child 2'; childLi2.appendChild(childLink2); nestedUl.appendChild(childLi1); nestedUl.appendChild(childLi2); li1.appendChild(nestedUl); const li2 = document.createElement('li'); const link2 = document.createElement('a'); link2.href = '/parent2'; link2.textContent = 'Parent 2'; li2.appendChild(link2); element.appendChild(li1); element.appendChild(li2); await element.updateComplete; const links = element.querySelectorAll('a'); // Should have parent + child links all focusable expect(links.length).toBeGreaterThanOrEqual(4); links.forEach((link) => { expect(link.tabIndex).toBeGreaterThanOrEqual(0); }); }); }); });