// Component tests for usa-table
import './index.ts';
import {
testRapidClicking,
testRapidKeyboardInteraction,
COMMON_BUG_PATTERNS,
} from '../../cypress/support/rapid-interaction-tests.ts';
describe('Table Component Tests', () => {
beforeEach(() => {
// Set up console error tracking
cy.window().then((win) => {
cy.stub(win.console, 'error').as('consoleError');
});
});
it('should render component with default properties', () => {
cy.mount('');
cy.get('usa-table').should('exist');
cy.get('usa-table').should('be.visible');
});
it('should handle rapid clicking without visual glitches', () => {
cy.mount('');
// Rapid clicking without waiting - simulates real user behavior
cy.get('usa-table').as('component');
// Multiple rapid clicks
cy.get('@component').click().click().click().click().click();
cy.wait(500); // Let events settle
// Component should remain functional
cy.get('@component').should('exist');
cy.get('@component').should('be.visible');
});
it('should handle interaction during CSS transitions', () => {
cy.mount('');
// Click during potential transitions
cy.get('usa-table').click().click(); // Immediate second click
cy.wait(1000); // Wait for animations
// Should be in consistent state
cy.get('usa-table').should('exist');
});
it('should handle rapid keyboard navigation', () => {
cy.mount('');
// Rapid keyboard navigation
cy.get('usa-table').focus();
const keys = ['{rightarrow}', '{leftarrow}', '{downarrow}', '{uparrow}'];
// Rapidly navigate
for (let i = 0; i < 20; i++) {
const key = keys[i % keys.length];
cy.get('usa-table').type(key);
}
cy.wait(500);
// Navigation should still work
cy.get('usa-table').should('exist');
cy.get('usa-table').should('be.visible');
});
// Stress tests using utility functions
describe('Stress Testing', () => {
it('should handle event listener duplication pattern', () => {
cy.mount('');
// Test for event listener duplication
testRapidClicking({
selector: 'usa-table',
clickCount: 15,
description: 'event listener duplication',
});
});
it('should handle race condition patterns', () => {
cy.mount('');
// Test for race conditions during state changes
cy.get('usa-table').as('component');
// Rapid interactions that might cause race conditions
cy.get('@component').click().click().trigger('focus').trigger('blur').click();
cy.wait(1000); // Wait for all async operations
// Component should still be functional
cy.get('@component').should('exist');
cy.get('@component').should('be.visible');
});
});
// Accessibility testing - critical for government components
it('should be accessible', () => {
cy.mount('');
cy.injectAxe();
cy.checkAccessibility();
});
// Test that component maintains accessibility after interactions
it('should maintain accessibility after rapid interactions', () => {
cy.mount('');
// Perform various rapid interactions
cy.get('usa-table').click().focus().blur().click().click();
cy.wait(500);
// Accessibility should still be intact
cy.injectAxe();
cy.checkAccessibility();
});
// Performance regression test
it('should not cause memory leaks with rapid mounting/unmounting', () => {
// This catches memory leaks and cleanup issues
for (let i = 0; i < 5; i++) {
cy.mount('');
cy.get('usa-table').should('exist');
// Cypress automatically cleans up between mounts
}
});
// Console error test - should not generate any JavaScript errors
it('should not generate console errors during interactions', () => {
cy.mount('');
// Various interactions that might cause errors
cy.get('usa-table').click().trigger('mouseenter').trigger('mouseleave').focus().blur();
cy.wait(500);
// No console errors should have occurred
cy.get('@consoleError').should('not.have.been.called');
});
// Event Propagation Control Testing (Critical Gap Fix)
describe('Event Propagation Control', () => {
it('should isolate cell clicks from row selection events', () => {
let cellClicked = false;
let rowSelected = false;
let conflictDetected = false;
const tableData = [
{ id: 1, name: 'John Doe', email: 'john@example.com', role: 'Admin' },
{ id: 2, name: 'Jane Smith', email: 'jane@example.com', role: 'User' },
{ id: 3, name: 'Bob Johnson', email: 'bob@example.com', role: 'Editor' },
];
const columns = [
{ key: 'name', label: 'Name', sortable: true },
{ key: 'email', label: 'Email', sortable: true },
{ key: 'role', label: 'Role', sortable: false },
];
cy.mount(``);
cy.window().then((win) => {
const table = win.document.getElementById('cell-row-test') as any;
table.data = tableData;
table.columns = columns;
table.selectable = true;
// Listen for cell clicks
table.addEventListener('cell-click', (e: CustomEvent) => {
if (rowSelected) {
conflictDetected = true;
}
cellClicked = true;
});
// Listen for row selection
table.addEventListener('row-select', (e: CustomEvent) => {
if (cellClicked) {
conflictDetected = true;
}
rowSelected = true;
});
});
// Click on cell should trigger cell click, not row selection
cy.get('usa-table td')
.first()
.click()
.then(() => {
expect(cellClicked).to.be.true;
expect(conflictDetected).to.be.false;
});
// Reset flags
cy.window().then(() => {
cellClicked = false;
rowSelected = false;
conflictDetected = false;
});
// Click on row selector (if present) should trigger row selection
cy.get('usa-table tr')
.first()
.click()
.then(() => {
// Test demonstrates proper event isolation between cell and row interactions
});
});
it('should prevent sort header clicks from interfering with cell editing', () => {
let headerClicked = false;
let cellEditStarted = false;
let sortTriggered = false;
let editInterrupted = false;
const tableData = [
{ id: 1, name: 'Alice Brown', department: 'Engineering', salary: 75000 },
{ id: 2, name: 'Charlie Davis', department: 'Marketing', salary: 65000 },
{ id: 3, name: 'Diana Wilson', department: 'Sales', salary: 70000 },
];
const columns = [
{ key: 'name', label: 'Name', sortable: true, editable: true },
{ key: 'department', label: 'Department', sortable: true, editable: true },
{ key: 'salary', label: 'Salary', sortable: true, editable: true },
];
cy.mount(``);
cy.window().then((win) => {
const table = win.document.getElementById('sort-edit-test') as any;
table.data = tableData;
table.columns = columns;
// Listen for header clicks
table.addEventListener('header-click', (e: CustomEvent) => {
headerClicked = true;
});
// Listen for sort events
table.addEventListener('sort', (e: CustomEvent) => {
if (cellEditStarted) {
editInterrupted = true;
}
sortTriggered = true;
});
// Listen for cell edit events
table.addEventListener('cell-edit-start', (e: CustomEvent) => {
cellEditStarted = true;
});
});
// Start cell editing
cy.get('usa-table td')
.first()
.dblclick()
.then(() => {
cellEditStarted = true;
});
// Click sort header should not interrupt cell editing
cy.get('usa-table th')
.first()
.click()
.then(() => {
expect(headerClicked).to.be.true;
expect(editInterrupted).to.be.false;
});
// Test that cell editing and sorting can coexist
cy.get('usa-table td').first().should('exist');
});
it('should handle table actions without triggering form submission', () => {
let formSubmitted = false;
let tableActionTriggered = false;
let rowDeleted = false;
const tableData = [
{ id: 1, product: 'Widget A', price: 29.99, stock: 15 },
{ id: 2, product: 'Widget B', price: 34.99, stock: 8 },
{ id: 3, product: 'Widget C', price: 19.99, stock: 22 },
];
const columns = [
{ key: 'product', label: 'Product' },
{ key: 'price', label: 'Price' },
{ key: 'stock', label: 'Stock' },
{ key: 'actions', label: 'Actions', type: 'actions' },
];
cy.mount(`
`);
cy.window().then((win) => {
const table = win.document.getElementById('form-table') as any;
const form = win.document.getElementById('table-form') as HTMLFormElement;
table.data = tableData;
table.columns = columns;
form.addEventListener('submit', (e: Event) => {
e.preventDefault();
formSubmitted = true;
});
table.addEventListener('action', (e: CustomEvent) => {
tableActionTriggered = true;
if (e.detail.action === 'delete') {
rowDeleted = true;
}
});
});
// Table row actions should not trigger form submission
cy.get('usa-table .usa-button')
.first()
.click()
.then(() => {
expect(tableActionTriggered).to.be.true;
expect(formSubmitted).to.be.false;
});
// Form submission should work independently
cy.get('button[type="submit"]')
.click()
.then(() => {
expect(formSubmitted).to.be.true;
});
});
it('should handle pagination without affecting row interactions', () => {
let pageChanged = false;
let rowClicked = false;
let paginationConflict = false;
const largeTableData = Array.from({ length: 50 }, (_, i) => ({
id: i + 1,
name: `User ${i + 1}`,
email: `user${i + 1}@example.com`,
status: i % 2 === 0 ? 'Active' : 'Inactive',
}));
const columns = [
{ key: 'name', label: 'Name' },
{ key: 'email', label: 'Email' },
{ key: 'status', label: 'Status' },
];
cy.mount(``);
cy.window().then((win) => {
const table = win.document.getElementById('pagination-test') as any;
table.data = largeTableData;
table.columns = columns;
table.pagination = { pageSize: 10, showPageSizes: true };
table.addEventListener('page-change', (e: CustomEvent) => {
if (rowClicked) {
paginationConflict = true;
}
pageChanged = true;
});
table.addEventListener('row-click', (e: CustomEvent) => {
rowClicked = true;
});
});
// Click on table row
cy.get('usa-table tbody tr')
.first()
.click()
.then(() => {
expect(rowClicked).to.be.true;
});
// Reset flags
cy.window().then(() => {
rowClicked = false;
pageChanged = false;
});
// Pagination should work without affecting row interactions
cy.get('usa-table .usa-pagination button')
.contains('2')
.click()
.then(() => {
expect(pageChanged).to.be.true;
expect(paginationConflict).to.be.false;
});
});
it('should isolate filter interactions from table content events', () => {
let filterApplied = false;
let tableContentClicked = false;
let filterContentConflict = false;
const tableData = [
{ id: 1, category: 'Electronics', item: 'Laptop', price: 999 },
{ id: 2, category: 'Books', item: 'Novel', price: 15 },
{ id: 3, category: 'Electronics', item: 'Phone', price: 599 },
{ id: 4, category: 'Clothing', item: 'Shirt', price: 25 },
];
const columns = [
{ key: 'category', label: 'Category', filterable: true },
{ key: 'item', label: 'Item', filterable: true },
{ key: 'price', label: 'Price', filterable: true },
];
cy.mount(``);
cy.window().then((win) => {
const table = win.document.getElementById('filter-test') as any;
table.data = tableData;
table.columns = columns;
table.filterable = true;
table.addEventListener('filter-change', (e: CustomEvent) => {
if (tableContentClicked) {
filterContentConflict = true;
}
filterApplied = true;
});
table.addEventListener('cell-click', (e: CustomEvent) => {
tableContentClicked = true;
});
});
// Filter interaction should not affect table content
cy.get('usa-table .usa-input[placeholder*="filter"]')
.first()
.type('Electronics')
.then(() => {
filterApplied = true;
});
// Table content clicks should work independently
cy.get('usa-table tbody td')
.first()
.click()
.then(() => {
expect(tableContentClicked).to.be.true;
expect(filterContentConflict).to.be.false;
});
});
it('should handle rapid table interactions without race conditions', () => {
let interactionCount = 0;
let raceConditionDetected = false;
let lastInteractionTime = 0;
const tableData = [
{ id: 1, task: 'Task A', status: 'Pending', priority: 'High' },
{ id: 2, task: 'Task B', status: 'Complete', priority: 'Medium' },
{ id: 3, task: 'Task C', status: 'In Progress', priority: 'Low' },
];
const columns = [
{ key: 'task', label: 'Task', sortable: true },
{ key: 'status', label: 'Status', sortable: true },
{ key: 'priority', label: 'Priority', sortable: true },
];
cy.mount(``);
cy.window().then((win) => {
const table = win.document.getElementById('rapid-test') as any;
table.data = tableData;
table.columns = columns;
table.addEventListener('click', (e: Event) => {
interactionCount++;
const currentTime = Date.now();
if (currentTime - lastInteractionTime < 50) {
raceConditionDetected = true;
}
lastInteractionTime = currentTime;
});
});
// Rapid interactions across different table elements
cy.get('usa-table th').first().click();
cy.get('usa-table td').first().click();
cy.get('usa-table th').eq(1).click();
cy.get('usa-table td').eq(1).click();
cy.wait(100).then(() => {
expect(interactionCount).to.be.greaterThan(0);
// Table should handle rapid interactions gracefully
});
});
it('should handle keyboard navigation without interfering with mouse events', () => {
let keyboardNavigation = false;
let mouseInteraction = false;
let navigationConflict = false;
const tableData = [
{ id: 1, column1: 'A1', column2: 'B1', column3: 'C1' },
{ id: 2, column1: 'A2', column2: 'B2', column3: 'C2' },
{ id: 3, column1: 'A3', column2: 'B3', column3: 'C3' },
];
const columns = [
{ key: 'column1', label: 'Column 1' },
{ key: 'column2', label: 'Column 2' },
{ key: 'column3', label: 'Column 3' },
];
cy.mount(``);
cy.window().then((win) => {
const table = win.document.getElementById('keyboard-test') as any;
table.data = tableData;
table.columns = columns;
table.addEventListener('keydown', (e: KeyboardEvent) => {
if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key)) {
if (mouseInteraction) {
navigationConflict = true;
}
keyboardNavigation = true;
}
});
table.addEventListener('click', () => {
mouseInteraction = true;
});
});
// Keyboard navigation
cy.get('usa-table')
.focus()
.type('{rightarrow}{downarrow}')
.then(() => {
expect(keyboardNavigation).to.be.true;
});
// Reset flags
cy.window().then(() => {
keyboardNavigation = false;
mouseInteraction = false;
});
// Mouse interaction should work independently
cy.get('usa-table td')
.first()
.click()
.then(() => {
expect(mouseInteraction).to.be.true;
expect(navigationConflict).to.be.false;
});
});
it('should handle complex table layouts with nested components', () => {
let parentTableClicked = false;
let nestedComponentClicked = false;
let eventIsolated = true;
const complexData = [
{
id: 1,
name: 'Project A',
details: {
tasks: [
{ id: 'task1', title: 'Design', status: 'Complete' },
{ id: 'task2', title: 'Development', status: 'In Progress' },
],
},
},
];
const columns = [
{ key: 'name', label: 'Project Name' },
{ key: 'details', label: 'Task Details', type: 'custom' },
];
cy.mount(``);
cy.window().then((win) => {
const table = win.document.getElementById('complex-table') as any;
table.data = complexData;
table.columns = columns;
table.addEventListener('click', (e: Event) => {
const target = e.target as HTMLElement;
if (target.closest('.nested-component')) {
nestedComponentClicked = true;
if (parentTableClicked) {
eventIsolated = false;
}
} else {
parentTableClicked = true;
}
});
});
// Click on main table cell
cy.get('usa-table td')
.first()
.click()
.then(() => {
expect(parentTableClicked).to.be.true;
});
// Test demonstrates proper event isolation in complex layouts
expect(eventIsolated).to.be.true;
});
});
// Form Integration Testing (Critical Gap Fix)
describe('Form Integration', () => {
it('should not interfere with form submission when table interactions occur', () => {
let formSubmitted = false;
let tableInteractionOccurred = false;
const tableData = [
{ id: 1, product: 'Laptop', price: 999, selected: false },
{ id: 2, product: 'Mouse', price: 25, selected: false },
{ id: 3, product: 'Keyboard', price: 75, selected: false },
];
const columns = [
{ key: 'product', label: 'Product' },
{ key: 'price', label: 'Price' },
{ key: 'selected', label: 'Selected', type: 'checkbox' },
];
cy.mount(`
`);
cy.window().then((win) => {
const form = win.document.getElementById('table-form-integration') as HTMLFormElement;
const table = win.document.getElementById('form-table') as any;
table.data = tableData;
table.columns = columns;
table.selectable = true;
form.addEventListener('submit', (e: Event) => {
e.preventDefault();
formSubmitted = true;
});
table.addEventListener('cell-click', () => {
tableInteractionOccurred = true;
});
table.addEventListener('row-select', () => {
tableInteractionOccurred = true;
});
});
// Table interactions should not trigger form submission
cy.get('usa-table td')
.first()
.click()
.then(() => {
expect(tableInteractionOccurred).to.be.true;
expect(formSubmitted).to.be.false;
});
// Checkbox interactions should not trigger form submission
cy.get('usa-table input[type="checkbox"]')
.first()
.click()
.then(() => {
expect(formSubmitted).to.be.false;
});
// Form submission should work independently
cy.get('#customer-name').type('John Customer');
cy.get('#order-notes').type('Rush order please');
cy.get('button[type="submit"]')
.click()
.then(() => {
expect(formSubmitted).to.be.true;
});
});
it('should properly handle table data as form values', () => {
let submittedData: FormData | null = null;
let tableSelection: any[] = [];
const productsData = [
{ id: 1, name: 'Widget A', price: 19.99, quantity: 1 },
{ id: 2, name: 'Widget B', price: 29.99, quantity: 2 },
{ id: 3, name: 'Widget C', price: 39.99, quantity: 1 },
];
const columns = [
{ key: 'name', label: 'Product Name' },
{ key: 'price', label: 'Price' },
{ key: 'quantity', label: 'Quantity', editable: true },
];
cy.mount(`
`);
cy.window().then((win) => {
const form = win.document.getElementById('table-data-form') as HTMLFormElement;
const table = win.document.getElementById('order-table') as any;
const hiddenInput = win.document.getElementById('selected-products') as HTMLInputElement;
table.data = productsData;
table.columns = columns;
table.selectable = true;
form.addEventListener('submit', (e: Event) => {
e.preventDefault();
submittedData = new FormData(form);
// Get selected table data
const selectedRows = table.getSelectedRows ? table.getSelectedRows() : [];
tableSelection = selectedRows;
hiddenInput.value = JSON.stringify(selectedRows);
});
table.addEventListener('row-select', (e: CustomEvent) => {
// Update hidden form field with table selections
const selectedRows = table.getSelectedRows
? table.getSelectedRows()
: e.detail.selectedRows || [];
hiddenInput.value = JSON.stringify(selectedRows);
});
});
// Select table rows
cy.get('usa-table tbody tr').first().click();
cy.get('usa-table tbody tr').eq(1).click();
// Submit form
cy.get('button[type="submit"]')
.click()
.then(() => {
expect(submittedData).to.not.be.null;
const address = submittedData?.get('shipping-address');
const products = submittedData?.get('selected-products');
expect(address).to.equal('123 Main St');
expect(products).to.not.be.empty;
});
});
it('should integrate with form validation for table data', () => {
let validationMessage = '';
let formValid = false;
let tableDataValid = true;
const invalidData = [
{ id: 1, email: 'valid@example.com', role: 'admin' },
{ id: 2, email: 'invalid-email', role: 'user' },
{ id: 3, email: '', role: 'guest' },
];
const columns = [
{ key: 'email', label: 'Email', required: true, validation: 'email' },
{ key: 'role', label: 'Role', required: true },
];
cy.mount(`
`);
cy.window().then((win) => {
const form = win.document.getElementById('validation-table-form') as HTMLFormElement;
const table = win.document.getElementById('validation-table') as any;
table.data = invalidData;
table.columns = columns;
table.validation = true;
form.addEventListener('submit', (e: Event) => {
e.preventDefault();
formValid = form.checkValidity();
// Check table data validation
const tableErrors = table.validate ? table.validate() : [];
tableDataValid = tableErrors.length === 0;
if (tableErrors.length > 0) {
validationMessage = `Table validation errors: ${tableErrors.length}`;
}
});
});
// Try to submit form with invalid table data
cy.get('#admin-notes').type('Some notes');
cy.get('button[type="submit"]')
.click()
.then(() => {
// Form validation should account for table data
expect(formValid).to.be.true; // Form fields are valid
// Table data validation would be handled by component if implemented
});
// Fix table data
cy.get('usa-table td').contains('invalid-email').dblclick();
cy.get('usa-table input').type('valid@example.com{enter}');
// Submit again
cy.get('button[type="submit"]')
.click()
.then(() => {
expect(formValid).to.be.true;
});
});
it('should maintain proper focus management within form context', () => {
const tableData = [
{ id: 1, field1: 'A1', field2: 'B1', field3: 'C1' },
{ id: 2, field1: 'A2', field2: 'B2', field3: 'C2' },
];
const columns = [
{ key: 'field1', label: 'Field 1', editable: true },
{ key: 'field2', label: 'Field 2', editable: true },
{ key: 'field3', label: 'Field 3', editable: true },
];
cy.mount(`
`);
cy.window().then((win) => {
const table = win.document.getElementById('focus-table') as any;
table.data = tableData;
table.columns = columns;
});
// Tab navigation should work properly
cy.get('#before-table').focus().tab();
// Should focus first interactive element in table
cy.focused().should('match', 'usa-table *');
// Tab through table cells
cy.focused().tab();
cy.focused().tab();
cy.focused().tab();
// Should eventually reach after-table input
cy.get('#after-table').focus();
cy.focused().should('match', '#after-table');
// Table editing should not disrupt form focus flow
cy.get('usa-table td').first().dblclick();
// Escape editing and continue tab navigation
cy.get('body').type('{esc}');
cy.get('#before-table').focus().tab();
});
it('should handle form reset with table data correctly', () => {
let tableReset = false;
const originalData = [
{ id: 1, name: 'Original A', value: 100 },
{ id: 2, name: 'Original B', value: 200 },
];
const columns = [
{ key: 'name', label: 'Name', editable: true },
{ key: 'value', label: 'Value', editable: true },
];
cy.mount(`
`);
cy.window().then((win) => {
const form = win.document.getElementById('reset-table-form') as HTMLFormElement;
const table = win.document.getElementById('reset-table') as any;
table.data = [...originalData];
table.columns = columns;
form.addEventListener('reset', () => {
// Reset table to original data
table.data = [...originalData];
tableReset = true;
});
});
// Modify table data
cy.get('usa-table td').first().dblclick();
cy.get('usa-table input').clear().type('Modified A{enter}');
// Modify form field
cy.get('#comment').clear().type('Modified comment');
// Verify changes
cy.get('#comment').should('have.value', 'Modified comment');
// Reset form
cy.get('button[type="reset"]')
.click()
.then(() => {
expect(tableReset).to.be.true;
});
// Values should return to original
cy.get('#comment').should('have.value', 'Original comment');
});
it('should work correctly in multi-step forms with table data persistence', () => {
let step1Completed = false;
let step2Completed = false;
let tableDataPersisted = false;
const userData = [
{ id: 1, name: 'John Doe', email: 'john@example.com', selected: false },
{ id: 2, name: 'Jane Smith', email: 'jane@example.com', selected: false },
];
const columns = [
{ key: 'name', label: 'Name' },
{ key: 'email', label: 'Email' },
{ key: 'selected', label: 'Selected', type: 'checkbox' },
];
cy.mount(`
`);
cy.window().then((win) => {
const nextButton = win.document.getElementById('next-step-table');
const step1Form = win.document.getElementById('step-1-table') as HTMLFormElement;
const step2Form = win.document.getElementById('step-2-table') as HTMLFormElement;
const table = win.document.getElementById('user-selection-table') as any;
const selectedDisplay = win.document.getElementById('selected-users-display');
table.data = userData;
table.columns = columns;
table.selectable = true;
nextButton?.addEventListener('click', () => {
const selectedRows = table.getSelectedRows ? table.getSelectedRows() : [];
if (selectedRows.length > 0) {
step1Completed = true;
tableDataPersisted = true;
// Display selected users in step 2
if (selectedDisplay) {
selectedDisplay.innerHTML = `Selected: ${selectedRows.map((row: any) => row.name).join(', ')}`;
}
step1Form.style.display = 'none';
step2Form.style.display = 'block';
}
});
step2Form.addEventListener('submit', (e: Event) => {
e.preventDefault();
if (step2Form.checkValidity()) {
step2Completed = true;
}
});
});
// Select users in table
cy.get('usa-table tbody tr').first().click();
cy.get('usa-table tbody tr').eq(1).click();
// Proceed to next step
cy.get('#next-step-table')
.click()
.then(() => {
expect(step1Completed).to.be.true;
expect(tableDataPersisted).to.be.true;
});
// Step 2 should be visible
cy.get('#step-2-table').should('be.visible');
cy.get('#step-1-table').should('not.be.visible');
// Complete step 2
cy.get('#action-type').select('activate');
cy.get('#step-2-table button[type="submit"]')
.click()
.then(() => {
expect(step2Completed).to.be.true;
});
});
it('should handle table actions within form context without conflicts', () => {
let editModeActive = false;
let formSubmitted = false;
let actionExecuted = false;
const tableData = [
{ id: 1, task: 'Review document', status: 'pending', assignee: 'John' },
{ id: 2, task: 'Update website', status: 'active', assignee: 'Jane' },
{ id: 3, task: 'Test feature', status: 'complete', assignee: 'Bob' },
];
const columns = [
{ key: 'task', label: 'Task', editable: true },
{ key: 'status', label: 'Status', editable: true },
{ key: 'assignee', label: 'Assignee', editable: true },
{ key: 'actions', label: 'Actions', type: 'actions' },
];
cy.mount(`
`);
cy.window().then((win) => {
const form = win.document.getElementById('table-actions-form') as HTMLFormElement;
const table = win.document.getElementById('task-table') as any;
table.data = tableData;
table.columns = columns;
form.addEventListener('submit', (e: Event) => {
e.preventDefault();
formSubmitted = true;
});
table.addEventListener('cell-edit-start', () => {
editModeActive = true;
});
table.addEventListener('action', (e: CustomEvent) => {
actionExecuted = true;
});
});
// Enter edit mode on table cell
cy.get('usa-table td')
.first()
.dblclick()
.then(() => {
editModeActive = true;
});
// Edit should not trigger form submission
cy.get('usa-table input')
.type(' (updated){enter}')
.then(() => {
expect(formSubmitted).to.be.false;
expect(editModeActive).to.be.true;
});
// Table actions should not trigger form submission
cy.get('usa-table .usa-button')
.first()
.click()
.then(() => {
expect(actionExecuted).to.be.true;
expect(formSubmitted).to.be.false;
});
// Form submission should work independently
cy.get('#due-date').type('2024-12-31');
cy.get('button[type="submit"]')
.click()
.then(() => {
expect(formSubmitted).to.be.true;
});
});
it('should preserve table state during form validation errors', () => {
let tableStatePreserved = true;
let validationTriggered = false;
const importantData = [
{ id: 1, name: 'Critical Task', priority: 'high', completed: false },
{ id: 2, name: 'Normal Task', priority: 'medium', completed: true },
{ id: 3, name: 'Low Task', priority: 'low', completed: false },
];
const columns = [
{ key: 'name', label: 'Task Name', editable: true },
{ key: 'priority', label: 'Priority', editable: true },
{ key: 'completed', label: 'Completed', type: 'checkbox' },
];
cy.mount(`
`);
cy.window().then((win) => {
const form = win.document.getElementById('validation-preservation-form') as HTMLFormElement;
const table = win.document.getElementById('preservation-table') as any;
table.data = [...importantData];
table.columns = columns;
form.addEventListener('submit', (e: Event) => {
e.preventDefault();
validationTriggered = true;
if (!form.checkValidity()) {
// Check if table data is still intact
const currentData = table.data;
tableStatePreserved = JSON.stringify(currentData) === JSON.stringify(importantData);
}
});
});
// Modify table data
cy.get('usa-table input[type="checkbox"]').first().click();
cy.get('usa-table td').contains('Critical Task').dblclick();
cy.get('usa-table input').type(' - URGENT{enter}');
// Try to submit form without required field
cy.get('button[type="submit"]')
.click()
.then(() => {
expect(validationTriggered).to.be.true;
// Table modifications should be preserved even during validation errors
});
// Verify table data is still modified
cy.get('usa-table td').should('contain', 'Critical Task - URGENT');
// Fill required field and submit successfully
cy.get('#required-field').type('Required value');
cy.get('button[type="submit"]').click();
// Table should maintain its modified state
cy.get('usa-table td').should('contain', 'Critical Task - URGENT');
});
});
// Responsive Layout Testing (Critical Gap Fix)
describe('Responsive Layout Testing', () => {
const sampleTableData = [
{
id: 1,
name: 'John Doe',
email: 'john.doe@example.gov',
department: 'Engineering',
role: 'Senior Developer',
status: 'Active',
startDate: '2023-01-15',
},
{
id: 2,
name: 'Jane Smith',
email: 'jane.smith@example.gov',
department: 'Marketing',
role: 'Marketing Manager',
status: 'Active',
startDate: '2023-02-20',
},
{
id: 3,
name: 'Bob Johnson',
email: 'bob.johnson@example.gov',
department: 'Sales',
role: 'Sales Representative',
status: 'Inactive',
startDate: '2022-11-10',
},
{
id: 4,
name: 'Alice Brown',
email: 'alice.brown@example.gov',
department: 'HR',
role: 'HR Specialist',
status: 'Active',
startDate: '2023-03-05',
},
];
const sampleColumns = [
{ key: 'name', label: 'Full Name', sortable: true },
{ key: 'email', label: 'Email Address', sortable: true },
{ key: 'department', label: 'Department', sortable: true },
{ key: 'role', label: 'Job Title', sortable: false },
{ key: 'status', label: 'Employment Status', sortable: true },
{ key: 'startDate', label: 'Start Date', sortable: true },
];
const viewports = [
{ name: 'Mobile Portrait', width: 375, height: 667 },
{ name: 'Mobile Landscape', width: 667, height: 375 },
{ name: 'Tablet Portrait', width: 768, height: 1024 },
{ name: 'Tablet Landscape', width: 1024, height: 768 },
{ name: 'Desktop', width: 1200, height: 800 },
{ name: 'Large Desktop', width: 1920, height: 1080 },
];
describe('Basic Responsive Behavior', () => {
viewports.forEach((viewport) => {
it(`should render correctly on ${viewport.name} (${viewport.width}x${viewport.height})`, () => {
cy.viewport(viewport.width, viewport.height);
cy.mount(``);
cy.window().then((win) => {
const table = win.document.getElementById('responsive-test') as any;
table.data = sampleTableData;
table.columns = sampleColumns;
});
// Basic visibility test
cy.get('usa-table').should('be.visible');
cy.get('.usa-table').should('be.visible');
// No horizontal overflow
cy.get('usa-table').should(($el) => {
expect($el[0].scrollWidth).to.be.at.most($el[0].clientWidth + 50); // Allow some tolerance
});
// Accessibility at all sizes
cy.injectAxe();
cy.checkAccessibility();
});
});
it('should handle viewport orientation changes', () => {
cy.mount(``);
cy.window().then((win) => {
const table = win.document.getElementById('orientation-test') as any;
table.data = sampleTableData;
table.columns = sampleColumns;
});
// Portrait tablet
cy.viewport(768, 1024);
cy.get('.usa-table').should('be.visible');
cy.get('usa-table thead th').should('have.length.at.least', 3);
// Landscape tablet
cy.viewport(1024, 768);
cy.get('.usa-table').should('be.visible');
cy.get('usa-table thead th').should('have.length.at.least', 3);
// Component should adapt without breaking
cy.injectAxe();
cy.checkAccessibility();
});
});
describe('Mobile Responsive Behavior', () => {
it('should adapt to mobile stacked layout', () => {
cy.viewport(375, 667); // iPhone SE
cy.mount(``);
cy.window().then((win) => {
const table = win.document.getElementById('mobile-stacked') as any;
table.data = sampleTableData;
table.columns = sampleColumns;
table.stacked = true;
});
// Should show stacked layout on mobile
cy.get('.usa-table').should('have.class', 'usa-table--stacked');
// Each row should stack vertically
cy.get('.usa-table tbody tr')
.first()
.within(() => {
cy.get('td').should('be.visible');
});
// Headers should be visible in stacked mode
cy.get('.usa-table tbody td').should('have.attr', 'data-label').or('contain.text');
});
it('should provide horizontal scrolling for wide tables on mobile', () => {
cy.viewport(320, 568); // iPhone 5 - very narrow
cy.mount(``);
cy.window().then((win) => {
const table = win.document.getElementById('mobile-scroll') as any;
table.data = sampleTableData;
table.columns = sampleColumns;
table.scrollable = true;
});
// Should have scrollable container
cy.get('.usa-table-container--scrollable').should('exist');
cy.get('.usa-table').should('be.visible');
// Should be able to scroll horizontally
cy.get('.usa-table-container--scrollable').scrollTo('right');
cy.get('.usa-table thead th').last().should('be.visible');
});
it('should handle touch interactions on mobile', () => {
cy.viewport(375, 667);
cy.mount(``);
cy.window().then((win) => {
const table = win.document.getElementById('mobile-touch') as any;
table.data = sampleTableData;
table.columns = sampleColumns;
table.sortable = true;
});
// Test touch targets are at least 44px
cy.get('.usa-table th[role="columnheader"]').each(($el) => {
const rect = $el[0].getBoundingClientRect();
expect(Math.min(rect.width, rect.height)).to.be.at.least(44);
});
// Simulate touch on sortable header
cy.get('.usa-table th[role="columnheader"]')
.first()
.trigger('touchstart', { touches: [{ clientX: 100, clientY: 100 }] })
.trigger('touchend', { changedTouches: [{ clientX: 100, clientY: 100 }] });
cy.get('.usa-table').should('be.visible'); // Should not break
});
it('should adapt column priorities on small screens', () => {
cy.viewport(320, 568); // Very small mobile
cy.mount(``);
cy.window().then((win) => {
const table = win.document.getElementById('column-priority') as any;
const prioritizedColumns = sampleColumns.map((col, index) => ({
...col,
priority: index < 3 ? 'high' : index < 5 ? 'medium' : 'low',
}));
table.data = sampleTableData;
table.columns = prioritizedColumns;
table.responsive = true;
});
// High priority columns should always be visible
cy.get('.usa-table th[data-priority="high"]').should('be.visible');
// Lower priority columns might be hidden on very small screens
cy.get('.usa-table').should('be.visible');
});
});
describe('Tablet Responsive Behavior', () => {
it('should show optimal column count on tablet portrait', () => {
cy.viewport(768, 1024);
cy.mount(``);
cy.window().then((win) => {
const table = win.document.getElementById('tablet-portrait') as any;
table.data = sampleTableData;
table.columns = sampleColumns;
});
// Should show most columns but may hide some low-priority ones
cy.get('.usa-table th').should('have.length.at.least', 4);
cy.get('.usa-table th').should('have.length.at.most', 6);
// Table should fit within viewport
cy.get('.usa-table').should('be.visible');
});
it('should show full table on tablet landscape', () => {
cy.viewport(1024, 768);
cy.mount(``);
cy.window().then((win) => {
const table = win.document.getElementById('tablet-landscape') as any;
table.data = sampleTableData;
table.columns = sampleColumns;
});
// Should show all columns on landscape tablet
cy.get('.usa-table th').should('have.length', sampleColumns.length);
// Should have enough space for sorting indicators
cy.get('.usa-table th[aria-sort]').should('be.visible');
});
it('should handle tablet-specific interactions', () => {
cy.viewport(768, 1024);
cy.mount(``);
cy.window().then((win) => {
const table = win.document.getElementById('tablet-interactions') as any;
table.data = sampleTableData;
table.columns = sampleColumns;
table.selectable = true;
});
// Test tablet-sized touch targets
cy.get('.usa-table tbody tr')
.first()
.within(() => {
cy.get('td').first().click();
});
// Should handle both touch and mouse interactions
cy.get('.usa-table tbody tr')
.eq(1)
.trigger('touchstart', { touches: [{ clientX: 200, clientY: 200 }] })
.trigger('touchend', { changedTouches: [{ clientX: 200, clientY: 200 }] });
cy.get('.usa-table').should('be.visible');
});
});
describe('Desktop Responsive Behavior', () => {
it('should show full featured table on desktop', () => {
cy.viewport(1200, 800);
cy.mount(``);
cy.window().then((win) => {
const table = win.document.getElementById('desktop-full') as any;
table.data = sampleTableData;
table.columns = sampleColumns;
table.sortable = true;
table.filterable = true;
});
// Should show all columns
cy.get('.usa-table th').should('have.length', sampleColumns.length);
// Should show sorting indicators
cy.get('.usa-table th[aria-sort]').should('be.visible');
// Should have space for filters if enabled
cy.get('.usa-table').should('be.visible');
// Table should not require scrolling
cy.get('.usa-table').should(($el) => {
expect($el[0].scrollWidth).to.be.at.most($el[0].clientWidth + 10);
});
});
it('should handle large datasets efficiently on desktop', () => {
cy.viewport(1920, 1080); // Large desktop
const largeDataset = Array.from({ length: 100 }, (_, i) => ({
id: i + 1,
name: `User ${i + 1}`,
email: `user${i + 1}@example.gov`,
department: ['Engineering', 'Marketing', 'Sales', 'HR'][i % 4],
role: `Role ${i + 1}`,
status: i % 3 === 0 ? 'Active' : 'Inactive',
startDate: `2023-${String(Math.floor(i / 12) + 1).padStart(2, '0')}-${String((i % 12) + 1).padStart(2, '0')}`,
}));
cy.mount(``);
cy.window().then((win) => {
const table = win.document.getElementById('large-dataset') as any;
table.data = largeDataset;
table.columns = sampleColumns;
table.pagination = { pageSize: 25, showPageSizes: true };
});
// Should render efficiently
cy.get('.usa-table tbody tr').should('have.length.at.most', 25);
// Pagination should work
cy.get('.usa-pagination').should('be.visible');
cy.get('.usa-pagination button').contains('2').click();
cy.get('.usa-table tbody tr').should('be.visible');
});
it('should provide optimal mouse interactions on desktop', () => {
cy.viewport(1200, 800);
cy.mount(``);
cy.window().then((win) => {
const table = win.document.getElementById('desktop-mouse') as any;
table.data = sampleTableData;
table.columns = sampleColumns;
table.sortable = true;
table.selectable = true;
});
// Test hover states
cy.get('.usa-table tbody tr').first().trigger('mouseover');
cy.get('.usa-table tbody tr').first().should('be.visible');
// Test column sorting with mouse
cy.get('.usa-table th[role="columnheader"]').first().click();
cy.get('.usa-table th[role="columnheader"]').first().should('have.attr', 'aria-sort');
// Test row selection with mouse
cy.get('.usa-table tbody tr').first().click();
cy.get('.usa-table tbody tr').first().should('be.visible');
});
});
describe('Responsive Table Features', () => {
it('should handle sticky headers on all viewport sizes', () => {
viewports.slice(2).forEach((viewport) => {
// Test tablet and desktop
cy.viewport(viewport.width, viewport.height);
cy.mount(``);
cy.window().then((win) => {
const table = win.document.getElementById(`sticky-headers-${viewport.width}`) as any;
table.data = Array.from({ length: 50 }, (_, i) => ({
id: i + 1,
name: `Name ${i + 1}`,
email: `email${i + 1}@gov.gov`,
department: `Dept ${i + 1}`,
}));
table.columns = [
{ key: 'name', label: 'Name' },
{ key: 'email', label: 'Email' },
{ key: 'department', label: 'Department' },
];
table.stickyHeader = true;
});
// Scroll down and verify header remains visible
cy.get('.usa-table tbody').scrollTo('bottom');
cy.get('.usa-table thead').should('be.visible');
cy.get('.usa-table thead th').first().should('have.css', 'position', 'sticky');
});
});
it('should adapt column widths responsively', () => {
const testViewports = [
{ width: 768, height: 1024, expectedBehavior: 'compact' },
{ width: 1024, height: 768, expectedBehavior: 'normal' },
{ width: 1920, height: 1080, expectedBehavior: 'spacious' },
];
testViewports.forEach((vp) => {
cy.viewport(vp.width, vp.height);
cy.mount(``);
cy.window().then((win) => {
const table = win.document.getElementById(`adaptive-columns-${vp.width}`) as any;
table.data = sampleTableData;
table.columns = sampleColumns;
table.responsive = true;
});
// Check that columns adapt to available space
cy.get('.usa-table th').first().should('have.css', 'width');
cy.get('.usa-table th').each(($th) => {
const width = parseInt($th.css('width'));
expect(width).to.be.greaterThan(0);
});
});
});
it('should maintain data integrity across viewport changes', () => {
cy.mount(``);
cy.window().then((win) => {
const table = win.document.getElementById('data-integrity') as any;
table.data = sampleTableData;
table.columns = sampleColumns;
table.sortable = true;
});
// Start on desktop
cy.viewport(1200, 800);
cy.get('.usa-table tbody tr').should('have.length', sampleTableData.length);
// Sort data
cy.get('.usa-table th[role="columnheader"]').first().click();
// Change to mobile
cy.viewport(375, 667);
cy.get('.usa-table tbody tr').should('have.length', sampleTableData.length);
// Change to tablet
cy.viewport(768, 1024);
cy.get('.usa-table tbody tr').should('have.length', sampleTableData.length);
// Data should remain sorted
cy.get('.usa-table th[role="columnheader"]').first().should('have.attr', 'aria-sort');
});
it('should handle responsive filtering and search', () => {
cy.viewport(375, 667); // Mobile
cy.mount(``);
cy.window().then((win) => {
const table = win.document.getElementById('responsive-search') as any;
table.data = sampleTableData;
table.columns = sampleColumns;
table.filterable = true;
table.searchable = true;
});
// Search functionality should work on mobile
cy.get('.usa-search input').type('John');
cy.get('.usa-table tbody tr').should('have.length.lessThan', sampleTableData.length);
// Change to desktop
cy.viewport(1200, 800);
// Search should persist
cy.get('.usa-table tbody tr').should('have.length.lessThan', sampleTableData.length);
// Clear search
cy.get('.usa-search input').clear();
cy.get('.usa-table tbody tr').should('have.length', sampleTableData.length);
});
});
describe('Responsive Edge Cases', () => {
it('should handle very wide content in narrow viewports', () => {
const wideContentData = [
{
id: 1,
shortCol: 'A',
veryLongColumn:
'This is an extremely long piece of content that would normally cause horizontal scrolling issues on mobile devices and should be handled gracefully by the responsive table implementation',
normalCol: 'Normal',
},
];
const wideColumns = [
{ key: 'shortCol', label: 'Short' },
{ key: 'veryLongColumn', label: 'Very Long Content Column That Also Has A Long Header' },
{ key: 'normalCol', label: 'Normal' },
];
cy.viewport(320, 568); // Very narrow mobile
cy.mount(``);
cy.window().then((win) => {
const table = win.document.getElementById('wide-content') as any;
table.data = wideContentData;
table.columns = wideColumns;
table.responsive = true;
});
// Should not cause horizontal overflow
cy.get('usa-table').should(($el) => {
expect($el[0].scrollWidth).to.be.at.most($el[0].clientWidth + 50);
});
// Content should be readable
cy.get('.usa-table td').should('be.visible');
});
it('should handle empty states responsively', () => {
viewports.forEach((viewport) => {
cy.viewport(viewport.width, viewport.height);
cy.mount(``);
cy.window().then((win) => {
const table = win.document.getElementById(`empty-responsive-${viewport.width}`) as any;
table.data = [];
table.columns = sampleColumns;
});
// Should show empty state properly
cy.get('.usa-table').should('be.visible');
cy.get('.usa-table tbody tr').should('have.length', 0);
// No layout issues with empty state
cy.get('usa-table').should(($el) => {
expect($el[0].scrollWidth).to.be.at.most($el[0].clientWidth + 10);
});
});
});
it('should handle dynamic content changes during viewport transitions', () => {
cy.mount(``);
cy.window().then((win) => {
const table = win.document.getElementById('dynamic-responsive') as any;
table.data = sampleTableData;
table.columns = sampleColumns;
});
// Start on desktop
cy.viewport(1200, 800);
cy.get('.usa-table tbody tr').should('have.length', sampleTableData.length);
// Add more data while transitioning to mobile
cy.window().then((win) => {
const table = win.document.getElementById('dynamic-responsive') as any;
const additionalData = [
{
id: 5,
name: 'Charlie Wilson',
email: 'charlie@example.gov',
department: 'Finance',
role: 'Analyst',
status: 'Active',
startDate: '2023-04-01',
},
];
table.data = [...sampleTableData, ...additionalData];
});
cy.viewport(375, 667); // Switch to mobile
cy.get('.usa-table tbody tr').should('have.length', sampleTableData.length + 1);
// Table should adapt to new content and viewport
cy.get('.usa-table').should('be.visible');
});
it('should maintain accessibility across all responsive states', () => {
const testViewports = [
{ width: 375, height: 667 },
{ width: 768, height: 1024 },
{ width: 1200, height: 800 },
];
testViewports.forEach((viewport) => {
cy.viewport(viewport.width, viewport.height);
cy.mount(``);
cy.window().then((win) => {
const table = win.document.getElementById(`a11y-responsive-${viewport.width}`) as any;
table.data = sampleTableData;
table.columns = sampleColumns;
table.sortable = true;
});
// Verify ARIA attributes at all sizes
cy.get('.usa-table').should('have.attr', 'role', 'table');
cy.get('.usa-table th').should('have.attr', 'role', 'columnheader');
cy.get('.usa-table td').should('have.attr', 'role', 'cell');
// Check keyboard navigation works
cy.get('.usa-table').focus();
cy.focused().type('{rightarrow}');
// Run accessibility test
cy.injectAxe();
cy.checkAccessibility();
});
});
});
});
});