import {describe, expect, it} from 'vitest' import {mount} from '@vue/test-utils' import type {DefineComponent} from 'vue' import RadioButton from './RadioButton.vue' type RadioButtonProps = { id?: string label?: string name?: string modelValue?: string | number | boolean | null | undefined value?: string | number | boolean | null | undefined inputClass?: string labelClass?: string groupClass?: string required?: boolean disabled?: boolean readonly?: boolean hint?: string error?: string success?: string } const RadioButtonForTest = RadioButton as DefineComponent const mountRadioButton = (props: RadioButtonProps = {}) => mount(RadioButtonForTest, { props, }) describe('MalioRadioButton', () => { it('renders the label text', () => { const wrapper = mountRadioButton({label: 'Option 1'}) expect(wrapper.get('.radio-text').text()).toBe('Option 1') }) it('applies provided id to input and label', () => { const wrapper = mountRadioButton({id: 'radio-id', label: 'Option 1'}) expect(wrapper.get('input').attributes('id')).toBe('radio-id') expect(wrapper.get('.radio-text').attributes('for')).toBe('radio-id') }) it('generates an id when missing and reuses it on label', () => { const wrapper = mountRadioButton({label: 'Option 1'}) const inputId = wrapper.get('input').attributes('id') expect(inputId?.startsWith('malio-radio-')).toBe(true) expect(wrapper.get('.radio-text').attributes('for')).toBe(inputId) }) it('applies the name attribute', () => { const wrapper = mountRadioButton({name: 'choice-group'}) expect(wrapper.get('input').attributes('name')).toBe('choice-group') }) it('sets required when true', () => { const wrapper = mountRadioButton({required: true}) expect(wrapper.get('input').attributes('required')).toBeDefined() }) it('sets disabled styles when true', () => { const wrapper = mountRadioButton({disabled: true, label: 'Option 1'}) expect(wrapper.get('input').attributes('disabled')).toBeDefined() expect(wrapper.get('.radio-control').classes()).toContain('is-disabled') expect(wrapper.get('.radio-text').classes()).toContain('cursor-not-allowed') }) it('checks the input when modelValue matches value', () => { const wrapper = mountRadioButton({modelValue: 'a', value: 'a'}) expect((wrapper.get('input').element as HTMLInputElement).checked).toBe(true) }) it('emits update:modelValue on change', async () => { const wrapper = mountRadioButton({modelValue: 'a', value: 'b'}) await wrapper.get('input').trigger('change') expect(wrapper.emitted('update:modelValue')?.[0]).toEqual(['b']) }) it('prevents updates when readonly', async () => { const wrapper = mountRadioButton({modelValue: 'a', value: 'b', readonly: true}) const input = wrapper.get('input') ;(input.element as HTMLInputElement).checked = true await input.trigger('change') expect(wrapper.emitted('update:modelValue')).toBeUndefined() expect((input.element as HTMLInputElement).checked).toBe(false) }) it('shows hint message and wires aria-describedby', () => { const wrapper = mountRadioButton({hint: 'Helpful hint'}) const input = wrapper.get('input') const message = wrapper.get('.radio-message') expect(message.text()).toBe('Helpful hint') expect(input.attributes('aria-describedby')).toBe(message.attributes('id')) expect(input.attributes('aria-invalid')).toBe('false') }) it('shows error state on control, label and helper text', () => { const wrapper = mountRadioButton({ label: 'Option 1', error: 'Selection required', }) expect(wrapper.get('.radio-control').classes()).toContain('is-error') expect(wrapper.get('.radio-text').classes()).toContain('text-m-error') expect(wrapper.get('.radio-message').classes()).toContain('text-m-error') expect(wrapper.get('input').attributes('aria-invalid')).toBe('true') }) it('shows success state when no error is present', () => { const wrapper = mountRadioButton({ label: 'Option 1', success: 'Selection saved', }) expect(wrapper.get('.radio-control').classes()).toContain('is-success') expect(wrapper.get('.radio-text').classes()).toContain('text-m-success') expect(wrapper.get('.radio-message').classes()).toContain('text-m-success') }) it('prioritizes error over success', () => { const wrapper = mountRadioButton({ error: 'Selection required', success: 'Selection saved', }) expect(wrapper.get('.radio-control').classes()).toContain('is-error') expect(wrapper.get('.radio-control').classes()).not.toContain('is-success') expect(wrapper.get('.radio-message').text()).toBe('Selection required') expect(wrapper.get('.radio-message').classes()).toContain('text-m-error') }) it('merges custom classes on group, input and label', () => { const wrapper = mountRadioButton({ label: 'Option 1', groupClass: 'mt-0 custom-group', inputClass: 'border-red-500', labelClass: 'font-bold', }) expect(wrapper.get('.radio-item').classes()).toContain('custom-group') expect(wrapper.get('.radio-item').classes()).toContain('mt-0') expect(wrapper.get('input').classes()).toContain('border-red-500') expect(wrapper.get('.radio-text').classes()).toContain('font-bold') }) })