// Component tests for usa-radio
import './index.ts';
describe('USA Radio Component Tests', () => {
it('should render radio button with default properties', () => {
cy.mount(`
Option 1
`);
cy.get('usa-radio').should('exist');
cy.get('usa-radio input[type="radio"]').should('have.class', 'usa-radio__input');
cy.get('usa-radio .usa-radio__label').should('contain.text', 'Option 1');
});
it('should handle radio button selection in a group', () => {
cy.mount(`
`);
// Initially none selected
cy.get('input[name="choice"]:checked').should('have.length', 0);
// Select option 2
cy.get('#radio2 input').check();
cy.get('#radio2 input').should('be.checked');
cy.get('#radio1 input').should('not.be.checked');
cy.get('#radio3 input').should('not.be.checked');
// Select option 3 (should deselect option 2)
cy.get('#radio3 input').check();
cy.get('#radio3 input').should('be.checked');
cy.get('#radio1 input').should('not.be.checked');
cy.get('#radio2 input').should('not.be.checked');
});
it('should handle clicking on label', () => {
cy.mount(`
Click this label
`);
// Click on label should select radio
cy.get('.usa-radio__label').click();
cy.get('input[type="radio"]').should('be.checked');
});
it('should emit change events', () => {
cy.mount(`
Test change event
`);
cy.window().then((win) => {
const radio = win.document.getElementById('test-radio') as any;
const changeSpy = cy.stub();
radio.addEventListener('change', changeSpy);
cy.get('input[type="radio"]').check();
cy.then(() => {
expect(changeSpy).to.have.been.called;
});
});
});
it('should handle initial checked state', () => {
cy.mount(`
`);
cy.get('#radio2 input').should('be.checked');
cy.get('#radio1 input').should('not.be.checked');
cy.get('#radio3 input').should('not.be.checked');
});
it('should handle disabled state', () => {
cy.mount(`
`);
cy.get('#radio2 input').should('be.disabled');
cy.get('#radio2 .usa-radio__label').should('have.class', 'usa-radio__label--disabled');
// Should not be selectable
cy.get('#radio2 .usa-radio__label').click({ force: true });
cy.get('#radio2 input').should('not.be.checked');
// Other options should still work
cy.get('#radio1 input').check();
cy.get('#radio1 input').should('be.checked');
});
it('should handle required state', () => {
cy.mount(`
`);
cy.get('#radio1 input').should('have.attr', 'required');
cy.get('#radio2 input').should('have.attr', 'required');
cy.get('#radio1 input').should('have.attr', 'aria-required', 'true');
cy.get('#radio2 input').should('have.attr', 'aria-required', 'true');
});
it('should handle error state', () => {
cy.mount(`
`);
cy.get('#radio1 input').should('have.attr', 'aria-invalid', 'true');
cy.get('#radio2 input').should('have.attr', 'aria-invalid', 'true');
cy.get('.usa-radio').should('have.class', 'usa-radio--error');
cy.get('.usa-error-message').should('contain.text', 'Please select an option');
});
it('should handle large size variant', () => {
cy.mount(`
Large radio button
`);
cy.get('.usa-radio').should('have.class', 'usa-radio--large');
});
it('should handle small size variant', () => {
cy.mount(`
Small radio button
`);
cy.get('.usa-radio').should('have.class', 'usa-radio--small');
});
it('should be keyboard accessible', () => {
cy.mount(`
`);
// Tab to first radio
cy.get('#radio1 input').focus();
cy.focused().should('have.attr', 'name', 'keyboard-group');
cy.focused().should('have.attr', 'value', 'option1');
// Arrow down to next radio
cy.focused().type('{downarrow}');
cy.focused().should('have.attr', 'value', 'option2');
cy.get('#radio2 input').should('be.checked');
// Arrow up to previous radio
cy.focused().type('{uparrow}');
cy.focused().should('have.attr', 'value', 'option1');
cy.get('#radio1 input').should('be.checked');
// Space to select current radio
cy.focused().type(' ');
cy.get('#radio1 input').should('be.checked');
});
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('contact-method'));
});
// Select phone option
cy.get('#contact-phone input').check();
// Submit form
cy.get('button[type="submit"]').click();
cy.then(() => {
expect(submitSpy).to.have.been.calledWith('phone');
});
});
});
it('should handle multiple radio groups independently', () => {
cy.mount(`
`);
// Select from both groups independently
cy.get('#size-medium input').check();
cy.get('#color-red input').check();
// Both should be selected
cy.get('#size-medium input').should('be.checked');
cy.get('#color-red input').should('be.checked');
// Selecting another from same group should deselect previous
cy.get('#size-large input').check();
cy.get('#size-large input').should('be.checked');
cy.get('#size-medium input').should('not.be.checked');
// Other group should remain unchanged
cy.get('#color-red input').should('be.checked');
});
it('should handle tile variant', () => {
cy.mount(`
Tile radio button with more content
`);
cy.get('.usa-radio').should('have.class', 'usa-radio--tile');
});
it('should handle description text', () => {
cy.mount(`
Option with description
`);
cy.get('.usa-radio__description').should(
'contain.text',
'This option includes additional explanatory text'
);
});
it('should handle aria attributes', () => {
cy.mount(`
Additional context for this choice
Radio with ARIA attributes
`);
cy.get('input[type="radio"]').should('have.attr', 'aria-describedby', 'radio-description');
});
it('should handle focus and blur events', () => {
cy.mount(`
Focus test radio
`);
cy.window().then((win) => {
const radio = win.document.getElementById('test-radio') as any;
const focusSpy = cy.stub();
const blurSpy = cy.stub();
radio.addEventListener('focus', focusSpy);
radio.addEventListener('blur', blurSpy);
cy.get('input[type="radio"]').focus();
cy.get('input[type="radio"]').blur();
cy.then(() => {
expect(focusSpy).to.have.been.called;
expect(blurSpy).to.have.been.called;
});
});
});
it('should handle validation on blur', () => {
cy.mount(`
`);
// Focus and blur without selecting (should trigger validation)
cy.get('#radio1 input').focus().blur();
cy.get('input[name="validation-group"]').should('have.attr', 'aria-invalid', 'true');
// Select an option (should clear validation)
cy.get('#radio2 input').check();
cy.get('input[name="validation-group"]').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(`
Custom styled radio
`);
cy.get('usa-radio').should('have.class', 'custom-radio-class');
cy.get('.usa-radio').should('exist');
});
it('should handle programmatic selection', () => {
cy.mount(`
`);
cy.window().then((win) => {
const radio2 = win.document.getElementById('radio2') as any;
// Set checked programmatically
radio2.checked = true;
cy.get('#radio2 input').should('be.checked');
cy.get('#radio1 input').should('not.be.checked');
});
});
it('should handle nested fieldsets properly', () => {
cy.mount(`
`);
// Each group should work independently
cy.get('#time-afternoon input').check();
cy.get('#method-email input').check();
cy.get('#time-afternoon input').should('be.checked');
cy.get('#method-email input').should('be.checked');
cy.get('#time-morning input').should('not.be.checked');
cy.get('#method-phone input').should('not.be.checked');
});
// Responsive Layout Tests
describe('Mobile Responsive Behavior', () => {
beforeEach(() => {
cy.viewport(375, 667); // iPhone SE
});
it('should display radio groups properly stacked on mobile', () => {
cy.mount(`
`);
// Radio buttons should stack vertically on mobile
cy.get('usa-radio').should('have.length', 4);
// Touch targets should be at least 44px high
cy.get('usa-radio').each(($radio) => {
cy.wrap($radio).should(($el) => {
const height = $el.outerHeight();
expect(height).to.be.at.least(44);
});
});
// Labels should be readable and not wrap awkwardly
cy.get('.usa-radio__label').each(($label) => {
cy.wrap($label).should('be.visible');
});
});
it('should handle mobile touch interactions', () => {
cy.mount(`
`);
// Touch interaction should work
cy.get('#mobile-credit .usa-radio__label').trigger('touchstart').trigger('touchend');
cy.get('#mobile-credit input').should('be.checked');
// Large size should be especially touch-friendly
cy.get('.usa-radio').should('have.class', 'usa-radio--large');
});
it('should handle mobile tile variant', () => {
cy.mount(`
`);
// Tile variants should be properly sized for mobile
cy.get('.usa-radio').should('have.class', 'usa-radio--tile');
// Description text should be visible
cy.get('.usa-radio__description').should('be.visible');
cy.get('.usa-radio__description').first().should('contain.text', 'Basic features');
// Tiles should stack vertically on mobile
cy.get('usa-radio').should('have.length', 3);
});
it('should handle mobile error states', () => {
cy.mount(`
`);
cy.get('.usa-error-message').should('be.visible');
cy.get('input[type="radio"]').should('have.attr', 'aria-invalid', 'true');
// Error message should be readable on mobile
cy.get('.usa-error-message').should('have.css', 'font-size');
});
it('should handle mobile form layout with multiple radio groups', () => {
cy.mount(`
`);
// Multiple fieldsets should stack properly on mobile
cy.get('.usa-fieldset').should('have.length', 2);
// Select from each group independently
cy.get('#shipping-express input').check();
cy.get('#gift-yes input').check();
cy.get('#shipping-express input').should('be.checked');
cy.get('#gift-yes input').should('be.checked');
});
});
describe('Tablet Responsive Behavior', () => {
beforeEach(() => {
cy.viewport(768, 1024); // iPad
});
it('should display radio groups in grid layout on tablet', () => {
cy.mount(`
`);
// Check grid layout on tablet
cy.get('.tablet\\:grid-col-6').should('have.length', 2);
cy.get('.tablet\\:grid-col-6').each(($col) => {
cy.wrap($col).should('have.css', 'width').and('not.equal', '768px');
});
});
it('should handle tablet tile variant with grid', () => {
cy.mount(`
`);
// Tiles should be in horizontal layout on tablet
cy.get('.tablet\\:grid-col-4').should('have.length', 3);
cy.get('.usa-radio--tile').should('have.length', 3);
// Standard should be preselected
cy.get('#tablet-standard input').should('be.checked');
// Should handle both touch and hover
cy.get('#tablet-premium .usa-radio__label').trigger('mouseover').click();
cy.get('#tablet-premium input').should('be.checked');
cy.get('#tablet-standard input').should('not.be.checked');
});
it('should handle tablet accessibility with mixed interaction', () => {
cy.mount(`
`);
// Should work with both touch and keyboard
cy.get('#tablet-personal input').focus();
cy.focused().type('{downarrow}');
cy.get('#tablet-business input').should('be.checked');
// Touch interaction should also work
cy.get('#tablet-nonprofit .usa-radio__label').trigger('touchstart').trigger('touchend');
cy.get('#tablet-nonprofit input').should('be.checked');
});
});
describe('Desktop Responsive Behavior', () => {
beforeEach(() => {
cy.viewport(1200, 800); // Desktop
});
it('should display radio groups in multi-column layout', () => {
cy.mount(`
`);
// Check three-column layout on desktop
cy.get('.desktop\\:grid-col-4').should('have.length', 3);
// Test independent group selection
cy.get('#desktop-sms input').check();
cy.get('#privacy-friends input').check();
cy.get('#theme-auto input').check();
cy.get('#desktop-sms input').should('be.checked');
cy.get('#privacy-friends input').should('be.checked');
cy.get('#theme-auto input').should('be.checked');
});
it('should handle desktop tile variant in grid layout', () => {
cy.mount(`
`);
// Four-column layout on desktop
cy.get('.desktop\\:grid-col-3').should('have.length', 4);
cy.get('.usa-radio--tile').should('have.length', 4);
// Should handle hover states
cy.get('#desktop-professional .usa-radio__label')
.trigger('mouseover')
.should('have.css', 'cursor', 'pointer');
// Select and verify
cy.get('#desktop-business input').check();
cy.get('#desktop-business input').should('be.checked');
});
it('should handle desktop keyboard navigation efficiently', () => {
cy.mount(`
`);
// Keyboard navigation should work efficiently
cy.get('#desktop-option1 input').focus();
cy.focused().should('have.attr', 'value', 'option1');
// Arrow navigation
cy.focused().type('{downarrow}');
cy.focused().should('have.attr', 'value', 'option2');
cy.get('#desktop-option2 input').should('be.checked');
cy.focused().type('{downarrow}');
cy.focused().should('have.attr', 'value', 'option3');
cy.get('#desktop-option3 input').should('be.checked');
// Arrow up
cy.focused().type('{uparrow}');
cy.focused().should('have.attr', 'value', 'option2');
cy.get('#desktop-option2 input').should('be.checked');
});
it('should handle desktop hover and focus states', () => {
cy.mount(`
`);
// Hover state
cy.get('#desktop-hover .usa-radio__label')
.trigger('mouseover')
.should('have.css', 'cursor', 'pointer');
// Focus state
cy.get('#desktop-hover input')
.focus()
.should('have.focus')
.should('have.css', 'outline-width')
.and('not.equal', '0px');
});
});
describe('Large Desktop Responsive Behavior', () => {
beforeEach(() => {
cy.viewport(1440, 900); // Large Desktop
});
it('should maintain proper spacing on large screens', () => {
cy.mount(`
`);
// Container should be properly centered
cy.get('.grid-container').should('have.css', 'max-width');
// Radio groups should have adequate spacing
cy.get('.usa-fieldset').should('have.length', 2);
// Tile variants should be properly sized
cy.get('.usa-radio--tile').should('have.length', 2);
});
it('should handle large desktop complex forms', () => {
cy.mount(`
`);
// Four-column layout
cy.get('.desktop\\:grid-col-3').should('have.length', 4);
// Test selections across all groups
cy.get('#large-business input').check();
cy.get('#large-premium input').check();
cy.get('#large-annual input').check();
cy.get('#large-support input').check();
// All should be independently selected
cy.get('#large-business input').should('be.checked');
cy.get('#large-premium input').should('be.checked');
cy.get('#large-annual input').should('be.checked');
cy.get('#large-support input').should('be.checked');
});
});
describe('Responsive Edge Cases', () => {
it('should handle viewport transitions smoothly', () => {
cy.mount(`
`);
// Test mobile to tablet transition
cy.viewport(375, 667);
cy.get('#transition-option1 input').check();
cy.get('#transition-option1 input').should('be.checked');
cy.viewport(768, 1024);
cy.get('#transition-option1 input').should('be.checked');
cy.viewport(1200, 800);
cy.get('#transition-option1 input').should('be.checked');
});
it('should handle long label text at different screen sizes', () => {
const longLabels = [
'This is a very long radio button label that might wrap to multiple lines on smaller screens',
'Another extremely long label that tests how the component handles text wrapping and layout at various viewport sizes',
'A third long label to test multiple radio buttons with extensive text content',
];
cy.mount(`
`);
// Test at different viewports
const viewports = [
[320, 568], // Small mobile
[768, 1024], // Tablet
[1200, 800], // Desktop
];
viewports.forEach(([width, height]) => {
cy.viewport(width, height);
// Labels should be visible and readable
cy.get('.usa-radio__label').each(($label) => {
cy.wrap($label).should('be.visible');
});
// Should not cause horizontal overflow
cy.get('.usa-fieldset').then(($fieldset) => {
expect($fieldset[0].scrollWidth).to.be.at.most($fieldset[0].clientWidth + 5);
});
});
});
it('should handle dynamic radio group updates', () => {
cy.mount(`
`);
const scenarios = [
{ viewport: [375, 667], options: 2 },
{ viewport: [768, 1024], options: 4 },
{ viewport: [1200, 800], options: 6 },
];
scenarios.forEach(({ viewport, options }) => {
cy.viewport(viewport[0], viewport[1]);
// Simulate adding more options based on viewport
cy.window().then((win) => {
const fieldset = win.document.getElementById('dynamic-fieldset');
// Clear existing options except first
const existingRadios = fieldset?.querySelectorAll('usa-radio');
if (existingRadios) {
for (let i = 1; i < existingRadios.length; i++) {
existingRadios[i].remove();
}
}
// Add options based on viewport
for (let i = 2; i <= options; i++) {
const radio = win.document.createElement('usa-radio');
radio.id = `dynamic-option-${i}`;
radio.setAttribute('name', 'dynamic-group');
radio.setAttribute('value', `option${i}`);
radio.textContent = `Option ${i}`;
fieldset?.appendChild(radio);
}
});
cy.get('usa-radio').should('have.length', options);
cy.get('#dynamic-initial input').check();
cy.get('#dynamic-initial input').should('be.checked');
});
});
it('should maintain accessibility at all screen sizes', () => {
cy.mount(`
`);
const viewports = [
[375, 667], // Mobile
[768, 1024], // Tablet
[1200, 800], // Desktop
];
viewports.forEach(([width, height]) => {
cy.viewport(width, height);
cy.injectAxe();
cy.checkAccessibility();
// Check ARIA attributes
cy.get('#accessible-1 input').should('have.attr', 'aria-describedby', 'option1-hint');
cy.get('#accessible-2 input')
.should('have.attr', 'required')
.should('have.attr', 'aria-required', 'true');
// Check keyboard navigation
cy.get('#accessible-1 input').focus().should('have.focus');
});
});
it('should handle radio groups in responsive forms', () => {
cy.mount(`
`);
const viewports = [
[375, 667], // Mobile (stacked)
[768, 1024], // Tablet (stacked)
[1200, 800], // Desktop (side by side)
];
viewports.forEach(([width, height]) => {
cy.viewport(width, height);
// All radio groups should work regardless of layout
cy.get('#form-radio-1 input').check();
cy.get('#form-radio-3 input').check();
cy.get('#form-radio-1 input').should('be.checked');
cy.get('#form-radio-3 input').should('be.checked');
// Switch selections
cy.get('#form-radio-2 input').check();
cy.get('#form-radio-4 input').check();
cy.get('#form-radio-2 input').should('be.checked');
cy.get('#form-radio-4 input').should('be.checked');
cy.get('#form-radio-1 input').should('not.be.checked');
cy.get('#form-radio-3 input').should('not.be.checked');
});
});
});
});