[test] Add component test for Vue Textarea widget (multiline STRING widget) (#5539)

* add component test for textarea widget

* [refactor] improve test structure and typing - addresses @DrJKL's review feedback

- Move helper functions outside describe block for better scoping
- Fix SimplifiedWidget options typing from Partial<TextareaProps> to Record<string, any>
- Replace emitted\![0] with emitted?.[0] for safer optional chaining
- Remove unused TextareaProps import
This commit is contained in:
Christian Byrne
2025-09-13 23:10:43 -07:00
committed by GitHub
parent ac107b45ea
commit c04af09956

View File

@@ -0,0 +1,258 @@
import { mount } from '@vue/test-utils'
import PrimeVue from 'primevue/config'
import Textarea from 'primevue/textarea'
import { describe, expect, it } from 'vitest'
import type { SimplifiedWidget } from '@/types/simplifiedWidget'
import WidgetTextarea from './WidgetTextarea.vue'
const createMockWidget = (
value: string = 'default text',
options: Record<string, any> = {},
callback?: (value: string) => void
): SimplifiedWidget<string> => ({
name: 'test_textarea',
type: 'string',
value,
options,
callback
})
const mountComponent = (
widget: SimplifiedWidget<string>,
modelValue: string,
readonly = false,
placeholder?: string
) => {
return mount(WidgetTextarea, {
global: {
plugins: [PrimeVue],
components: { Textarea }
},
props: {
widget,
modelValue,
readonly,
placeholder
}
})
}
const setTextareaValueAndTrigger = async (
wrapper: ReturnType<typeof mount>,
value: string,
trigger: 'blur' | 'input' = 'blur'
) => {
const textarea = wrapper.find('textarea')
if (!(textarea.element instanceof HTMLTextAreaElement)) {
throw new Error(
'Textarea element not found or is not an HTMLTextAreaElement'
)
}
await textarea.setValue(value)
await textarea.trigger(trigger)
return textarea
}
describe('WidgetTextarea Value Binding', () => {
describe('Vue Event Emission', () => {
it('emits Vue event when textarea value changes on blur', async () => {
const widget = createMockWidget('hello')
const wrapper = mountComponent(widget, 'hello')
await setTextareaValueAndTrigger(wrapper, 'world', 'blur')
const emitted = wrapper.emitted('update:modelValue')
expect(emitted).toBeDefined()
expect(emitted?.[0]).toContain('world')
})
it('emits Vue event when textarea value changes on input', async () => {
const widget = createMockWidget('initial')
const wrapper = mountComponent(widget, 'initial')
await setTextareaValueAndTrigger(wrapper, 'new content', 'input')
const emitted = wrapper.emitted('update:modelValue')
expect(emitted).toBeDefined()
expect(emitted?.[0]).toContain('new content')
})
it('handles empty string values', async () => {
const widget = createMockWidget('something')
const wrapper = mountComponent(widget, 'something')
await setTextareaValueAndTrigger(wrapper, '')
const emitted = wrapper.emitted('update:modelValue')
expect(emitted).toBeDefined()
expect(emitted?.[0]).toContain('')
})
it('handles multiline text correctly', async () => {
const widget = createMockWidget('single line')
const wrapper = mountComponent(widget, 'single line')
const multilineText = 'Line 1\nLine 2\nLine 3'
await setTextareaValueAndTrigger(wrapper, multilineText)
const emitted = wrapper.emitted('update:modelValue')
expect(emitted).toBeDefined()
expect(emitted?.[0]).toContain(multilineText)
})
it('handles special characters correctly', async () => {
const widget = createMockWidget('normal')
const wrapper = mountComponent(widget, 'normal')
const specialText = 'special @#$%^&*()[]{}|\\:";\'<>?,./'
await setTextareaValueAndTrigger(wrapper, specialText)
const emitted = wrapper.emitted('update:modelValue')
expect(emitted).toBeDefined()
expect(emitted?.[0]).toContain(specialText)
})
it('handles missing callback gracefully', async () => {
const widget = createMockWidget('test', {}, undefined)
const wrapper = mountComponent(widget, 'test')
await setTextareaValueAndTrigger(wrapper, 'new value')
// Should still emit Vue event
const emitted = wrapper.emitted('update:modelValue')
expect(emitted).toBeDefined()
expect(emitted?.[0]).toContain('new value')
})
})
describe('User Interactions', () => {
it('emits update:modelValue on blur', async () => {
const widget = createMockWidget('original')
const wrapper = mountComponent(widget, 'original')
await setTextareaValueAndTrigger(wrapper, 'updated')
const emitted = wrapper.emitted('update:modelValue')
expect(emitted).toBeDefined()
expect(emitted?.[0]).toContain('updated')
})
it('emits update:modelValue on input', async () => {
const widget = createMockWidget('start')
const wrapper = mountComponent(widget, 'start')
await setTextareaValueAndTrigger(wrapper, 'finish', 'input')
const emitted = wrapper.emitted('update:modelValue')
expect(emitted).toBeDefined()
expect(emitted?.[0]).toContain('finish')
})
})
describe('Readonly Mode', () => {
it('disables textarea when readonly', () => {
const widget = createMockWidget('readonly test')
const wrapper = mountComponent(widget, 'readonly test', true)
const textarea = wrapper.find('textarea')
if (!(textarea.element instanceof HTMLTextAreaElement)) {
throw new Error(
'Textarea element not found or is not an HTMLTextAreaElement'
)
}
expect(textarea.element.disabled).toBe(true)
})
})
describe('Component Rendering', () => {
it('renders textarea component', () => {
const widget = createMockWidget('test value')
const wrapper = mountComponent(widget, 'test value')
const textarea = wrapper.find('textarea')
expect(textarea.exists()).toBe(true)
})
it('displays initial value in textarea', () => {
const widget = createMockWidget('initial content')
const wrapper = mountComponent(widget, 'initial content')
const textarea = wrapper.find('textarea')
if (!(textarea.element instanceof HTMLTextAreaElement)) {
throw new Error(
'Textarea element not found or is not an HTMLTextAreaElement'
)
}
expect(textarea.element.value).toBe('initial content')
})
it('uses widget name as placeholder when no placeholder provided', () => {
const widget = createMockWidget('test')
const wrapper = mountComponent(widget, 'test')
const textarea = wrapper.find('textarea')
expect(textarea.attributes('placeholder')).toBe('test_textarea')
})
it('uses provided placeholder when specified', () => {
const widget = createMockWidget('test')
const wrapper = mountComponent(
widget,
'test',
false,
'Custom placeholder'
)
const textarea = wrapper.find('textarea')
expect(textarea.attributes('placeholder')).toBe('Custom placeholder')
})
it('sets default rows attribute', () => {
const widget = createMockWidget('test')
const wrapper = mountComponent(widget, 'test')
const textarea = wrapper.find('textarea')
expect(textarea.attributes('rows')).toBe('3')
})
})
describe('Edge Cases', () => {
it('handles very long text', async () => {
const widget = createMockWidget('short')
const wrapper = mountComponent(widget, 'short')
const longText = 'a'.repeat(10000)
await setTextareaValueAndTrigger(wrapper, longText)
const emitted = wrapper.emitted('update:modelValue')
expect(emitted).toBeDefined()
expect(emitted?.[0]).toContain(longText)
})
it('handles unicode characters', async () => {
const widget = createMockWidget('ascii')
const wrapper = mountComponent(widget, 'ascii')
const unicodeText = '🎨 Unicode: αβγ 中文 العربية 🚀'
await setTextareaValueAndTrigger(wrapper, unicodeText)
const emitted = wrapper.emitted('update:modelValue')
expect(emitted).toBeDefined()
expect(emitted?.[0]).toContain(unicodeText)
})
it('handles text with tabs and spaces', async () => {
const widget = createMockWidget('normal')
const wrapper = mountComponent(widget, 'normal')
const formattedText = '\tIndented line\n Spaced line\n\t\tDouble indent'
await setTextareaValueAndTrigger(wrapper, formattedText)
const emitted = wrapper.emitted('update:modelValue')
expect(emitted).toBeDefined()
expect(emitted?.[0]).toContain(formattedText)
})
})
})