// Component tests for usa-time-picker
import './index.ts';
describe('USA Time Picker Component Tests', () => {
it('should render time picker with default properties', () => {
cy.mount(` `);
cy.get('usa-time-picker').should('exist');
cy.get('.usa-time-picker').should('exist');
cy.get('.usa-time-picker__input').should('exist');
});
it('should handle time input via typing', () => {
cy.mount(` `);
// Type time in various formats
cy.get('.usa-time-picker__input').type('2:30 PM');
cy.get('.usa-time-picker__input').should('have.value', '2:30 PM');
// Clear and try different format
cy.get('.usa-time-picker__input').clear().type('14:30');
cy.get('.usa-time-picker__input').should('have.value', '14:30');
});
it('should handle dropdown list when enabled', () => {
cy.mount(` `);
// Should show dropdown toggle button
cy.get('.usa-time-picker__toggle').should('exist');
// Click to open dropdown
cy.get('.usa-time-picker__toggle').click();
cy.get('.usa-time-picker__list').should('be.visible');
// Should have time options
cy.get('.usa-time-picker__list-option').should('exist');
cy.get('.usa-time-picker__list-option').first().should('contain.text', '12:00 AM');
});
it('should handle time selection from dropdown', () => {
cy.mount(` `);
// Open dropdown
cy.get('.usa-time-picker__toggle').click();
// Select a time option
cy.get('.usa-time-picker__list-option').contains('9:00 AM').click();
// Should close dropdown and set value
cy.get('.usa-time-picker__input').should('have.value', '9:00 AM');
cy.get('.usa-time-picker__list').should('not.be.visible');
});
it('should handle 24-hour format', () => {
cy.mount(` `);
// Type 24-hour format
cy.get('.usa-time-picker__input').type('14:30');
cy.get('.usa-time-picker__input').should('have.value', '14:30');
// Should accept valid 24-hour times
cy.get('.usa-time-picker__input').clear().type('23:59');
cy.get('.usa-time-picker__input').should('have.value', '23:59');
});
it('should handle 12-hour format with AM/PM', () => {
cy.mount(` `);
// Type 12-hour format
cy.get('.usa-time-picker__input').type('2:30 PM');
cy.get('.usa-time-picker__input').should('have.value', '2:30 PM');
// Should accept different AM/PM formats
cy.get('.usa-time-picker__input').clear().type('9:15 am');
cy.get('.usa-time-picker__input').should('have.value', '9:15 AM');
});
it('should validate time input', () => {
cy.mount(` `);
// Enter invalid time
cy.get('.usa-time-picker__input').type('25:70').blur();
cy.get('.usa-time-picker__input').should('have.attr', 'aria-invalid', 'true');
// Enter valid time
cy.get('.usa-time-picker__input').clear().type('10:30 AM').blur();
cy.get('.usa-time-picker__input').should('not.have.attr', 'aria-invalid', 'true');
});
it('should handle step intervals in dropdown', () => {
cy.mount(` `);
// Open dropdown
cy.get('.usa-time-picker__toggle').click();
// Should show options in 15-minute increments
cy.get('.usa-time-picker__list-option').eq(1).should('contain.text', '12:15 AM');
cy.get('.usa-time-picker__list-option').eq(2).should('contain.text', '12:30 AM');
cy.get('.usa-time-picker__list-option').eq(3).should('contain.text', '12:45 AM');
});
it('should emit change events', () => {
cy.mount(` `);
cy.window().then((win) => {
const timePicker = win.document.getElementById('test-time-picker') as any;
const changeSpy = cy.stub();
timePicker.addEventListener('change', changeSpy);
// Type a time
cy.get('.usa-time-picker__input').type('3:45 PM').blur();
cy.then(() => {
expect(changeSpy).to.have.been.called;
});
});
});
it('should handle disabled state', () => {
cy.mount(` `);
cy.get('.usa-time-picker__input').should('be.disabled');
cy.get('.usa-time-picker').should('have.class', 'usa-time-picker--disabled');
});
it('should handle required state', () => {
cy.mount(` `);
cy.get('.usa-time-picker__input').should('have.attr', 'required');
cy.get('.usa-time-picker__input').should('have.attr', 'aria-required', 'true');
});
it('should handle error state', () => {
cy.mount(`
`);
cy.get('.usa-time-picker__input').should('have.attr', 'aria-invalid', 'true');
cy.get('.usa-time-picker').should('have.class', 'usa-time-picker--error');
cy.get('.usa-error-message').should('contain.text', 'Please enter a valid time');
});
it('should handle time range restrictions', () => {
cy.mount(`
`);
// Enter time before min time
cy.get('.usa-time-picker__input').type('8:00 AM').blur();
cy.get('.usa-time-picker__input').should('have.attr', 'aria-invalid', 'true');
// Enter time after max time
cy.get('.usa-time-picker__input').clear().type('6:00 PM').blur();
cy.get('.usa-time-picker__input').should('have.attr', 'aria-invalid', 'true');
// Enter time within range
cy.get('.usa-time-picker__input').clear().type('2:00 PM').blur();
cy.get('.usa-time-picker__input').should('not.have.attr', 'aria-invalid', 'true');
});
it('should handle keyboard navigation in dropdown', () => {
cy.mount(` `);
// Focus input and open dropdown with arrow down
cy.get('.usa-time-picker__input').focus().type('{downarrow}');
cy.get('.usa-time-picker__list').should('be.visible');
// First option should be focused
cy.get('.usa-time-picker__list-option--focused').should('contain.text', '12:00 AM');
// Arrow down to next option
cy.get('.usa-time-picker__input').type('{downarrow}');
cy.get('.usa-time-picker__list-option--focused').should('contain.text', '12:30 AM');
// Enter to select
cy.get('.usa-time-picker__input').type('{enter}');
cy.get('.usa-time-picker__input').should('have.value', '12:30 AM');
cy.get('.usa-time-picker__list').should('not.be.visible');
});
it('should handle escape key to close dropdown', () => {
cy.mount(` `);
// Open dropdown
cy.get('.usa-time-picker__toggle').click();
cy.get('.usa-time-picker__list').should('be.visible');
// Press escape to close
cy.get('.usa-time-picker__input').type('{esc}');
cy.get('.usa-time-picker__list').should('not.be.visible');
});
it('should handle form integration', () => {
cy.mount(`
`);
cy.window().then((win) => {
const form = win.document.getElementById('test-form') as HTMLFormElement;
const submitSpy = cy.stub();
form.addEventListener('submit', (e) => {
e.preventDefault();
const formData = new FormData(form);
submitSpy(formData.get('meetingTime'));
});
// Enter a time and submit
cy.get('.usa-time-picker__input').type('2:00 PM');
cy.get('button[type="submit"]').click();
cy.then(() => {
expect(submitSpy).to.have.been.calledWith('2:00 PM');
});
});
});
it('should handle auto-complete suggestions', () => {
cy.mount(` `);
// Start typing partial time
cy.get('.usa-time-picker__input').type('2');
// Should show suggestions
cy.get('.usa-time-picker__suggestions').should('be.visible');
cy.get('.usa-time-picker__suggestion').should('contain.text', '2:00');
// Click suggestion
cy.get('.usa-time-picker__suggestion').contains('2:00 PM').click();
cy.get('.usa-time-picker__input').should('have.value', '2:00 PM');
});
it('should handle custom placeholder text', () => {
cy.mount(`
`);
cy.get('.usa-time-picker__input').should('have.attr', 'placeholder', 'Enter appointment time');
});
it('should handle time parsing and formatting', () => {
cy.mount(` `);
// Test various input formats
const timeInputs = [
{ input: '230p', expected: '2:30 PM' },
{ input: '1430', expected: '2:30 PM' },
{ input: '2:30', expected: '2:30 AM' },
{ input: '14:30', expected: '2:30 PM' },
];
timeInputs.forEach(({ input, expected }) => {
cy.get('.usa-time-picker__input').clear().type(input).blur();
cy.get('.usa-time-picker__input').should('have.value', expected);
});
});
it('should handle clear functionality', () => {
cy.mount(` `);
// Set a time first
cy.get('.usa-time-picker__input').type('3:00 PM');
// Should show clear button
cy.get('.usa-time-picker__clear-button').should('be.visible');
// Click clear button
cy.get('.usa-time-picker__clear-button').click();
cy.get('.usa-time-picker__input').should('have.value', '');
cy.get('.usa-time-picker__clear-button').should('not.be.visible');
});
it('should handle focus and blur events', () => {
cy.mount(` `);
cy.window().then((win) => {
const timePicker = win.document.getElementById('test-time-picker') as any;
const focusSpy = cy.stub();
const blurSpy = cy.stub();
timePicker.addEventListener('focus', focusSpy);
timePicker.addEventListener('blur', blurSpy);
cy.get('.usa-time-picker__input').focus();
cy.get('.usa-time-picker__input').blur();
cy.then(() => {
expect(focusSpy).to.have.been.called;
expect(blurSpy).to.have.been.called;
});
});
});
it('should handle aria attributes correctly', () => {
cy.mount(`
Meeting Time
Enter time in HH:MM AM/PM format
`);
cy.get('.usa-time-picker__input')
.should('have.attr', 'role', 'combobox')
.should('have.attr', 'aria-expanded', 'false')
.should('have.attr', 'aria-labelledby', 'time-label')
.should('have.attr', 'aria-describedby', 'time-hint');
// Open dropdown
cy.get('.usa-time-picker__toggle').click();
cy.get('.usa-time-picker__input').should('have.attr', 'aria-expanded', 'true');
cy.get('.usa-time-picker__list')
.should('have.attr', 'role', 'listbox')
.should('have.attr', 'aria-labelledby', 'time-label');
});
it('should handle validation on blur', () => {
cy.mount(`
`);
// Focus and blur without entering time (should trigger validation)
cy.get('.usa-time-picker__input').focus().blur();
cy.get('.usa-time-picker__input').should('have.attr', 'aria-invalid', 'true');
// Enter valid time and blur (should clear validation)
cy.get('.usa-time-picker__input').type('10:00 AM').blur();
cy.get('.usa-time-picker__input').should('not.have.attr', 'aria-invalid', 'true');
});
it('should be accessible', () => {
cy.mount(`
`);
cy.injectAxe();
cy.checkAccessibility();
});
it('should handle custom CSS classes', () => {
cy.mount(`
`);
cy.get('usa-time-picker').should('have.class', 'custom-time-picker-class');
cy.get('.usa-time-picker').should('exist');
});
it('should handle programmatic time setting', () => {
cy.mount(` `);
cy.window().then((win) => {
const timePicker = win.document.getElementById('test-time-picker') as any;
// Set time programmatically
timePicker.value = '15:30';
cy.get('.usa-time-picker__input').should('have.value', '3:30 PM');
// Clear time programmatically
timePicker.value = '';
cy.get('.usa-time-picker__input').should('have.value', '');
});
});
it('should handle filtering dropdown options', () => {
cy.mount(` `);
// Open dropdown and type to filter
cy.get('.usa-time-picker__toggle').click();
cy.get('.usa-time-picker__input').type('2:');
// Should show only options matching filter
cy.get('.usa-time-picker__list-option').should('contain.text', '2:00');
cy.get('.usa-time-picker__list-option').should('not.contain.text', '1:00');
});
it('should handle click outside to close dropdown', () => {
cy.mount(`
Outside Button
`);
// Open dropdown
cy.get('.usa-time-picker__toggle').click();
cy.get('.usa-time-picker__list').should('be.visible');
// Click outside
cy.get('#outside-button').click();
cy.get('.usa-time-picker__list').should('not.be.visible');
});
it('should handle seconds input when enabled', () => {
cy.mount(` `);
// Type time with seconds
cy.get('.usa-time-picker__input').type('2:30:45 PM');
cy.get('.usa-time-picker__input').should('have.value', '2:30:45 PM');
// Should validate seconds
cy.get('.usa-time-picker__input').clear().type('2:30:75 PM').blur();
cy.get('.usa-time-picker__input').should('have.attr', 'aria-invalid', 'true');
});
it('should handle touch interactions', () => {
cy.mount(` `);
// Touch to open dropdown
cy.get('.usa-time-picker__toggle').trigger('touchstart');
cy.get('.usa-time-picker__list').should('be.visible');
// Touch option to select
cy.get('.usa-time-picker__list-option').contains('10:00 AM').trigger('touchstart');
cy.get('.usa-time-picker__input').should('have.value', '10:00 AM');
cy.get('.usa-time-picker__list').should('not.be.visible');
});
it('should handle business hours filtering', () => {
cy.mount(`
`);
// Open dropdown
cy.get('.usa-time-picker__toggle').click();
// Should only show business hours
cy.get('.usa-time-picker__list-option').first().should('contain.text', '9:00 AM');
cy.get('.usa-time-picker__list-option').should('not.contain.text', '8:00 AM');
cy.get('.usa-time-picker__list-option').should('not.contain.text', '6:00 PM');
});
// Disabled State Robustness Testing (Critical Gap Fix)
describe('Disabled State Protection', () => {
it('should completely prevent time selection when disabled', () => {
let timeChangeTriggered = false;
let inputActivated = false;
cy.mount(`
`);
cy.window().then((win) => {
const timePicker = win.document.getElementById('disabled-time-test') as any;
const input = timePicker.querySelector('.usa-time-picker__input') as HTMLInputElement;
// Listen for any time change events (should not occur)
timePicker.addEventListener('change', () => {
timeChangeTriggered = true;
});
input.addEventListener('input', () => {
timeChangeTriggered = true;
});
// Listen for input activation attempts
input.addEventListener('click', () => {
inputActivated = true;
});
input.addEventListener('focus', () => {
inputActivated = true;
});
});
// Attempt to type time when disabled (should be prevented)
cy.get('.usa-time-picker__input')
.type('2:30 PM', { force: true })
.then(() => {
// Time input should be completely blocked
expect(timeChangeTriggered).to.be.false;
});
// Click attempts should not activate input
cy.get('.usa-time-picker__input')
.click({ force: true })
.then(() => {
expect(inputActivated).to.be.false;
});
// Input should remain disabled throughout
cy.get('.usa-time-picker__input').should('be.disabled');
cy.get('.usa-time-picker').should('have.class', 'usa-time-picker--disabled');
cy.get('.usa-time-picker__input').should('have.value', '');
});
it('should prevent dropdown interactions when disabled', () => {
let dropdownOpened = false;
let toggleClicked = false;
let optionSelected = false;
cy.mount(`
`);
cy.window().then((win) => {
const timePicker = win.document.getElementById('disabled-dropdown-test') as any;
const toggle = timePicker.querySelector('.usa-time-picker__toggle') as HTMLButtonElement;
// Listen for dropdown events (should not occur when disabled)
timePicker.addEventListener('dropdown-open', () => {
dropdownOpened = true;
});
toggle?.addEventListener('click', () => {
toggleClicked = true;
});
// Listen for option selection (should not occur)
timePicker.addEventListener('option-select', () => {
optionSelected = true;
});
});
// Attempt to open dropdown when disabled
cy.get('.usa-time-picker__toggle')
.click({ force: true })
.then(() => {
expect(dropdownOpened).to.be.false;
expect(toggleClicked).to.be.false;
});
// Dropdown should not be visible
cy.get('.usa-time-picker__list').should('not.exist').or('not.be.visible');
// Toggle button should be disabled
cy.get('.usa-time-picker__toggle').should('be.disabled');
});
it('should prevent keyboard input when disabled', () => {
let keydownTriggered = false;
let inputTriggered = false;
let focusTriggered = false;
cy.mount(`
`);
cy.window().then((win) => {
const timePicker = win.document.getElementById('disabled-keyboard-test') as any;
const input = timePicker.querySelector('.usa-time-picker__input') as HTMLInputElement;
// Listen for keyboard events (should be minimal when disabled)
input.addEventListener('keydown', () => {
keydownTriggered = true;
});
input.addEventListener('input', () => {
inputTriggered = true;
});
input.addEventListener('focus', () => {
focusTriggered = true;
});
});
// Attempt keyboard interactions when disabled
cy.get('.usa-time-picker__input').focus({ force: true });
cy.get('.usa-time-picker__input').type('2:30 PM', { force: true });
cy.get('.usa-time-picker__input').type('{downarrow}', { force: true });
cy.get('.usa-time-picker__input').type('{enter}', { force: true });
cy.wait(100).then(() => {
// Most keyboard interactions should be prevented
expect(inputTriggered).to.be.false;
});
// Input should not accept any text
cy.get('.usa-time-picker__input').should('have.value', '');
cy.get('.usa-time-picker__input').should('be.disabled');
});
it('should manage focus properly when disabled', () => {
cy.mount(`
`);
// Time picker should not be focusable when disabled
cy.get('.usa-time-picker__input').should('be.disabled');
cy.get('.usa-time-picker__input').should('have.attr', 'tabindex', '-1');
// Tab navigation should skip disabled time picker
cy.get('#before-input').focus().tab();
cy.focused().should('have.id', 'after-input');
// Dropdown toggle should also not be focusable
cy.get('.usa-time-picker__toggle').should('be.disabled');
});
it('should maintain consistent visual disabled state', () => {
cy.mount(`
`);
// Initial disabled state verification
cy.get('.usa-time-picker').should('have.class', 'usa-time-picker--disabled');
cy.get('.usa-time-picker__input').should('be.disabled');
cy.get('.usa-time-picker__input').should('have.attr', 'aria-disabled', 'true');
cy.get('.usa-time-picker__toggle').should('be.disabled');
// Attempt various interactions that might change visual state
cy.get('.usa-time-picker__input').click({ force: true });
cy.get('.usa-time-picker__input').hover();
cy.get('.usa-time-picker__toggle').click({ force: true });
cy.get('.usa-time-picker__toggle').hover();
// Visual state should remain consistently disabled
cy.get('.usa-time-picker').should('have.class', 'usa-time-picker--disabled');
cy.get('.usa-time-picker__input').should('be.disabled');
cy.get('.usa-time-picker__input').should('have.attr', 'aria-disabled', 'true');
cy.get('.usa-time-picker__toggle').should('be.disabled');
// Should not show hover or active states
cy.get('.usa-time-picker__input').should('not.have.class', 'usa-time-picker__input--hover');
cy.get('.usa-time-picker__input').should('not.have.class', 'usa-time-picker__input--active');
cy.get('.usa-time-picker__toggle').should('not.have.class', 'usa-time-picker__toggle--hover');
});
it('should prevent time clearing when disabled', () => {
let timeCleared = false;
let clearButtonClicked = false;
// First enable and set a time
cy.mount(`
`);
// Set time while enabled
cy.get('.usa-time-picker__input').type('3:00 PM');
cy.get('.usa-time-picker__input').should('have.value', '3:00 PM');
// Now disable the component
cy.window().then((win) => {
const timePicker = win.document.getElementById('clear-disabled-test') as any;
timePicker.disabled = true;
// Listen for clear events
timePicker.addEventListener('time-clear', () => {
timeCleared = true;
});
});
cy.window().then((win) => {
const clearButton = win.document.querySelector(
'.usa-time-picker__clear-button'
) as HTMLButtonElement;
if (clearButton) {
clearButton.addEventListener('click', () => {
clearButtonClicked = true;
});
}
});
// Attempt to clear time when disabled
cy.get('.usa-time-picker__clear-button')
.click({ force: true })
.then(() => {
expect(timeCleared).to.be.false;
});
// Time should still be present
cy.get('.usa-time-picker__input').should('have.value', '3:00 PM');
// Clear button should be disabled or hidden
cy.get('.usa-time-picker__clear-button').should('be.disabled');
});
it('should ignore all interaction types when disabled', () => {
let anyEventTriggered = false;
const eventTypes = [
'click',
'dblclick',
'mousedown',
'mouseup',
'focus',
'blur',
'keydown',
'keyup',
'input',
];
cy.mount(`
`);
cy.window().then((win) => {
const timePicker = win.document.getElementById('comprehensive-disabled-test') as any;
const input = timePicker.querySelector('.usa-time-picker__input') as HTMLInputElement;
const toggle = timePicker.querySelector('.usa-time-picker__toggle') as HTMLButtonElement;
// Listen for any significant events
eventTypes.forEach((eventType) => {
input.addEventListener(eventType, () => {
anyEventTriggered = true;
});
toggle?.addEventListener(eventType, () => {
anyEventTriggered = true;
});
});
// Listen for component-specific events
timePicker.addEventListener('change', () => {
anyEventTriggered = true;
});
timePicker.addEventListener('time-select', () => {
anyEventTriggered = true;
});
timePicker.addEventListener('dropdown-open', () => {
anyEventTriggered = true;
});
});
// Attempt comprehensive interactions
cy.get('.usa-time-picker__input').click({ force: true });
cy.get('.usa-time-picker__input').dblclick({ force: true });
cy.get('.usa-time-picker__input').focus({ force: true });
cy.get('.usa-time-picker__input').type('2:30 PM', { force: true });
cy.get('.usa-time-picker__toggle').click({ force: true });
cy.wait(100).then(() => {
// Component should have ignored most interactions
expect(anyEventTriggered).to.be.false;
});
// Component should remain in disabled state
cy.get('.usa-time-picker__input').should('be.disabled');
cy.get('.usa-time-picker').should('have.class', 'usa-time-picker--disabled');
cy.get('.usa-time-picker__input').should('have.value', '');
});
it('should handle disabled state transitions properly', () => {
let stateTransitionHandled = false;
cy.mount(`
`);
// Start enabled and set a time
cy.get('.usa-time-picker__input').type('4:15 PM');
cy.get('.usa-time-picker__input').should('have.value', '4:15 PM');
// Transition to disabled
cy.window().then((win) => {
const timePicker = win.document.getElementById('transition-test') as any;
timePicker.disabled = true;
stateTransitionHandled = true;
});
cy.wait(100);
// Should be properly disabled
cy.get('.usa-time-picker__input').should('be.disabled');
cy.get('.usa-time-picker').should('have.class', 'usa-time-picker--disabled');
cy.get('.usa-time-picker__toggle').should('be.disabled');
// Time should still be visible but not editable
cy.get('.usa-time-picker__input').should('have.value', '4:15 PM');
// Transition back to enabled
cy.window().then((win) => {
const timePicker = win.document.getElementById('transition-test') as any;
timePicker.disabled = false;
});
cy.wait(100);
// Should be properly enabled again
cy.get('.usa-time-picker__input').should('not.be.disabled');
cy.get('.usa-time-picker').should('not.have.class', 'usa-time-picker--disabled');
cy.get('.usa-time-picker__toggle').should('not.be.disabled');
// Should be able to edit time again
cy.get('.usa-time-picker__input').clear().type('5:30 PM');
cy.get('.usa-time-picker__input').should('have.value', '5:30 PM');
expect(stateTransitionHandled).to.be.true;
});
it('should respect disabled state in form contexts', () => {
let formSubmitted = false;
let disabledFieldIncluded = false;
cy.mount(`
`);
// Set time in enabled input
cy.get('#enabled-form-input .usa-time-picker__input').type('2:00 PM');
cy.window().then((win) => {
const form = win.document.getElementById('disabled-form-test') as HTMLFormElement;
form.addEventListener('submit', (e) => {
e.preventDefault();
formSubmitted = true;
const formData = new FormData(form);
if (formData.has('disabled-time')) {
disabledFieldIncluded = true;
}
});
});
// Submit form
cy.get('button[type="submit"]')
.click()
.then(() => {
expect(formSubmitted).to.be.true;
expect(disabledFieldIncluded).to.be.false; // Disabled field should not be included
});
});
it('should prevent programmatic value changes when disabled', () => {
cy.mount(`
`);
cy.window().then((win) => {
const timePicker = win.document.getElementById('programmatic-disabled-test') as any;
// Attempt to set value programmatically when disabled
timePicker.value = '10:30 AM';
});
// Value should not be set when disabled
cy.get('.usa-time-picker__input').should('have.value', '');
// Component should remain disabled
cy.get('.usa-time-picker__input').should('be.disabled');
cy.get('.usa-time-picker').should('have.class', 'usa-time-picker--disabled');
});
it('should maintain disabled state during validation attempts', () => {
cy.mount(`
`);
// Attempt to trigger validation when disabled
cy.get('.usa-time-picker__input').focus({ force: true }).blur({ force: true });
// Should not show validation errors when disabled
cy.get('.usa-time-picker__input').should('not.have.attr', 'aria-invalid', 'true');
// Should remain disabled
cy.get('.usa-time-picker__input').should('be.disabled');
cy.get('.usa-time-picker').should('have.class', 'usa-time-picker--disabled');
});
});
});