/**
* @fileoverview Accordion Behavioral Tests
*
* This file contains comprehensive Cypress tests for the USA Accordion component,
* focusing on actual visual behavior and user interactions rather than just API testing.
*
* Tests cover:
* - Accordion expansion/collapse behavior
* - Visual rendering and display properties
* - Multi-selectable mode functionality
* - Click and keyboard interaction handling
* - Content visibility and accessibility
* - Focus management and aria attributes
* - Performance and responsiveness
*
* These tests complement unit tests by verifying actual DOM behavior and visual rendering
* that users experience, catching issues that unit tests might miss.
*/
import { html } from 'lit';
// Sample accordion data for testing
const sampleItems = [
{
id: 'test-item-1',
title: 'First Accordion Item',
content: '
Content for the first accordion item with some detailed information.
',
expanded: false,
},
{
id: 'test-item-2',
title: 'Second Accordion Item',
content: 'Content for the second accordion item with different information.
',
expanded: true,
},
{
id: 'test-item-3',
title: 'Third Accordion Item',
content: 'Content for the third accordion item with more details and examples.
',
expanded: false,
},
];
describe('USA Accordion - Behavioral Testing', () => {
beforeEach(() => {
// Import component before each test
cy.window().then((win) => {
if (!win.customElements.get('usa-accordion')) {
return import('./usa-accordion.ts');
}
});
});
describe('Basic Rendering and Structure', () => {
it('should render with proper USWDS structure and classes', () => {
cy.mount(html` `);
// Verify main accordion container
cy.get('usa-accordion .usa-accordion').should('exist');
// Verify each accordion item has proper structure
cy.get('.usa-accordion__heading').should('have.length', 3);
cy.get('.usa-accordion__button').should('have.length', 3);
cy.get('.usa-accordion__content').should('have.length', 3);
// Verify proper ARIA attributes
cy.get('.usa-accordion__button').each(($btn, index) => {
cy.wrap($btn).should('have.attr', 'aria-expanded');
cy.wrap($btn).should('have.attr', 'aria-controls');
cy.wrap($btn).should('have.attr', 'type', 'button');
});
});
it('should have proper heading hierarchy structure', () => {
cy.mount(html` `);
// Verify each accordion section has h4 heading
cy.get('.usa-accordion__heading').should('have.length', 3);
cy.get('h4.usa-accordion__heading').should('have.length', 3);
// Verify buttons are properly nested in headings
cy.get('h4.usa-accordion__heading .usa-accordion__button').should('have.length', 3);
});
it('should render content with proper prose styling', () => {
cy.mount(html` `);
// Verify content containers have proper classes
cy.get('.usa-accordion__content').each(($content) => {
cy.wrap($content).should('have.class', 'usa-accordion__content');
cy.wrap($content).should('have.class', 'usa-prose');
});
});
});
describe('Content Visibility and Toggle Behavior', () => {
it('should show expanded content and hide collapsed content properly', () => {
cy.mount(html` `);
// Check initial state - second item should be expanded
cy.get('#test-item-1-content').should('have.attr', 'hidden');
cy.get('#test-item-2-content').should('not.have.attr', 'hidden');
cy.get('#test-item-3-content').should('have.attr', 'hidden');
// Verify corresponding aria-expanded states
cy.get('#test-item-1-button').should('have.attr', 'aria-expanded', 'false');
cy.get('#test-item-2-button').should('have.attr', 'aria-expanded', 'true');
cy.get('#test-item-3-button').should('have.attr', 'aria-expanded', 'false');
});
it('should toggle content visibility when button is clicked', () => {
cy.mount(html` `);
// Click first button to expand
cy.get('#test-item-1-button').click();
cy.get('#test-item-1-content').should('not.have.attr', 'hidden');
cy.get('#test-item-1-button').should('have.attr', 'aria-expanded', 'true');
// Click again to collapse
cy.get('#test-item-1-button').click();
cy.get('#test-item-1-content').should('have.attr', 'hidden');
cy.get('#test-item-1-button').should('have.attr', 'aria-expanded', 'false');
});
it('should close other items when one is opened in single-select mode', () => {
cy.mount(html`
`);
// Initially, second item is expanded
cy.get('#test-item-2-content').should('not.have.attr', 'hidden');
// Click first button - should expand first and close second
cy.get('#test-item-1-button').click();
cy.get('#test-item-1-content').should('not.have.attr', 'hidden');
cy.get('#test-item-2-content').should('have.attr', 'hidden');
// Verify aria states
cy.get('#test-item-1-button').should('have.attr', 'aria-expanded', 'true');
cy.get('#test-item-2-button').should('have.attr', 'aria-expanded', 'false');
});
it('should allow multiple items open in multiselectable mode', () => {
cy.mount(html`
`);
// Click first button to expand
cy.get('#test-item-1-button').click();
// Both first and second items should be open
cy.get('#test-item-1-content').should('not.have.attr', 'hidden');
cy.get('#test-item-2-content').should('not.have.attr', 'hidden');
// Click third button to expand
cy.get('#test-item-3-button').click();
// All three should now be open
cy.get('#test-item-1-content').should('not.have.attr', 'hidden');
cy.get('#test-item-2-content').should('not.have.attr', 'hidden');
cy.get('#test-item-3-content').should('not.have.attr', 'hidden');
});
});
describe('Click Event Handling and Interaction', () => {
it('should handle rapid successive clicks properly', () => {
cy.mount(html` `);
const button = cy.get('#test-item-1-button');
const content = cy.get('#test-item-1-content');
// Rapid clicks
button.click().click().click();
// Should end up collapsed (odd number of clicks)
content.should('have.attr', 'hidden');
button.should('have.attr', 'aria-expanded', 'false');
// One more click should expand
button.click();
content.should('not.have.attr', 'hidden');
button.should('have.attr', 'aria-expanded', 'true');
});
it('should emit accordion-toggle events with proper details', () => {
let eventDetails: any = null;
cy.mount(html`
(eventDetails = e.detail)}"
>
`);
cy.get('#test-item-1-button')
.click()
.then(() => {
expect(eventDetails).to.not.be.null;
expect(eventDetails.expanded).to.be.true;
expect(eventDetails.item.id).to.equal('test-item-1');
expect(eventDetails.index).to.equal(0);
expect(eventDetails.allItems).to.have.length(3);
});
});
it('should handle click events on content area without toggling', () => {
cy.mount(html` `);
// Expand first item
cy.get('#test-item-1-button').click();
cy.get('#test-item-1-content').should('not.have.attr', 'hidden');
// Click inside content area
cy.get('#test-item-1-content p').click();
// Should remain expanded
cy.get('#test-item-1-content').should('not.have.attr', 'hidden');
cy.get('#test-item-1-button').should('have.attr', 'aria-expanded', 'true');
});
});
describe('Keyboard Navigation and Accessibility', () => {
it('should toggle when Enter key is pressed on button', () => {
cy.mount(html` `);
cy.get('#test-item-1-button').focus().type('{enter}');
cy.get('#test-item-1-content').should('not.have.attr', 'hidden');
cy.get('#test-item-1-button').should('have.attr', 'aria-expanded', 'true');
// Toggle back
cy.get('#test-item-1-button').type('{enter}');
cy.get('#test-item-1-content').should('have.attr', 'hidden');
cy.get('#test-item-1-button').should('have.attr', 'aria-expanded', 'false');
});
it('should toggle when Space key is pressed on button', () => {
cy.mount(html` `);
cy.get('#test-item-1-button').focus().type(' ');
cy.get('#test-item-1-content').should('not.have.attr', 'hidden');
cy.get('#test-item-1-button').should('have.attr', 'aria-expanded', 'true');
// Toggle back
cy.get('#test-item-1-button').type(' ');
cy.get('#test-item-1-content').should('have.attr', 'hidden');
cy.get('#test-item-1-button').should('have.attr', 'aria-expanded', 'false');
});
it('should support tab navigation between accordion buttons', () => {
cy.mount(html` `);
// Focus first button
cy.get('#test-item-1-button').focus();
cy.focused().should('have.id', 'test-item-1-button');
// Tab to next button
cy.focused().tab();
cy.focused().should('have.id', 'test-item-2-button');
// Tab to third button
cy.focused().tab();
cy.focused().should('have.id', 'test-item-3-button');
});
it('should have proper ARIA attributes for screen readers', () => {
cy.mount(html` `);
cy.get('.usa-accordion__button').each(($btn, index) => {
const expectedControlsId = `test-item-${index + 1}-content`;
cy.wrap($btn)
.should('have.attr', 'aria-controls', expectedControlsId)
.should('have.attr', 'aria-expanded')
.should('have.attr', 'type', 'button');
});
// Verify content regions have proper IDs
cy.get('.usa-accordion__content').each(($content, index) => {
const expectedId = `test-item-${index + 1}-content`;
cy.wrap($content).should('have.attr', 'id', expectedId);
});
});
});
describe('Visual States and CSS Classes', () => {
it('should apply bordered class when bordered property is true', () => {
cy.mount(html` `);
cy.get('.usa-accordion').should('have.class', 'usa-accordion--bordered');
});
it('should apply multiselectable class when multiselectable is true', () => {
cy.mount(html`
`);
cy.get('.usa-accordion').should('have.class', 'usa-accordion--multiselectable');
});
it('should have proper data attributes for USWDS integration', () => {
cy.mount(html`
`);
cy.get('.usa-accordion').should('have.attr', 'data-allow-multiple');
});
it('should maintain visual consistency during rapid interactions', () => {
cy.mount(html` `);
// Perform rapid toggles and verify visual state remains consistent
for (let i = 0; i < 5; i++) {
cy.get('#test-item-1-button').click();
cy.wait(50); // Small delay to check intermediate states
}
// Final state should be collapsed (odd number of clicks)
cy.get('#test-item-1-content').should('have.attr', 'hidden');
cy.get('#test-item-1-button').should('have.attr', 'aria-expanded', 'false');
});
});
describe('Content Rendering and HTML Support', () => {
it('should render HTML content properly in accordion items', () => {
const htmlContent = [
{
id: 'html-test',
title: 'HTML Content Test',
content:
'Paragraph
Bold text',
expanded: true,
},
];
cy.mount(html` `);
cy.get('#html-test-content').within(() => {
cy.get('p').should('contain', 'Paragraph');
cy.get('ul li').should('have.length', 2);
cy.get('strong').should('contain', 'Bold text');
});
});
it('should handle empty content gracefully', () => {
const emptyContent = [
{
id: 'empty-test',
title: 'Empty Content Test',
content: '',
expanded: true,
},
];
cy.mount(html` `);
cy.get('#empty-test-content').should('exist');
cy.get('#empty-test-button').click();
cy.get('#empty-test-content').should('have.attr', 'hidden');
});
});
describe('Dynamic Content Updates', () => {
it('should handle dynamic item additions properly', () => {
const initialItems = [sampleItems[0]];
cy.mount(html`
`);
cy.get('.usa-accordion__button').should('have.length', 1);
// Simulate adding items dynamically
cy.window().then((win) => {
const accordion = win.document.getElementById('dynamic-accordion') as any;
if (accordion) {
accordion.items = sampleItems; // Update with all items
}
});
cy.get('.usa-accordion__button').should('have.length', 3);
});
it('should maintain state when items are reordered', () => {
cy.mount(html`
`);
// Expand first item
cy.get('#test-item-1-button').click();
cy.get('#test-item-1-content').should('not.have.attr', 'hidden');
// Simulate reordering items
const reorderedItems = [sampleItems[2], sampleItems[0], sampleItems[1]];
cy.window().then((win) => {
const accordion = win.document.getElementById('reorder-accordion') as any;
if (accordion) {
accordion.items = reorderedItems;
}
});
// Verify structure is updated but expanded state is preserved based on item ID
cy.get('.usa-accordion__button').should('have.length', 3);
});
});
describe('Performance and Responsiveness', () => {
it('should handle large numbers of accordion items efficiently', () => {
const manyItems = Array.from({ length: 20 }, (_, i) => ({
id: `performance-item-${i}`,
title: `Performance Test Item ${i + 1}`,
content: `Content for performance test item ${i + 1}
`,
expanded: false,
}));
cy.mount(html` `);
cy.get('.usa-accordion__button').should('have.length', 20);
// Test clicking multiple items rapidly
cy.get('#performance-item-0-button').click();
cy.get('#performance-item-5-button').click();
cy.get('#performance-item-10-button').click();
// Only the last clicked should be open (single select mode)
cy.get('#performance-item-0-content').should('have.attr', 'hidden');
cy.get('#performance-item-5-content').should('have.attr', 'hidden');
cy.get('#performance-item-10-content').should('not.have.attr', 'hidden');
});
it('should respond quickly to user interactions', () => {
cy.mount(html` `);
const startTime = Date.now();
cy.get('#test-item-1-button')
.click()
.then(() => {
const endTime = Date.now();
const responseTime = endTime - startTime;
// Response should be under 100ms for good UX
expect(responseTime).to.be.lessThan(100);
});
});
});
describe('Error Handling and Edge Cases', () => {
it('should handle accordion with no items gracefully', () => {
cy.mount(html` `);
cy.get('usa-accordion').should('exist');
cy.get('.usa-accordion__button').should('have.length', 0);
});
it('should handle items with missing or invalid IDs', () => {
const itemsWithBadIds = [
{ id: '', title: 'No ID', content: 'Content
', expanded: false },
{ id: 'valid-id', title: 'Valid ID', content: 'Content
', expanded: false },
];
cy.mount(html` `);
// Should still render and be functional
cy.get('.usa-accordion__button').should('have.length', 2);
// Should auto-generate IDs for items without valid IDs
cy.get('.usa-accordion__button').first().should('have.attr', 'id');
});
it('should maintain accessibility even with dynamic content changes', () => {
cy.mount(html`
`);
// Add more items dynamically
cy.window().then((win) => {
const accordion = win.document.getElementById('accessibility-test') as any;
if (accordion) {
accordion.items = sampleItems;
}
});
// Verify all buttons still have proper ARIA attributes
cy.get('.usa-accordion__button').each(($btn) => {
cy.wrap($btn)
.should('have.attr', 'aria-controls')
.should('have.attr', 'aria-expanded')
.should('have.attr', 'type', 'button');
});
});
});
describe('Integration with USWDS JavaScript', () => {
it('should work with or without USWDS JavaScript loaded', () => {
cy.mount(html` `);
// Basic functionality should work regardless of USWDS JavaScript
cy.get('#test-item-1-button').click();
cy.get('#test-item-1-content').should('not.have.attr', 'hidden');
cy.get('#test-item-1-button').should('have.attr', 'aria-expanded', 'true');
});
it('should maintain consistent behavior across different environments', () => {
cy.mount(html` `);
// Test consistent toggle behavior
const testToggleConsistency = () => {
cy.get('#test-item-1-button').click();
cy.get('#test-item-1-content').should('not.have.attr', 'hidden');
cy.get('#test-item-1-button').click();
cy.get('#test-item-1-content').should('have.attr', 'hidden');
};
// Run test multiple times to ensure consistency
testToggleConsistency();
testToggleConsistency();
testToggleConsistency();
});
});
});