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);
});
});
});
});