import { GovukComponentTestHelper } from '../../test-utils/GovukComponentTestHelper' import { setupComponentTest } from '../../test-utils/setupComponentTest' import { GovUKCharacterCount } from './govukCharacterCount' vi.mock('nunjucks') describe('govukCharacterCount', () => { setupComponentTest() const helper = new GovukComponentTestHelper(GovUKCharacterCount) describe('Data transformation', () => { it('sets default values correctly', async () => { const params = await helper.getParams({ code: 'test-field' }) expect(params.id).toBe('test-field') expect(params.name).toBe('test-field') expect(params.rows).toBe('5') expect(params.value).toBeUndefined() }) it('uses custom ID over code', async () => { const params = await helper.getParams({ code: 'test-field', id: 'custom-id', }) expect(params.id).toBe('custom-id') expect(params.name).toBe('test-field') }) it('passes through custom rows', async () => { const params = await helper.getParams({ code: 'test-field', rows: '10', }) expect(params.rows).toBe('10') }) it('passes through value', async () => { const params = await helper.getParams({ code: 'test-field', value: 'Initial text', }) expect(params.value).toBe('Initial text') }) }) describe('Character and word limit logic', () => { it('sets maxlength when only maxLength provided', async () => { const params = await helper.getParams({ code: 'test-field', maxLength: 500, }) expect(params.maxlength).toBe(500) expect(params.maxwords).toBeUndefined() }) it('sets maxwords when only maxWords provided', async () => { const params = await helper.getParams({ code: 'test-field', maxWords: 150, maxLength: undefined, }) expect(params.maxwords).toBe(150) expect(params.maxlength).toBeUndefined() }) it('prioritizes maxWords over maxLength when both provided', async () => { const params = await helper.getParams({ code: 'test-field', maxWords: 150, maxLength: 1000, }) expect(params.maxwords).toBe(150) expect(params.maxlength).toBeUndefined() }) it('passes through threshold value', async () => { const params = await helper.getParams({ code: 'test-field', threshold: '75', }) expect(params.threshold).toBe('75') }) }) describe('Label transformation', () => { it('converts string label to object format', async () => { const params = await helper.getParams({ code: 'test-field', label: 'Describe your experience', }) expect(params.label).toEqual({ text: 'Describe your experience', }) }) it('passes through object label unchanged', async () => { const labelObj = { text: 'Complex Label', classes: 'govuk-label--l', isPageHeading: true, } const params = await helper.getParams({ code: 'test-field', label: labelObj, }) expect(params.label).toEqual(labelObj) }) it('passes through label with HTML', async () => { const labelObj = { html: 'HTML Label', } const params = await helper.getParams({ code: 'test-field', label: labelObj, }) expect(params.label).toEqual(labelObj) }) }) describe('Hint transformation', () => { it('converts string hint to object format', async () => { const params = await helper.getParams({ code: 'test-field', hint: 'Do not include personal information', }) expect(params.hint).toEqual({ text: 'Do not include personal information', }) }) it('passes through object hint unchanged', async () => { const hintObj = { text: 'Complex hint text', classes: 'custom-hint-class', id: 'hint-id', } const params = await helper.getParams({ code: 'test-field', hint: hintObj, }) expect(params.hint).toEqual(hintObj) }) it('passes undefined hint when not provided', async () => { const params = await helper.getParams({ code: 'test-field', hint: undefined, }) expect(params.hint).toBeUndefined() }) }) describe('Error message transformation', () => { it('transforms error array to error message object', async () => { const params = await helper.getParams({ code: 'test-field', errors: [{ message: 'Enter a valid description' }], }) expect(params.errorMessage).toEqual({ text: 'Enter a valid description', }) }) it('uses first error when multiple provided', async () => { const params = await helper.getParams({ code: 'test-field', errors: [{ message: 'First error' }, { message: 'Second error' }], }) expect(params.errorMessage).toEqual({ text: 'First error', }) }) it('sets errorMessage to undefined when no errors', async () => { const params = await helper.getParams({ code: 'test-field' }) expect(params.errorMessage).toBeUndefined() }) }) describe('Textarea attributes', () => { it('passes through all textarea attributes without transformation', async () => { const params = await helper.getParams({ code: 'test-field', spellcheck: false, formGroup: { classes: 'custom-form-group', attributes: { 'data-test': 'value' }, beforeInput: { text: 'Before text' }, afterInput: { html: 'After HTML' }, }, classes: 'custom-character-count', attributes: { 'data-testid': 'character-count-test', 'data-module': 'custom-module', }, }) expect(params.spellcheck).toBe(false) expect(params.formGroup).toEqual({ classes: 'custom-form-group', attributes: { 'data-test': 'value' }, beforeInput: { text: 'Before text' }, afterInput: { html: 'After HTML' }, }) expect(params.classes).toBe('custom-character-count') expect(params.attributes).toEqual({ 'data-testid': 'character-count-test', 'data-module': 'custom-module', }) }) }) describe('Count message customization', () => { it('passes through all custom text messages for characters', async () => { const params = await helper.getParams({ code: 'test-field', maxLength: 200, textareaDescriptionText: 'You can enter up to %{count} characters', charactersUnderLimitText: { one: 'You have %{count} character remaining', other: 'You have %{count} characters remaining', }, charactersAtLimitText: 'You have reached the character limit', charactersOverLimitText: { one: 'You are %{count} character over the limit', other: 'You are %{count} characters over the limit', }, countMessage: { classes: 'custom-count-message', }, }) expect(params.textareaDescriptionText).toBe('You can enter up to %{count} characters') expect(params.charactersUnderLimitText).toEqual({ one: 'You have %{count} character remaining', other: 'You have %{count} characters remaining', }) expect(params.charactersAtLimitText).toBe('You have reached the character limit') expect(params.charactersOverLimitText).toEqual({ one: 'You are %{count} character over the limit', other: 'You are %{count} characters over the limit', }) expect(params.countMessage).toEqual({ classes: 'custom-count-message', }) }) it('passes through all custom text messages for words', async () => { const params = await helper.getParams({ code: 'test-field', maxWords: 100, wordsUnderLimitText: { one: 'You have %{count} word remaining', other: 'You have %{count} words remaining', }, wordsAtLimitText: 'You have reached the word limit', wordsOverLimitText: { one: 'You are %{count} word over the limit', other: 'You are %{count} words over the limit', }, }) expect(params.wordsUnderLimitText).toEqual({ one: 'You have %{count} word remaining', other: 'You have %{count} words remaining', }) expect(params.wordsAtLimitText).toBe('You have reached the word limit') expect(params.wordsOverLimitText).toEqual({ one: 'You are %{count} word over the limit', other: 'You are %{count} words over the limit', }) }) }) describe('Template and context', () => { it('calls nunjucks with correct template path', async () => { const { template } = await helper.executeComponent({ code: 'test-field', }) expect(template).toBe('govuk/components/character-count/template.njk') }) it('wraps params in context object', async () => { const { context } = (await helper.executeComponent({ code: 'test-field', })) as { context: any } expect(context).toHaveProperty('params') expect(context.params).toHaveProperty('id') expect(context.params).toHaveProperty('name') expect(context.params).toHaveProperty('rows') }) }) describe('DOM rendering smoke test', () => { it('renders a valid character count component', async () => { const html = await helper.renderWithNunjucks({ code: 'feedback-text', variant: 'govukCharacterCount', label: 'Provide feedback', hint: 'Do not include personal information', value: 'This is some initial text', maxLength: 200, threshold: '75', spellcheck: false, errors: [{ message: 'Your feedback is too long' }], }) expect(html).toContainText('govuk-character-count') expect(html).toContainText('govuk-textarea') expect(html).toContainText('feedback-text') expect(html).toContainText('Provide feedback') expect(html).toContainText('Do not include personal information') expect(html).toContainText('Your feedback is too long') expect(html).toContainText('This is some initial text') expect(html).toContainText('data-maxlength="200"') expect(html).toContainText('data-threshold="75"') expect(html).toContainText('spellcheck="false"') }) }) })