import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import './usa-date-picker.ts'; import type { USADatePicker } from './usa-date-picker.js'; // Helper to wait for date picker button structure to be created async function waitForDatePickerButton(element: USADatePicker): Promise { // Wait for fallback structure creation (component creates it after 100ms) await new Promise(resolve => setTimeout(resolve, 150)); return element.querySelector('.usa-date-picker__button'); } import { testComponentAccessibility, USWDS_A11Y_CONFIG, } from '../../../__tests__/accessibility-utils.js'; import { waitForUpdate, testPropertyChanges, validateComponentJavaScript, } from '../../../__tests__/test-utils.js'; import { testUSWDSIntegration, assertUSWDSIntegration, USWDS_TEST_CONFIGS, setupUSWDSIntegrationMonitoring, } from '../../../__tests__/uswds-integration-utils.js'; import { testKeyboardNavigation, verifyKeyboardOnlyUsable, getFocusableElements, } from '../../../__tests__/keyboard-navigation-utils.js'; describe('USADatePicker', () => { let element: USADatePicker; // Setup USWDS integration monitoring for all tests setupUSWDSIntegrationMonitoring(); beforeEach(() => { element = document.createElement('usa-date-picker') as USADatePicker; 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-DATE-PICKER'); }); }); describe('Properties', () => { it('should handle value changes', async () => { await testPropertyChanges( element, 'value', ['2024-01-15', '2024-12-31', ''], async (el, value) => { expect(el.value).toBe(value); // Get the visible input - USWDS creates an external input that users interact with const externalInput = el.querySelector('.usa-date-picker__external-input') as HTMLInputElement; const originalInput = el.querySelector(`#${el.inputId}`) as HTMLInputElement; const visibleInput = externalInput || originalInput; expect(visibleInput?.value).toBe(value); } ); }); it('should handle label changes', async () => { await testPropertyChanges( element, 'label', ['Start Date', 'End Date', 'Choose Date'], async (el, value) => { expect(el.label).toBe(value); const label = el.querySelector('label'); expect(label?.textContent?.trim()).toContain(value); } ); }); it('should handle hint changes', async () => { await testPropertyChanges( element, 'hint', ['Select your preferred date', 'Date must be in the future', ''], async (el, value) => { expect(el.hint).toBe(value); if (value) { const hint = el.querySelector('.usa-hint'); expect(hint?.textContent?.trim()).toBe(value); } } ); }); it('should handle placeholder changes', async () => { await testPropertyChanges( element, 'placeholder', ['dd/mm/yyyy', 'yyyy-mm-dd', 'mm/dd/yyyy'], async (el, value) => { expect(el.placeholder).toBe(value); const input = el.querySelector('input') as HTMLInputElement; expect(input?.placeholder).toBe(value); } ); }); it('should handle disabled state', async () => { element.disabled = true; await waitForUpdate(element); // Wait for USWDS to enhance and for state sync await new Promise(resolve => setTimeout(resolve, 150)); // Check both the original input and USWDS-created external input const input = element.querySelector('input') as HTMLInputElement; const externalInput = element.querySelector('.usa-date-picker__external-input') as HTMLInputElement; const button = element.querySelector('.usa-date-picker__button') as HTMLButtonElement; // The external input (if created by USWDS) should be disabled const activeInput = externalInput || input; expect(activeInput.disabled).toBe(true); if (button) { expect(button.disabled).toBe(true); } }); it('should handle required state', async () => { element.required = true; await waitForUpdate(element); const input = element.querySelector('input') as HTMLInputElement; const label = element.querySelector('label'); expect(input.required).toBe(true); expect(label?.innerHTML).toContain('required'); }); it('should handle min and max date attributes', async () => { element.minDate = '2024-01-01'; element.maxDate = '2024-12-31'; await waitForUpdate(element); const datePicker = element.querySelector('.usa-date-picker'); expect(datePicker?.getAttribute('data-min-date')).toBe('2024-01-01'); expect(datePicker?.getAttribute('data-max-date')).toBe('2024-12-31'); }); }); describe('Rendering', () => { it('should render date picker with correct structure', async () => { element.label = 'Test Date'; element.hint = 'Test hint'; await waitForUpdate(element); // Wait for fallback structure creation await new Promise(resolve => setTimeout(resolve, 150)); const formGroup = element.querySelector('.usa-form-group'); const label = element.querySelector('.usa-label'); const hint = element.querySelector('.usa-hint'); const datePicker = element.querySelector('.usa-date-picker'); const input = element.querySelector('.usa-input'); const button = await waitForDatePickerButton(element); expect(formGroup).toBeTruthy(); expect(label).toBeTruthy(); expect(hint).toBeTruthy(); expect(datePicker).toBeTruthy(); expect(input).toBeTruthy(); expect(button).toBeTruthy(); }); it('should not render hint when not provided', async () => { element.hint = ''; await waitForUpdate(element); const hint = element.querySelector('.usa-hint'); expect(hint).toBe(null); }); it('should render required asterisk when required', async () => { element.required = true; await waitForUpdate(element); const requiredAbbr = element.querySelector('abbr[title="required"]'); expect(requiredAbbr).toBeTruthy(); expect(requiredAbbr?.textContent).toBe('*'); }); it('should set form group class for required fields', async () => { element.required = true; await waitForUpdate(element); const formGroup = element.querySelector('.usa-form-group'); expect(formGroup?.classList.contains('usa-form-group--required')).toBe(true); }); }); describe('ARIA and Accessibility', () => { it('should generate unique input ID when not provided', async () => { await waitForUpdate(element); const input = element.querySelector('input'); expect(input?.id).toBe('date-picker-input'); }); it('should use custom input ID when provided', async () => { element.inputId = 'custom-date-input'; await waitForUpdate(element); const input = element.querySelector('input'); expect(input?.id).toBe('custom-date-input'); }); it('should connect label to input', async () => { element.inputId = 'test-date'; element.label = 'Test Label'; await waitForUpdate(element); const label = element.querySelector('label'); const input = element.querySelector('input'); expect(label?.getAttribute('for')).toBe('test-date'); expect(input?.id).toBe('test-date'); }); it('should connect hint via aria-describedby', async () => { element.inputId = 'test-date'; element.hint = 'Test hint'; await waitForUpdate(element); const input = element.querySelector('input'); const hint = element.querySelector('.usa-hint'); expect(input?.getAttribute('aria-describedby')).toBe('test-date-hint'); expect(hint?.id).toBe('test-date-hint'); }); it('should set button aria attributes correctly', async () => { await waitForUpdate(element); const button = await waitForDatePickerButton(element); expect(button?.getAttribute('aria-haspopup')).toBe('true'); expect(button?.getAttribute('aria-label')).toBe('Toggle calendar'); }); }); describe('Events', () => { it('should dispatch date-change event when input changes', async () => { let eventFired = false; let eventDetail: any = null; element.addEventListener('date-change', (e: any) => { eventFired = true; eventDetail = e.detail; }); await waitForUpdate(element); const input = element.querySelector('input') as HTMLInputElement; input.value = '2024-01-15'; input.dispatchEvent(new Event('input', { bubbles: true })); // Wait for event to be processed await new Promise((resolve) => setTimeout(resolve, 0)); expect(eventFired).toBe(true); expect(eventDetail.value).toBe('2024-01-15'); expect(eventDetail.date).toBeInstanceOf(Date); expect(element.value).toBe('2024-01-15'); }); it('should handle null date in event detail for empty value', async () => { let eventDetail: any = null; element.addEventListener('date-change', (e: any) => { eventDetail = e.detail; }); await waitForUpdate(element); const input = element.querySelector('input') as HTMLInputElement; input.value = ''; input.dispatchEvent(new Event('input', { bubbles: true })); // Wait for event to be processed await new Promise((resolve) => setTimeout(resolve, 0)); expect(eventDetail.value).toBe(''); expect(eventDetail.date).toBe(null); }); }); describe('Public Methods', () => { it('should focus the input when focus() is called', async () => { await waitForUpdate(element); const input = element.querySelector('input') as HTMLInputElement; const focusSpy = vi.spyOn(input, 'focus'); element.focus(); expect(focusSpy).toHaveBeenCalled(); }); it('should clear the value when clear() is called', async () => { element.value = '2024-01-15'; await waitForUpdate(element); const input = element.querySelector('input') as HTMLInputElement; expect(input.value).toBe('2024-01-15'); element.clear(); expect(element.value).toBe(''); expect(input.value).toBe(''); }); }); describe('Form Integration', () => { it('should work within a form', async () => { const form = document.createElement('form'); element.name = 'test-date'; element.value = '2024-01-15'; form.appendChild(element); document.body.appendChild(form); await waitForUpdate(element); const formData = new FormData(form); expect(formData.get('test-date')).toBe('2024-01-15'); form.remove(); }); it('should support form validation', async () => { element.required = true; element.value = ''; await waitForUpdate(element); const input = element.querySelector('input') as HTMLInputElement; expect(input.checkValidity()).toBe(false); element.value = '2024-01-15'; await waitForUpdate(element); expect(input.checkValidity()).toBe(true); }); }); // NOTE: Calendar UI tests moved to Cypress (cypress/e2e/date-picker-calendar.cy.ts) describe('Component Lifecycle Stability (CRITICAL)', () => { // NOTE: Browser-dependent calendar tests moved to Cypress (cypress/e2e/date-picker-calendar.cy.ts) it('should remain in DOM after property updates (not auto-dismiss)', async () => { const originalParent = element.parentElement; element.value = '2024-12-25'; element.name = 'updated-date'; element.label = 'Updated Date Label'; element.hint = 'Updated hint text'; element.placeholder = 'yyyy-mm-dd'; element.disabled = true; element.required = true; element.minDate = '2024-01-01'; element.maxDate = '2024-12-31'; element.error = 'Date is invalid'; element.readonly = true; await element.updateComplete; expect(document.body.contains(element)).toBe(true); expect(element.isConnected).toBe(true); expect(element.parentElement).toBe(originalParent); }); }); describe('Storybook Integration Tests (CRITICAL)', () => { // NOTE: Browser-dependent Storybook rendering tests moved to Cypress (cypress/e2e/date-picker-calendar.cy.ts) it('should handle Storybook control updates without component removal', async () => { const mockStorybookUpdate = vi.fn(); element.addEventListener('input', mockStorybookUpdate); element.addEventListener('change', mockStorybookUpdate); element.addEventListener('date-picker-open', mockStorybookUpdate); element.addEventListener('date-picker-close', mockStorybookUpdate); // Simulate Storybook controls panel updates element.label = 'Controls Updated Date'; element.placeholder = 'yyyy-mm-dd'; element.hint = 'Updated from controls'; element.value = '2024-07-04'; element.minDate = '2024-06-01'; element.maxDate = '2024-08-31'; element.required = true; element.disabled = false; await element.updateComplete; expect(document.body.contains(element)).toBe(true); expect(element.isConnected).toBe(true); expect(element.value).toBe('2024-07-04'); expect(element.label).toBe('Controls Updated Date'); const input = element.querySelector('input'); const label = element.querySelector('label'); expect(input?.value).toBe('2024-07-04'); expect(input?.placeholder).toBe('yyyy-mm-dd'); expect(label?.textContent).toContain('Controls Updated Date'); }); it('should maintain event listeners during Storybook interactions', async () => { const inputSpy = vi.fn(); const changeSpy = vi.fn(); const openSpy = vi.fn(); const closeSpy = vi.fn(); element.addEventListener('input', inputSpy); element.addEventListener('change', changeSpy); element.addEventListener('date-picker-open', openSpy); element.addEventListener('date-picker-close', closeSpy); await element.updateComplete; const input = element.querySelector('input') as HTMLInputElement; const toggleButton = element.querySelector('.usa-date-picker__button'); // Test input event if (input) { input.value = '12/31/2024'; input.dispatchEvent(new Event('input', { bubbles: true })); input.dispatchEvent(new Event('change', { bubbles: true })); } // Test calendar toggle element.toggleCalendar(); await element.updateComplete; element.toggleCalendar(); await element.updateComplete; // Test button if it exists if (toggleButton) { toggleButton.dispatchEvent(new Event('click', { bubbles: true })); } expect(inputSpy).toHaveBeenCalled(); expect(changeSpy).toHaveBeenCalled(); // Component should handle toggle calls without errors expect(() => element.toggleCalendar()).not.toThrow(); expect(() => element.toggleCalendar()).not.toThrow(); 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 () => { // Setup date picker with comprehensive configuration element.label = 'Select appointment date'; element.name = 'appointment-date'; element.value = ''; element.placeholder = 'mm/dd/yyyy'; element.disabled = false; element.required = false; element.minDate = '01/01/2020'; element.maxDate = '12/31/2030'; await waitForUpdate(element); // Run comprehensive accessibility audit await testComponentAccessibility(element, USWDS_A11Y_CONFIG.FULL_COMPLIANCE); }); it('should pass accessibility tests when required', async () => { element.label = 'Required birth date'; element.name = 'birth-date'; element.placeholder = 'Enter your birth date (mm/dd/yyyy)'; element.required = true; element.value = ''; await waitForUpdate(element); await testComponentAccessibility(element, USWDS_A11Y_CONFIG.FULL_COMPLIANCE); }); it('should pass accessibility tests when disabled', async () => { element.label = 'Disabled date field'; element.name = 'disabled-date'; element.placeholder = 'Date not available'; element.disabled = true; element.value = '12/25/2024'; await waitForUpdate(element); await testComponentAccessibility(element, USWDS_A11Y_CONFIG.FULL_COMPLIANCE); }); it('should pass accessibility tests with preselected value', async () => { element.label = 'Event date'; element.name = 'event-date'; element.value = '07/04/2024'; element.placeholder = 'mm/dd/yyyy'; element.minDate = '01/01/2024'; element.maxDate = '12/31/2024'; await waitForUpdate(element); await testComponentAccessibility(element, USWDS_A11Y_CONFIG.FULL_COMPLIANCE); }); }); describe('USWDS Enhancement Integration (CRITICAL)', () => { let mockUSWDS: any; beforeEach(async () => { // Mock USWDS object that should be loaded mockUSWDS = { init: vi.fn(), datePicker: { init: vi.fn(), enhanceDatePicker: vi.fn((element) => { // Simulate what real USWDS does - transforms the DOM const input = element.querySelector('input[type="text"]'); const button = element.querySelector('.usa-date-picker__button'); if (input && button && !element.querySelector('.usa-date-picker__calendar')) { // Create mock enhanced calendar structure const calendar = document.createElement('div'); calendar.className = 'usa-date-picker__calendar'; calendar.setAttribute('role', 'application'); calendar.setAttribute('aria-label', 'Calendar'); calendar.setAttribute('tabindex', '-1'); calendar.hidden = true; // Add calendar header const header = document.createElement('div'); header.className = 'usa-date-picker__calendar__header'; const prevYear = document.createElement('button'); prevYear.className = 'usa-date-picker__calendar__previous-year'; prevYear.textContent = '‹‹'; const monthBtn = document.createElement('button'); monthBtn.className = 'usa-date-picker__calendar__month-selection'; monthBtn.textContent = 'January'; const yearBtn = document.createElement('button'); yearBtn.className = 'usa-date-picker__calendar__year-selection'; yearBtn.textContent = '2024'; header.appendChild(prevYear); header.appendChild(monthBtn); header.appendChild(yearBtn); calendar.appendChild(header); // Add calendar table const table = document.createElement('table'); table.className = 'usa-date-picker__calendar__table'; table.setAttribute('role', 'grid'); calendar.appendChild(table); element.appendChild(calendar); element.classList.add('usa-date-picker--enhanced'); // Set up button behavior button.addEventListener('click', () => { calendar.hidden = !calendar.hidden; input.setAttribute('aria-expanded', calendar.hidden ? 'false' : 'true'); }); } }), }, }; // Clear any existing USWDS delete (window as any).USWDS; }); afterEach(() => { delete (window as any).USWDS; vi.restoreAllMocks(); }); it('should start as basic input with button (progressive enhancement)', async () => { const input = element.querySelector('input[type="text"]'); const button = await waitForDatePickerButton(element); const calendar = element.querySelector('.usa-date-picker__calendar'); expect(input).toBeTruthy(); expect(button).toBeTruthy(); // In test environment where USWDS loads successfully, calendar should be created // Progressive enhancement means graceful degradation when USWDS fails, not absence of features expect(calendar ? calendar : null).toBeDefined(); // Allow either enhanced or basic state }); it('should load USWDS script when not available', async () => { // Progressive enhancement: component renders basic structure immediately // USWDS script loading happens asynchronously and doesn't block functionality delete (window as any).USWDS; const newElement = document.createElement('usa-date-picker') as USADatePicker; newElement.label = 'Test Date'; document.body.appendChild(newElement); await waitForUpdate(newElement); // Wait for fallback structure creation when USWDS is not available await new Promise(resolve => setTimeout(resolve, 250)); // Component should render basic structure regardless of USWDS availability const input = newElement.querySelector('input'); const button = newElement.querySelector('.usa-date-picker__button'); expect(input).toBeTruthy(); expect(button).toBeTruthy(); expect(newElement.label).toBe('Test Date'); newElement.remove(); }); it('should handle enhancement errors gracefully', async () => { // Mock USWDS with failing enhancement (window as any).USWDS = { datePicker: { enhanceDatePicker: vi.fn().mockImplementation(() => { throw new Error('Enhancement failed'); }), }, }; const newElement = document.createElement('usa-date-picker') as USADatePicker; newElement.label = 'Error Test Date'; document.body.appendChild(newElement); await waitForUpdate(newElement); await new Promise((resolve) => setTimeout(resolve, 50)); // Should not crash, input should still work const input = newElement.querySelector('input[type="text"]'); const button = newElement.querySelector('.usa-date-picker__button'); expect(input).toBeTruthy(); expect(button).toBeTruthy(); newElement.remove(); }); describe('JavaScript Implementation Validation', () => { it('should pass JavaScript implementation validation', async () => { // Validate USWDS JavaScript implementation patterns const componentPath = `${process.cwd()}/src/components/date-picker/usa-date-picker.ts`; const validation = validateComponentJavaScript(componentPath, 'date-picker'); 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('USWDS JavaScript Integration', () => { it('should successfully integrate with USWDS JavaScript (primary test)', async () => { element.label = 'Test Date'; await waitForUpdate(element); // Wait for fallback button structure to be created (happens after 100ms) await new Promise(resolve => setTimeout(resolve, 150)); // Run comprehensive USWDS integration test const result = await testUSWDSIntegration( element, USWDS_TEST_CONFIGS.DATE_PICKER_FALLBACK_OK ); // Assert integration is correct assertUSWDSIntegration(result); // Additional specific assertions expect(result.elementCounts.buttons).toBe(1); expect(result.elementCounts.inputs).toBeGreaterThanOrEqual(1); expect(result.hasDoubleInitialization).toBe(false); }); it('should prevent double initialization', async () => { element.label = 'Test Date'; await waitForUpdate(element); // Wait additional time for any potential double initialization await new Promise((resolve) => setTimeout(resolve, 200)); // Count critical elements const buttons = element.querySelectorAll('.usa-date-picker__button'); const calendars = element.querySelectorAll('.usa-date-picker__calendar'); expect(buttons.length).toBeLessThanOrEqual(1); expect(calendars.length).toBeLessThanOrEqual(1); if (buttons.length > 1) { throw new Error( `Double initialization detected: ${buttons.length} date picker buttons found` ); } }); it('should handle USWDS import failures gracefully', async () => { // Mock USWDS import failure const originalImport = window.import; vi.stubGlobal('import', vi.fn().mockRejectedValue(new Error('require is not defined'))); try { const testElement = document.createElement('usa-date-picker') as USADatePicker; testElement.label = 'Test Date'; document.body.appendChild(testElement); await waitForUpdate(testElement); // Wait for fallback structure creation when USWDS import fails await new Promise(resolve => setTimeout(resolve, 250)); // Should gracefully fall back to web component behavior const buttons = testElement.querySelectorAll('.usa-date-picker__button'); expect(buttons.length).toBe(1); // Should have fallback button testElement.remove(); } finally { // Restore original import if (originalImport) { vi.stubGlobal('import', originalImport); } } }); it('should have correct USWDS CSS classes and structure', async () => { element.label = 'Test Date'; await waitForUpdate(element); // Verify USWDS structure const datePicker = element.querySelector('.usa-date-picker'); expect(datePicker).toBeTruthy(); const input = element.querySelector('.usa-date-picker input'); expect(input).toBeTruthy(); const button = await waitForDatePickerButton(element); expect(button).toBeTruthy(); // Verify proper USWDS structure - USWDS creates wrapper elements const wrapper = element.querySelector('.usa-date-picker__wrapper'); if (wrapper) { // USWDS enhanced structure: input and button are in wrapper expect(button?.parentElement).toBe(wrapper); expect(wrapper.parentElement).toBe(datePicker); } else { // Fallback structure: input and button directly in date-picker expect(input?.parentElement).toBe(datePicker); expect(button?.parentElement).toBe(datePicker); } }); it('should detect and report integration health', async () => { element.label = 'Test Date'; await waitForUpdate(element); const result = await testUSWDSIntegration(element, { componentType: 'date-picker', expectUSWDSEnhancement: false, // Allow both USWDS and fallback allowFallback: true, maxElementCounts: { buttons: 1, calendars: 1, inputs: 2 }, }); // Should have clean integration (no errors) expect(result.errors).toEqual([]); // Should have successful initialization expect(result.hasCorrectInitialization).toBe(true); // Should not have double initialization expect(result.hasDoubleInitialization).toBe(false); // Log result for debugging console.log('Date Picker Integration Result:', { isUSWDSLoaded: result.isUSWDSLoaded, hasCorrectInitialization: result.hasCorrectInitialization, elementCounts: result.elementCounts, warnings: result.warnings, }); }); it('should validate Vite pre-bundling is working', async () => { // Test that USWDS modules can be imported (Vite pre-bundling working) try { const mainBundle = await import('@uswds/uswds'); expect(mainBundle).toBeDefined(); const datePicker = await import('@uswds/uswds/js/usa-date-picker'); expect(datePicker).toBeDefined(); console.log('✅ Vite pre-bundling is working correctly for USWDS modules'); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); if (errorMessage.includes('require is not defined')) { throw new Error( 'CRITICAL: Vite pre-bundling is NOT working. USWDS modules are still in CommonJS format. ' + 'Check vite.config.ts optimizeDeps configuration!' ); } throw error; } }); }); /** * MANDATORY Pattern Tests: data-default-value * * These tests validate the USWDS-native initial value pattern. * See docs/USWDS_INITIAL_VALUE_PATTERN.md for complete specification. * * CRITICAL: This pattern is MANDATORY and must not be changed. */ describe('MANDATORY Pattern: data-default-value', () => { it('should set data-default-value attribute on wrapper div', async () => { const testElement = document.createElement('usa-date-picker') as USADatePicker; testElement.value = '2024-03-15'; document.body.appendChild(testElement); await waitForUpdate(testElement); const wrapper = testElement.querySelector('.usa-date-picker'); expect(wrapper?.getAttribute('data-default-value')).toBe('2024-03-15'); testElement.remove(); }); it('should set data-web-component-managed attribute on wrapper', async () => { const testElement = document.createElement('usa-date-picker') as USADatePicker; document.body.appendChild(testElement); await waitForUpdate(testElement); const wrapper = testElement.querySelector('.usa-date-picker'); expect(wrapper?.getAttribute('data-web-component-managed')).toBe('true'); testElement.remove(); }); it('should set value attribute on input element', async () => { const testElement = document.createElement('usa-date-picker') as USADatePicker; testElement.value = '2024-03-15'; document.body.appendChild(testElement); await waitForUpdate(testElement); const input = testElement.querySelector('.usa-input') as HTMLInputElement; expect(input?.value).toBe('2024-03-15'); testElement.remove(); }); it('should set both input value AND data-default-value', async () => { // CRITICAL: Both are required for USWDS to work correctly // - input value: USWDS reads this initially for format detection // - data-default-value: USWDS uses this to restore after clearing const testElement = document.createElement('usa-date-picker') as USADatePicker; testElement.value = '2024-06-15'; document.body.appendChild(testElement); await waitForUpdate(testElement); const wrapper = testElement.querySelector('.usa-date-picker'); const input = testElement.querySelector('.usa-input') as HTMLInputElement; expect(input?.value).toBe('2024-06-15'); expect(wrapper?.getAttribute('data-default-value')).toBe('2024-06-15'); testElement.remove(); }); it('should handle empty value correctly in both attributes', async () => { const testElement = document.createElement('usa-date-picker') as USADatePicker; testElement.value = ''; document.body.appendChild(testElement); await waitForUpdate(testElement); const wrapper = testElement.querySelector('.usa-date-picker'); const input = testElement.querySelector('.usa-input') as HTMLInputElement; expect(input?.value).toBe(''); expect(wrapper?.getAttribute('data-default-value')).toBe(''); testElement.remove(); }); it('should update data-default-value when value changes', async () => { const testElement = document.createElement('usa-date-picker') as USADatePicker; testElement.value = '2024-01-01'; document.body.appendChild(testElement); await waitForUpdate(testElement); // Change the value testElement.value = '2024-12-31'; await waitForUpdate(testElement); const wrapper = testElement.querySelector('.usa-date-picker'); expect(wrapper?.getAttribute('data-default-value')).toBe('2024-12-31'); testElement.remove(); }); it('should set data-default-value via attribute', async () => { const testElement = document.createElement('usa-date-picker') as USADatePicker; testElement.setAttribute('value', '2024-12-25'); document.body.appendChild(testElement); await waitForUpdate(testElement); const wrapper = testElement.querySelector('.usa-date-picker'); expect(wrapper?.getAttribute('data-default-value')).toBe('2024-12-25'); testElement.remove(); }); }); /** * Regression Tests: Initial Value Persistence * * These tests catch the bug fixed in commit d6019090: * - Initial date values being cleared after USWDS initialization * * Critical behavior to prevent: * 1. Initial value must NOT be cleared when USWDS transforms the component * 2. Value must sync to both original input and USWDS-created external input * 3. Value must be visible to the user after page load * * NOTE: These tests verify the BEHAVIOR (values persist). * The MANDATORY pattern tests above verify the IMPLEMENTATION (attributes set correctly). */ describe('Regression: Initial Value Persistence', () => { it('should preserve initial value set before USWDS initialization', async () => { // Create element with initial value BEFORE adding to DOM const testElement = document.createElement('usa-date-picker') as USADatePicker; testElement.label = 'Test Date'; testElement.value = '2024-03-15'; // Add to DOM and wait for USWDS initialization document.body.appendChild(testElement); await waitForUpdate(testElement); // Wait additional time for USWDS transformation and value sync await new Promise(resolve => setTimeout(resolve, 150)); // CRITICAL: Value must persist after USWDS initialization expect(testElement.value).toBe('2024-03-15'); // Verify value in original input const originalInput = testElement.querySelector('.usa-input') as HTMLInputElement; expect(originalInput?.value).toBe('2024-03-15'); // Cleanup testElement.remove(); }); it('should preserve initial value set via attribute', async () => { // Create element with attribute-based initial value const testElement = document.createElement('usa-date-picker') as USADatePicker; testElement.setAttribute('value', '2024-12-25'); testElement.label = 'Test Date'; document.body.appendChild(testElement); await waitForUpdate(testElement); await new Promise(resolve => setTimeout(resolve, 150)); // Value set via attribute must persist expect(testElement.value).toBe('2024-12-25'); expect(testElement.getAttribute('value')).toBe('2024-12-25'); testElement.remove(); }); it('should handle empty initial value correctly', async () => { // Ensure empty values don't cause issues const testElement = document.createElement('usa-date-picker') as USADatePicker; testElement.label = 'Test Date'; testElement.value = ''; document.body.appendChild(testElement); await waitForUpdate(testElement); await new Promise(resolve => setTimeout(resolve, 150)); // Empty value should remain empty expect(testElement.value).toBe(''); const input = testElement.querySelector('.usa-input') as HTMLInputElement; expect(input?.value).toBe(''); testElement.remove(); }); it('should allow value updates after initial load', async () => { // Verify that values can be changed after initialization const testElement = document.createElement('usa-date-picker') as USADatePicker; testElement.label = 'Test Date'; testElement.value = '2024-01-01'; document.body.appendChild(testElement); await waitForUpdate(testElement); await new Promise(resolve => setTimeout(resolve, 150)); // Change the value testElement.value = '2024-12-31'; await waitForUpdate(testElement); // New value should be reflected expect(testElement.value).toBe('2024-12-31'); testElement.remove(); }); }); /** * USWDS Integration Requirements Tests * * These tests validate critical USWDS integration patterns for the Date Picker component. * They ensure that USWDS JavaScript can properly enhance the component with: * - Default value handling via data-default-value attribute * - Component enhancement signal via data-enhanced attribute * - Placeholder display behavior * * These tests prevent regressions like: * - Default dates not displaying * - Placeholder not showing when no value * - USWDS initialization failures * * See: /tmp/combo-box-complete-summary.md for pattern details */ describe('USWDS Integration Requirements', () => { it('should include data-default-value attribute on wrapper element', async () => { element.value = '2024-12-25'; await waitForUpdate(element); const wrapper = element.querySelector('.usa-date-picker'); expect(wrapper).toBeTruthy(); expect(wrapper?.hasAttribute('data-default-value')).toBe(true); expect(wrapper?.getAttribute('data-default-value')).toBe('2024-12-25'); }); it('should include data-default-value empty string when no value', async () => { element.value = ''; await waitForUpdate(element); const wrapper = element.querySelector('.usa-date-picker'); expect(wrapper).toBeTruthy(); expect(wrapper?.hasAttribute('data-default-value')).toBe(true); expect(wrapper?.getAttribute('data-default-value')).toBe(''); }); it('should include data-enhanced="false" on date picker wrapper', async () => { await waitForUpdate(element); const wrapper = element.querySelector('.usa-date-picker'); expect(wrapper).toBeTruthy(); expect(wrapper?.getAttribute('data-enhanced')).toBe('false'); }); it('should render placeholder when set', async () => { element.placeholder = 'Select a date'; await waitForUpdate(element); const input = element.querySelector('input') as HTMLInputElement; expect(input).toBeTruthy(); expect(input?.getAttribute('placeholder')).toBe('Select a date'); }); it('should display placeholder when no value set', async () => { element.placeholder = 'mm/dd/yyyy'; element.value = ''; await waitForUpdate(element); const input = element.querySelector('input') as HTMLInputElement; expect(input).toBeTruthy(); expect(input?.getAttribute('placeholder')).toBe('mm/dd/yyyy'); expect(input?.value).toBe(''); }); it('should maintain data-default-value when value changes', async () => { element.value = '2024-01-01'; await waitForUpdate(element); let wrapper = element.querySelector('.usa-date-picker'); expect(wrapper?.getAttribute('data-default-value')).toBe('2024-01-01'); element.value = '2024-12-31'; await waitForUpdate(element); wrapper = element.querySelector('.usa-date-picker'); expect(wrapper?.getAttribute('data-default-value')).toBe('2024-12-31'); }); }); describe('Regression Prevention: Hidden Attribute Bug Fix (2025-10-08)', () => { // Prevents regression of the same bug found in combo box // BUG: Setting element.hidden = false didn't remove the HTML attribute in Lit components // FIX: Added explicit removeAttribute('hidden') and setAttribute('hidden', '') it('should properly manage hidden attribute on calendar element', async () => { element.value = '2024-01-15'; await waitForUpdate(element); // In jsdom, USWDS doesn't actually run, but we verify the structure exists // that the behavior file would manipulate const wrapper = element.querySelector('.usa-date-picker'); expect(wrapper).toBeTruthy(); // The calendar element would be created by USWDS enhancement in browser // This test ensures the wrapper exists for USWDS to work with expect(wrapper?.hasAttribute('data-enhanced')).toBe(true); }); it('should render correct DOM structure for calendar manipulation', async () => { // Validates that the component renders the structure needed by usa-date-picker-behavior.ts element.label = 'Test date'; element.value = '2024-01-01'; await waitForUpdate(element); // Structure needed for USWDS mirrored behavior: // 1. Container with .usa-date-picker class const container = element.querySelector('.usa-date-picker'); expect(container).toBeTruthy(); // 2. Input element const input = element.querySelector('input'); expect(input).toBeTruthy(); expect(input?.classList.contains('usa-input')).toBe(true); // 3. data-enhanced attribute on container expect(container?.hasAttribute('data-enhanced')).toBe(true); // 4. data-default-value on wrapper expect(container?.hasAttribute('data-default-value')).toBe(true); // This structure allows usa-date-picker-behavior.ts to: // - Find the container via DATE_PICKER selector // - Create and show/hide the calendar with proper attribute manipulation // - Handle date selection and updates }); it('should maintain data-enhanced as string type', async () => { // CRITICAL: data-enhanced must be a string, not boolean element.value = '2024-01-15'; await waitForUpdate(element); const wrapper = element.querySelector('.usa-date-picker'); // Verify it's a string (starts as "false", may become "true" after enhancement) expect(typeof wrapper?.getAttribute('data-enhanced')).toBe('string'); // Verify it's not null or undefined expect(wrapper?.hasAttribute('data-enhanced')).toBe(true); }); }); describe('Keyboard Navigation (WCAG 2.1)', () => { // NOTE: Calendar keyboard interaction tests moved to Cypress (cypress/e2e/date-picker-calendar.cy.ts) // Calendar keyboard interactions require real browser and USWDS JavaScript it('should allow keyboard navigation to date input', async () => { element.name = 'birth-date'; element.label = 'Birth date'; await waitForUpdate(element); const focusableElements = getFocusableElements(element); // Should have at least the input and calendar button expect(focusableElements.length).toBeGreaterThanOrEqual(1); }); it('should be keyboard-only usable', async () => { element.name = 'birth-date'; element.label = 'Birth date'; await waitForUpdate(element); await verifyKeyboardOnlyUsable(element); }); it('should handle calendar button keyboard activation', async () => { element.name = 'birth-date'; element.label = 'Birth date'; await waitForUpdate(element); const button = await waitForDatePickerButton(element); if (button) { // Button should be keyboard accessible expect(button.tabIndex).toBeGreaterThanOrEqual(0); const enterEvent = new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', keyCode: 13, bubbles: true, cancelable: true, }); button.dispatchEvent(enterEvent); // USWDS would open calendar on button activation expect(button.classList.contains('usa-date-picker__button')).toBe(true); } else { // Fallback mode - no button present is acceptable expect(true).toBe(true); } }); }); });