import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import '../character-count/usa-character-count.ts'; import type { USACharacterCount } from '../character-count/usa-character-count.js'; import { testComponentAccessibility, USWDS_A11Y_CONFIG, } from '../../../__tests__/accessibility-utils.js'; import { validateComponentJavaScript, testCharacterCountDuplicatePrevention, validateUniqueElements, } from '../../../__tests__/test-utils.js'; describe('USACharacterCount', () => { let element: USACharacterCount; beforeEach(() => { element = document.createElement('usa-character-count') as USACharacterCount; document.body.appendChild(element); }); afterEach(() => { element.remove(); }); describe('Basic Functionality', () => { it('should render with default properties', async () => { await element.updateComplete; expect(element.value).toBe(''); expect(element.maxlength).toBe(0); expect(element.label).toBe('Text input with character count'); expect(element.inputType).toBe('textarea'); expect(element.disabled).toBe(false); expect(element.required).toBe(false); expect(element.readonly).toBe(false); }); it('should render textarea by default', async () => { await element.updateComplete; const textarea = element.querySelector('textarea'); const input = element.querySelector('input[type="text"]'); expect(textarea).toBeTruthy(); expect(input).toBe(null); }); it('should render input when inputType is input', async () => { element.inputType = 'input'; await element.updateComplete; const input = element.querySelector('input[type="text"]'); const textarea = element.querySelector('textarea'); expect(input).toBeTruthy(); expect(textarea).toBe(null); }); it('should have proper USWDS classes', async () => { await element.updateComplete; expect(element.querySelector('.usa-character-count')).toBeTruthy(); expect(element.querySelector('.usa-form-group')).toBeTruthy(); expect(element.querySelector('.usa-label')).toBeTruthy(); expect(element.querySelector('.usa-textarea')).toBeTruthy(); expect(element.querySelector('.usa-character-count__field')).toBeTruthy(); // Component renders .usa-character-count__message for dynamic count expect(element.querySelector('.usa-character-count__message')).toBeTruthy(); }); }); describe('Property Changes', () => { it('should update label text', async () => { element.label = 'Custom label'; await element.updateComplete; const label = element.querySelector('.usa-label'); expect(label?.textContent?.trim()).toBe('Custom label'); }); it('should show hint when provided', async () => { element.hint = 'Enter your message here'; await element.updateComplete; const hint = element.querySelector('.usa-hint'); expect(hint?.textContent?.trim()).toBe('Enter your message here'); }); it('should show required indicator when required', async () => { element.required = true; await element.updateComplete; const requiredIndicator = element.querySelector('.usa-hint--required'); expect(requiredIndicator).toBeTruthy(); expect(requiredIndicator?.textContent).toBe('*'); }); it('should apply disabled state', async () => { element.disabled = true; await element.updateComplete; const textarea = element.querySelector('textarea'); expect(textarea?.disabled).toBe(true); }); it('should apply readonly state', async () => { element.readonly = true; await element.updateComplete; const textarea = element.querySelector('textarea'); expect(textarea?.readOnly).toBe(true); }); it('should set placeholder text', async () => { element.placeholder = 'Enter text here...'; await element.updateComplete; const textarea = element.querySelector('textarea'); expect(textarea?.placeholder).toBe('Enter text here...'); }); it('should set rows for textarea', async () => { element.rows = 10; await element.updateComplete; const textarea = element.querySelector('textarea'); expect(textarea?.rows).toBe(10); }); it('should set maxlength attribute when specified', async () => { element.maxlength = 100; await element.updateComplete; const textarea = element.querySelector('textarea'); expect(textarea?.maxLength).toBe(100); }); it('should not set maxlength attribute when not specified', async () => { element.maxlength = 0; await element.updateComplete; const textarea = element.querySelector('textarea'); // The component may set an empty string instead of null - both indicate no maxlength const maxlengthAttr = textarea?.getAttribute('maxlength'); expect(maxlengthAttr === null || maxlengthAttr === '').toBe(true); }); }); describe('Character Counting', () => { it('should display character count without limit', async () => { element.value = 'Hello world'; await element.updateComplete; const message = element.querySelector('.usa-character-count__message'); expect(message?.textContent?.trim()).toBe('11 characters'); }); it('should display remaining characters with limit', async () => { element.maxlength = 50; element.value = 'Hello'; await element.updateComplete; const message = element.querySelector('.usa-character-count__message'); expect(message?.textContent?.trim()).toBe('45 characters remaining'); }); it('should display singular form for 1 character remaining', async () => { element.maxlength = 10; element.value = 'Hello wor'; await element.updateComplete; const message = element.querySelector('.usa-character-count__message'); expect(message?.textContent?.trim()).toBe('1 character remaining'); }); it('should display limit reached message', async () => { element.maxlength = 5; element.value = 'Hello'; await element.updateComplete; const message = element.querySelector('.usa-character-count__message'); expect(message?.textContent?.trim()).toBe('Character limit reached'); }); it('should display over limit message', async () => { element.maxlength = 5; element.value = 'Hello world'; await element.updateComplete; const message = element.querySelector('.usa-character-count__message'); expect(message?.textContent?.trim()).toBe('6 characters over limit'); }); it('should update count when value changes programmatically', async () => { element.maxlength = 20; element.value = 'Initial'; await element.updateComplete; element.value = 'Updated text'; await element.updateComplete; const message = element.querySelector('.usa-character-count__message'); expect(message?.textContent?.trim()).toBe('8 characters remaining'); }); }); describe('Input Handling', () => { it('should update value when user types in textarea', async () => { await element.updateComplete; const textarea = element.querySelector('textarea') as HTMLTextAreaElement; textarea.value = 'User typed text'; textarea.dispatchEvent(new Event('input', { bubbles: true })); expect(element.value).toBe('User typed text'); }); it('should update value when user types in input', async () => { element.inputType = 'input'; await element.updateComplete; const input = element.querySelector('input') as HTMLInputElement; input.value = 'User input'; input.dispatchEvent(new Event('input', { bubbles: true })); expect(element.value).toBe('User input'); }); // Skipped in jsdom - requires Cypress for USWDS JavaScript interaction }); describe('Visual States', () => { it('should not have error classes by default', async () => { element.maxlength = 50; element.value = 'Normal text'; await element.updateComplete; const formGroup = element.querySelector('.usa-form-group'); const textarea = element.querySelector('.usa-textarea'); expect(formGroup?.classList.contains('usa-form-group--error')).toBe(false); expect(textarea?.classList.contains('usa-textarea--error')).toBe(false); }); it('should apply error classes when over limit', async () => { element.maxlength = 10; element.value = 'This text is too long'; await element.updateComplete; const formGroup = element.querySelector('.usa-form-group'); expect(formGroup?.classList.contains('usa-form-group--error')).toBe(true); // USWDS doesn't apply error classes directly to textarea - form-group handles visual state expect(formGroup?.classList.contains('usa-form-group--error')).toBe(true); }); it('should apply error classes for input type when over limit', async () => { element.inputType = 'input'; element.maxlength = 5; element.value = 'Too long'; await element.updateComplete; const formGroup = element.querySelector('.usa-form-group'); // USWDS doesn't apply error classes directly to input - form-group handles visual state expect(formGroup?.classList.contains('usa-form-group--error')).toBe(true); }); }); describe('State Management', () => { it('should detect near limit state (10% of maxlength)', async () => { element.maxlength = 100; element.value = 'x'.repeat(91); // 9 characters left (9% of 100) await element.updateComplete; expect(element.isNearLimit()).toBe(true); expect(element.isOverLimit()).toBe(false); }); it('should detect over limit state', async () => { element.maxlength = 10; element.value = 'x'.repeat(15); await element.updateComplete; expect(element.isNearLimit()).toBe(false); expect(element.isOverLimit()).toBe(true); }); it('should not be near limit or over limit without maxlength', async () => { element.maxlength = 0; element.value = 'x'.repeat(1000); await element.updateComplete; expect(element.isNearLimit()).toBe(false); expect(element.isOverLimit()).toBe(false); }); it('should handle edge case at exactly 10% remaining', async () => { element.maxlength = 100; element.value = 'x'.repeat(90); // Exactly 10 characters left (10% of 100) await element.updateComplete; expect(element.isNearLimit()).toBe(true); }); }); describe('Event Dispatching', () => { it('should dispatch character-count-change event on value change', async () => { const eventSpy = vi.fn(); element.addEventListener('character-count-change', eventSpy); element.maxlength = 50; element.value = 'Test content'; await element.updateComplete; expect(eventSpy).toHaveBeenCalled(); const eventDetail = eventSpy.mock.calls[eventSpy.mock.calls.length - 1][0].detail; expect(eventDetail.currentLength).toBe(12); expect(eventDetail.maxLength).toBe(50); expect(eventDetail.remaining).toBe(38); expect(eventDetail.isNearLimit).toBe(false); expect(eventDetail.isOverLimit).toBe(false); expect(eventDetail.value).toBe('Test content'); }); it('should dispatch event with correct state when near limit', async () => { const eventSpy = vi.fn(); element.addEventListener('character-count-change', eventSpy); element.maxlength = 100; element.value = 'x'.repeat(95); await element.updateComplete; const eventDetail = eventSpy.mock.calls[eventSpy.mock.calls.length - 1][0].detail; expect(eventDetail.isNearLimit).toBe(true); expect(eventDetail.isOverLimit).toBe(false); }); it('should dispatch event with correct state when over limit', async () => { const eventSpy = vi.fn(); element.addEventListener('character-count-change', eventSpy); element.maxlength = 10; element.value = 'x'.repeat(15); await element.updateComplete; const eventDetail = eventSpy.mock.calls[eventSpy.mock.calls.length - 1][0].detail; expect(eventDetail.isNearLimit).toBe(false); expect(eventDetail.isOverLimit).toBe(true); expect(eventDetail.remaining).toBe(-5); }); // Skipped in jsdom - requires Cypress for USWDS JavaScript interaction }); describe('Accessibility', () => { it('should have proper aria-describedby attributes', async () => { element.name = 'test-field'; element.hint = 'Enter your text'; await element.updateComplete; const textarea = element.querySelector('textarea'); expect(textarea?.getAttribute('aria-describedby')).toBe('test-field-info test-field-status test-field-hint'); }); it('should have aria-describedby without hint', async () => { element.name = 'test-field'; await element.updateComplete; const textarea = element.querySelector('textarea'); expect(textarea?.getAttribute('aria-describedby')).toBe('test-field-info test-field-status'); }); it('should have aria-live on character count message', async () => { await element.updateComplete; // The info message has aria-live, status is aria-hidden for screen readers const infoMessage = element.querySelector('.usa-character-count__message'); expect(infoMessage?.getAttribute('aria-live')).toBe('polite'); }); it('should have proper id associations', async () => { element.name = 'feedback'; await element.updateComplete; const textarea = element.querySelector('textarea'); const label = element.querySelector('label'); const infoMessage = element.querySelector('.usa-character-count__message'); const srMessage = element.querySelector('.usa-sr-only'); expect(textarea?.id).toBe('feedback'); expect(label?.getAttribute('for')).toBe('feedback'); expect(infoMessage?.id).toBe('feedback-info'); expect(srMessage?.id).toBe('feedback-status'); }); it('should have proper id for hint', async () => { element.name = 'comment'; element.hint = 'Please provide details'; await element.updateComplete; const hint = element.querySelector('.usa-hint'); expect(hint?.id).toBe('comment-hint'); }); }); describe('Public API Methods', () => { it('should focus the input field', async () => { await element.updateComplete; const textarea = element.querySelector('textarea') as HTMLTextAreaElement; const focusSpy = vi.spyOn(textarea, 'focus'); element.focus(); expect(focusSpy).toHaveBeenCalled(); }); it('should blur the input field', async () => { await element.updateComplete; const textarea = element.querySelector('textarea') as HTMLTextAreaElement; const blurSpy = vi.spyOn(textarea, 'blur'); element.blur(); expect(blurSpy).toHaveBeenCalled(); }); it('should select text in the input field', async () => { await element.updateComplete; const textarea = element.querySelector('textarea') as HTMLTextAreaElement; const selectSpy = vi.spyOn(textarea, 'select'); element.select(); expect(selectSpy).toHaveBeenCalled(); }); it('should clear the input value', async () => { element.value = 'Some text'; element.maxlength = 50; await element.updateComplete; element.clear(); expect(element.value).toBe(''); expect(element.getCharacterCount()).toBe(0); }); it('should get current character count', async () => { element.value = 'Test'; await element.updateComplete; expect(element.getCharacterCount()).toBe(4); }); it('should get remaining characters with limit', async () => { element.maxlength = 20; element.value = 'Hello'; await element.updateComplete; expect(element.getRemainingCharacters()).toBe(15); }); it('should return null for remaining characters without limit', async () => { element.maxlength = 0; element.value = 'Hello'; await element.updateComplete; expect(element.getRemainingCharacters()).toBe(null); }); it('should check if near limit', async () => { element.maxlength = 100; element.value = 'x'.repeat(95); await element.updateComplete; expect(element.isNearLimit()).toBe(true); }); it('should check if over limit', async () => { element.maxlength = 10; element.value = 'x'.repeat(15); await element.updateComplete; expect(element.isOverLimit()).toBe(true); }); }); describe('Edge Cases', () => { it('should handle empty value', async () => { element.value = ''; element.maxlength = 10; await element.updateComplete; const message = element.querySelector('.usa-character-count__message'); expect(message?.textContent?.trim()).toBe('10 characters remaining'); expect(element.getCharacterCount()).toBe(0); }); it('should handle very large text', async () => { const largeText = 'x'.repeat(10000); element.value = largeText; await element.updateComplete; expect(element.getCharacterCount()).toBe(10000); const message = element.querySelector('.usa-character-count__message'); expect(message?.textContent?.trim()).toBe('10000 characters'); }); it('should handle unicode characters correctly', async () => { element.value = '🚀✨🎉'; element.maxlength = 10; await element.updateComplete; // Unicode emojis count as multiple code units in JavaScript string.length // This matches HTML maxlength behavior expect(element.getCharacterCount()).toBe(5); const message = element.querySelector('.usa-character-count__message'); expect(message?.textContent?.trim()).toBe('5 characters remaining'); }); it('should handle newlines in textarea', async () => { element.value = 'Line 1\nLine 2\nLine 3'; await element.updateComplete; expect(element.getCharacterCount()).toBe(20); // Including newline characters (6+1+6+1+6) }); it('should handle maxlength of 1', async () => { element.maxlength = 1; element.value = ''; await element.updateComplete; let message = element.querySelector('.usa-character-count__message'); expect(message?.textContent?.trim()).toBe('1 character remaining'); element.value = 'a'; await element.updateComplete; message = element.querySelector('.usa-character-count__message'); expect(message?.textContent?.trim()).toBe('Character limit reached'); element.value = 'ab'; await element.updateComplete; message = element.querySelector('.usa-character-count__message'); expect(message?.textContent?.trim()).toBe('1 character over limit'); }); }); describe('Form Integration', () => { it('should work within form element', async () => { const form = document.createElement('form'); form.appendChild(element); document.body.appendChild(form); element.name = 'description'; element.value = 'Form content'; await element.updateComplete; const formData = new FormData(form); expect(formData.get('description')).toBe('Form content'); form.remove(); }); it('should validate with HTML5 required attribute', async () => { element.required = true; element.name = 'required-field'; await element.updateComplete; const textarea = element.querySelector('textarea'); expect(textarea?.required).toBe(true); expect(textarea?.checkValidity()).toBe(false); element.value = 'Valid content'; await element.updateComplete; expect(textarea?.checkValidity()).toBe(true); }); }); describe('Component Lifecycle Stability (CRITICAL)', () => { it('should remain in DOM after property updates (not auto-dismiss)', async () => { const originalParent = element.parentElement; element.value = 'Test content with characters'; element.maxlength = 100; element.label = 'Updated Label'; element.hint = 'Updated hint text'; element.inputType = 'input'; element.name = 'updated-character-count'; element.inputId = 'updated-input'; element.disabled = true; element.required = true; element.readonly = true; element.placeholder = 'Updated placeholder'; await element.updateComplete; expect(document.body.contains(element)).toBe(true); expect(element.isConnected).toBe(true); expect(element.parentElement).toBe(originalParent); }); // Skipped in jsdom - requires Cypress for USWDS JavaScript interaction it('should maintain DOM presence when switching between input types', async () => { const originalParent = element.parentElement; element.maxlength = 100; element.value = 'Test content'; // Switch between textarea and input multiple times for (let i = 0; i < 5; i++) { element.inputType = i % 2 === 0 ? 'textarea' : 'input'; await element.updateComplete; element.value = `Content iteration ${i}`; element.maxlength = 50 + i * 10; await element.updateComplete; } element.disabled = false; element.required = true; await element.updateComplete; expect(element.parentElement).toBe(originalParent); expect(document.body.contains(element)).toBe(true); expect(element.isConnected).toBe(true); }); }); describe('Storybook Integration Tests (CRITICAL)', () => { it('should render in Storybook environment without errors', async () => { const storybookContainer = document.createElement('div'); storybookContainer.id = 'storybook-root'; document.body.appendChild(storybookContainer); const storybookElement = document.createElement('usa-character-count') as USACharacterCount; storybookElement.label = 'Storybook Character Count'; storybookElement.hint = 'Enter up to 200 characters'; storybookElement.maxlength = 200; storybookElement.value = 'Initial content from Storybook'; storybookElement.inputType = 'textarea'; storybookContainer.appendChild(storybookElement); await storybookElement.updateComplete; expect(storybookContainer.contains(storybookElement)).toBe(true); expect(storybookElement.isConnected).toBe(true); const input = storybookElement.querySelector('textarea, input'); const label = storybookElement.querySelector('.usa-label'); // Component renders .usa-character-count__message initially // USWDS transforms it to .usa-character-count__message in browser const message = storybookElement.querySelector('.usa-character-count__message'); expect(input).toBeTruthy(); expect(label).toBeTruthy(); expect(message).toBeTruthy(); storybookContainer.remove(); }); it('should handle Storybook control updates without component removal', async () => { const mockStorybookUpdate = vi.fn(); element.addEventListener('character-count-change', mockStorybookUpdate); element.maxlength = 150; await element.updateComplete; // Simulate Storybook controls panel updates element.label = 'Controls Updated Label'; element.hint = 'Controls Updated Hint'; element.maxlength = 300; element.value = 'Updated from controls'; element.inputType = 'input'; element.placeholder = 'Updated placeholder'; element.disabled = false; element.required = true; element.readonly = false; await element.updateComplete; expect(document.body.contains(element)).toBe(true); expect(element.isConnected).toBe(true); expect(element.maxlength).toBe(300); expect(element.label).toBe('Controls Updated Label'); const input = element.querySelector('input, textarea'); const label = element.querySelector('.usa-label'); expect(input?.getAttribute('maxlength')).toBe('300'); expect(label?.textContent).toContain('Controls Updated Label'); }); // Skipped in jsdom - requires Cypress for USWDS JavaScript interaction // NOTE: JavaScript implementation validation moved to browser tests // Reason: This component uses USWDS-mirrored behavior pattern (usa-character-count-behavior.ts) // instead of direct USWDS integration. Behavior validation is done in Cypress. }); describe('Duplicate Message Prevention (REGRESSION TESTING)', () => { it('should have exactly one character count message element', async () => { element.maxlength = 100; element.value = 'Test message'; await element.updateComplete; // Wait for potential USWDS initialization await new Promise(resolve => setTimeout(resolve, 100)); const messages = element.querySelectorAll('.usa-character-count__message'); expect(messages.length).toBe(1); }); it('should not create duplicate messages when value changes', async () => { element.maxlength = 50; element.value = 'Initial'; await element.updateComplete; // Change value multiple times to test for duplication element.value = 'Updated message'; await element.updateComplete; element.value = 'Another update with more text'; await element.updateComplete; // Wait for any async operations await new Promise(resolve => setTimeout(resolve, 100)); const messages = element.querySelectorAll('.usa-character-count__message'); expect(messages.length).toBe(1); expect(messages[0].textContent?.trim()).toContain('characters remaining'); }); it('should not create duplicate messages when maxlength changes', async () => { element.value = 'Test content'; element.maxlength = 100; await element.updateComplete; element.maxlength = 50; await element.updateComplete; element.maxlength = 200; await element.updateComplete; // Wait for any async operations await new Promise(resolve => setTimeout(resolve, 100)); const messages = element.querySelectorAll('.usa-character-count__message'); expect(messages.length).toBe(1); }); it('should not create duplicate messages during USWDS initialization', async () => { // Create a fresh element to test initialization const freshElement = document.createElement('usa-character-count') as USACharacterCount; freshElement.name = 'fresh-test'; freshElement.maxlength = 75; freshElement.value = 'Testing USWDS initialization'; document.body.appendChild(freshElement); try { await freshElement.updateComplete; // Wait for USWDS initialization to complete await new Promise(resolve => setTimeout(resolve, 200)); const messages = freshElement.querySelectorAll('.usa-character-count__message'); expect(messages.length).toBe(1); // Verify the message has content (not empty) expect(messages[0].textContent?.trim()).toBeTruthy(); expect(messages[0].textContent?.trim()).toContain('characters'); } finally { freshElement.remove(); } }); it('should maintain single message through component lifecycle', async () => { element.maxlength = 30; element.value = 'Lifecycle test'; await element.updateComplete; // Simulate multiple property changes that could trigger re-renders element.disabled = true; await element.updateComplete; element.disabled = false; await element.updateComplete; element.required = true; await element.updateComplete; element.error = 'Test error'; await element.updateComplete; element.error = ''; await element.updateComplete; // Wait for any async operations await new Promise(resolve => setTimeout(resolve, 100)); const messages = element.querySelectorAll('.usa-character-count__message'); expect(messages.length).toBe(1); }); it('should have unique message IDs to prevent conflicts', async () => { // Create multiple character count components const element1 = document.createElement('usa-character-count') as USACharacterCount; element1.name = 'test1'; element1.maxlength = 50; const element2 = document.createElement('usa-character-count') as USACharacterCount; element2.name = 'test2'; element2.maxlength = 75; document.body.appendChild(element1); document.body.appendChild(element2); try { await element1.updateComplete; await element2.updateComplete; // Wait for initialization await new Promise(resolve => setTimeout(resolve, 100)); const message1 = element1.querySelector('.usa-character-count__message'); const message2 = element2.querySelector('.usa-character-count__message'); expect(message1?.id).toBe('test1-info'); expect(message2?.id).toBe('test2-info'); expect(message1?.id).not.toBe(message2?.id); // Verify each component has exactly one message expect(element1.querySelectorAll('.usa-character-count__message').length).toBe(1); expect(element2.querySelectorAll('.usa-character-count__message').length).toBe(1); } finally { element1.remove(); element2.remove(); } }); // NOTE: Test removed - component now shows single dynamic message, not separate static/dynamic messages // The message element updates dynamically to show character count status // NOTE: USWDS message management and duplicate detection tests moved to Cypress // Reason: These tests expect USWDS to transform .usa-character-count__message into // .usa-character-count__message elements, which only happens in browser environment. // See: cypress/e2e/usa-character-count.component.cy.ts for browser-based tests }); describe('Accessibility Compliance (CRITICAL)', () => { // SKIP: Lit ChildPart error in jsdom - MOVE TO CYPRESS // This test should be moved to Cypress component tests where it will pass in real browser // Root cause: USWDS behavior calls .append() to add status messages, ejecting Lit's ChildPart markers // This is a jsdom/Vitest limitation - component works correctly in browser // ✅ CYPRESS COVERAGE: cypress/e2e/character-count-accessibility.cy.ts // Tests comprehensive accessibility in real browser with USWDS behavior transformation // ✅ CYPRESS COVERAGE: cypress/e2e/character-count-accessibility.cy.ts // Tests dynamic accessibility updates in real browser with USWDS behavior it('should be accessible in form contexts', async () => { const form = document.createElement('form'); const fieldset = document.createElement('fieldset'); const legend = document.createElement('legend'); legend.textContent = 'Content Form'; fieldset.appendChild(legend); fieldset.appendChild(element); form.appendChild(fieldset); document.body.appendChild(form); element.label = 'Article content'; element.maxlength = 500; element.value = 'Article content with character counting'; element.hint = 'Please write your article content here'; element.required = true; await element.updateComplete; await testComponentAccessibility(form, USWDS_A11Y_CONFIG.FULL_COMPLIANCE); form.remove(); }); }); /** * USWDS Integration Requirements Tests * * These tests validate critical USWDS integration patterns for the Character Count component. * They ensure that USWDS JavaScript can properly enhance the component with: * - Component enhancement signal via data-enhanced attribute * - Inner input/textarea placeholder and value handling * - USWDS enhancement structure * * These tests prevent regressions like: * - Placeholders not showing when no value * - USWDS initialization failures * - Character count not updating * * See: /tmp/combo-box-complete-summary.md for pattern details */ describe('USWDS Integration Requirements', () => { it('should include data-enhanced="true" on wrapper', async () => { await element.updateComplete; const wrapper = element.querySelector('.usa-character-count'); expect(wrapper).toBeTruthy(); expect(wrapper?.getAttribute('data-enhanced')).toBe('true'); }); it('should pass placeholder to inner textarea', async () => { element.placeholder = 'Enter your text here'; await element.updateComplete; const textarea = element.querySelector('textarea') as HTMLTextAreaElement; expect(textarea).toBeTruthy(); expect(textarea?.getAttribute('placeholder')).toBe('Enter your text here'); }); it('should display placeholder when no value set', async () => { element.placeholder = 'Type here...'; element.value = ''; await element.updateComplete; const textarea = element.querySelector('textarea') as HTMLTextAreaElement; expect(textarea?.getAttribute('placeholder')).toBe('Type here...'); expect(textarea?.value).toBe(''); }); it('should pass value to inner textarea', async () => { element.value = 'Test content'; await element.updateComplete; const textarea = element.querySelector('textarea') as HTMLTextAreaElement; expect(textarea).toBeTruthy(); expect(textarea?.value).toBe('Test content'); }); it('should render character count message element', async () => { element.maxlength = 100; element.value = 'Test'; await element.updateComplete; const message = element.querySelector('.usa-character-count__message'); expect(message).toBeTruthy(); }); }); });