/** * Button Group Layout Tests * Prevents regression of button alignment and spacing issues within groups */ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import '../button-group/index.ts'; import type { USAButtonGroup } from './usa-button-group.js'; import { validateComponentJavaScript } from '../../../__tests__/test-utils.js'; describe('USAButtonGroup Layout Tests', () => { let element: USAButtonGroup; beforeEach(() => { element = document.createElement('usa-button-group') as USAButtonGroup; element.buttons = [ { text: 'Primary', variant: 'primary' }, { text: 'Secondary', variant: 'secondary' }, { text: 'Outline', variant: 'outline' }, { text: 'Disabled', variant: 'primary', disabled: true } ]; document.body.appendChild(element); }); afterEach(() => { element.remove(); }); it('should have correct USWDS button group DOM structure', async () => { await element.updateComplete; const buttonGroup = element.querySelector('.usa-button-group'); const buttonItems = element.querySelectorAll('.usa-button-group__item'); const buttons = element.querySelectorAll('.usa-button'); expect(buttonGroup, 'Should have button group container').toBeTruthy(); expect(buttonItems.length, 'Should have button items').toBe(4); expect(buttons.length, 'Should have buttons').toBe(4); // Check nesting structure buttonItems.forEach((item, index) => { const button = item.querySelector('.usa-button'); expect(buttonGroup!.contains(item), `Button item ${index} should be inside button group`).toBe(true); expect(item.contains(button!), `Button ${index} should be inside its item`).toBe(true); }); }); it('should position buttons correctly within button group items', async () => { await element.updateComplete; const buttonGroup = element.querySelector('.usa-button-group'); const buttonItems = element.querySelectorAll('.usa-button-group__item'); const buttons = element.querySelectorAll('.usa-button'); expect(buttonGroup!.tagName, 'Button group should be a list element').toBe('UL'); // Each button should be properly contained within its item buttons.forEach((button, index) => { const item = buttonItems[index]; expect(item.contains(button), `Button ${index} should be inside its corresponding item`).toBe(true); expect(item.tagName, `Button item ${index} should be a list item`).toBe('LI'); }); }); it('should handle button variants correctly', async () => { await element.updateComplete; const buttons = element.querySelectorAll('.usa-button'); // Check button classes match variants expect(buttons[0].classList.contains('usa-button'), 'Primary button should have base class').toBe(true); expect(buttons[1].classList.contains('usa-button--secondary'), 'Secondary button should have secondary class').toBe(true); expect(buttons[2].classList.contains('usa-button--outline'), 'Outline button should have outline class').toBe(true); expect((buttons[3] as HTMLButtonElement).disabled, 'Disabled button should be disabled').toBe(true); }); it('should handle segmented button group variant correctly', async () => { element.type = 'segmented'; await element.updateComplete; const buttonGroup = element.querySelector('.usa-button-group'); expect( buttonGroup!.classList.contains('usa-button-group--segmented'), 'Should have segmented class when type is segmented' ).toBe(true); }); it('should handle default button group correctly', async () => { element.type = 'default'; await element.updateComplete; const buttonGroup = element.querySelector('.usa-button-group'); expect( buttonGroup!.classList.contains('usa-button-group--segmented'), 'Should not have segmented class when type is default' ).toBe(false); }); it('should handle slotted content correctly', async () => { // Create a new element with slotted content instead of buttons array const slottedElement = document.createElement('usa-button-group') as USAButtonGroup; slottedElement.innerHTML = `
  • `; document.body.appendChild(slottedElement); await slottedElement.updateComplete; const buttonGroup = slottedElement.querySelector('.usa-button-group'); const slottedButtons = slottedElement.querySelectorAll('button'); expect(buttonGroup, 'Should have button group container with slotted content').toBeTruthy(); expect(slottedButtons.length, 'Should have slotted buttons').toBe(2); slottedElement.remove(); 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).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('Visual Regression Prevention', () => { it('should pass visual layout checks for button alignment', async () => { await element.updateComplete; const buttonGroup = element.querySelector('.usa-button-group') as HTMLElement; const buttonItems = element.querySelectorAll('.usa-button-group__item') as NodeListOf; const buttons = element.querySelectorAll('.usa-button') as NodeListOf; const groupRect = buttonGroup.getBoundingClientRect(); // All button items should be within the button group buttonItems.forEach((item, index) => { const itemRect = item.getBoundingClientRect(); expect( itemRect.top >= groupRect.top && itemRect.bottom <= groupRect.bottom, `Button item ${index} should be vertically within button group` ).toBe(true); }); // All buttons should be within their respective items buttons.forEach((button, index) => { const buttonRect = button.getBoundingClientRect(); const itemRect = buttonItems[index].getBoundingClientRect(); expect( buttonRect.top >= itemRect.top && buttonRect.bottom <= itemRect.bottom, `Button ${index} should be vertically within its item` ).toBe(true); expect( buttonRect.left >= itemRect.left && buttonRect.right <= itemRect.right, `Button ${index} should be horizontally within its item` ).toBe(true); }); }); it('should pass visual layout checks for button spacing', async () => { await element.updateComplete; const buttons = element.querySelectorAll('.usa-button') as NodeListOf; // Check that buttons are horizontally aligned (same top position within tolerance) if (buttons.length > 1) { const firstButtonRect = buttons[0].getBoundingClientRect(); for (let i = 1; i < buttons.length; i++) { const buttonRect = buttons[i].getBoundingClientRect(); // Buttons should be horizontally aligned (within 5px tolerance) expect( Math.abs(buttonRect.top - firstButtonRect.top) <= 5, `Button ${i} should be horizontally aligned with first button` ).toBe(true); // Each button (except first) should be positioned to the right of previous const prevButtonRect = buttons[i - 1].getBoundingClientRect(); expect( buttonRect.left >= prevButtonRect.left, `Button ${i} should be positioned at or to the right of previous button` ).toBe(true); } } }); it('should pass visual layout checks for segmented button group', async () => { element.type = 'segmented'; await element.updateComplete; const buttonGroup = element.querySelector('.usa-button-group') as HTMLElement; const buttons = element.querySelectorAll('.usa-button') as NodeListOf; const groupRect = buttonGroup.getBoundingClientRect(); // In segmented button groups, buttons should be more tightly connected buttons.forEach((button, index) => { const buttonRect = button.getBoundingClientRect(); expect( buttonRect.top >= groupRect.top && buttonRect.bottom <= groupRect.bottom, `Segmented button ${index} should be vertically within group` ).toBe(true); // Check that buttons maintain consistent height in segmented view if (index > 0) { const firstButtonRect = buttons[0].getBoundingClientRect(); expect( Math.abs(buttonRect.height - firstButtonRect.height) <= 2, `Segmented button ${index} should have similar height to other buttons` ).toBe(true); } }); }); it('should maintain proper button interaction states', async () => { await element.updateComplete; const buttons = element.querySelectorAll('.usa-button') as NodeListOf; // Test button focus states buttons.forEach((button, index) => { if (!button.disabled) { button.focus(); expect(document.activeElement, `Button ${index} should be focusable`).toBe(button); } }); // Test disabled button const disabledButton = buttons[3]; expect(disabledButton.disabled, 'Fourth button should be disabled').toBe(true); // Try to focus disabled button - it should not receive focus disabledButton.focus(); expect(document.activeElement, 'Disabled button should not be focusable').not.toBe(disabledButton); }); it('should handle button click interactions correctly', async () => { await element.updateComplete; const buttons = element.querySelectorAll('.usa-button') as NodeListOf; // Set up event listener to test custom events let clickEventFired = false; let clickEventDetail: any = null; element.addEventListener('button-click', (e: any) => { clickEventFired = true; clickEventDetail = e.detail; }); // Click first button buttons[0].click(); await element.updateComplete; expect(clickEventFired, 'Button click event should be fired').toBe(true); expect(clickEventDetail.index, 'Event should contain correct button index').toBe(0); expect(clickEventDetail.button.text, 'Event should contain correct button data').toBe('Primary'); }); it('should handle different button types correctly', async () => { // Update buttons with different types element.buttons = [ { text: 'Submit', variant: 'primary', type: 'submit' }, { text: 'Reset', variant: 'secondary', type: 'reset' }, { text: 'Button', variant: 'outline', type: 'button' } ]; await element.updateComplete; const buttons = element.querySelectorAll('button') as NodeListOf; expect(buttons[0].type, 'First button should have submit type').toBe('submit'); expect(buttons[1].type, 'Second button should have reset type').toBe('reset'); expect(buttons[2].type, 'Third button should have button type').toBe('button'); }); it('should maintain layout consistency across different viewport conditions', async () => { await element.updateComplete; const buttonGroup = element.querySelector('.usa-button-group') as HTMLElement; const buttonItems = element.querySelectorAll('.usa-button-group__item') as NodeListOf; // Test that button group maintains list semantics expect(buttonGroup.tagName, 'Button group should be an unordered list').toBe('UL'); expect(buttonGroup.getAttribute('role'), 'Button group should not override list role').toBeNull(); // Test that all items are proper list items buttonItems.forEach((item, index) => { expect(item.tagName, `Item ${index} should be a list item`).toBe('LI'); }); }); it('should handle empty button group gracefully', async () => { // Test with empty buttons array element.buttons = []; await element.updateComplete; const buttonGroup = element.querySelector('.usa-button-group'); const buttonItems = element.querySelectorAll('.usa-button-group__item'); expect(buttonGroup, 'Should still have button group container').toBeTruthy(); expect(buttonItems.length, 'Should have no button items when empty').toBe(0); }); }); });