// Component tests for usa-file-input import './index.ts'; describe('USA File Input Component Tests', () => { it('should render file input with default properties', () => { cy.mount(``); cy.get('usa-file-input').should('exist'); cy.get('usa-file-input input[type="file"]').should('have.class', 'usa-file-input__input'); cy.get('.usa-file-input__target').should('exist'); }); it('should handle single file selection', () => { cy.mount(` `); // Create a test file }); it('should handle multiple file selection', () => { cy.mount(` `); cy.get('input[type="file"]').should('have.attr', 'multiple'); }); it('should emit change events', () => { cy.mount(` `); cy.window().then((win) => { const fileInput = win.document.getElementById('test-file-input') as any; const changeSpy = cy.stub(); fileInput.addEventListener('change', changeSpy); cy.get('input[type="file"]').selectFile( { contents: Cypress.Buffer.from('test'), fileName: 'test.txt', mimeType: 'text/plain', }, { force: true } ); cy.then(() => { expect(changeSpy).to.have.been.called; }); }); }); it('should handle disabled state', () => { cy.mount(` `); cy.get('input[type="file"]').should('be.disabled'); cy.get('.usa-file-input').should('have.class', 'usa-file-input--disabled'); cy.get('.usa-file-input__target').should('have.attr', 'aria-disabled', 'true'); }); it('should handle required state', () => { cy.mount(` `); cy.get('input[type="file"]').should('have.attr', 'required'); cy.get('input[type="file"]').should('have.attr', 'aria-required', 'true'); }); it('should handle error state', () => { cy.mount(` `); cy.get('input[type="file"]').should('have.attr', 'aria-invalid', 'true'); cy.get('.usa-file-input').should('have.class', 'usa-file-input--error'); cy.get('.usa-error-message').should('contain.text', 'Please select a valid file'); }); it('should handle file type restrictions', () => { cy.mount(` `); cy.get('input[type="file"]').should('have.attr', 'accept', '.pdf,.doc,.docx'); // Should show accepted file types in UI cy.get('.usa-file-input__accepted-files-message').should('contain.text', '.pdf, .doc, .docx'); }); it('should handle file size validation', () => { cy.mount(` `); // Create a large file (simulated) const largeFileContent = 'x'.repeat(6000000); // 6MB file cy.get('input[type="file"]').selectFile( { contents: Cypress.Buffer.from(largeFileContent), fileName: 'large-file.txt', mimeType: 'text/plain', }, { force: true } ); // Should show error for file too large cy.get('.usa-file-input__preview-heading--error').should('contain.text', 'large-file.txt'); cy.get('.usa-file-input__preview-heading .usa-file-input__preview-heading-error').should( 'contain.text', 'File is too large' ); }); it('should handle drag and drop functionality', () => { cy.mount(` `); cy.get('.usa-file-input__drag-text').should( 'contain.text', 'Drag files here or choose from folder' ); // Test drag events cy.get('.usa-file-input__target') .trigger('dragenter') .should('have.class', 'usa-file-input__target--dragged'); cy.get('.usa-file-input__target') .trigger('dragleave') .should('not.have.class', 'usa-file-input__target--dragged'); }); it('should handle file removal', () => { cy.mount(` `); // Add a file first cy.get('input[type="file"]').selectFile( { contents: Cypress.Buffer.from('test content'), fileName: 'removable-file.txt', mimeType: 'text/plain', }, { force: true } ); cy.get('.usa-file-input__preview').should('contain.text', 'removable-file.txt'); // Remove the file cy.get('.usa-file-input__preview-close').click(); cy.get('.usa-file-input__preview').should('not.exist'); }); it('should be keyboard accessible', () => { cy.mount(` `); // Tab to file input cy.get('input[type="file"]').focus(); cy.focused().should('have.attr', 'name', 'keyboard-accessible'); // Enter key should open file dialog (browser behavior) cy.focused().type('{enter}'); // Space key should also work (browser behavior) cy.focused().type(' '); }); it('should handle form integration', () => { cy.mount(`
`); // Add a file cy.get('input[type="file"]').selectFile( { contents: Cypress.Buffer.from('form test content'), fileName: 'form-test.txt', mimeType: 'text/plain', }, { force: true } ); 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); const file = formData.get('uploaded-file') as File; submitSpy(file ? file.name : null); }); cy.get('button[type="submit"]').click(); cy.then(() => { expect(submitSpy).to.have.been.calledWith('form-test.txt'); }); }); }); it('should handle custom instructions', () => { cy.mount(` `); cy.get('.usa-file-input__instructions').should( 'contain.text', 'Select or drag government documents' ); cy.get('.usa-file-input__instructions').should('contain.text', 'Maximum file size: 10MB'); }); it('should handle image preview for image files', () => { cy.mount(` `); cy.get('input[type="file"]').selectFile( { contents: Cypress.Buffer.from('fake image data'), fileName: 'test-image.jpg', mimeType: 'image/jpeg', }, { force: true } ); cy.get('.usa-file-input__preview').should('contain.text', 'test-image.jpg'); cy.get('.usa-file-input__preview-image').should('exist'); }); it('should handle aria attributes', () => { cy.mount(`
Choose files to upload to your application
`); cy.get('input[type="file"]') .should('have.attr', 'aria-labelledby', 'file-label') .should('have.attr', 'aria-describedby', 'file-hint'); }); it('should handle focus and blur events', () => { cy.mount(` `); cy.window().then((win) => { const fileInput = win.document.getElementById('test-file-input') as any; const focusSpy = cy.stub(); const blurSpy = cy.stub(); fileInput.addEventListener('focus', focusSpy); fileInput.addEventListener('blur', blurSpy); cy.get('input[type="file"]').focus(); cy.get('input[type="file"]').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 file (should trigger validation) cy.get('input[type="file"]').focus().blur(); cy.get('input[type="file"]').should('have.attr', 'aria-invalid', 'true'); // Select file and blur (should clear validation) cy.get('input[type="file"]').selectFile( { contents: Cypress.Buffer.from('validation test'), fileName: 'validation-test.txt', mimeType: 'text/plain', }, { force: true } ); cy.get('input[type="file"]').blur(); cy.get('input[type="file"]').should('not.have.attr', 'aria-invalid', 'true'); }); it('should be accessible', () => { cy.mount(`
Select one or more documents to upload
`); cy.injectAxe(); cy.checkAccessibility(); }); it('should handle custom CSS classes', () => { cy.mount(` `); cy.get('usa-file-input').should('have.class', 'custom-file-input-class'); cy.get('.usa-file-input').should('exist'); }); it('should handle file validation messages', () => { cy.mount(` `); // Try to upload wrong file type cy.get('input[type="file"]').selectFile( { contents: Cypress.Buffer.from('not a pdf'), fileName: 'document.txt', mimeType: 'text/plain', }, { force: true } ); cy.get('.usa-file-input__preview-heading--error').should('contain.text', 'document.txt'); cy.get('.usa-file-input__preview-heading .usa-file-input__preview-heading-error').should( 'contain.text', 'Invalid file type' ); }); it('should handle empty file selection', () => { cy.mount(` `); // Add file then clear selection cy.get('input[type="file"]').selectFile( { contents: Cypress.Buffer.from('temp'), fileName: 'temp.txt', mimeType: 'text/plain', }, { force: true } ); cy.get('.usa-file-input__preview').should('exist'); // Clear file selection cy.get('input[type="file"]').selectFile([], { force: true }); cy.get('.usa-file-input__preview').should('not.exist'); }); it('should handle progress indication during upload', () => { cy.mount(` `); cy.get('input[type="file"]').selectFile( { contents: Cypress.Buffer.from('upload progress test'), fileName: 'progress-test.pdf', mimeType: 'application/pdf', }, { force: true } ); // Should show progress indicator during processing cy.get('.usa-file-input__preview').should('contain.text', 'progress-test.pdf'); cy.get('.usa-file-input__preview-progress').should('exist'); }); // Disabled State Robustness Testing (Critical Gap Fix) describe('Disabled State Protection', () => { it('should completely prevent file selection when disabled', () => { let fileChangeTriggered = false; let inputActivated = false; cy.mount(` `); cy.window().then((win) => { const fileInput = win.document.getElementById('disabled-file-test') as any; const nativeInput = fileInput.querySelector('input[type="file"]') as HTMLInputElement; // Listen for any file change events (should not occur) fileInput.addEventListener('change', () => { fileChangeTriggered = true; }); nativeInput.addEventListener('change', () => { fileChangeTriggered = true; }); // Listen for input activation attempts nativeInput.addEventListener('click', () => { inputActivated = true; }); nativeInput.addEventListener('focus', () => { inputActivated = true; }); }); // Attempt to select file when disabled (should be prevented) cy.get('input[type="file"]') .selectFile( { contents: Cypress.Buffer.from('disabled test'), fileName: 'disabled-test.txt', mimeType: 'text/plain', }, { force: true } ) .then(() => { // File selection should be completely blocked expect(fileChangeTriggered).to.be.false; }); // Click attempts should not activate input cy.get('.usa-file-input__target') .click({ force: true }) .then(() => { expect(inputActivated).to.be.false; }); // Input should remain disabled throughout cy.get('input[type="file"]').should('be.disabled'); cy.get('.usa-file-input').should('have.class', 'usa-file-input--disabled'); }); it('should prevent drag-and-drop interactions when disabled', () => { let dragEnterTriggered = false; let dropTriggered = false; let dragStateChanged = false; cy.mount(` `); cy.window().then((win) => { const fileInput = win.document.getElementById('disabled-drag-test') as any; const target = fileInput.querySelector('.usa-file-input__target') as HTMLElement; // Listen for drag events (should not occur when disabled) target.addEventListener('dragenter', () => { dragEnterTriggered = true; }); target.addEventListener('drop', () => { dropTriggered = true; }); // Listen for visual state changes (should not occur) const observer = new MutationObserver(() => { if (target.classList.contains('usa-file-input__target--dragged')) { dragStateChanged = true; } }); observer.observe(target, { attributes: true, attributeFilter: ['class'] }); }); // Attempt drag events when disabled cy.get('.usa-file-input__target') .trigger('dragenter', { force: true }) .trigger('dragover', { force: true }) .trigger('drop', { force: true }); cy.wait(100).then(() => { // All drag interactions should be prevented expect(dragEnterTriggered).to.be.false; expect(dropTriggered).to.be.false; expect(dragStateChanged).to.be.false; }); // Target should not show drag state cy.get('.usa-file-input__target').should('not.have.class', 'usa-file-input__target--dragged'); }); it('should prevent keyboard access when disabled', () => { let keydownTriggered = false; let focusTriggered = false; let activationAttempted = false; cy.mount(` `); cy.window().then((win) => { const fileInput = win.document.getElementById('disabled-keyboard-test') as any; const nativeInput = fileInput.querySelector('input[type="file"]') as HTMLInputElement; // Listen for keyboard events (should be minimal when disabled) nativeInput.addEventListener('keydown', () => { keydownTriggered = true; }); nativeInput.addEventListener('focus', () => { focusTriggered = true; }); // Listen for activation attempts fileInput.addEventListener('activate', () => { activationAttempted = true; }); }); // Attempt keyboard interactions when disabled cy.get('input[type="file"]').focus({ force: true }); cy.get('input[type="file"]').type('{enter}', { force: true }); cy.get('input[type="file"]').type(' ', { force: true }); cy.wait(100).then(() => { // Focus should be prevented or ineffective expect(activationAttempted).to.be.false; }); // Input should not be focusable when disabled cy.get('input[type="file"]').should('be.disabled'); cy.get('input[type="file"]').should('have.attr', 'tabindex', '-1'); }); it('should maintain consistent visual disabled state', () => { cy.mount(` `); // Initial disabled state verification cy.get('.usa-file-input').should('have.class', 'usa-file-input--disabled'); cy.get('.usa-file-input__target').should('have.attr', 'aria-disabled', 'true'); cy.get('input[type="file"]').should('be.disabled'); // Attempt various interactions that might change visual state cy.get('.usa-file-input__target').click({ force: true }); cy.get('.usa-file-input__target').hover(); cy.get('.usa-file-input__target').trigger('mouseenter'); cy.get('.usa-file-input__target').trigger('mouseleave'); // Visual state should remain consistently disabled cy.get('.usa-file-input').should('have.class', 'usa-file-input--disabled'); cy.get('.usa-file-input__target').should('have.attr', 'aria-disabled', 'true'); cy.get('input[type="file"]').should('be.disabled'); // Should not show hover or active states cy.get('.usa-file-input__target').should('not.have.class', 'usa-file-input__target--hover'); cy.get('.usa-file-input__target').should('not.have.class', 'usa-file-input__target--active'); }); it('should prevent file removal when disabled', () => { let fileRemoved = false; let removeButtonClicked = false; // First enable and add a file cy.mount(` `); // Add file while enabled cy.get('input[type="file"]').selectFile( { contents: Cypress.Buffer.from('test for removal'), fileName: 'test-removal.txt', mimeType: 'text/plain', }, { force: true } ); cy.get('.usa-file-input__preview').should('contain.text', 'test-removal.txt'); // Now disable the component cy.window().then((win) => { const fileInput = win.document.getElementById('removal-disabled-test') as any; fileInput.disabled = true; // Listen for removal events fileInput.addEventListener('file-remove', () => { fileRemoved = true; }); }); cy.window().then((win) => { const removeButton = win.document.querySelector( '.usa-file-input__preview-close' ) as HTMLButtonElement; if (removeButton) { removeButton.addEventListener('click', () => { removeButtonClicked = true; }); } }); // Attempt to remove file when disabled cy.get('.usa-file-input__preview-close') .click({ force: true }) .then(() => { expect(fileRemoved).to.be.false; }); // File should still be present cy.get('.usa-file-input__preview').should('contain.text', 'test-removal.txt'); // Remove button should be disabled or hidden cy.get('.usa-file-input__preview-close').should('be.disabled'); }); it('should ignore all interaction types when disabled', () => { let anyEventTriggered = false; const eventTypes = [ 'click', 'dblclick', 'mousedown', 'mouseup', 'focus', 'blur', 'keydown', 'keyup', ]; cy.mount(` `); cy.window().then((win) => { const fileInput = win.document.getElementById('comprehensive-disabled-test') as any; const nativeInput = fileInput.querySelector('input[type="file"]') as HTMLInputElement; const target = fileInput.querySelector('.usa-file-input__target') as HTMLElement; // Listen for any significant events eventTypes.forEach((eventType) => { nativeInput.addEventListener(eventType, () => { anyEventTriggered = true; }); target.addEventListener(eventType, () => { anyEventTriggered = true; }); }); // Listen for component-specific events fileInput.addEventListener('change', () => { anyEventTriggered = true; }); fileInput.addEventListener('file-add', () => { anyEventTriggered = true; }); fileInput.addEventListener('file-remove', () => { anyEventTriggered = true; }); }); // Attempt comprehensive interactions cy.get('.usa-file-input__target').click({ force: true }); cy.get('.usa-file-input__target').dblclick({ force: true }); cy.get('input[type="file"]').focus({ force: true }); cy.get('input[type="file"]').type('{enter}', { force: true }); cy.get('input[type="file"]').type(' ', { force: true }); // Attempt file selection cy.get('input[type="file"]').selectFile( { contents: Cypress.Buffer.from('comprehensive test'), fileName: 'comprehensive-test.txt', mimeType: 'text/plain', }, { force: true } ); cy.wait(100).then(() => { // Component should have ignored all interactions expect(anyEventTriggered).to.be.false; }); // Component should remain in disabled state cy.get('input[type="file"]').should('be.disabled'); cy.get('.usa-file-input').should('have.class', 'usa-file-input--disabled'); }); it('should handle disabled state transitions properly', () => { let stateTransitionHandled = false; cy.mount(` `); // Start enabled and add a file cy.get('input[type="file"]').selectFile( { contents: Cypress.Buffer.from('transition test'), fileName: 'transition-test.txt', mimeType: 'text/plain', }, { force: true } ); cy.get('.usa-file-input__preview').should('contain.text', 'transition-test.txt'); // Transition to disabled cy.window().then((win) => { const fileInput = win.document.getElementById('transition-test') as any; fileInput.disabled = true; stateTransitionHandled = true; }); cy.wait(100); // Should be properly disabled cy.get('input[type="file"]').should('be.disabled'); cy.get('.usa-file-input').should('have.class', 'usa-file-input--disabled'); // File should still be visible but not removable cy.get('.usa-file-input__preview').should('contain.text', 'transition-test.txt'); cy.get('.usa-file-input__preview-close').should('be.disabled'); // Transition back to enabled cy.window().then((win) => { const fileInput = win.document.getElementById('transition-test') as any; fileInput.disabled = false; }); cy.wait(100); // Should be properly enabled again cy.get('input[type="file"]').should('not.be.disabled'); cy.get('.usa-file-input').should('not.have.class', 'usa-file-input--disabled'); cy.get('.usa-file-input__preview-close').should('not.be.disabled'); expect(stateTransitionHandled).to.be.true; }); it('should respect disabled state in form contexts', () => { let formSubmitted = false; let disabledFieldIncluded = false; cy.mount(`
`); // Add file to enabled input cy.get('#enabled-form-input input[type="file"]').selectFile( { contents: Cypress.Buffer.from('enabled form test'), fileName: 'enabled-test.txt', mimeType: 'text/plain', }, { force: true } ); cy.window().then((win) => { const form = win.document.getElementById('disabled-form-test') as HTMLFormElement; form.addEventListener('submit', (e) => { e.preventDefault(); formSubmitted = true; const formData = new FormData(form); if (formData.has('disabled-file')) { disabledFieldIncluded = true; } }); }); // Submit form cy.get('button[type="submit"]') .click() .then(() => { expect(formSubmitted).to.be.true; expect(disabledFieldIncluded).to.be.false; // Disabled field should not be included }); }); }); // Edge Case Testing (Critical Gap Fix) describe('Edge Case Testing', () => { describe('Boundary Conditions', () => { it('should handle extremely large file sizes gracefully', () => { cy.mount(``); // Create large file that exceeds max size const largeContent = 'x'.repeat(2 * 1024 * 1024); // 2MB > 1MB limit cy.get('input[type="file"]').selectFile( { contents: Cypress.Buffer.from(largeContent), fileName: 'large-file.txt', mimeType: 'text/plain', }, { force: true } ); // Should show error for file too large cy.get('.usa-error-message').should('contain.text', 'file is too large'); // Component should remain functional cy.get('input[type="file"]').should('exist'); cy.get('.usa-file-input__target').should('be.visible'); }); it('should handle maximum number of files limit', () => { cy.mount(``); // Try to upload more files than the limit const files = Array.from({ length: 5 }, (_, i) => ({ contents: Cypress.Buffer.from(`content ${i}`), fileName: `file-${i}.txt`, mimeType: 'text/plain', })); cy.get('input[type="file"]').selectFile(files, { force: true }); // Should limit to max files and show appropriate message cy.get('.usa-file-input__preview').should('have.length.at.most', 3); // Should show error about too many files cy.get('.usa-error-message').should('contain.text', 'too many files'); }); it('should handle invalid file types without crashing', () => { cy.mount( `` ); // Upload unsupported file type cy.get('input[type="file"]').selectFile( { contents: Cypress.Buffer.from('executable content'), fileName: 'virus.exe', mimeType: 'application/octet-stream', }, { force: true } ); // Should reject file and show error cy.get('.usa-error-message').should('contain.text', 'file type not allowed'); // Component should remain functional cy.get('.usa-file-input__target').should('be.visible'); cy.get('input[type="file"]').should('exist'); }); it('should handle empty and corrupt files', () => { const testFiles = [ { name: 'empty.txt', content: '', description: 'empty file' }, { name: 'null.txt', content: null, description: 'null content' }, { name: 'corrupt.pdf', content: 'not-a-pdf', description: 'corrupt file' }, ]; testFiles.forEach(({ name, content, description }) => { cy.mount(``); cy.get('input[type="file"]').selectFile( { contents: content ? Cypress.Buffer.from(content) : Cypress.Buffer.alloc(0), fileName: name, mimeType: 'application/pdf', }, { force: true } ); // Should handle gracefully without crashing cy.get('usa-file-input').should('be.visible'); // May show warning or accept depending on validation rules cy.get('.usa-file-input__target').should('exist'); cy.get('usa-file-input').remove(); }); }); it('should handle files with special characters in names', () => { const specialFiles = [ 'file with spaces.txt', 'file@#$%^&*().txt', 'file[brackets].txt', 'file(parentheses).txt', 'файл-кириллица.txt', '文件中文名.txt', "file'quote.txt", 'file"doublequote.txt', ]; specialFiles.forEach((fileName) => { cy.mount(``); cy.get('input[type="file"]').selectFile( { contents: Cypress.Buffer.from('test content'), fileName: fileName, mimeType: 'text/plain', }, { force: true } ); // Should handle special characters without breaking cy.get('.usa-file-input__preview').should('be.visible'); cy.get('.usa-file-input__preview').should('contain.text', fileName); cy.get('usa-file-input').remove(); }); }); }); describe('Error Recovery', () => { it('should recover from drag and drop failures', () => { cy.mount(``); cy.window().then((win) => { const fileInput = win.document.getElementById('drag-recovery-test') as any; // Simulate failed drag operation fileInput.addEventListener('dragover', (e: DragEvent) => { e.preventDefault(); // Simulate error during drag throw new Error('Drag operation failed'); }); }); // Should handle drag failure gracefully cy.get('.usa-file-input__target') .trigger('dragenter') .trigger('dragover', { force: true }) .trigger('dragleave'); // Component should remain functional cy.get('.usa-file-input__target').should('be.visible'); cy.get('input[type="file"]').should('exist'); // Regular file selection should still work cy.get('input[type="file"]').selectFile( { contents: Cypress.Buffer.from('recovery test'), fileName: 'recovery.txt', mimeType: 'text/plain', }, { force: true } ); cy.get('.usa-file-input__preview').should('contain.text', 'recovery.txt'); }); it('should handle file reading failures gracefully', () => { cy.mount(``); cy.window().then((win) => { const fileInput = win.document.getElementById('read-failure-test') as any; // Mock FileReader to simulate failure const originalFileReader = win.FileReader; win.FileReader = class MockFileReader { readAsDataURL() { setTimeout(() => { if (this.onerror) { this.onerror(new Error('File read failed')); } }, 10); } readAsText() { setTimeout(() => { if (this.onerror) { this.onerror(new Error('File read failed')); } }, 10); } } as any; // Restore after test setTimeout(() => { win.FileReader = originalFileReader; }, 1000); }); cy.get('input[type="file"]').selectFile( { contents: Cypress.Buffer.from('test content'), fileName: 'read-failure.txt', mimeType: 'text/plain', }, { force: true } ); // Should handle read failure gracefully cy.get('.usa-file-input__preview').should('exist'); cy.get('usa-file-input').should('be.visible'); }); it('should recover from DOM manipulation during file operations', () => { cy.mount(`
`); cy.window().then((win) => { const container = win.document.getElementById('container'); const fileInput = win.document.getElementById('dom-manipulation-test') as any; // Remove and re-add during file operation fileInput.addEventListener('change', () => { setTimeout(() => { container?.removeChild(fileInput); setTimeout(() => { container?.appendChild(fileInput); }, 50); }, 10); }); }); cy.get('input[type="file"]').selectFile( { contents: Cypress.Buffer.from('dom test'), fileName: 'dom-test.txt', mimeType: 'text/plain', }, { force: true } ); cy.wait(200); // Should recover and be functional cy.get('#dom-manipulation-test').should('exist'); cy.get('#dom-manipulation-test input[type="file"]').should('exist'); }); it('should handle memory cleanup on file removal and re-addition', () => { cy.mount(`
`); cy.window().then((win) => { const container = win.document.getElementById('cleanup-container'); // Create and destroy multiple file inputs for (let i = 0; i < 10; i++) { const fileInput = win.document.createElement('usa-file-input') as any; fileInput.id = `cleanup-test-${i}`; fileInput.multiple = true; container?.appendChild(fileInput); // Simulate file operations const input = fileInput.querySelector('input[type="file"]'); if (input) { const event = new Event('change'); Object.defineProperty(event, 'target', { value: { files: [ { name: `file-${i}.txt`, size: 1024, type: 'text/plain', }, ], }, enumerable: true, }); input.dispatchEvent(event); } // Remove from DOM container?.removeChild(fileInput); } // Create final test input const finalInput = win.document.createElement('usa-file-input') as any; finalInput.id = 'final-cleanup-test'; container?.appendChild(finalInput); }); // Final input should work without performance issues cy.get('#final-cleanup-test input[type="file"]').selectFile( { contents: Cypress.Buffer.from('final test'), fileName: 'final.txt', mimeType: 'text/plain', }, { force: true } ); cy.get('#final-cleanup-test .usa-file-input__preview').should('contain.text', 'final.txt'); }); }); describe('Performance Stress Testing', () => { it('should handle rapid file selection and removal', () => { let operationTimes: number[] = []; cy.mount(``); cy.window().then((win) => { const fileInput = win.document.getElementById('rapid-operations') as any; fileInput.addEventListener('change', () => { operationTimes.push(performance.now()); }); }); // Rapidly add and remove files for (let i = 0; i < 10; i++) { cy.get('input[type="file"]').selectFile( { contents: Cypress.Buffer.from(`content ${i}`), fileName: `rapid-${i}.txt`, mimeType: 'text/plain', }, { force: true } ); if (i % 2 === 0) { cy.get('.usa-file-input__preview-close').first().click(); } } cy.wait(100).then(() => { // Should maintain consistent performance if (operationTimes.length > 1) { const intervals = operationTimes.slice(1).map((time, i) => time - operationTimes[i]); const avgInterval = intervals.reduce((a, b) => a + b, 0) / intervals.length; intervals.forEach((interval) => { expect(interval).to.be.lessThan(avgInterval * 3); }); } }); }); it('should handle large numbers of simultaneous file uploads', () => { const largeFileSet = Array.from({ length: 50 }, (_, i) => ({ contents: Cypress.Buffer.from(`file content ${i}`), fileName: `bulk-file-${i}.txt`, mimeType: 'text/plain', })); cy.mount(``); // Upload large number of files cy.get('input[type="file"]').selectFile(largeFileSet.slice(0, 25), { force: true }); // Should handle without performance degradation cy.get('.usa-file-input__preview').should('have.length', 25); // Should still be responsive cy.get('.usa-file-input__preview-close').first().click(); cy.get('.usa-file-input__preview').should('have.length', 24); }); it('should handle file validation performance under load', () => { let validationCount = 0; cy.mount( `` ); cy.window().then((win) => { const fileInput = win.document.getElementById('validation-performance') as any; fileInput.addEventListener('file-validation', () => { validationCount++; }); }); // Upload mix of valid and invalid files const mixedFiles = [ { name: 'valid1.txt', type: 'text/plain' }, { name: 'invalid1.exe', type: 'application/octet-stream' }, { name: 'valid2.pdf', type: 'application/pdf' }, { name: 'invalid2.zip', type: 'application/zip' }, { name: 'valid3.doc', type: 'application/msword' }, ].map((file) => ({ contents: Cypress.Buffer.from('test content'), fileName: file.name, mimeType: file.type, })); cy.get('input[type="file"]').selectFile(mixedFiles, { force: true }); // Should validate all files without performance issues cy.get('.usa-file-input__preview').should('have.length.at.least', 3); // Valid files cy.get('.usa-error-message').should('exist'); // Invalid file errors }); it('should handle preview generation stress test', () => { cy.mount(``); // Upload files that require different preview types const previewFiles = Array.from({ length: 15 }, (_, i) => { const types = ['text/plain', 'image/jpeg', 'application/pdf']; const extensions = ['.txt', '.jpg', '.pdf']; const type = types[i % 3]; const ext = extensions[i % 3]; return { contents: Cypress.Buffer.from(`preview content ${i}`), fileName: `preview-${i}${ext}`, mimeType: type, }; }); cy.get('input[type="file"]').selectFile(previewFiles, { force: true }); // Should generate previews without performance degradation cy.get('.usa-file-input__preview').should('have.length', 15); // All previews should be responsive cy.get('.usa-file-input__preview-close').each(($closeBtn) => { cy.wrap($closeBtn).should('be.visible'); }); }); }); describe('Mobile Compatibility', () => { it('should handle touch file selection', () => { cy.mount(``); // Touch events on file input area cy.get('.usa-file-input__target').trigger('touchstart').trigger('touchend'); // Should remain functional cy.get('.usa-file-input__target').should('be.visible'); cy.get('input[type="file"]').should('exist'); // File selection should work after touch cy.get('input[type="file"]').selectFile( { contents: Cypress.Buffer.from('touch test'), fileName: 'touch-test.txt', mimeType: 'text/plain', }, { force: true } ); cy.get('.usa-file-input__preview').should('contain.text', 'touch-test.txt'); }); it('should handle mobile viewport constraints', () => { cy.mount(``); // Test mobile viewport cy.viewport(320, 568); cy.get('input[type="file"]').selectFile( [ { contents: Cypress.Buffer.from('mobile file 1'), fileName: 'mobile-file-1.txt', mimeType: 'text/plain', }, { contents: Cypress.Buffer.from('mobile file 2'), fileName: 'mobile-file-2.txt', mimeType: 'text/plain', }, ], { force: true } ); // Should display files appropriately in mobile layout cy.get('.usa-file-input__preview').should('have.length', 2); cy.get('.usa-file-input__preview').each(($preview) => { cy.wrap($preview).should('be.visible'); }); // Should handle file removal on mobile cy.get('.usa-file-input__preview-close').first().click(); cy.get('.usa-file-input__preview').should('have.length', 1); }); it('should handle orientation changes during file operations', () => { cy.mount(``); // Portrait mode cy.viewport(320, 568); cy.get('input[type="file"]').selectFile( { contents: Cypress.Buffer.from('orientation test'), fileName: 'orientation.txt', mimeType: 'text/plain', }, { force: true } ); cy.get('.usa-file-input__preview').should('contain.text', 'orientation.txt'); // Landscape mode (simulate rotation) cy.viewport(568, 320); cy.get('.usa-file-input__preview').should('be.visible'); // Should remain functional after orientation change cy.get('.usa-file-input__preview-close').click(); cy.get('.usa-file-input__preview').should('not.exist'); }); it('should handle limited storage scenarios', () => { cy.mount(``); cy.window().then((win) => { // Mock storage quota exceeded const originalCreateObjectURL = win.URL.createObjectURL; win.URL.createObjectURL = function () { throw new DOMException('Quota exceeded', 'QuotaExceededError'); }; // Restore after test setTimeout(() => { win.URL.createObjectURL = originalCreateObjectURL; }, 1000); }); cy.get('input[type="file"]').selectFile( { contents: Cypress.Buffer.from('storage test'), fileName: 'storage-test.txt', mimeType: 'text/plain', }, { force: true } ); // Should handle storage limitation gracefully cy.get('usa-file-input').should('be.visible'); cy.get('.usa-file-input__target').should('exist'); }); }); describe('Accessibility Edge Cases', () => { it('should handle screen reader with file operations', () => { cy.mount( `` ); // Should have proper ARIA attributes cy.get('input[type="file"]').should('have.attr', 'aria-label', 'Document Upload'); cy.get('input[type="file"]').selectFile( [ { contents: Cypress.Buffer.from('accessible file 1'), fileName: 'accessible-1.txt', mimeType: 'text/plain', }, { contents: Cypress.Buffer.from('accessible file 2'), fileName: 'accessible-2.txt', mimeType: 'text/plain', }, ], { force: true } ); // File previews should be accessible cy.get('.usa-file-input__preview').each(($preview) => { cy.wrap($preview).should('have.attr', 'role'); }); // Remove buttons should be accessible cy.get('.usa-file-input__preview-close').each(($closeBtn) => { cy.wrap($closeBtn).should('have.attr', 'aria-label'); }); }); it('should handle high contrast mode for file interface', () => { cy.mount(``); // Check drag area has sufficient contrast cy.get('.usa-file-input__target').then(($target) => { const styles = window.getComputedStyle($target[0]); expect(styles.borderColor).to.not.equal('transparent'); expect(styles.backgroundColor).to.not.equal('transparent'); }); cy.get('input[type="file"]').selectFile( { contents: Cypress.Buffer.from('contrast test'), fileName: 'contrast.txt', mimeType: 'text/plain', }, { force: true } ); // File preview should have sufficient contrast cy.get('.usa-file-input__preview').then(($preview) => { const styles = window.getComputedStyle($preview[0]); expect(styles.color).to.not.equal('transparent'); expect(styles.backgroundColor).to.not.equal('transparent'); }); }); it('should handle keyboard navigation with multiple files', () => { cy.mount(``); cy.get('input[type="file"]').selectFile( [ { contents: Cypress.Buffer.from('kbd file 1'), fileName: 'kbd-1.txt', mimeType: 'text/plain', }, { contents: Cypress.Buffer.from('kbd file 2'), fileName: 'kbd-2.txt', mimeType: 'text/plain', }, { contents: Cypress.Buffer.from('kbd file 3'), fileName: 'kbd-3.txt', mimeType: 'text/plain', }, ], { force: true } ); // Should be able to navigate through file remove buttons cy.get('.usa-file-input__preview-close').first().focus(); cy.focused().should('match', '.usa-file-input__preview-close'); // Tab to next remove button cy.focused().tab(); cy.focused().should('match', '.usa-file-input__preview-close'); // Space/Enter should remove file cy.focused().type(' '); cy.get('.usa-file-input__preview').should('have.length', 2); }); it('should handle focus management with dynamic file list', () => { cy.mount(`
`); cy.get('input[type="file"]').selectFile( [ { contents: Cypress.Buffer.from('focus 1'), fileName: 'focus-1.txt', mimeType: 'text/plain', }, { contents: Cypress.Buffer.from('focus 2'), fileName: 'focus-2.txt', mimeType: 'text/plain', }, ], { force: true } ); // Focus should move properly through the interface cy.get('#focus-before').focus().tab(); cy.focused().should('match', 'input[type="file"]'); cy.focused().tab(); cy.focused().should('match', '.usa-file-input__preview-close'); cy.focused().tab(); cy.focused().should('match', '.usa-file-input__preview-close'); cy.focused().tab(); cy.focused().should('match', '#focus-after'); }); it('should announce file operations to screen readers', () => { cy.mount(`
`); cy.window().then((win) => { const fileInput = win.document.getElementById('sr-announce') as any; const liveRegion = win.document.getElementById('sr-live-region'); fileInput.addEventListener('file-added', (e: CustomEvent) => { if (liveRegion) { liveRegion.textContent = `File added: ${e.detail.fileName}`; } }); fileInput.addEventListener('file-removed', (e: CustomEvent) => { if (liveRegion) { liveRegion.textContent = `File removed: ${e.detail.fileName}`; } }); }); // Add file cy.get('input[type="file"]').selectFile( { contents: Cypress.Buffer.from('screen reader test'), fileName: 'sr-test.txt', mimeType: 'text/plain', }, { force: true } ); // Remove file cy.get('.usa-file-input__preview-close').click(); // Live region should contain announcements cy.get('#sr-live-region').should('not.be.empty'); }); }); describe('Browser Compatibility Edge Cases', () => { it('should handle different file API support levels', () => { cy.mount(``); cy.window().then((win) => { // Mock limited File API support const originalFileReader = win.FileReader; delete (win as any).FileReader; // Component should fall back gracefully cy.get('input[type="file"]').selectFile( { contents: Cypress.Buffer.from('api compat test'), fileName: 'api-compat.txt', mimeType: 'text/plain', }, { force: true } ); // Should still function without full File API cy.get('.usa-file-input__preview').should('contain.text', 'api-compat.txt'); // Restore FileReader (win as any).FileReader = originalFileReader; }); }); it('should handle drag and drop API differences', () => { cy.mount(``); cy.window().then((win) => { // Mock older drag API const originalDataTransfer = win.DataTransfer; win.DataTransfer = class MockDataTransfer { files: FileList = [] as any; types: string[] = []; getData() { return ''; } setData() {} } as any; // Should handle gracefully cy.get('.usa-file-input__target') .trigger('dragenter') .trigger('dragover') .trigger('drop'); // Component should remain functional cy.get('.usa-file-input__target').should('be.visible'); // Restore DataTransfer win.DataTransfer = originalDataTransfer; }); }); it('should handle MIME type detection variations', () => { const mimeVariations = [ { fileName: 'test.txt', providedMime: 'text/plain', expectedBehavior: 'accept' }, { fileName: 'test.pdf', providedMime: 'application/pdf', expectedBehavior: 'accept' }, { fileName: 'test.unknown', providedMime: 'application/octet-stream', expectedBehavior: 'handle gracefully', }, { fileName: 'test.txt', providedMime: '', expectedBehavior: 'fallback detection' }, ]; mimeVariations.forEach(({ fileName, providedMime, expectedBehavior }) => { cy.mount(``); cy.get('input[type="file"]').selectFile( { contents: Cypress.Buffer.from('mime test'), fileName: fileName, mimeType: providedMime, }, { force: true } ); // Should handle MIME type variations appropriately cy.get('usa-file-input').should('be.visible'); cy.get('usa-file-input').remove(); }); }); }); }); });