import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import './usa-select.ts'; import type { USASelect } from './usa-select.js'; import { testComponentAccessibility, USWDS_A11Y_CONFIG, } from '../../../__tests__/accessibility-utils.js'; import { validateComponentJavaScript } from '../../../__tests__/test-utils.js'; describe('USASelect', () => { let element: USASelect; beforeEach(() => { element = document.createElement('usa-select') as USASelect; document.body.appendChild(element); }); afterEach(() => { element.remove(); }); describe('Default Properties', () => { it('should have correct default properties', async () => { await element.updateComplete; expect(element.name).toBe(''); expect(element.value).toBe(''); expect(element.label).toBe(''); expect(element.hint).toBe(''); expect(element.error).toBe(''); expect(element.success).toBe(''); expect(element.disabled).toBe(false); expect(element.required).toBe(false); expect(element.options).toEqual([]); expect(element.defaultOption).toBe(''); }); }); describe('Basic Select Properties', () => { it('should set name property', async () => { element.name = 'test-name'; await element.updateComplete; const select = element.querySelector('select') as HTMLSelectElement; expect(select?.name).toBe('test-name'); }); it('should set value property', async () => { element.options = [ { value: 'option1', text: 'Option 1' }, { value: 'option2', text: 'Option 2' }, ]; element.value = 'option1'; await element.updateComplete; const select = element.querySelector('select') as HTMLSelectElement; expect(select?.value).toBe('option1'); }); it('should set disabled state', async () => { element.disabled = true; await element.updateComplete; const select = element.querySelector('select') as HTMLSelectElement; expect(select?.disabled).toBe(true); }); it('should set required state', async () => { element.required = true; await element.updateComplete; const select = element.querySelector('select') as HTMLSelectElement; expect(select?.required).toBe(true); }); }); describe('Label and Helper Text', () => { it('should render label text', async () => { element.label = 'Test select'; await element.updateComplete; const label = element.querySelector('.usa-label'); expect(label?.textContent?.trim()).toContain('Test select'); }); it('should associate label with select via ID', async () => { element.id = 'test-select'; element.label = 'Test select'; await element.updateComplete; const select = element.querySelector('select'); const label = element.querySelector('label'); expect(select?.id).toBe('test-select'); expect(label?.getAttribute('for')).toBe('test-select'); }); it('should render hint text', async () => { element.hint = 'This is helper text'; await element.updateComplete; const hint = element.querySelector('.usa-hint'); expect(hint?.textContent?.trim()).toBe('This is helper text'); }); it('should not render label when empty', async () => { element.label = ''; await element.updateComplete; const label = element.querySelector('label'); expect(label).toBeNull(); }); it('should generate ID when not provided', async () => { await element.updateComplete; const select = element.querySelector('select'); expect(select?.id).toMatch(/^select-[a-z0-9]{9}$/); }); it('should show required asterisk in label', async () => { element.label = 'Required field'; element.required = true; await element.updateComplete; const requiredSpan = element.querySelector('.usa-hint--required'); expect(requiredSpan?.textContent?.trim()).toBe('*'); }); }); describe('Options Rendering', () => { it('should render options from array', async () => { element.options = [ { value: 'value1', text: 'Option 1' }, { value: 'value2', text: 'Option 2' }, { value: 'value3', text: 'Option 3' }, ]; await element.updateComplete; const options = element.querySelectorAll('option'); expect(options.length).toBe(3); expect(options[0].value).toBe('value1'); expect(options[0].textContent?.trim()).toBe('Option 1'); expect(options[1].value).toBe('value2'); expect(options[1].textContent?.trim()).toBe('Option 2'); }); it('should render default option when provided', async () => { element.defaultOption = '- Select an option -'; element.options = [{ value: 'value1', text: 'Option 1' }]; await element.updateComplete; const options = element.querySelectorAll('option'); expect(options.length).toBe(2); expect(options[0].value).toBe(''); expect(options[0].textContent?.trim()).toBe('- Select an option -'); }); it('should handle disabled options', async () => { element.options = [ { value: 'value1', text: 'Option 1' }, { value: 'value2', text: 'Option 2', disabled: true }, ]; await element.updateComplete; const options = element.querySelectorAll('option'); expect(options[0].disabled).toBe(false); expect(options[1].disabled).toBe(true); }); it('should mark selected option', async () => { element.options = [ { value: 'value1', text: 'Option 1' }, { value: 'value2', text: 'Option 2' }, ]; element.value = 'value2'; await element.updateComplete; const options = element.querySelectorAll('option'); expect(options[0].selected).toBe(false); expect(options[1].selected).toBe(true); }); it('should support slotted options', async () => { element.innerHTML = ` `; await element.updateComplete; const slottedOptions = element.querySelectorAll('option'); expect(slottedOptions.length).toBe(2); expect(slottedOptions[0].value).toBe('slot1'); }); }); describe('Error State', () => { it('should render error message when provided', async () => { element.error = 'This field is required'; await element.updateComplete; const errorMsg = element.querySelector('.usa-error-message'); expect(errorMsg?.textContent?.includes('This field is required')).toBe(true); expect(errorMsg?.getAttribute('role')).toBe('alert'); }); it('should apply error class to select when error exists', async () => { element.error = 'Test error'; await element.updateComplete; const select = element.querySelector('select'); expect(select?.classList.contains('usa-select--error')).toBe(true); }); it('should set aria-invalid when error exists', async () => { element.error = 'Test error'; await element.updateComplete; const select = element.querySelector('select'); expect(select?.getAttribute('aria-invalid')).toBe('true'); }); it('should not render error message when empty', async () => { element.error = ''; await element.updateComplete; const errorMsg = element.querySelector('.usa-error-message'); expect(errorMsg).toBeNull(); }); }); describe('Success State', () => { it('should render success message when provided', async () => { element.success = 'Good choice!'; await element.updateComplete; const successMsg = element.querySelector('span[role="status"].usa-hint'); expect(successMsg?.textContent?.includes('Good choice!')).toBe(true); expect(successMsg?.getAttribute('role')).toBe('status'); }); it('should apply success class to select when success exists', async () => { element.success = 'Test success'; await element.updateComplete; const select = element.querySelector('select'); expect(select?.classList.contains('usa-select--success')).toBe(true); }); it('should not render success message when empty', async () => { element.success = ''; await element.updateComplete; const successMsg = element.querySelector('span[role="status"].usa-hint'); expect(successMsg).toBeNull(); }); }); describe('ARIA Attributes', () => { it('should associate select with hint via aria-describedby', async () => { element.id = 'test-select'; element.hint = 'Test hint'; await element.updateComplete; const select = element.querySelector('select'); expect(select?.getAttribute('aria-describedby')).toBe('test-select-hint'); }); it('should associate select with error via aria-describedby', async () => { element.id = 'test-select'; element.error = 'Test error'; await element.updateComplete; const select = element.querySelector('select'); expect(select?.getAttribute('aria-describedby')).toBe('test-select-error'); }); it('should associate select with success via aria-describedby', async () => { element.id = 'test-select'; element.success = 'Test success'; await element.updateComplete; const select = element.querySelector('select'); expect(select?.getAttribute('aria-describedby')).toBe('test-select-success'); }); it('should associate select with multiple elements via aria-describedby', async () => { element.id = 'test-select'; element.hint = 'Test hint'; element.error = 'Test error'; await element.updateComplete; const select = element.querySelector('select'); expect(select?.getAttribute('aria-describedby')).toBe('test-select-hint test-select-error'); }); it('should have correct IDs for hint, error, and success elements', async () => { element.id = 'test-select'; element.hint = 'Test hint'; element.error = 'Test error'; element.success = 'Test success'; await element.updateComplete; const hint = element.querySelector('.usa-hint'); const error = element.querySelector('.usa-error-message'); const success = element.querySelector('span[role="status"].usa-hint'); expect(hint?.id).toBe('test-select-hint'); expect(error?.id).toBe('test-select-error'); expect(success?.id).toBe('test-select-success'); }); it('should not set aria-describedby when no hint, error, or success', async () => { element.id = 'test-select'; await element.updateComplete; const select = element.querySelector('select'); expect(select?.hasAttribute('aria-describedby')).toBe(false); }); it('should pass comprehensive accessibility tests (same as Storybook)', async () => { element.label = 'Test Select'; element.options = [ { value: 'option1', text: 'Option 1' }, { value: 'option2', text: 'Option 2' }, ]; await element.updateComplete; await testComponentAccessibility(element, USWDS_A11Y_CONFIG.FULL_COMPLIANCE); }); }); describe('Event Handling', () => { it('should dispatch change event when selection changes', async () => { element.options = [ { value: 'option1', text: 'Option 1' }, { value: 'option2', text: 'Option 2' }, ]; await element.updateComplete; let changeEventDetail: any; element.addEventListener('change', (e: any) => { changeEventDetail = e.detail; }); const select = element.querySelector('select') as HTMLSelectElement; // Simulate the change by setting value and creating a change event select.value = 'option1'; const changeEvent = new Event('change', { bubbles: true }); Object.defineProperty(changeEvent, 'target', { writable: false, value: select, }); // Manually trigger the component's handleChange method (element as any).handleChange(changeEvent); await element.updateComplete; expect(changeEventDetail.value).toBe('option1'); expect(element.value).toBe('option1'); }); it('should dispatch input event when selection changes', async () => { element.options = [ { value: 'option1', text: 'Option 1' }, { value: 'option2', text: 'Option 2' }, ]; await element.updateComplete; let inputEventDetail: any; element.addEventListener('input', (e: any) => { inputEventDetail = e.detail; }); const select = element.querySelector('select') as HTMLSelectElement; // Simulate the change by setting value and creating a change event select.value = 'option2'; const changeEvent = new Event('change', { bubbles: true }); Object.defineProperty(changeEvent, 'target', { writable: false, value: select, }); // Manually trigger the component's handleChange method (element as any).handleChange(changeEvent); await element.updateComplete; expect(inputEventDetail.value).toBe('option2'); }); }); describe('USWDS CSS Classes', () => { it('should always have base usa-select class', async () => { await element.updateComplete; const select = element.querySelector('select'); expect(select?.classList.contains('usa-select')).toBe(true); }); it('should have correct label class', async () => { element.label = 'Test'; await element.updateComplete; const label = element.querySelector('label'); expect(label?.classList.contains('usa-label')).toBe(true); }); it('should have correct hint class', async () => { element.hint = 'Test hint'; await element.updateComplete; const hint = element.querySelector('.usa-hint'); expect(hint).toBeTruthy(); }); it('should have proper USWDS structure', async () => { element.label = 'Test Label'; element.hint = 'Test Hint'; element.error = 'Test Error'; element.success = 'Test Success'; await element.updateComplete; const label = element.querySelector('.usa-label'); const hint = element.querySelector('.usa-hint'); const error = element.querySelector('.usa-error-message'); const success = element.querySelector('span[role="status"].usa-hint'); const select = element.querySelector('.usa-select'); expect(label).toBeTruthy(); expect(hint).toBeTruthy(); expect(error).toBeTruthy(); expect(success).toBeTruthy(); expect(select).toBeTruthy(); }); }); describe('Light DOM Rendering', () => { it('should render in light DOM for USWDS compatibility', async () => { await element.updateComplete; expect(element.shadowRoot).toBeNull(); expect(element.querySelector('select')).toBeTruthy(); }); }); describe('Form Integration', () => { it('should work with form data when option selected', async () => { const form = document.createElement('form'); element.name = 'test-select'; element.options = [ { value: 'option1', text: 'Option 1' }, { value: 'option2', text: 'Option 2' }, ]; element.value = 'option1'; form.appendChild(element); document.body.appendChild(form); await element.updateComplete; const formData = new FormData(form); expect(formData.get('test-select')).toBe('option1'); form.remove(); }); it('should return empty string for default option', async () => { const form = document.createElement('form'); element.name = 'test-select'; element.defaultOption = '- Select -'; element.options = [{ value: 'option1', text: 'Option 1' }]; element.value = ''; // Default option selected form.appendChild(element); document.body.appendChild(form); await element.updateComplete; const formData = new FormData(form); expect(formData.get('test-select')).toBe(''); form.remove(); }); }); describe('ID Management', () => { it('should use provided ID consistently', async () => { const newElement = document.createElement('usa-select') as USASelect; newElement.id = 'custom-select'; newElement.label = 'Test'; newElement.hint = 'Test hint'; newElement.error = 'Test error'; newElement.success = 'Test success'; document.body.appendChild(newElement); await newElement.updateComplete; const select = newElement.querySelector('select'); const label = newElement.querySelector('label'); const hint = newElement.querySelector('.usa-hint'); const error = newElement.querySelector('.usa-error-message'); const success = newElement.querySelector('span[role="status"].usa-hint'); expect(select?.id).toBe('custom-select'); expect(label?.getAttribute('for')).toBe('custom-select'); expect(hint?.id).toBe('custom-select-hint'); expect(error?.id).toBe('custom-select-error'); expect(success?.id).toBe('custom-select-success'); newElement.remove(); }); }); describe('Options Update', () => { it('should update options dynamically', async () => { element.options = [{ value: 'option1', text: 'Option 1' }]; await element.updateComplete; let options = element.querySelectorAll('option'); expect(options.length).toBe(1); // Update options element.options = [ { value: 'option1', text: 'Option 1' }, { value: 'option2', text: 'Option 2' }, { value: 'option3', text: 'Option 3' }, ]; await element.updateComplete; options = element.querySelectorAll('option'); expect(options.length).toBe(3); }); it('should preserve selection when options change', async () => { element.options = [ { value: 'option1', text: 'Option 1' }, { value: 'option2', text: 'Option 2' }, ]; element.value = 'option2'; await element.updateComplete; // Update options but keep the selected one element.options = [ { value: 'option1', text: 'Updated Option 1' }, { value: 'option2', text: 'Updated Option 2' }, { value: 'option3', text: 'New Option 3' }, ]; await element.updateComplete; const select = element.querySelector('select') as HTMLSelectElement; expect(select.value).toBe('option2'); expect(element.value).toBe('option2'); }); }); describe('Application Use Cases', () => { describe('Federal Geographic Selection', () => { it('should handle US states selection', async () => { const stateOptions = [ { value: 'AL', text: 'Alabama' }, { value: 'AK', text: 'Alaska' }, { value: 'CA', text: 'California' }, { value: 'FL', text: 'Florida' }, { value: 'TX', text: 'Texas' }, ]; element.label = 'State of Residence'; element.name = 'state'; element.required = true; element.defaultOption = '- Select your state -'; element.options = stateOptions; element.hint = 'Required for tax and legal purposes'; await element.updateComplete; const options = element.querySelectorAll('option'); expect(options.length).toBe(6); // 5 states + default option expect(options[0].value).toBe(''); expect(options[0].textContent?.trim()).toBe('- Select your state -'); expect(options[1].value).toBe('AL'); expect(options[1].textContent?.trim()).toBe('Alabama'); const select = element.querySelector('select'); expect(select?.required).toBe(true); }); it('should handle federal territories and districts', async () => { const territoryOptions = [ { value: 'DC', text: 'District of Columbia' }, { value: 'PR', text: 'Puerto Rico' }, { value: 'VI', text: 'U.S. Virgin Islands' }, { value: 'GU', text: 'Guam' }, { value: 'AS', text: 'American Samoa' }, { value: 'MP', text: 'Northern Mariana Islands' }, ]; element.label = 'Federal Territory'; element.options = territoryOptions; element.defaultOption = '- Select territory -'; await element.updateComplete; const options = element.querySelectorAll('option'); expect(options.length).toBe(7); // 6 territories + default expect(options[1].value).toBe('DC'); expect(options[2].value).toBe('PR'); }); it('should handle federal regional offices', async () => { const regionalOptions = [ { value: 'region1', text: 'Region 1 - Boston' }, { value: 'region2', text: 'Region 2 - New York' }, { value: 'region3', text: 'Region 3 - Philadelphia' }, { value: 'region4', text: 'Region 4 - Atlanta' }, { value: 'region5', text: 'Region 5 - Chicago' }, { value: 'region6', text: 'Region 6 - Dallas' }, { value: 'region7', text: 'Region 7 - Kansas City' }, { value: 'region8', text: 'Region 8 - Denver' }, { value: 'region9', text: 'Region 9 - San Francisco' }, { value: 'region10', text: 'Region 10 - Seattle' }, ]; element.label = 'EPA Regional Office'; element.options = regionalOptions; element.hint = 'Select your EPA regional jurisdiction'; await element.updateComplete; expect(element.options.length).toBe(10); expect(element.options[0].text).toContain('Boston'); expect(element.options[9].text).toContain('Seattle'); }); }); describe('Federal Agency Selection', () => { it('should handle cabinet-level departments', async () => { const departmentOptions = [ { value: 'DOD', text: 'Department of Defense' }, { value: 'DHS', text: 'Department of Homeland Security' }, { value: 'DOJ', text: 'Department of Justice' }, { value: 'DOT', text: 'Department of Transportation' }, { value: 'HHS', text: 'Department of Health and Human Services' }, { value: 'ED', text: 'Department of Education' }, { value: 'VA', text: 'Department of Veterans Affairs' }, { value: 'USDA', text: 'Department of Agriculture' }, { value: 'DOI', text: 'Department of the Interior' }, { value: 'DOE', text: 'Department of Energy' }, { value: 'DOL', text: 'Department of Labor' }, { value: 'HUD', text: 'Department of Housing and Urban Development' }, { value: 'STATE', text: 'Department of State' }, { value: 'TREASURY', text: 'Department of the Treasury' }, { value: 'COMMERCE', text: 'Department of Commerce' }, ]; element.label = 'Federal Department'; element.name = 'federal_department'; element.options = departmentOptions; element.defaultOption = '- Select department -'; element.required = true; await element.updateComplete; const options = element.querySelectorAll('option'); expect(options.length).toBe(16); // 15 departments + default expect(options[1].textContent?.trim()).toBe('Department of Defense'); expect(options[15].textContent?.trim()).toBe('Department of Commerce'); }); it('should handle independent federal agencies', async () => { const agencyOptions = [ { value: 'EPA', text: 'Environmental Protection Agency' }, { value: 'NASA', text: 'National Aeronautics and Space Administration' }, { value: 'NSF', text: 'National Science Foundation' }, { value: 'SBA', text: 'Small Business Administration' }, { value: 'SSA', text: 'Social Security Administration' }, { value: 'USAID', text: 'U.S. Agency for International Development' }, { value: 'GSA', text: 'General Services Administration' }, { value: 'OPM', text: 'Office of Personnel Management' }, ]; element.label = 'Independent Agency'; element.options = agencyOptions; element.hint = 'Select the independent organization'; await element.updateComplete; expect(element.options.length).toBe(8); const epaOption = element.options.find((opt) => opt.value === 'EPA'); expect(epaOption?.text).toBe('Environmental Protection Agency'); }); }); describe('Federal Employment Classifications', () => { it('should handle GS pay scales', async () => { const gsLevels = [ { value: 'GS-7', text: 'GS-7 ($35,854 - $46,609)' }, { value: 'GS-9', text: 'GS-9 ($43,251 - $56,227)' }, { value: 'GS-11', text: 'GS-11 ($52,276 - $67,960)' }, { value: 'GS-12', text: 'GS-12 ($62,722 - $81,540)' }, { value: 'GS-13', text: 'GS-13 ($74,596 - $97,020)' }, { value: 'GS-14', text: 'GS-14 ($88,136 - $114,579)' }, { value: 'GS-15', text: 'GS-15 ($103,690 - $134,798)' }, ]; element.label = 'Target GS Level'; element.name = 'gs_level'; element.options = gsLevels; element.hint = 'Select desired pay grade for federal position'; await element.updateComplete; const options = element.querySelectorAll('option'); expect(options.length).toBe(7); expect(options[0].value).toBe('GS-7'); expect(options[0].textContent?.includes('$35,854')).toBe(true); }); it('should handle security clearance levels', async () => { const clearanceLevels = [ { value: 'PUBLIC_TRUST', text: 'Public Trust' }, { value: 'CONFIDENTIAL', text: 'Confidential' }, { value: 'SECRET', text: 'Secret' }, { value: 'TOP_SECRET', text: 'Top Secret' }, { value: 'TOP_SECRET_SCI', text: 'Top Secret/SCI' }, { value: 'Q_CLEARANCE', text: 'Q Clearance (DOE)' }, { value: 'L_CLEARANCE', text: 'L Clearance (DOE)' }, ]; element.label = 'Security Clearance Level'; element.name = 'clearance_level'; element.options = clearanceLevels; element.defaultOption = '- Select clearance level -'; element.hint = 'Current or eligible security clearance'; await element.updateComplete; const secretOption = element.options.find((opt) => opt.value === 'SECRET'); expect(secretOption?.text).toBe('Secret'); const topSecretOption = element.options.find((opt) => opt.value === 'TOP_SECRET_SCI'); expect(topSecretOption?.text).toBe('Top Secret/SCI'); }); it('should handle federal work schedules', async () => { const scheduleOptions = [ { value: 'FULL_TIME', text: 'Full-Time (40 hours/week)' }, { value: 'PART_TIME', text: 'Part-Time (less than 40 hours)' }, { value: 'INTERMITTENT', text: 'Intermittent (as needed)' }, { value: 'SEASONAL', text: 'Seasonal' }, { value: 'TEMPORARY', text: 'Temporary (1 year or less)' }, { value: 'TELEWORK', text: 'Telework Eligible' }, { value: 'REMOTE', text: 'Remote Work' }, ]; element.label = 'Work Schedule Type'; element.options = scheduleOptions; element.defaultOption = '- Select schedule -'; await element.updateComplete; const options = element.querySelectorAll('option'); expect(options.length).toBe(8); // 7 schedules + default expect(options[1].textContent?.includes('Full-Time')).toBe(true); }); }); describe('Federal Benefits and Services', () => { it('should handle Medicare plan types', async () => { const medicareOptions = [ { value: 'PART_A', text: 'Part A - Hospital Insurance' }, { value: 'PART_B', text: 'Part B - Medical Insurance' }, { value: 'PART_C', text: 'Part C - Medicare Advantage' }, { value: 'PART_D', text: 'Part D - Prescription Drug Coverage' }, { value: 'SUPPLEMENT', text: 'Medicare Supplement Insurance' }, { value: 'MEDICAID_DUAL', text: 'Medicare-Medicaid (Dual Eligible)' }, ]; element.label = 'Medicare Plan Type'; element.name = 'medicare_plan'; element.options = medicareOptions; element.hint = 'Select your current Medicare coverage'; element.required = true; await element.updateComplete; const partAOption = element.options.find((opt) => opt.value === 'PART_A'); expect(partAOption?.text).toBe('Part A - Hospital Insurance'); const select = element.querySelector('select'); expect(select?.required).toBe(true); }); it('should handle VA disability ratings', async () => { const disabilityRatings = [ { value: '0', text: '0% - Non-compensable' }, { value: '10', text: '10% - Minimal disability' }, { value: '20', text: '20% - Slight disability' }, { value: '30', text: '30% - Moderate disability' }, { value: '40', text: '40% - More than moderate' }, { value: '50', text: '50% - Considerable disability' }, { value: '60', text: '60% - Considerable disability' }, { value: '70', text: '70% - Severe disability' }, { value: '80', text: '80% - Severe disability' }, { value: '90', text: '90% - Very severe disability' }, { value: '100', text: '100% - Total disability' }, ]; element.label = 'VA Disability Rating'; element.name = 'disability_rating'; element.options = disabilityRatings; element.hint = 'Current VA disability compensation rating'; element.defaultOption = '- Select rating -'; await element.updateComplete; const options = element.querySelectorAll('option'); expect(options.length).toBe(12); // 11 ratings + default expect(options[11].value).toBe('100'); expect(options[11].textContent?.includes('Total disability')).toBe(true); }); it('should handle Social Security benefit types', async () => { const benefitTypes = [ { value: 'RETIREMENT', text: 'Retirement Benefits' }, { value: 'DISABILITY', text: 'Social Security Disability Insurance (SSDI)' }, { value: 'SSI', text: 'Supplemental Security Income (SSI)' }, { value: 'SURVIVOR', text: 'Survivor Benefits' }, { value: 'SPOUSAL', text: 'Spousal Benefits' }, { value: 'CHILD', text: 'Child Benefits' }, { value: 'MEDICARE', text: 'Medicare Benefits Only' }, ]; element.label = 'Social Security Benefit Type'; element.name = 'benefit_type'; element.options = benefitTypes; element.defaultOption = '- Select benefit type -'; element.hint = 'Type of Social Security benefits you receive or are applying for'; await element.updateComplete; const ssdiOption = element.options.find((opt) => opt.value === 'DISABILITY'); expect(ssdiOption?.text).toBe('Social Security Disability Insurance (SSDI)'); }); }); describe('Federal Tax and Legal Classifications', () => { it('should handle tax filing status', async () => { const filingStatus = [ { value: 'SINGLE', text: 'Single' }, { value: 'MARRIED_JOINT', text: 'Married Filing Jointly' }, { value: 'MARRIED_SEPARATE', text: 'Married Filing Separately' }, { value: 'HEAD_HOUSEHOLD', text: 'Head of Household' }, { value: 'QUALIFYING_WIDOW', text: 'Qualifying Widow(er)' }, ]; element.label = 'Federal Tax Filing Status'; element.name = 'filing_status'; element.options = filingStatus; element.required = true; element.hint = 'Your filing status for the most recent tax year'; await element.updateComplete; const marriedJointOption = element.options.find((opt) => opt.value === 'MARRIED_JOINT'); expect(marriedJointOption?.text).toBe('Married Filing Jointly'); const select = element.querySelector('select'); expect(select?.required).toBe(true); }); it('should handle federal court jurisdictions', async () => { const courtJurisdictions = [ { value: 'FIRST', text: '1st Circuit - Boston' }, { value: 'SECOND', text: '2nd Circuit - New York' }, { value: 'THIRD', text: '3rd Circuit - Philadelphia' }, { value: 'FOURTH', text: '4th Circuit - Richmond' }, { value: 'FIFTH', text: '5th Circuit - New Orleans' }, { value: 'SIXTH', text: '6th Circuit - Cincinnati' }, { value: 'SEVENTH', text: '7th Circuit - Chicago' }, { value: 'EIGHTH', text: '8th Circuit - St. Louis' }, { value: 'NINTH', text: '9th Circuit - San Francisco' }, { value: 'TENTH', text: '10th Circuit - Denver' }, { value: 'ELEVENTH', text: '11th Circuit - Atlanta' }, { value: 'DC', text: 'D.C. Circuit - Washington' }, { value: 'FEDERAL', text: 'Federal Circuit - Washington' }, ]; element.label = 'Federal Circuit Court'; element.name = 'court_circuit'; element.options = courtJurisdictions; element.defaultOption = '- Select circuit -'; element.hint = 'Federal appellate court jurisdiction'; await element.updateComplete; expect(element.options.length).toBe(13); const ninthCircuit = element.options.find((opt) => opt.value === 'NINTH'); expect(ninthCircuit?.text).toBe('9th Circuit - San Francisco'); }); it('should handle immigration status categories', async () => { const immigrationStatus = [ { value: 'CITIZEN', text: 'U.S. Citizen' }, { value: 'PERMANENT_RESIDENT', text: 'Permanent Resident (Green Card)' }, { value: 'H1B', text: 'H-1B Temporary Worker' }, { value: 'L1', text: 'L-1 Intracompany Transfer' }, { value: 'F1', text: 'F-1 Student' }, { value: 'J1', text: 'J-1 Exchange Visitor' }, { value: 'TN', text: 'TN NAFTA Professional' }, { value: 'O1', text: 'O-1 Extraordinary Ability' }, { value: 'ASYLUM', text: 'Asylum Status' }, { value: 'REFUGEE', text: 'Refugee Status' }, { value: 'TPS', text: 'Temporary Protected Status' }, { value: 'DACA', text: 'DACA Recipient' }, ]; element.label = 'Immigration Status'; element.name = 'immigration_status'; element.options = immigrationStatus; element.required = true; element.hint = 'Current U.S. immigration or citizenship status'; await element.updateComplete; const citizenOption = element.options.find((opt) => opt.value === 'CITIZEN'); expect(citizenOption?.text).toBe('U.S. Citizen'); const h1bOption = element.options.find((opt) => opt.value === 'H1B'); expect(h1bOption?.text).toBe('H-1B Temporary Worker'); }); }); describe('Federal Industry and Classification Codes', () => { it('should handle NAICS codes (major sectors)', async () => { const naicsCodes = [ { value: '11', text: '11 - Agriculture, Forestry, Fishing and Hunting' }, { value: '21', text: '21 - Mining, Quarrying, and Oil and Gas Extraction' }, { value: '22', text: '22 - Utilities' }, { value: '23', text: '23 - Construction' }, { value: '31-33', text: '31-33 - Manufacturing' }, { value: '42', text: '42 - Wholesale Trade' }, { value: '44-45', text: '44-45 - Retail Trade' }, { value: '48-49', text: '48-49 - Transportation and Warehousing' }, { value: '51', text: '51 - Information' }, { value: '52', text: '52 - Finance and Insurance' }, { value: '53', text: '53 - Real Estate and Rental and Leasing' }, { value: '54', text: '54 - Professional, Scientific, and Technical Services' }, { value: '55', text: '55 - Management of Companies and Enterprises' }, { value: '56', text: '56 - Administrative and Support and Waste Management' }, { value: '61', text: '61 - Educational Services' }, { value: '62', text: '62 - Health Care and Social Assistance' }, { value: '71', text: '71 - Arts, Entertainment, and Recreation' }, { value: '72', text: '72 - Accommodation and Food Services' }, { value: '81', text: '81 - Other Services (except Public Administration)' }, { value: '92', text: '92 - Public Administration' }, ]; element.label = 'NAICS Industry Code'; element.name = 'naics_code'; element.options = naicsCodes; element.defaultOption = '- Select industry sector -'; element.hint = 'North American Industry Classification System code'; await element.updateComplete; const publicAdminOption = element.options.find((opt) => opt.value === '92'); expect(publicAdminOption?.text).toBe('92 - Public Administration'); const manufacturingOption = element.options.find((opt) => opt.value === '31-33'); expect(manufacturingOption?.text).toBe('31-33 - Manufacturing'); }); it('should handle SOC occupation codes (major groups)', async () => { const socCodes = [ { value: '11-0000', text: '11-0000 - Management Occupations' }, { value: '13-0000', text: '13-0000 - Business and Financial Operations' }, { value: '15-0000', text: '15-0000 - Computer and Mathematical Occupations' }, { value: '17-0000', text: '17-0000 - Architecture and Engineering Occupations' }, { value: '19-0000', text: '19-0000 - Life, Physical, and Social Science Occupations' }, { value: '21-0000', text: '21-0000 - Community and Social Service Occupations' }, { value: '23-0000', text: '23-0000 - Legal Occupations' }, { value: '25-0000', text: '25-0000 - Education, Training, and Library Occupations' }, { value: '27-0000', text: '27-0000 - Arts, Design, Entertainment, Sports, and Media' }, { value: '29-0000', text: '29-0000 - Healthcare Practitioners and Technical' }, { value: '31-0000', text: '31-0000 - Healthcare Support Occupations' }, { value: '33-0000', text: '33-0000 - Protective Service Occupations' }, { value: '35-0000', text: '35-0000 - Food Preparation and Serving Related' }, { value: '37-0000', text: '37-0000 - Building and Grounds Cleaning and Maintenance' }, ]; element.label = 'SOC Occupation Code'; element.name = 'soc_code'; element.options = socCodes; element.defaultOption = '- Select occupation group -'; element.hint = 'Standard Occupational Classification code'; await element.updateComplete; const legalOption = element.options.find((opt) => opt.value === '23-0000'); expect(legalOption?.text).toBe('23-0000 - Legal Occupations'); const computerOption = element.options.find((opt) => opt.value === '15-0000'); expect(computerOption?.text).toBe('15-0000 - Computer and Mathematical Occupations'); }); }); }); describe('Event Handling for Application Use Cases', () => { it('should handle state selection change for tax purposes', async () => { element.name = 'tax_state'; element.label = 'State for Tax Purposes'; element.options = [ { value: 'CA', text: 'California' }, { value: 'TX', text: 'Texas' }, { value: 'FL', text: 'Florida' }, ]; await element.updateComplete; let changeEventDetail: any; element.addEventListener('change', (e: any) => { changeEventDetail = e.detail; }); const select = element.querySelector('select') as HTMLSelectElement; select.value = 'CA'; const changeEvent = new Event('change', { bubbles: true }); Object.defineProperty(changeEvent, 'target', { writable: false, value: select, }); (element as any).handleChange(changeEvent); await element.updateComplete; expect(changeEventDetail.value).toBe('CA'); expect(element.value).toBe('CA'); }); it('should handle organization selection for employment', async () => { element.name = 'target_agency'; element.label = 'Target Federal Agency'; element.options = [ { value: 'DOD', text: 'Department of Defense' }, { value: 'DHS', text: 'Department of Homeland Security' }, { value: 'EPA', text: 'Environmental Protection Agency' }, ]; await element.updateComplete; let inputEventDetail: any; element.addEventListener('input', (e: any) => { inputEventDetail = e.detail; }); const select = element.querySelector('select') as HTMLSelectElement; select.value = 'EPA'; const changeEvent = new Event('change', { bubbles: true }); Object.defineProperty(changeEvent, 'target', { writable: false, value: select, }); (element as any).handleChange(changeEvent); await element.updateComplete; expect(inputEventDetail.value).toBe('EPA'); expect(element.value).toBe('EPA'); }); }); describe('Accessibility for Government Compliance', () => { it('should meet Section 508 requirements for federal forms', async () => { element.id = 'federal-agency-select'; element.label = 'Federal Agency'; element.hint = 'Select the organization for your application'; element.required = true; element.options = [ { value: 'DOD', text: 'Department of Defense' }, { value: 'DHS', text: 'Department of Homeland Security' }, ]; await element.updateComplete; const select = element.querySelector('select'); const label = element.querySelector('label'); const hint = element.querySelector('.usa-hint:not(.usa-hint--required)'); // Label association expect(label?.getAttribute('for')).toBe('federal-agency-select'); expect(select?.id).toBe('federal-agency-select'); // ARIA attributes expect(select?.getAttribute('aria-describedby')).toBe('federal-agency-select-hint'); expect(hint?.id).toBe('federal-agency-select-hint'); // Required field indication expect(select?.required).toBe(true); const requiredSpan = element.querySelector('.usa-hint--required'); expect(requiredSpan?.textContent?.trim()).toBe('*'); }); it('should provide proper error announcements for government forms', async () => { element.label = 'Security Clearance Level'; element.error = 'Security clearance level is required for this position'; element.options = [ { value: 'SECRET', text: 'Secret' }, { value: 'TOP_SECRET', text: 'Top Secret' }, ]; await element.updateComplete; const select = element.querySelector('select'); const errorMsg = element.querySelector('.usa-error-message'); expect(select?.getAttribute('aria-invalid')).toBe('true'); expect(errorMsg?.getAttribute('role')).toBe('alert'); expect(errorMsg?.textContent?.includes('Security clearance level is required')).toBe(true); }); it('should support keyboard navigation for federal accessibility', async () => { element.label = 'Federal Department'; element.options = [ { value: 'DOJ', text: 'Department of Justice' }, { value: 'DOT', text: 'Department of Transportation' }, { value: 'HHS', text: 'Department of Health and Human Services' }, ]; await element.updateComplete; const select = element.querySelector('select') as HTMLSelectElement; // Should be keyboard accessible expect(select.tabIndex).toBeGreaterThanOrEqual(0); expect(select.tagName).toBe('SELECT'); // Native select provides keyboard navigation automatically expect(select.options.length).toBe(3); }); }); describe('Performance for Government Applications', () => { it('should handle large government option sets efficiently', async () => { // Simulate all US states + territories (56 total) const allStatesTerritories = [ // US States (50) { value: 'AL', text: 'Alabama' }, { value: 'AK', text: 'Alaska' }, { value: 'AZ', text: 'Arizona' }, { value: 'AR', text: 'Arkansas' }, { value: 'CA', text: 'California' }, { value: 'CO', text: 'Colorado' }, { value: 'CT', text: 'Connecticut' }, { value: 'DE', text: 'Delaware' }, { value: 'FL', text: 'Florida' }, { value: 'GA', text: 'Georgia' }, { value: 'HI', text: 'Hawaii' }, { value: 'ID', text: 'Idaho' }, { value: 'IL', text: 'Illinois' }, { value: 'IN', text: 'Indiana' }, { value: 'IA', text: 'Iowa' }, { value: 'KS', text: 'Kansas' }, { value: 'KY', text: 'Kentucky' }, { value: 'LA', text: 'Louisiana' }, { value: 'ME', text: 'Maine' }, { value: 'MD', text: 'Maryland' }, { value: 'MA', text: 'Massachusetts' }, { value: 'MI', text: 'Michigan' }, { value: 'MN', text: 'Minnesota' }, { value: 'MS', text: 'Mississippi' }, { value: 'MO', text: 'Missouri' }, { value: 'MT', text: 'Montana' }, { value: 'NE', text: 'Nebraska' }, { value: 'NV', text: 'Nevada' }, { value: 'NH', text: 'New Hampshire' }, { value: 'NJ', text: 'New Jersey' }, { value: 'NM', text: 'New Mexico' }, { value: 'NY', text: 'New York' }, { value: 'NC', text: 'North Carolina' }, { value: 'ND', text: 'North Dakota' }, { value: 'OH', text: 'Ohio' }, { value: 'OK', text: 'Oklahoma' }, { value: 'OR', text: 'Oregon' }, { value: 'PA', text: 'Pennsylvania' }, { value: 'RI', text: 'Rhode Island' }, { value: 'SC', text: 'South Carolina' }, { value: 'SD', text: 'South Dakota' }, { value: 'TN', text: 'Tennessee' }, { value: 'TX', text: 'Texas' }, { value: 'UT', text: 'Utah' }, { value: 'VT', text: 'Vermont' }, { value: 'VA', text: 'Virginia' }, { value: 'WA', text: 'Washington' }, { value: 'WV', text: 'West Virginia' }, { value: 'WI', text: 'Wisconsin' }, { value: 'WY', text: 'Wyoming' }, // Federal District and Territories (6) { value: 'DC', text: 'District of Columbia' }, { value: 'AS', text: 'American Samoa' }, { value: 'GU', text: 'Guam' }, { value: 'MP', text: 'Northern Mariana Islands' }, { value: 'PR', text: 'Puerto Rico' }, { value: 'VI', text: 'U.S. Virgin Islands' }, ]; const startTime = performance.now(); element.label = 'State/Territory'; element.options = allStatesTerritories; element.defaultOption = '- Select state or territory -'; await element.updateComplete; const endTime = performance.now(); const renderTime = endTime - startTime; // Should render large option set quickly (under 200ms in test environment) expect(renderTime).toBeLessThan(200); const options = element.querySelectorAll('option'); expect(options.length).toBe(57); // 56 + default option // Verify first and last options expect(options[1].value).toBe('AL'); expect(options[1].textContent?.trim()).toBe('Alabama'); expect(options[56].value).toBe('VI'); expect(options[56].textContent?.trim()).toBe('U.S. Virgin Islands'); }); it('should handle rapid government form updates efficiently', async () => { const startTime = performance.now(); // Simulate rapid updates like user navigating through government form const scenarios = [ { options: [ { value: 'AL', text: 'Alabama' }, { value: 'AK', text: 'Alaska' }, ], value: 'AL', }, { options: [ { value: 'DOD', text: 'Department of Defense' }, { value: 'DHS', text: 'Homeland Security' }, ], value: 'DOD', }, { options: [ { value: 'GS-11', text: 'GS-11' }, { value: 'GS-12', text: 'GS-12' }, ], value: 'GS-12', }, { options: [ { value: 'SECRET', text: 'Secret' }, { value: 'TOP_SECRET', text: 'Top Secret' }, ], value: 'SECRET', }, ]; for (let i = 0; i < scenarios.length; i++) { element.options = scenarios[i].options; element.value = scenarios[i].value; await element.updateComplete; } const endTime = performance.now(); const duration = endTime - startTime; // Should complete rapid updates within reasonable time (500ms) expect(duration).toBeLessThan(500); expect(element.value).toBe('SECRET'); expect(element.options.length).toBe(2); }); it('should maintain performance with complex government content', async () => { const complexOptions = [ { value: 'COMPLEX_1', text: 'Department of Health and Human Services - Centers for Disease Control and Prevention (CDC) - National Center for Environmental Health', }, { value: 'COMPLEX_2', text: 'Department of Defense - Defense Logistics Agency - Defense Contract Management Agency (DCMA) - International Directorate', }, { value: 'COMPLEX_3', text: 'Department of Homeland Security - U.S. Citizenship and Immigration Services (USCIS) - Field Operations Directorate', }, ]; const startTime = performance.now(); element.label = 'Detailed Federal Organization'; element.options = complexOptions; element.hint = 'Select the specific federal organization unit'; await element.updateComplete; const endTime = performance.now(); expect(endTime - startTime).toBeLessThan(50); // Should render complex content quickly expect(element.textContent?.includes('Centers for Disease Control')).toBeTruthy(); }); }); describe('Form Integration for Government Applications', () => { it('should integrate with federal tax forms', async () => { const form = document.createElement('form'); element.name = 'filing_status'; element.label = 'Tax Filing Status'; element.required = true; element.options = [ { value: 'SINGLE', text: 'Single' }, { value: 'MARRIED_JOINT', text: 'Married Filing Jointly' }, { value: 'MARRIED_SEPARATE', text: 'Married Filing Separately' }, { value: 'HEAD_HOUSEHOLD', text: 'Head of Household' }, ]; element.value = 'MARRIED_JOINT'; form.appendChild(element); document.body.appendChild(form); await element.updateComplete; const formData = new FormData(form); expect(formData.get('filing_status')).toBe('MARRIED_JOINT'); form.remove(); }); it('should integrate with federal employment applications', async () => { const form = document.createElement('form'); // Create multiple government-related selects const agencySelect = document.createElement('usa-select') as USASelect; agencySelect.name = 'target_agency'; agencySelect.label = 'Target Agency'; agencySelect.options = [ { value: 'EPA', text: 'Environmental Protection Agency' }, { value: 'NASA', text: 'National Aeronautics and Space Administration' }, ]; agencySelect.value = 'EPA'; const clearanceSelect = document.createElement('usa-select') as USASelect; clearanceSelect.name = 'clearance_level'; clearanceSelect.label = 'Security Clearance'; clearanceSelect.options = [ { value: 'PUBLIC_TRUST', text: 'Public Trust' }, { value: 'SECRET', text: 'Secret' }, ]; clearanceSelect.value = 'SECRET'; form.appendChild(agencySelect); form.appendChild(clearanceSelect); document.body.appendChild(form); await agencySelect.updateComplete; await clearanceSelect.updateComplete; const formData = new FormData(form); expect(formData.get('target_agency')).toBe('EPA'); expect(formData.get('clearance_level')).toBe('SECRET'); form.remove(); }); }); // CRITICAL TESTS - Prevent auto-dismiss and lifecycle bugs describe('Component Lifecycle Stability (CRITICAL)', () => { let element: USASelect; beforeEach(() => { element = document.createElement('usa-select') as USASelect; document.body.appendChild(element); }); afterEach(() => { element?.remove(); }); it('should remain in DOM after property updates (not auto-dismiss)', async () => { // Apply initial properties with options element.name = 'test-select'; element.value = ''; element.label = 'Test Select'; element.hint = 'Test hint'; element.disabled = false; element.required = false; element.options = [ { value: 'option1', text: 'Option 1' }, { value: 'option2', text: 'Option 2' }, ]; element.defaultOption = 'Choose an option'; await element.updateComplete; // Verify element exists after initial render expect(document.body.contains(element)).toBe(true); expect(element.querySelector(`[class*="usa-"]`)).toBeTruthy(); // Update properties (this is where bugs often occur) element.value = 'option1'; element.disabled = true; element.error = 'Test error'; await element.updateComplete; // CRITICAL: Element should still exist in DOM expect(document.body.contains(element)).toBe(true); expect(element.querySelector(`[class*="usa-"]`)).toBeTruthy(); // Multiple rapid property updates const propertySets = [ { name: 'select1', value: 'opt1', label: 'Select 1', hint: 'First hint', disabled: false, required: true, error: '', success: 'Success message', options: [ { value: 'opt1', text: 'First Option' }, { value: 'opt2', text: 'Second Option' }, ], }, { name: 'select2', value: 'opt2', label: 'Select 2', hint: 'Second hint', disabled: true, required: false, error: 'Error message', success: '', options: [ { value: 'opt1', text: 'Option A' }, { value: 'opt2', text: 'Option B', disabled: true }, { value: 'opt3', text: 'Option C' }, ], }, { name: 'select3', value: '', label: 'Select 3', hint: '', disabled: false, required: true, error: 'Another error', success: '', options: [ { value: 'a', text: 'A' }, { value: 'b', text: 'B' }, { value: 'c', text: 'C' }, ], }, ]; for (const props of propertySets) { Object.assign(element, props); await element.updateComplete; // CRITICAL: Element should survive all updates expect(document.body.contains(element)).toBe(true); expect(element.querySelector(`[class*="usa-"]`)).toBeTruthy(); } }); it('should not fire unintended events on property changes', async () => { const eventSpies = [ vi.fn(), // Generic event spy vi.fn(), // Close/dismiss spy vi.fn(), // Submit/action spy ]; // Common event names that might be fired accidentally const commonEvents = ['close', 'dismiss', 'submit', 'action']; commonEvents.forEach((eventName, index) => { if (eventSpies[index]) { element.addEventListener(eventName, eventSpies[index]); } }); // Set up initial state element.name = 'test'; element.label = 'Test Select'; element.options = [ { value: 'test1', text: 'Test Option 1' }, { value: 'test2', text: 'Test Option 2' }, ]; await element.updateComplete; // Update properties should NOT fire these unintended events element.value = 'test1'; await element.updateComplete; element.disabled = true; await element.updateComplete; element.error = 'Test error'; await element.updateComplete; element.options = [ { value: 'new1', text: 'New Option 1' }, { value: 'new2', text: 'New Option 2' }, { value: 'new3', text: 'New Option 3' }, ]; await element.updateComplete; element.success = 'Success message'; await element.updateComplete; // Verify no unintended events were fired eventSpies.forEach((spy, _index) => { if (spy) { expect(spy).not.toHaveBeenCalled(); } }); // Verify element is still in DOM expect(document.body.contains(element)).toBe(true); }); it('should handle rapid property updates without breaking', async () => { // Simulate rapid updates like form validation or dynamic option changes const startTime = performance.now(); const propertySets = [ { value: 'rapid1', disabled: false, error: '', required: true, options: [{ value: 'rapid1', text: 'Rapid Option 1' }], }, { value: 'rapid2', disabled: true, error: 'Error', required: false, options: [{ value: 'rapid2', text: 'Rapid Option 2' }], }, { value: '', disabled: false, error: '', required: true, options: [{ value: 'rapid3', text: 'Rapid Option 3' }], }, { value: 'rapid4', disabled: false, error: 'Number error', required: false, options: [ { value: 'rapid4', text: 'Option A' }, { value: 'rapid5', text: 'Option B' }, ], }, ]; element.label = 'Rapid Test'; element.name = 'rapid-select'; for (let i = 0; i < 20; i++) { const props = propertySets[i % propertySets.length]; Object.assign(element, props); await element.updateComplete; // Element should remain stable expect(document.body.contains(element)).toBe(true); expect(element.querySelector(`[class*="usa-"]`)).toBeTruthy(); // Verify select element is still properly connected const select = element.querySelector('select'); expect(select).toBeTruthy(); } const endTime = performance.now(); // Should complete updates reasonably fast (under 1000ms for select complexity) expect(endTime - startTime).toBeLessThan(1000); // Final verification expect(document.body.contains(element)).toBe(true); }); describe('JavaScript Implementation Validation', () => { it('should pass JavaScript implementation validation', async () => { // Validate USWDS JavaScript implementation patterns const componentPath = `${process.cwd()}/src/components/select/usa-select.ts`; const validation = validateComponentJavaScript(componentPath, 'select'); 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('Storybook Integration Tests (CRITICAL)', () => { let element: USASelect; beforeEach(() => { element = document.createElement('usa-select') as USASelect; document.body.appendChild(element); }); afterEach(() => { element?.remove(); }); it('should render correctly when created via Storybook patterns', async () => { // Simulate how Storybook creates components with args const storybookArgs = { name: 'storybook-select', value: 'option2', label: 'Storybook Select', hint: 'Choose your preferred option from the dropdown', disabled: false, required: true, options: [ { value: 'option1', text: 'Option 1' }, { value: 'option2', text: 'Option 2' }, { value: 'option3', text: 'Option 3', disabled: true }, ], defaultOption: 'Please select an option', }; // Apply args like Storybook would Object.assign(element, storybookArgs); await element.updateComplete; // Should render without blank frames expect(document.body.contains(element)).toBe(true); expect(element.querySelector(`[class*="usa-"]`)).toBeTruthy(); // Should have expected content structure const hasContent = element.querySelector('select') !== null && element.querySelector('label') !== null && (element.textContent?.trim().length || 0) > 0; expect(hasContent).toBe(true); // Verify select-specific rendering const select = element.querySelector('select') as HTMLSelectElement; const label = element.querySelector('label'); const hint = element.querySelector('.usa-hint:not(.usa-hint--required)'); const options = element.querySelectorAll('option'); expect(select?.value).toBe('option2'); expect(select?.required).toBe(true); expect(select?.name).toBe('storybook-select'); expect(label).toBeTruthy(); expect(hint?.textContent?.trim()).toBe('Choose your preferred option from the dropdown'); expect(options).toHaveLength(4); // 3 options + default option expect(options[0]?.textContent?.trim()).toBe('Please select an option'); expect(options[1]?.textContent?.trim()).toBe('Option 1'); expect(options[2]?.textContent?.trim()).toBe('Option 2'); expect(options[3]?.disabled).toBe(true); }); it('should handle Storybook controls updates without breaking', async () => { // Simulate initial Storybook state const initialArgs = { name: 'controls-test', value: '', label: 'Controls Test', hint: '', disabled: false, required: false, error: '', success: '', options: [ { value: 'initial1', text: 'Initial Option 1' }, { value: 'initial2', text: 'Initial Option 2' }, ], }; Object.assign(element, initialArgs); await element.updateComplete; // Verify initial state expect(document.body.contains(element)).toBe(true); // Simulate user changing controls in Storybook const storybookUpdates = [ { value: 'initial1' }, { disabled: true }, { label: 'Updated Label' }, { hint: 'Updated hint text' }, { error: 'Validation error' }, { required: true }, { options: [ { value: 'new1', text: 'New Option 1' }, { value: 'new2', text: 'New Option 2' }, { value: 'new3', text: 'New Option 3' }, ], }, { value: 'new2' }, { success: 'Success message' }, { disabled: false, error: '', value: 'new3' }, ]; for (const update of storybookUpdates) { Object.assign(element, update); await element.updateComplete; // Should not cause blank frame or auto-dismiss expect(document.body.contains(element)).toBe(true); expect(element.querySelector(`[class*="usa-"]`)).toBeTruthy(); // Verify select element remains functional const select = element.querySelector('select'); expect(select).toBeTruthy(); } }); it('should maintain visual state during hot reloads', async () => { const initialArgs = { name: 'hotreload-test', value: 'selected-option', label: 'Hot Reload Test', hint: 'Test hint for hot reload', disabled: true, required: true, error: 'Test error', success: '', options: [ { value: 'option-a', text: 'Option A' }, { value: 'selected-option', text: 'Selected Option' }, { value: 'option-c', text: 'Option C', disabled: true }, ], defaultOption: 'Select an option', }; Object.assign(element, initialArgs); await element.updateComplete; // Verify initial complex state const select = element.querySelector('select') as HTMLSelectElement; const initialValue = select?.value; const initialDisabled = select?.disabled; const initialName = select?.name; const initialOptionsCount = select?.options.length; // Simulate hot reload (property reassignment with same values) Object.assign(element, initialArgs); await element.updateComplete; // Should maintain state without disappearing expect(document.body.contains(element)).toBe(true); expect(element.querySelector(`[class*="usa-"]`)).toBeTruthy(); // Should maintain form state const selectAfter = element.querySelector('select') as HTMLSelectElement; expect(selectAfter?.value).toBe(initialValue); expect(selectAfter?.disabled).toBe(initialDisabled); expect(selectAfter?.name).toBe(initialName); expect(selectAfter?.options.length).toBe(initialOptionsCount); expect(element.querySelector('.usa-error-message')).toBeTruthy(); expect(element.querySelector('.usa-hint')).toBeTruthy(); }); }); });