`);
cy.get('usa-modal').should('exist');
cy.get('.usa-modal').should('exist');
cy.get('.usa-modal').should('not.have.class', 'is-visible');
});
it('should open and close modal', () => {
cy.mount(`
`);
// Modal should be open
cy.get('.usa-modal').should('have.class', 'is-visible');
// Press escape to close
cy.get('body').type('{esc}');
cy.get('.usa-modal').should('not.have.class', 'is-visible');
});
it('should handle backdrop click to close modal', () => {
cy.mount(`
Backdrop Test Modal
Click outside to close.
`);
// Modal should be open
cy.get('.usa-modal').should('have.class', 'is-visible');
// Click backdrop to close
cy.get('.usa-modal-wrapper').click(10, 10); // Click outside content area
cy.get('.usa-modal').should('not.have.class', 'is-visible');
});
it('should trap focus within modal', () => {
cy.mount(`
Focus Trap Modal
`);
// Focus should be trapped in modal
cy.get('#modal-button-1').should('be.visible');
// Tab through focusable elements
cy.get('#modal-button-1').focus();
cy.focused().should('have.id', 'modal-button-1');
cy.focused().tab();
cy.focused().should('have.id', 'modal-input');
cy.focused().tab();
cy.focused().should('have.id', 'modal-button-2');
cy.focused().tab();
cy.focused().should('have.attr', 'data-close-modal');
// Tab from last element should cycle back to first
cy.focused().tab();
cy.focused().should('have.id', 'modal-button-1');
});
it('should restore focus to trigger element on close', () => {
cy.mount(`
Focus Restoration Modal
This modal should restore focus to trigger button.
`);
cy.window().then((win) => {
const modal = win.document.getElementById('test-modal') as any;
const triggerButton = win.document.getElementById('trigger-button');
triggerButton?.addEventListener('click', () => {
modal.show();
});
});
// Focus trigger and open modal
cy.get('#trigger-button').focus().click();
cy.get('.usa-modal').should('have.class', 'is-visible');
// Close modal
cy.get('[data-close-modal]').click();
cy.get('.usa-modal').should('not.have.class', 'is-visible');
// Focus should be restored to trigger button
cy.focused().should('have.id', 'trigger-button');
});
it('should handle large modal variant', () => {
cy.mount(`
`);
cy.get('.usa-modal').should('have.class', 'usa-modal--forced-action');
// Should not close on escape when forced-action
cy.get('body').type('{esc}');
cy.get('.usa-modal').should('have.class', 'is-visible');
// Should not close on backdrop click
cy.get('.usa-modal-wrapper').click(10, 10);
cy.get('.usa-modal').should('have.class', 'is-visible');
});
it('should emit modal events', () => {
cy.mount(`
`);
cy.window().then((win) => {
const modal = win.document.getElementById('scroll-lock-modal') as any;
// Body should be scrollable initially
cy.get('body').should('not.have.class', 'usa-js-no-click');
// Open modal
modal.show();
// Body scroll should be locked
cy.get('body').should('have.css', 'overflow', 'hidden');
// Close modal
modal.hide();
// Body scroll should be restored
cy.get('body').should('not.have.css', 'overflow', 'hidden');
});
});
it('should handle multiple modals correctly', () => {
cy.mount(`
First Modal
This is the first modal.
Second Modal
This is the second modal.
`);
cy.window().then((win) => {
const modal1 = win.document.getElementById('modal1') as any;
const modal2 = win.document.getElementById('modal2') as any;
const openModal2Btn = win.document.getElementById('open-modal2');
openModal2Btn?.addEventListener('click', () => {
modal2.show();
});
// Open first modal
modal1.show();
cy.get('#modal1 .usa-modal').should('have.class', 'is-visible');
// Open second modal from first
cy.get('#open-modal2').click();
cy.get('#modal2 .usa-modal').should('have.class', 'is-visible');
// Close second modal
cy.get('#modal2 [data-close-modal]').click();
cy.get('#modal2 .usa-modal').should('not.have.class', 'is-visible');
cy.get('#modal1 .usa-modal').should('have.class', 'is-visible');
});
});
it('should be accessible', () => {
cy.mount(`
`);
// Should work without header or footer slots
cy.get('.usa-modal__content').should('contain.text', 'Minimal modal content');
});
it('should handle focus on first focusable element when opened', () => {
cy.mount(`
Focus Test
Some text content
`);
cy.window().then((win) => {
const modal = win.document.getElementById('focus-modal') as any;
modal.show();
// First focusable element should be focused
cy.focused().should('have.id', 'first-focusable');
});
});
it('should handle keyboard navigation within modal', () => {
cy.mount(`
Keyboard Navigation
`);
// Navigate through all focusable elements
cy.get('#btn1').focus();
cy.focused().tab();
cy.focused().should('have.id', 'input1');
cy.focused().tab();
cy.focused().should('have.id', 'select1');
cy.focused().tab();
cy.focused().should('have.attr', 'data-close-modal');
// Shift+Tab should go backwards
cy.focused().tab({ shift: true });
cy.focused().should('have.id', 'select1');
});
describe('Edge Case Testing', () => {
describe('Boundary Conditions', () => {
it('should handle extremely long modal content', () => {
const longContent = 'A'.repeat(10000);
cy.mount(`
Long Content Modal
${longContent}
`);
cy.get('.usa-modal__content').should('be.visible');
cy.get('.usa-modal__content').should('contain.text', longContent);
cy.get('.usa-modal').should('have.class', 'is-visible');
});
it('should handle modal with no content', () => {
cy.mount(``);
cy.get('.usa-modal').should('be.visible');
cy.get('.usa-modal__content').should('exist');
cy.get('body').type('{esc}');
cy.get('.usa-modal').should('not.have.class', 'is-visible');
});
it('should handle special characters in content', () => {
const specialContent = 'Special chars: <>&"\'%@#$!^*()[]{}|\\`~';
cy.mount(`
${focusableElements}
`);
// Should focus first element
cy.get('#btn-0').should('be.focused');
// Should handle tab traversal
cy.focused().tab();
cy.focused().should('have.id', 'btn-1');
// Should wrap around after last element
for (let i = 0; i < 52; i++) {
cy.focused().tab();
}
cy.focused().should('have.id', 'btn-0');
});
});
describe('Error Recovery', () => {
it('should handle DOM manipulation during modal lifecycle', () => {
cy.mount(`
';
}
});
modal.show();
});
cy.get('.usa-modal').should('have.class', 'is-visible');
// Add content dynamically
cy.get('#add-content').click();
cy.get('#dynamic-content').should('contain.text', 'New paragraph added');
// Modal should still function correctly
cy.get('body').type('{esc}');
cy.get('.usa-modal').should('not.have.class', 'is-visible');
});
it('should handle missing USWDS JavaScript gracefully', () => {
cy.window().then((win) => {
// Temporarily remove USWDS if it exists
const originalUSWDS = (win as any).USWDS;
delete (win as any).USWDS;
cy.mount(`
No USWDS Test
Modal should still work without USWDS JavaScript
`);
cy.window().then((win2) => {
const modal = win2.document.getElementById('no-uswds-modal') as any;
// Should still show/hide without errors
expect(() => modal.show()).not.to.throw;
cy.get('.usa-modal').should('have.class', 'is-visible');
expect(() => modal.hide()).not.to.throw;
cy.get('.usa-modal').should('not.have.class', 'is-visible');
// Restore USWDS if it existed
if (originalUSWDS) {
(win2 as any).USWDS = originalUSWDS;
}
});
});
});
it('should handle rapid show/hide operations', () => {
cy.mount(`
Rapid Operations Test
Testing rapid show/hide operations
`);
cy.window().then((win) => {
const modal = win.document.getElementById('rapid-modal') as any;
// Rapid show/hide operations
for (let i = 0; i < 10; i++) {
modal.show();
modal.hide();
}
// Should end in closed state
cy.get('.usa-modal').should('not.have.class', 'is-visible');
// Should still be functional
modal.show();
cy.get('.usa-modal').should('have.class', 'is-visible');
});
});
it('should handle memory cleanup on removal', () => {
cy.mount(`
Cleanup Test
Testing memory cleanup
`);
cy.window().then((win) => {
const modal = win.document.getElementById('cleanup-modal') as any;
const container = win.document.getElementById('modal-container');
modal.show();
cy.get('.usa-modal').should('have.class', 'is-visible');
// Remove modal from DOM
container?.removeChild(modal);
// Should not cause errors
cy.get('body').should('exist'); // Basic DOM integrity check
});
});
});
describe('Performance Stress Testing', () => {
it('should handle multiple modals being created and destroyed', () => {
cy.mount(``);
cy.window().then((win) => {
const container = win.document.getElementById('modal-stress-container');
// Create and destroy multiple modals
for (let i = 0; i < 20; i++) {
const modal = win.document.createElement('usa-modal');
modal.id = `stress-modal-${i}`;
modal.innerHTML = `
Stress Modal ${i}
Content for modal ${i}
`;
container?.appendChild(modal);
// Show and hide quickly
(modal as any).show();
(modal as any).hide();
// Remove from DOM
container?.removeChild(modal);
}
// DOM should be clean
cy.get('#modal-stress-container').children().should('have.length', 0);
});
});
it('should handle large numbers of event listeners', () => {
cy.mount(`
Event Stress Test
Testing many event listeners
`);
cy.window().then((win) => {
const modal = win.document.getElementById('event-stress-modal') as any;
// Add many event listeners
for (let i = 0; i < 100; i++) {
modal.addEventListener('usa-modal:show', () => {
// Empty listener
});
modal.addEventListener('usa-modal:hide', () => {
// Empty listener
});
}
// Should still function normally
modal.hide();
cy.get('.usa-modal').should('not.have.class', 'is-visible');
modal.show();
cy.get('.usa-modal').should('have.class', 'is-visible');
});
});
it('should handle content updates during animation', () => {
cy.mount(`
`);
// Focus should skip disabled elements
cy.get('#enabled-btn-1').should('be.focused');
cy.focused().tab();
cy.focused().should('have.id', 'enabled-btn-2');
cy.focused().tab();
cy.focused().should('have.attr', 'data-close-modal');
// Should not focus disabled elements during reverse tabbing
cy.focused().tab({ shift: true });
cy.focused().should('have.id', 'enabled-btn-2');
});
});
describe('Browser Compatibility', () => {
it('should handle missing modern JavaScript APIs gracefully', () => {
cy.window().then((win) => {
// Temporarily remove modern APIs
const originalQuerySelectorAll = win.document.querySelectorAll;
const originalGetBoundingClientRect = Element.prototype.getBoundingClientRect;
// Restore after test
cy.mount(`
Compatibility Test
Testing browser compatibility fallbacks.
`);
cy.window().then((win2) => {
const modal = win2.document.getElementById('compat-modal') as any;
// Should still function
expect(() => modal.show()).not.to.throw;
cy.get('.usa-modal').should('have.class', 'is-visible');
// Restore APIs
win2.document.querySelectorAll = originalQuerySelectorAll;
Element.prototype.getBoundingClientRect = originalGetBoundingClientRect;
});
});
});
it('should handle event delegation differences', () => {
cy.mount(`
Event Delegation Test
`);
// Add buttons dynamically
cy.window().then((win) => {
const container = win.document.getElementById('button-container');
for (let i = 2; i <= 5; i++) {
const btn = win.document.createElement('button');
btn.className = 'dynamic-close';
btn.setAttribute('data-close-modal', '');
btn.textContent = `Dynamic Close ${i}`;
container?.appendChild(btn);
}
});
// All close buttons should work
cy.get('.dynamic-close').first().click();
cy.get('.usa-modal').should('not.have.class', 'is-visible');
});
it('should handle CSS custom properties fallbacks', () => {
cy.mount(`
CSS Fallback Test
Testing CSS custom properties fallbacks.
`);
// Modal should still be visible even without CSS custom property support
cy.get('.usa-modal').should('be.visible');
cy.get('.usa-modal__content').should('be.visible');
});
});
describe('Integration Edge Cases', () => {
it('should handle modal within modal scenarios', () => {
cy.mount(`
`;
}
}, 100);
});
cy.get('.usa-modal').should('have.class', 'is-visible');
cy.get('#async-content', { timeout: 1000 }).should('contain.text', 'Async content loaded');
cy.get('#async-action').should('be.visible');
cy.get('#async-action').click(); // Should not cause errors
});
});
});
describe('Regression Tests for Modal Issues', () => {
context('HTML Description Content Rendering (REGRESSION)', () => {
it('should render complex HTML content with tables and layouts', () => {
const htmlContent = `
This modal demonstrates the large variant with complex content.
Sample Data Comparison (demonstrates why large modal is needed)
Component
Dev Environment
Storybook Environment
USWDS Transform
Event Handlers
Status
Combo Box
✅ Working
✅ Working
✅ Applied
✅ Attached
Fully Functional
Modal
✅ Working
✅ Working
✅ Applied
✅ Attached
Fully Functional
Environment Details
Development Server
All components load correctly with USWDS transformation.
Implementation Notes
USWDS Integration
Components use the official USWDS loader utility.
Large Modal Usage: This large modal variant provides the extra width needed.
`;
cy.mount(`
`);
// Verify the modal renders and content is accessible
cy.get('.usa-modal').should('have.class', 'usa-modal--lg');
cy.get('[data-testid="regression-table"]').should('exist');
cy.get('[data-testid="regression-table"] thead tr').should('have.length', 1);
cy.get('[data-testid="regression-table"] tbody tr').should('have.length', 2);
cy.get('[data-testid="regression-grid"]').should('exist');
cy.get('[data-testid="regression-grid"] .usa-grid-col-6').should('have.length', 2);
cy.get('[data-testid="regression-alert"]').should('exist');
cy.get('[data-testid="regression-alert"]').should('have.class', 'usa-alert--info');
});
it('should preserve content when USWDS moves modal to document body', () => {
cy.mount(`
`);
// Wait for modal to be processed by USWDS
cy.wait(300);
// Content should still be accessible regardless of where USWDS moves it
cy.get('[data-testid="preserved-content"]').should('exist');
cy.get('[data-testid="preserved-content"]').should('contain', 'This content should be preserved');
});
});
context('Modal Reopening Functionality (REGRESSION)', () => {
it('should open and close multiple times without breaking', () => {
cy.mount(`
`);
cy.window().then((win) => {
const modal = win.document.getElementById('regression-reopening-modal') as any;
// Test 5 cycles of opening and closing
for (let cycle = 1; cycle <= 5; cycle++) {
// Open modal
modal.open = true;
cy.get('body').should('have.class', 'usa-modal--open');
// Close modal
modal.open = false;
cy.get('body').should('not.have.class', 'usa-modal--open');
}
// Verify modal still works after multiple cycles
modal.open = true;
cy.get('body').should('have.class', 'usa-modal--open');
// Close with close button
cy.get('.usa-modal__close').click();
cy.get('body').should('not.have.class', 'usa-modal--open');
});
});
it('should handle rapid open/close cycles without breaking', () => {
cy.mount(`
`);
cy.window().then((win) => {
const modal = win.document.getElementById('rapid-cycles-modal') as any;
// Rapid cycles
for (let i = 0; i < 10; i++) {
modal.open = !modal.open;
}
// Should end in a consistent state and still be functional
cy.get('usa-modal').should('exist');
modal.open = true;
cy.get('body').should('have.class', 'usa-modal--open');
});
});
it('should handle close button clicks multiple times', () => {
cy.mount(`
`);
cy.window().then((win) => {
const modal = win.document.getElementById('close-button-regression') as any;
// Test 3 cycles with close button
for (let cycle = 1; cycle <= 3; cycle++) {
// Open modal
modal.open = true;
cy.get('body').should('have.class', 'usa-modal--open');
// Click close button
cy.get('.usa-modal__close').click();
cy.get('body').should('not.have.class', 'usa-modal--open');
// Verify modal property is updated
expect(modal.open).to.be.false;
}
});
});
});
context('Large Modal Width Utilization (REGRESSION)', () => {
it('should properly utilize larger width for wide content', () => {
const wideTableContent = `
Column 1
Column 2
Column 3
Column 4
Column 5
Column 6
Column 7
Column 8
Data 1
Data 2
Data 3
Data 4
Data 5
Data 6
Data 7
Data 8
`;
// Test normal modal first
cy.mount(`
`);
cy.get('.usa-modal').should('not.have.class', 'usa-modal--lg');
// Store normal modal width for comparison
cy.get('.usa-modal').then($normalModal => {
const normalWidth = $normalModal[0].getBoundingClientRect().width;
// Test large modal
cy.mount(`
`);
cy.get('.usa-modal').should('have.class', 'usa-modal--lg');
cy.get('[data-testid="wide-regression-table"]').should('exist');
cy.get('[data-testid="wide-regression-table"] th').should('have.length', 8);
// Large modal should be wider than normal modal
cy.get('.usa-modal').then($largeModal => {
const largeWidth = $largeModal[0].getBoundingClientRect().width;
expect(largeWidth).to.be.greaterThan(normalWidth);
});
});
});
it('should toggle between large and normal modal sizes correctly', () => {
cy.mount(`
`);
cy.window().then((win) => {
const modal = win.document.getElementById('size-toggle-modal') as any;
// Start as normal modal
modal.large = false;
modal.open = true;
cy.get('.usa-modal').should('not.have.class', 'usa-modal--lg');
// Switch to large
modal.large = true;
cy.get('.usa-modal').should('have.class', 'usa-modal--lg');
// Switch back to normal
modal.large = false;
cy.get('.usa-modal').should('not.have.class', 'usa-modal--lg');
// Modal should still be functional
modal.open = false;
cy.get('body').should('not.have.class', 'usa-modal--open');
});
});
});
context('Complete Integration Test (REGRESSION)', () => {
it('should handle all regression scenarios in sequence', () => {
const complexContent = `
This is a comprehensive regression test.
Test
Status
Content Rendering
✅
Reopening
✅
Large Modal
✅
`;
cy.mount(`
`);
cy.window().then((win) => {
const modal = win.document.getElementById('integration-regression-modal') as any;
// Test 1: Content rendering
modal.open = true;
cy.get('[data-testid="integration-table"]').should('exist');
cy.get('.usa-modal').should('have.class', 'usa-modal--lg');
// Test 2: Closing and reopening multiple times
for (let i = 0; i < 3; i++) {
modal.open = false;
cy.get('body').should('not.have.class', 'usa-modal--open');
modal.open = true;
cy.get('body').should('have.class', 'usa-modal--open');
cy.get('[data-testid="integration-table"]').should('exist');
}
// Test 3: Size toggling
modal.large = false;
cy.get('.usa-modal').should('not.have.class', 'usa-modal--lg');
modal.large = true;
cy.get('.usa-modal').should('have.class', 'usa-modal--lg');
// Test 4: Close with button
cy.get('.usa-modal__close').click();
cy.get('body').should('not.have.class', 'usa-modal--open');
// Test 5: Final reopen to confirm everything still works
modal.open = true;
cy.get('body').should('have.class', 'usa-modal--open');
cy.get('[data-testid="integration-table"]').should('exist');
cy.get('.usa-modal').should('have.class', 'usa-modal--lg');
});
});
});
});
});