import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import './usa-button-group.ts'; import type { USAButtonGroup, ButtonGroupItem } from './usa-button-group.js'; import { testComponentAccessibility, USWDS_A11Y_CONFIG, } from '../../../__tests__/accessibility-utils.js'; import { waitForUpdate, validateComponentJavaScript } from '../../../__tests__/test-utils.js'; describe('USAButtonGroup', () => { let element: USAButtonGroup; const sampleButtons: ButtonGroupItem[] = [ { text: 'Primary Button', variant: 'primary', type: 'button', }, { text: 'Secondary Button', variant: 'secondary', type: 'button', }, { text: 'Outline Button', variant: 'outline', type: 'button', }, { text: 'Disabled Button', variant: 'base', type: 'button', disabled: true, }, ]; beforeEach(() => { element = document.createElement('usa-button-group') as USAButtonGroup; document.body.appendChild(element); }); afterEach(() => { element.remove(); }); describe('Basic Functionality', () => { it('should create and render correctly', async () => { await waitForUpdate(element); expect(element).toBeTruthy(); expect(element.tagName).toBe('USA-BUTTON-GROUP'); }); it('should have default properties', () => { expect(element.type).toBe('default'); expect(element.buttons).toEqual([]); }); it('should render empty button group with slot', async () => { await waitForUpdate(element); const group = element.querySelector('.usa-button-group'); expect(group).toBeTruthy(); const slot = element.querySelector('slot'); expect(slot).toBeTruthy(); }); }); describe('Properties', () => { it('should handle type changes', async () => { element.type = 'segmented'; await waitForUpdate(element); const group = element.querySelector('.usa-button-group'); expect(group?.classList.contains('usa-button-group--segmented')).toBe(true); }); it('should handle buttons array changes', async () => { element.buttons = sampleButtons; await waitForUpdate(element); const buttons = element.querySelectorAll('button'); expect(buttons.length).toBe(4); const items = element.querySelectorAll('.usa-button-group__item'); expect(items.length).toBe(4); }); it('should render programmatic buttons instead of slot when buttons provided', async () => { element.buttons = sampleButtons; await waitForUpdate(element); const buttons = element.querySelectorAll('button'); expect(buttons.length).toBe(4); const slot = element.querySelector('slot'); expect(slot).toBeFalsy(); // No slot when buttons are programmatically provided }); }); describe('Button Rendering', () => { beforeEach(async () => { element.buttons = sampleButtons; await waitForUpdate(element); }); it('should render buttons with correct text', () => { const buttons = element.querySelectorAll('button'); expect(buttons[0].textContent?.trim()).toBe('Primary Button'); expect(buttons[1].textContent?.trim()).toBe('Secondary Button'); expect(buttons[2].textContent?.trim()).toBe('Outline Button'); expect(buttons[3].textContent?.trim()).toBe('Disabled Button'); }); it('should render buttons with correct CSS classes', () => { const buttons = element.querySelectorAll('button'); // Primary button (default, no additional class) expect(buttons[0].classList.contains('usa-button')).toBe(true); expect(buttons[0].classList.contains('usa-button--primary')).toBe(false); // Secondary button expect(buttons[1].classList.contains('usa-button')).toBe(true); expect(buttons[1].classList.contains('usa-button--secondary')).toBe(true); // Outline button expect(buttons[2].classList.contains('usa-button')).toBe(true); expect(buttons[2].classList.contains('usa-button--outline')).toBe(true); // Base button expect(buttons[3].classList.contains('usa-button')).toBe(true); expect(buttons[3].classList.contains('usa-button--base')).toBe(true); }); it('should render buttons with correct types', () => { const buttons = element.querySelectorAll('button'); buttons.forEach((button) => { expect((button as HTMLButtonElement).type).toBe('button'); }); }); it('should handle button disabled state', () => { const buttons = element.querySelectorAll('button'); expect((buttons[0] as HTMLButtonElement).disabled).toBe(false); expect((buttons[1] as HTMLButtonElement).disabled).toBe(false); expect((buttons[2] as HTMLButtonElement).disabled).toBe(false); expect((buttons[3] as HTMLButtonElement).disabled).toBe(true); }); it('should render each button in a list item', () => { const items = element.querySelectorAll('.usa-button-group__item'); const buttons = element.querySelectorAll('button'); expect(items.length).toBe(buttons.length); items.forEach((item, index) => { const button = item.querySelector('button'); expect(button).toBe(buttons[index]); }); }); }); describe('Button Types and Variants', () => { it('should handle all button types', async () => { const typedButtons: ButtonGroupItem[] = [ { text: 'Button Type', type: 'button' }, { text: 'Submit Type', type: 'submit' }, { text: 'Reset Type', type: 'reset' }, ]; element.buttons = typedButtons; await waitForUpdate(element); const buttons = element.querySelectorAll('button'); expect((buttons[0] as HTMLButtonElement).type).toBe('button'); expect((buttons[1] as HTMLButtonElement).type).toBe('submit'); expect((buttons[2] as HTMLButtonElement).type).toBe('reset'); }); it('should default to button type when not specified', async () => { element.buttons = [{ text: 'Default Type' }]; await waitForUpdate(element); const button = element.querySelector('button') as HTMLButtonElement; expect(button.type).toBe('button'); }); it('should handle all button variants', async () => { const variantButtons: ButtonGroupItem[] = [ { text: 'Primary', variant: 'primary' }, { text: 'Secondary', variant: 'secondary' }, { text: 'Outline', variant: 'outline' }, { text: 'Base', variant: 'base' }, ]; element.buttons = variantButtons; await waitForUpdate(element); const buttons = element.querySelectorAll('button'); // Primary doesn't add additional class expect(buttons[0].classList.contains('usa-button')).toBe(true); expect(buttons[0].classList.contains('usa-button--primary')).toBe(false); // Other variants add their respective classes expect(buttons[1].classList.contains('usa-button--secondary')).toBe(true); expect(buttons[2].classList.contains('usa-button--outline')).toBe(true); expect(buttons[3].classList.contains('usa-button--base')).toBe(true); }); it('should handle buttons without variant specified', async () => { element.buttons = [{ text: 'No Variant' }]; await waitForUpdate(element); const button = element.querySelector('button'); expect(button?.classList.contains('usa-button')).toBe(true); expect(button?.className).toBe('usa-button'); }); }); describe('Group Types', () => { it('should render default button group', async () => { element.type = 'default'; element.buttons = sampleButtons; await waitForUpdate(element); const group = element.querySelector('.usa-button-group'); expect(group?.classList.contains('usa-button-group')).toBe(true); expect(group?.classList.contains('usa-button-group--segmented')).toBe(false); }); it('should render segmented button group', async () => { element.type = 'segmented'; element.buttons = sampleButtons; await waitForUpdate(element); const group = element.querySelector('.usa-button-group'); expect(group?.classList.contains('usa-button-group')).toBe(true); expect(group?.classList.contains('usa-button-group--segmented')).toBe(true); }); it('should update group type dynamically', async () => { element.buttons = sampleButtons; element.type = 'default'; await waitForUpdate(element); let group = element.querySelector('.usa-button-group'); expect(group?.classList.contains('usa-button-group--segmented')).toBe(false); element.type = 'segmented'; await waitForUpdate(element); group = element.querySelector('.usa-button-group'); expect(group?.classList.contains('usa-button-group--segmented')).toBe(true); }); }); describe('Event Handling', () => { beforeEach(async () => { element.buttons = sampleButtons; await waitForUpdate(element); }); it('should dispatch button-click event when button clicked', async () => { let eventDetail: any = null; element.addEventListener('button-click', (e: any) => { eventDetail = e.detail; }); const firstButton = element.querySelector('button') as HTMLButtonElement; firstButton.click(); expect(eventDetail).toBeTruthy(); expect(eventDetail.button).toEqual(sampleButtons[0]); expect(eventDetail.index).toBe(0); }); it('should dispatch events for all buttons', async () => { const events: any[] = []; element.addEventListener('button-click', (e: any) => { events.push(e.detail); }); const buttons = element.querySelectorAll('button'); // Click all non-disabled buttons buttons.forEach((button, _index) => { if (!(button as HTMLButtonElement).disabled) { button.click(); } }); expect(events.length).toBe(3); // 4 buttons, but 1 is disabled expect(events[0].index).toBe(0); expect(events[1].index).toBe(1); expect(events[2].index).toBe(2); }); it('should call onclick handler if provided', async () => { const onClickSpy = vi.fn(); element.buttons = [ { text: 'Clickable Button', onclick: onClickSpy, }, ]; await waitForUpdate(element); const button = element.querySelector('button') as HTMLButtonElement; button.click(); expect(onClickSpy).toHaveBeenCalledOnce(); }); it('should have correct event properties', async () => { let capturedEvent: CustomEvent | null = null; element.addEventListener('button-click', (e: any) => { capturedEvent = e; }); const button = element.querySelector('button') as HTMLButtonElement; button.click(); expect(capturedEvent).toBeTruthy(); if (capturedEvent) { expect((capturedEvent as any).type).toBe('button-click'); expect((capturedEvent as any).bubbles).toBe(true); expect((capturedEvent as any).composed).toBe(true); } }); it('should not dispatch events for disabled buttons', async () => { let eventFired = false; element.addEventListener('button-click', () => { eventFired = true; }); const buttons = element.querySelectorAll('button'); const disabledButton = Array.from(buttons).find((btn) => (btn as HTMLButtonElement).disabled); expect(disabledButton).toBeTruthy(); disabledButton?.click(); expect(eventFired).toBe(false); }); }); describe('Slot Usage', () => { it('should render slot when no buttons provided', async () => { element.buttons = []; await waitForUpdate(element); const group = element.querySelector('.usa-button-group'); const slot = element.querySelector('slot'); expect(group).toBeTruthy(); expect(slot).toBeTruthy(); }); it('should support slotted content', async () => { element.buttons = []; // Ensure we use slot mode // Add slotted content const listItem = document.createElement('li'); listItem.className = 'usa-button-group__item'; listItem.innerHTML = ''; element.appendChild(listItem); await waitForUpdate(element); const slot = element.querySelector('slot'); expect(slot).toBeTruthy(); const slottedButton = element.querySelector('button'); expect(slottedButton?.textContent?.trim()).toBe('Slotted Button'); }); it('should prefer programmatic buttons over slot', async () => { // Add slotted content first const listItem = document.createElement('li'); listItem.innerHTML = ''; element.appendChild(listItem); // Then set programmatic buttons element.buttons = [{ text: 'Programmatic Button' }]; await waitForUpdate(element); // Should render programmatic button, not slot const buttons = element.querySelectorAll('button'); expect(buttons.length).toBe(1); expect(buttons[0].textContent?.trim()).toBe('Programmatic Button'); const slot = element.querySelector('slot'); expect(slot).toBeFalsy(); }); }); describe('Complex Scenarios', () => { it('should handle dynamic button updates', async () => { // Start with some buttons element.buttons = sampleButtons.slice(0, 2); await waitForUpdate(element); expect(element.querySelectorAll('button').length).toBe(2); // Add more buttons element.buttons = sampleButtons; await waitForUpdate(element); expect(element.querySelectorAll('button').length).toBe(4); // Remove buttons element.buttons = [sampleButtons[0]]; await waitForUpdate(element); expect(element.querySelectorAll('button').length).toBe(1); expect(element.querySelector('button')?.textContent?.trim()).toBe('Primary Button'); }); it('should handle mixed button configurations', async () => { const mixedButtons: ButtonGroupItem[] = [ { text: 'Submit Form', type: 'submit', variant: 'primary' }, { text: 'Cancel', type: 'button', variant: 'outline' }, { text: 'Reset', type: 'reset', variant: 'secondary', disabled: true }, { text: 'Base Action', variant: 'base' }, // Default type ]; element.buttons = mixedButtons; await waitForUpdate(element); const buttons = element.querySelectorAll('button'); // Check types expect((buttons[0] as HTMLButtonElement).type).toBe('submit'); expect((buttons[1] as HTMLButtonElement).type).toBe('button'); expect((buttons[2] as HTMLButtonElement).type).toBe('reset'); expect((buttons[3] as HTMLButtonElement).type).toBe('button'); // Default // Check variants expect(buttons[0].classList.contains('usa-button')).toBe(true); // Primary is default expect(buttons[1].classList.contains('usa-button--outline')).toBe(true); expect(buttons[2].classList.contains('usa-button--secondary')).toBe(true); expect(buttons[3].classList.contains('usa-button--base')).toBe(true); // Check disabled state expect((buttons[0] as HTMLButtonElement).disabled).toBe(false); expect((buttons[1] as HTMLButtonElement).disabled).toBe(false); expect((buttons[2] as HTMLButtonElement).disabled).toBe(true); expect((buttons[3] as HTMLButtonElement).disabled).toBe(false); }); it('should handle switching between slot and programmatic modes', async () => { // Start in slot mode element.buttons = []; await waitForUpdate(element); expect(element.querySelector('slot')).toBeTruthy(); expect(element.querySelectorAll('button').length).toBe(0); // Switch to programmatic mode element.buttons = sampleButtons.slice(0, 2); await waitForUpdate(element); expect(element.querySelector('slot')).toBeFalsy(); expect(element.querySelectorAll('button').length).toBe(2); // Switch back to slot mode element.buttons = []; await waitForUpdate(element); expect(element.querySelector('slot')).toBeTruthy(); }); it('should handle empty and edge cases', async () => { // Empty buttons array element.buttons = []; await waitForUpdate(element); const group = element.querySelector('.usa-button-group'); expect(group).toBeTruthy(); expect(element.querySelector('slot')).toBeTruthy(); // Single button element.buttons = [{ text: 'Single Button' }]; await waitForUpdate(element); expect(element.querySelectorAll('button').length).toBe(1); expect(element.querySelector('button')?.textContent?.trim()).toBe('Single Button'); // Button with empty text element.buttons = [{ text: '' }]; await waitForUpdate(element); expect(element.querySelectorAll('button').length).toBe(1); expect(element.querySelector('button')?.textContent?.trim()).toBe(''); }); }); describe('Accessibility', () => { beforeEach(async () => { element.buttons = sampleButtons; await waitForUpdate(element); }); it('should have proper semantic structure', () => { const group = element.querySelector('.usa-button-group'); expect(group?.tagName).toBe('UL'); const items = element.querySelectorAll('.usa-button-group__item'); items.forEach((item) => { expect(item.tagName).toBe('LI'); }); }); it('should have focusable buttons', () => { const buttons = element.querySelectorAll('button'); const enabledButtons = Array.from(buttons).filter( (btn) => !(btn as HTMLButtonElement).disabled ); enabledButtons.forEach((button) => { expect(button.tabIndex).not.toBe(-1); }); }); it('should support keyboard interaction', () => { const buttons = element.querySelectorAll('button'); const firstButton = buttons[0] as HTMLButtonElement; // Should be focusable firstButton.focus(); expect(document.activeElement).toBe(firstButton); }); it('should maintain button semantics', () => { const buttons = element.querySelectorAll('button'); buttons.forEach((button) => { expect(button.tagName).toBe('BUTTON'); expect(button.getAttribute('type')).toBeTruthy(); }); }); it('should handle disabled state properly', () => { const buttons = element.querySelectorAll('button'); const disabledButton = Array.from(buttons).find((btn) => (btn as HTMLButtonElement).disabled); expect(disabledButton).toBeTruthy(); expect((disabledButton as HTMLButtonElement).disabled).toBe(true); expect(disabledButton?.getAttribute('disabled')).toBe(''); }); }); describe('Form Integration', () => { it('should work within forms', async () => { // Create a fresh element for form testing to avoid DOM manipulation issues const formElement = document.createElement('usa-button-group') as USAButtonGroup; const form = document.createElement('form'); form.appendChild(formElement); document.body.appendChild(form); formElement.buttons = [ { text: 'Submit', type: 'submit' }, { text: 'Reset', type: 'reset' }, { text: 'Button', type: 'button' }, ]; await waitForUpdate(formElement); const buttons = formElement.querySelectorAll('button'); expect(buttons.length).toBe(3); expect(buttons[0]).toBeTruthy(); expect((buttons[0] as HTMLButtonElement).type).toBe('submit'); expect((buttons[1] as HTMLButtonElement).type).toBe('reset'); expect((buttons[2] as HTMLButtonElement).type).toBe('button'); form.remove(); }); it('should handle form submission types correctly', async () => { element.buttons = [ { text: 'Save Draft', type: 'button' }, { text: 'Submit Application', type: 'submit' }, { text: 'Clear Form', type: 'reset' }, ]; await waitForUpdate(element); const buttons = element.querySelectorAll('button'); const submitButton = Array.from(buttons).find( (btn) => (btn as HTMLButtonElement).type === 'submit' ); const resetButton = Array.from(buttons).find( (btn) => (btn as HTMLButtonElement).type === 'reset' ); expect(submitButton).toBeTruthy(); expect(resetButton).toBeTruthy(); expect(submitButton?.textContent?.trim()).toBe('Submit Application'); expect(resetButton?.textContent?.trim()).toBe('Clear Form'); }); }); describe('Error Handling', () => { it('should handle malformed button objects', async () => { const malformedButtons = [ { text: 'Valid Button' }, { text: 'Invalid Variant', variant: 'invalid' as any }, { text: 'Invalid Type', type: 'invalid' as any }, ]; expect(() => { element.buttons = malformedButtons; }).not.toThrow(); await waitForUpdate(element); const buttons = element.querySelectorAll('button'); expect(buttons.length).toBe(3); }); it('should handle null and undefined values gracefully', async () => { const edgeCaseButtons = [ { text: 'Normal Button' }, { text: 'Undefined Variant', variant: undefined }, { text: 'Null Onclick', onclick: null as any }, ]; expect(() => { element.buttons = edgeCaseButtons; }).not.toThrow(); await waitForUpdate(element); expect(element.querySelectorAll('button').length).toBe(3); }); }); describe('CRITICAL: Component Lifecycle Stability', () => { beforeEach(() => { element = document.createElement('usa-button-group') as USAButtonGroup; document.body.appendChild(element); }); it('should remain in DOM after property changes', async () => { await waitForUpdate(element); expect(document.body.contains(element)).toBe(true); expect(element.isConnected).toBe(true); element.buttons = [{ text: 'Test Button', variant: 'primary' }]; await waitForUpdate(element); expect(document.body.contains(element)).toBe(true); expect(element.isConnected).toBe(true); element.ariaLabel = 'Custom button group'; await waitForUpdate(element); expect(document.body.contains(element)).toBe(true); expect(element.isConnected).toBe(true); element.buttons = sampleButtons; await waitForUpdate(element); expect(document.body.contains(element)).toBe(true); expect(element.isConnected).toBe(true); }); it('should maintain element stability during button array updates', async () => { const originalElement = element; const buttonSets = [ [{ text: 'Single Button', variant: 'primary' }], [ { text: 'Button 1', variant: 'primary' }, { text: 'Button 2', variant: 'secondary' }, ], sampleButtons, [{ text: 'Final Button', variant: 'outline', disabled: true }], ]; for (const buttons of buttonSets) { element.buttons = buttons; await waitForUpdate(element); expect(element).toBe(originalElement); expect(document.body.contains(element)).toBe(true); expect(element.isConnected).toBe(true); } }); it('should preserve DOM connection through slot to buttons transition', async () => { // Start with empty buttons (slot mode) element.buttons = []; await waitForUpdate(element); expect(document.body.contains(element)).toBe(true); expect(element.isConnected).toBe(true); // Switch to button array mode element.buttons = [{ text: 'Programmatic Button', variant: 'primary' }]; await waitForUpdate(element); expect(document.body.contains(element)).toBe(true); expect(element.isConnected).toBe(true); // Switch back to slot mode element.buttons = []; await waitForUpdate(element); expect(document.body.contains(element)).toBe(true); expect(element.isConnected).toBe(true); }); }); describe('CRITICAL: Event System Stability', () => { beforeEach(async () => { element = document.createElement('usa-button-group') as USAButtonGroup; element.buttons = [ { text: 'Click Me', variant: 'primary', type: 'button' }, { text: 'Submit Form', variant: 'secondary', type: 'submit' }, { text: 'Disabled', variant: 'outline', disabled: true }, ]; document.body.appendChild(element); await waitForUpdate(element); }); it('should not pollute global event handling', async () => { const globalClickSpy = vi.fn(); document.addEventListener('click', globalClickSpy); const buttonClickSpy = vi.fn(); element.addEventListener('button-click', buttonClickSpy); const button = element.querySelector('button[type="button"]') as HTMLButtonElement; if (button) { button.click(); await waitForUpdate(element); } expect(document.body.contains(element)).toBe(true); expect(element.isConnected).toBe(true); document.removeEventListener('click', globalClickSpy); }); it('should maintain stability during button interactions', async () => { const buttonClickSpy = vi.fn(); element.addEventListener('button-click', buttonClickSpy); const buttons = element.querySelectorAll( 'button:not([disabled])' ) as NodeListOf; for (const button of buttons) { button.click(); await waitForUpdate(element); expect(document.body.contains(element)).toBe(true); expect(element.isConnected).toBe(true); } expect(buttonClickSpy.mock.calls.length).toBe(buttons.length); }); it('should maintain stability during rapid button array changes', async () => { const buttonConfigurations = [ [{ text: 'Config 1', variant: 'primary' }], [ { text: 'Config 2A', variant: 'secondary' }, { text: 'Config 2B', variant: 'outline' }, ], [], [{ text: 'Config 4', variant: 'base', disabled: true }], ]; for (const config of buttonConfigurations) { element.buttons = config; await waitForUpdate(element); expect(document.body.contains(element)).toBe(true); expect(element.isConnected).toBe(true); } }); }); describe('CRITICAL: Button Group State Management Stability', () => { beforeEach(async () => { element = document.createElement('usa-button-group') as USAButtonGroup; document.body.appendChild(element); await waitForUpdate(element); }); it('should maintain DOM connection during complex button configurations', async () => { const complexConfigurations = [ { buttons: [{ text: 'Simple', variant: 'primary' }], ariaLabel: 'Simple group', }, { buttons: [ { text: 'Submit', type: 'submit', variant: 'primary' }, { text: 'Reset', type: 'reset', variant: 'secondary' }, { text: 'Cancel', type: 'button', variant: 'outline' }, ], ariaLabel: 'Form actions', }, { buttons: [ { text: 'Enabled', variant: 'base' }, { text: 'Disabled', variant: 'base', disabled: true }, ], ariaLabel: 'Mixed states', }, ]; for (const config of complexConfigurations) { Object.assign(element, config); await waitForUpdate(element); expect(document.body.contains(element)).toBe(true); expect(element.isConnected).toBe(true); } }); it('should preserve element stability during button type variations', async () => { const originalElement = element; const buttonTypeScenarios = [ [{ text: 'Button', type: 'button' }], [{ text: 'Submit', type: 'submit' }], [{ text: 'Reset', type: 'reset' }], [{ text: 'Default Type' }], // No type specified ]; for (const buttons of buttonTypeScenarios) { element.buttons = buttons; await waitForUpdate(element); expect(element).toBe(originalElement); expect(document.body.contains(element)).toBe(true); expect(element.isConnected).toBe(true); } }); it('should maintain stability during button variant changes', async () => { const variantScenarios = [ [{ text: 'Primary', variant: 'primary' }], [{ text: 'Secondary', variant: 'secondary' }], [{ text: 'Outline', variant: 'outline' }], [{ text: 'Base', variant: 'base' }], [{ text: 'Unstyled', variant: 'unstyled' }], ]; for (const buttons of variantScenarios) { element.buttons = buttons; await waitForUpdate(element); expect(document.body.contains(element)).toBe(true); expect(element.isConnected).toBe(true); } }); }); describe('Accessibility Compliance (CRITICAL)', () => { it('should pass comprehensive accessibility tests (same as Storybook)', async () => { // Test with empty button group (slot mode) element.buttons = []; await waitForUpdate(element); await testComponentAccessibility(element, USWDS_A11Y_CONFIG.FULL_COMPLIANCE); // Test with single button element.buttons = [{ text: 'Single Button', variant: 'primary', type: 'button' }]; await waitForUpdate(element); await testComponentAccessibility(element, USWDS_A11Y_CONFIG.FULL_COMPLIANCE); // Test with multiple buttons of different variants element.buttons = [ { text: 'Primary Action', variant: 'primary', type: 'button' }, { text: 'Secondary Action', variant: 'secondary', type: 'button' }, { text: 'Outline Action', variant: 'outline', type: 'button' }, { text: 'Base Action', variant: 'base', type: 'button' }, ]; await waitForUpdate(element); await testComponentAccessibility(element, USWDS_A11Y_CONFIG.FULL_COMPLIANCE); // Test with mixed button types and states element.buttons = [ { text: 'Submit Form', variant: 'primary', type: 'submit' }, { text: 'Reset Form', variant: 'secondary', type: 'reset' }, { text: 'Cancel', variant: 'outline', type: 'button' }, { text: 'Disabled Action', variant: 'base', type: 'button', disabled: true }, ]; await waitForUpdate(element); await testComponentAccessibility(element, USWDS_A11Y_CONFIG.FULL_COMPLIANCE); // Test segmented button group element.type = 'segmented'; element.buttons = [ { text: 'View', variant: 'outline', type: 'button' }, { text: 'Edit', variant: 'outline', type: 'button' }, { text: 'Delete', variant: 'outline', type: 'button', disabled: true }, ]; await waitForUpdate(element); await testComponentAccessibility(element, USWDS_A11Y_CONFIG.FULL_COMPLIANCE); // Test with aria-label element.ariaLabel = 'Document actions'; await waitForUpdate(element); await testComponentAccessibility(element, USWDS_A11Y_CONFIG.FULL_COMPLIANCE); }); it('should maintain accessibility during dynamic button updates', async () => { // Start with basic configuration element.buttons = [{ text: 'Initial Button', variant: 'primary' }]; await waitForUpdate(element); await testComponentAccessibility(element, USWDS_A11Y_CONFIG.FULL_COMPLIANCE); // Add more buttons element.buttons = [ { text: 'Button 1', variant: 'primary' }, { text: 'Button 2', variant: 'secondary' }, { text: 'Button 3', variant: 'outline' }, ]; await waitForUpdate(element); await testComponentAccessibility(element, USWDS_A11Y_CONFIG.FULL_COMPLIANCE); // Change to segmented type element.type = 'segmented'; await waitForUpdate(element); await testComponentAccessibility(element, USWDS_A11Y_CONFIG.FULL_COMPLIANCE); // Switch back to slot mode element.buttons = []; await waitForUpdate(element); await testComponentAccessibility(element, USWDS_A11Y_CONFIG.FULL_COMPLIANCE); }); // SKIP: jsdom limitation - MOVE TO CYPRESS // ✅ CYPRESS COVERAGE: cypress/e2e/button-group-accessibility.cy.ts // Tests form context accessibility in real browser environment describe('JavaScript Implementation Validation', () => { it('should pass JavaScript implementation validation', async () => { // Validate USWDS JavaScript implementation patterns const componentPath = `${process.cwd()}/src/components/button-group/usa-button-group.ts`; const validation = validateComponentJavaScript(componentPath, 'button-group'); if (!validation.isValid) { console.warn('JavaScript validation issues:', validation.issues); } // JavaScript validation should pass for critical integration patterns expect(validation.score).toBeGreaterThanOrEqual(50); // Allow some non-critical issues // Critical USWDS integration should be present (or properly classified as presentational) const criticalIssues = validation.issues.filter((issue) => issue.includes('Missing USWDS JavaScript integration') ); // Note: button-group may be presentational - allow this validation to be lenient expect(criticalIssues.length).toBeLessThanOrEqual(1); }); }); }); 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-button-group') as USAButtonGroup; element.buttons = [ { text: 'Storybook Primary', variant: 'primary', type: 'button' }, { text: 'Storybook Secondary', variant: 'secondary', type: 'button' }, ]; element.ariaLabel = 'Storybook button group'; storyContainer.appendChild(element); await waitForUpdate(element); // Simulate Storybook control updates element.buttons = [ { text: 'Updated Primary', variant: 'outline', type: 'submit' }, { text: 'Updated Secondary', variant: 'base', disabled: true }, { text: 'New Button', variant: 'unstyled', type: 'reset' }, ]; element.ariaLabel = 'Updated Storybook group'; await waitForUpdate(element); expect(storyContainer.contains(element)).toBe(true); expect(element.isConnected).toBe(true); expect(element.ariaLabel).toBe('Updated Storybook group'); storyContainer.remove(); }); it('should handle Storybook args updates without component removal', async () => { element = document.createElement('usa-button-group') as USAButtonGroup; document.body.appendChild(element); await waitForUpdate(element); const storyArgs = [ { ariaLabel: 'Story args test 1', buttons: [{ text: 'Args Button 1', variant: 'primary' }], }, { ariaLabel: 'Story args test 2', buttons: [ { text: 'Args Button 2A', variant: 'secondary' }, { text: 'Args Button 2B', variant: 'outline', disabled: true }, ], }, { ariaLabel: 'Story args test 3', buttons: [], }, ]; for (const args of storyArgs) { Object.assign(element, args); await waitForUpdate(element); 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-button-group') as USAButtonGroup; document.body.appendChild(element); // Simulate complex Storybook scenario with rapid changes const interactions = [ () => { element.ariaLabel = 'Interactive group'; }, () => { element.buttons = [{ text: 'Interactive 1', variant: 'primary' }]; }, () => { element.buttons = [ { text: 'Interactive 1', variant: 'primary' }, { text: 'Interactive 2', variant: 'secondary', disabled: true }, ]; }, () => { element.ariaLabel = 'Updated interactive group'; }, () => { element.buttons = []; }, ]; for (const interaction of interactions) { interaction(); await waitForUpdate(element); expect(document.body.contains(element)).toBe(true); expect(element.isConnected).toBe(true); } }); }); });