From 8eef0777d2fde3544e89af3dc6eaa5e89208d7a0 Mon Sep 17 00:00:00 2001 From: PiePie <45820630+ImaginingMaker@users.noreply.github.com> Date: Wed, 19 Nov 2025 00:02:18 +0800 Subject: [PATCH] test(radio): improve the formatting and coverage of test cases --- .../__snapshots__/radio.test.tsx.snap | 90 +- .../components/radio/__tests__/radio.test.tsx | 810 +++++++++++------- 2 files changed, 552 insertions(+), 348 deletions(-) diff --git a/packages/components/radio/__tests__/__snapshots__/radio.test.tsx.snap b/packages/components/radio/__tests__/__snapshots__/radio.test.tsx.snap index 5592abd32c..c7f6bedf42 100644 --- a/packages/components/radio/__tests__/__snapshots__/radio.test.tsx.snap +++ b/packages/components/radio/__tests__/__snapshots__/radio.test.tsx.snap @@ -1,6 +1,6 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`Radio Component > props.checked is equal to true 1`] = ` +exports[`Radio > props > :checked input element 1`] = ` `; -exports[`Radio Component > props.checked works fine 1`] = ` +exports[`Radio > props > :checked[false] explicit 1`] = ` `; -exports[`Radio Component > props.checked works fine 2`] = ` +exports[`Radio > props > :checked[true] 1`] = ` `; -exports[`Radio Component > props.default works fine 1`] = ` +exports[`Radio > props > :default[function] 1`] = ` `; -exports[`Radio Component > props.disabled works fine 1`] = ` +exports[`Radio > props > :default[slot] 1`] = ` `; -exports[`Radio Component > props.disabled works fine 2`] = ` +exports[`Radio > props > :disabled[false] explicit 1`] = ` `; -exports[`Radio Component > slots.default works fine 1`] = ` +exports[`Radio > props > :disabled[true] 1`] = ` `; -exports[`RadioGroup Component > props.disabled is equal true 1`] = ` +exports[`RadioGroup > props > :disabled with children 1`] = `
@@ -201,6 +201,7 @@ exports[`RadioGroup Component > props.disabled is equal true 1`] = ` > props.disabled is equal true 1`] = ` > props.disabled is equal true 1`] = ` class="t-radio__label" > - - Radio3 - + Radio3 @@ -251,7 +248,7 @@ exports[`RadioGroup Component > props.disabled is equal true 1`] = ` > props.disabled is equal true 1`] = `
`; -exports[`RadioGroup Component > props.disabled is equal true 2`] = ` +exports[`RadioGroup > props > :disabled with options 1`] = `
@@ -307,7 +304,6 @@ exports[`RadioGroup Component > props.disabled is equal true 2`] = ` > props.disabled is equal true 2`] = ` > props.disabled is equal true 2`] = ` class="t-radio__label" > - Radio3 + + Radio3 + @@ -354,7 +354,7 @@ exports[`RadioGroup Component > props.disabled is equal true 2`] = ` > props.disabled is equal true 2`] = `
`; -exports[`RadioGroup Component > props.name is equal to 'custom-radio-name' 1`] = ` +exports[`RadioGroup > props > :name with children 1`] = `
@@ -411,6 +411,7 @@ exports[`RadioGroup Component > props.name is equal to 'custom-radio-name' 1`] = > props.name is equal to 'custom-radio-name' 1`] = > props.name is equal to 'custom-radio-name' 1`] = class="t-radio__label" > - - Radio3 - + Radio3 @@ -460,7 +457,7 @@ exports[`RadioGroup Component > props.name is equal to 'custom-radio-name' 1`] = > props.name is equal to 'custom-radio-name' 1`] =
`; -exports[`RadioGroup Component > props.name is equal to 'custom-radio-name' 2`] = ` +exports[`RadioGroup > props > :name with options 1`] = `
@@ -517,7 +514,6 @@ exports[`RadioGroup Component > props.name is equal to 'custom-radio-name' 2`] = > props.name is equal to 'custom-radio-name' 2`] = > props.name is equal to 'custom-radio-name' 2`] = class="t-radio__label" > - Radio3 + + Radio3 + @@ -563,7 +563,7 @@ exports[`RadioGroup Component > props.name is equal to 'custom-radio-name' 2`] = > { - it('props.allowUncheck works fine', async () => { - const onChangeFn = vi.fn(); - const wrapper = mount(); - wrapper.findComponent(Radio).trigger('click'); - await wrapper.vm.$nextTick(); - expect(onChangeFn).toHaveBeenCalled(); - expect(onChangeFn.mock.calls[0][0]).toBe(false); - expect(onChangeFn.mock.calls[0][1].e.type).toBe('click'); - }); - - it('props.checked works fine', () => { - // checked default value is false - const wrapper1 = mount(); - expect(wrapper1.classes('t-is-checked')).toBeFalsy(); - // checked = true - const wrapper2 = mount(); - expect(wrapper2.classes('t-is-checked')).toBeTruthy(); - expect(wrapper2.element).toMatchSnapshot(); - // checked = false - const wrapper3 = mount(); - expect(wrapper3.classes('t-is-checked')).toBeFalsy(); - expect(wrapper3.element).toMatchSnapshot(); - }); - - it(`props.checked is equal to true`, () => { - const wrapper = mount(); - const domWrapper = wrapper.find('input'); - expect(domWrapper.element.checked).toBeTruthy(); - expect(wrapper.element).toMatchSnapshot(); - }); - - it('props.default works fine', () => { - const wrapper = mount( TNode}>); - expect(wrapper.find('.custom-node').exists()).toBeTruthy(); - expect(wrapper.element).toMatchSnapshot(); - }); - - it('slots.default works fine', () => { - const wrapper = mount( TNode }}>); - expect(wrapper.find('.custom-node').exists()).toBeTruthy(); - expect(wrapper.element).toMatchSnapshot(); - }); - - it('props.disabled works fine', () => { - // disabled default value is undefined - const wrapper1 = mount(Text); - expect(wrapper1.classes('t-is-disabled')).toBeFalsy(); - // disabled = true - const wrapper2 = mount(Text); - expect(wrapper2.classes('t-is-disabled')).toBeTruthy(); - expect(wrapper2.element).toMatchSnapshot(); - // disabled = false - const wrapper3 = mount(Text); - expect(wrapper3.classes('t-is-disabled')).toBeFalsy(); - expect(wrapper3.element).toMatchSnapshot(); - }); - - it('props.disabled works fine', async () => { - const onChangeFn = vi.fn(); - const wrapper = mount( - - Text - , - ); - wrapper.findComponent(Radio).trigger('click'); - await wrapper.vm.$nextTick(); - expect(onChangeFn).not.toHaveBeenCalled(); - }); - - it('props.name works fine', () => { - const wrapper = mount().find('input'); - expect(wrapper.attributes('name')).toBe('radio-gender-name'); - }); - - it('Events.change: checked default value is false, click radio and trigger change', async () => { - const onChangeFn = vi.fn(); - const wrapper = mount(); - wrapper.find('.t-radio__label').trigger('click'); - await wrapper.vm.$nextTick(); - expect(onChangeFn).toHaveBeenCalled(); - expect(onChangeFn.mock.calls[0][0]).toBe(true); - expect(onChangeFn.mock.calls[0][1].e.type).toBe('click'); - }); - it('Events.change: checked value is true, without allowUncheck, click radio and can not trigger change', async () => { - const onChangeFn = vi.fn(); - const wrapper = mount(); - wrapper.find('.t-radio__label').trigger('click'); - await wrapper.vm.$nextTick(); - expect(onChangeFn).not.toHaveBeenCalled(); - }); -}); - -describe('RadioGroup Component', () => { - it('props.allowUncheck works fine', async () => { - const onChangeFn = vi.fn(); - const wrapper = getRadioGroupDefaultMount({ value: 1, allowUncheck: true }, { onChange: onChangeFn }); - wrapper.find('.t-radio').trigger('click'); - await wrapper.vm.$nextTick(); - expect(onChangeFn).toHaveBeenCalled(); - expect(onChangeFn.mock.calls[0][0]).toBe(undefined); - expect(onChangeFn.mock.calls[0][1].e.type).toBe('click'); - }); - - it('props.allowUncheck works fine', async () => { - const onChangeFn = vi.fn(); - const wrapper = getRadioGroupKidsMount({ value: 1, allowUncheck: true }, { onChange: onChangeFn }); - wrapper.find('.t-radio').trigger('click'); - await wrapper.vm.$nextTick(); - expect(onChangeFn).toHaveBeenCalled(); - expect(onChangeFn.mock.calls[0][0]).toBe(undefined); - expect(onChangeFn.mock.calls[0][1].e.type).toBe('click'); - }); - - it('props.disabled is equal true', () => { - const wrapper = getRadioGroupDefaultMount({ disabled: true }); - expect(wrapper.findAll('.t-radio.t-is-disabled').length).toBe(4); - expect(wrapper.element).toMatchSnapshot(); - }); - - it('props.disabled is equal true', () => { - const wrapper = getRadioGroupKidsMount({ disabled: true }); - expect(wrapper.findAll('.t-radio.t-is-disabled').length).toBe(4); - expect(wrapper.element).toMatchSnapshot(); - }); - - it('Props.disabled: disabled radio can not trigger change', async () => { - const onChangeFn = vi.fn(); - const wrapper = getRadioGroupDefaultMount({ disabled: true }, { onChange: onChangeFn }); - wrapper.find('.t-radio').trigger('click'); - await wrapper.vm.$nextTick(); - expect(onChangeFn).not.toHaveBeenCalled(); - }); - - it('Props.disabled: disabled radio can not trigger change', async () => { - const onChangeFn = vi.fn(); - const wrapper = getRadioGroupKidsMount({ disabled: true }, { onChange: onChangeFn }); - wrapper.find('.t-radio').trigger('click'); - await wrapper.vm.$nextTick(); - expect(onChangeFn).not.toHaveBeenCalled(); - }); - - it(`props.name is equal to 'custom-radio-name'`, () => { - const wrapper = getRadioGroupDefaultMount({ name: 'custom-radio-name' }); - const domWrapper = wrapper.find('input'); - expect(domWrapper.attributes('name')).toBe('custom-radio-name'); - expect(wrapper.element).toMatchSnapshot(); - }); - - it(`props.name is equal to 'custom-radio-name'`, () => { - const wrapper = getRadioGroupKidsMount({ name: 'custom-radio-name' }); - const domWrapper = wrapper.find('input'); - expect(domWrapper.attributes('name')).toBe('custom-radio-name'); - expect(wrapper.element).toMatchSnapshot(); - }); - - it('props.options works fine. `{".t-radio":4}` should exist', () => { - const wrapper = getRadioGroupDefaultMount(); - expect(wrapper.findAll('.t-radio').length).toBe(4); - }); - - it('props.options works fine. `".custom-node"` should exist', () => { - const wrapper = getRadioGroupDefaultMount(); - expect(wrapper.find('.custom-node').exists()).toBeTruthy(); - }); - - it('props.options works fine. `{".t-radio.t-is-disabled":1}` should exist', () => { - const wrapper = getRadioGroupDefaultMount(); - expect(wrapper.findAll('.t-radio.t-is-disabled').length).toBe(1); - }); - - it(`props.value is equal to '2'`, () => { - const wrapper = getRadioGroupDefaultMount({ value: '2' }); - const domWrapper = wrapper.find('.t-radio.t-is-checked input'); - expect(domWrapper.element.value).toBe('2'); - }); - - it(`props.value is equal to '2'`, () => { - const wrapper = getRadioGroupKidsMount({ value: '2' }); - const domWrapper = wrapper.find('.t-radio.t-is-checked input'); - expect(domWrapper.element.value).toBe('2'); - }); +describe('Radio', () => { + describe('props', () => { + it(':allowUncheck', async () => { + const onChangeFn = vi.fn(); + const wrapper = mount(); + wrapper.findComponent(Radio).trigger('click'); + await wrapper.vm.$nextTick(); + expect(onChangeFn).toHaveBeenCalled(); + expect(onChangeFn.mock.calls[0][0]).toBe(false); + expect(onChangeFn.mock.calls[0][1].e.type).toBe('click'); + }); - const variantClassNameList = ['t-radio-group__outline', 't-radio-group--primary-filled', 't-radio-group--filled']; - ['outline', 'primary-filled', 'default-filled'].forEach((item, index) => { - it(`props.variant is equal to ${item}`, () => { - const wrapper = mount(); - if (typeof variantClassNameList[index] === 'string') { - expect(wrapper.classes(variantClassNameList[index])).toBeTruthy(); - } else if (typeof variantClassNameList[index] === 'object') { - const classNameKey = Object.keys(variantClassNameList[index])[0]; - expect(wrapper.classes(classNameKey)).toBeFalsy(); - } + it(':checked[false]', () => { + const wrapper = mount(); + expect(wrapper.classes('t-is-checked')).toBeFalsy(); }); - }); - it('Events.change: default value is 2, trigger change after click', async () => { - const onChangeFn = vi.fn(); - const wrapper = getRadioGroupDefaultMount({ value: 2 }, { onChange: onChangeFn }); - wrapper.find('.t-radio').trigger('click'); - await wrapper.vm.$nextTick(); - expect(onChangeFn).toHaveBeenCalled(); - expect(onChangeFn.mock.calls[0][0]).toBe(1); - expect(onChangeFn.mock.calls[0][1].e.type).toBe('click'); - }); - it('Events.change: default value is empty, trigger change after click', async () => { - const onChangeFn = vi.fn(); - const wrapper = getRadioGroupDefaultMount({}, { onChange: onChangeFn }); - wrapper.find('.t-radio').trigger('click'); - await wrapper.vm.$nextTick(); - expect(onChangeFn).toHaveBeenCalled(); - expect(onChangeFn.mock.calls[0][0]).toBe(1); - expect(onChangeFn.mock.calls[0][1].e.type).toBe('click'); - }); + it(':checked[true]', () => { + const wrapper = mount(); + expect(wrapper.classes('t-is-checked')).toBeTruthy(); + expect(wrapper.element).toMatchSnapshot(); + }); - it('Events.change: default value is 2, trigger change after click', async () => { - const onChangeFn = vi.fn(); - const wrapper = getRadioGroupKidsMount({ value: 2 }, { onChange: onChangeFn }); - wrapper.find('.t-radio').trigger('click'); - await wrapper.vm.$nextTick(); - expect(onChangeFn).toHaveBeenCalled(); - expect(onChangeFn.mock.calls[0][0]).toBe(1); - expect(onChangeFn.mock.calls[0][1].e.type).toBe('click'); - }); - it('Events.change: default value is empty, trigger change after click', async () => { - const onChangeFn = vi.fn(); - const wrapper = getRadioGroupKidsMount({}, { onChange: onChangeFn }); - wrapper.find('.t-radio').trigger('click'); - await wrapper.vm.$nextTick(); - expect(onChangeFn).toHaveBeenCalled(); - expect(onChangeFn.mock.calls[0][0]).toBe(1); - expect(onChangeFn.mock.calls[0][1].e.type).toBe('click'); - }); -}); + it(':checked[false] explicit', () => { + const wrapper = mount(); + expect(wrapper.classes('t-is-checked')).toBeFalsy(); + expect(wrapper.element).toMatchSnapshot(); + }); -describe('Radio', () => { - // test props api - describe(':props', () => { - it(':checked', () => { - const wrapper = mount(() => ); - const radio = wrapper.find('.t-radio'); - expect(radio.exists()).toBeTruthy(); - expect(radio.classes()).toContain('t-is-checked'); + it(':checked input element', () => { + const wrapper = mount(); + const domWrapper = wrapper.find('input'); + expect(domWrapper.element.checked).toBeTruthy(); + expect(wrapper.element).toMatchSnapshot(); }); + it(':defaultChecked', () => { const wrapper = mount(() => ); const radio = wrapper.find('.t-radio'); expect(radio.exists()).toBeTruthy(); expect(radio.classes()).toContain('t-is-checked'); }); - it(':disabled', () => { - const wrapper = mount(() => ); - const radio = wrapper.find('.t-radio'); - expect(radio.classes()).toContain('t-is-disabled'); + + it(':default[function]', () => { + const wrapper = mount( TNode}>); + expect(wrapper.find('.custom-node').exists()).toBeTruthy(); + expect(wrapper.element).toMatchSnapshot(); }); - it(':name', () => { - const wrapper = mount(() => ); - const input = wrapper.find('.t-radio input'); - expect(input.element.getAttribute('name')).toBe('name'); + + it(':default[slot]', () => { + const wrapper = mount( TNode }}>); + expect(wrapper.find('.custom-node').exists()).toBeTruthy(); + expect(wrapper.element).toMatchSnapshot(); }); - it(':default', () => { + + it(':default[string]', () => { const wrapper = mount(() => ); const label = wrapper.find('.t-radio__label'); expect(label.exists()).toBeTruthy(); expect(label.text()).toBe('label'); }); + + it(':disabled[false]', () => { + const wrapper = mount(Text); + expect(wrapper.classes('t-is-disabled')).toBeFalsy(); + }); + + it(':disabled[true]', () => { + const wrapper = mount(Text); + expect(wrapper.classes('t-is-disabled')).toBeTruthy(); + expect(wrapper.element).toMatchSnapshot(); + }); + + it(':disabled[false] explicit', () => { + const wrapper = mount(Text); + expect(wrapper.classes('t-is-disabled')).toBeFalsy(); + expect(wrapper.element).toMatchSnapshot(); + }); + + it(':disabled prevents onChange', async () => { + const onChangeFn = vi.fn(); + const wrapper = mount( + + Text + , + ); + wrapper.findComponent(Radio).trigger('click'); + await wrapper.vm.$nextTick(); + expect(onChangeFn).not.toHaveBeenCalled(); + }); + it(':label', () => { const wrapper = mount(() => ); const label = wrapper.find('.t-radio__label'); expect(label.exists()).toBeTruthy(); expect(label.text()).toBe('label'); }); - it(':allowUncheck', async () => { + + it(':name', () => { + const wrapper = mount().find('input'); + expect(wrapper.attributes('name')).toBe('radio-gender-name'); + }); + + it(':name attribute', () => { + const wrapper = mount(() => ); + const input = wrapper.find('.t-radio input'); + expect(input.element.getAttribute('name')).toBe('name'); + }); + }); + + describe('events', () => { + it(':onChange when unchecked', async () => { + const onChangeFn = vi.fn(); + const wrapper = mount(); + wrapper.find('.t-radio__label').trigger('click'); + await wrapper.vm.$nextTick(); + expect(onChangeFn).toHaveBeenCalled(); + expect(onChangeFn.mock.calls[0][0]).toBe(true); + expect(onChangeFn.mock.calls[0][1].e.type).toBe('click'); + }); + + it(':onChange when checked without allowUncheck', async () => { + const onChangeFn = vi.fn(); + const wrapper = mount(); + wrapper.find('.t-radio__label').trigger('click'); + await wrapper.vm.$nextTick(); + expect(onChangeFn).not.toHaveBeenCalled(); + }); + + it(':onChange with allowUncheck', async () => { const fn = vi.fn(); const wrapper = mount(() => ( @@ -289,16 +146,6 @@ describe('Radio', () => { await nextTick(); expect(fn).toBeCalled(); }); - }); - describe(':events', () => { - it(':onChange', async () => { - const fn = vi.fn(); - const wrapper = mount(() => label); - const radio = wrapper.find('.t-radio'); - await radio.trigger('click'); - await nextTick(); - expect(fn).toBeCalled(); - }); it(':onClick', async () => { const fn = vi.fn(); @@ -311,8 +158,53 @@ describe('Radio', () => { }); describe('RadioGroup', () => { - describe(':props', () => { - it(':disabled', () => { + describe('props', () => { + it(':allowUncheck with options', async () => { + const onChangeFn = vi.fn(); + const wrapper = getRadioGroupDefaultMount({ value: 1, allowUncheck: true }, { onChange: onChangeFn }); + wrapper.find('.t-radio').trigger('click'); + await wrapper.vm.$nextTick(); + expect(onChangeFn).toHaveBeenCalled(); + expect(onChangeFn.mock.calls[0][0]).toBe(undefined); + expect(onChangeFn.mock.calls[0][1].e.type).toBe('click'); + }); + + it(':allowUncheck with children', async () => { + const onChangeFn = vi.fn(); + const wrapper = getRadioGroupKidsMount({ value: 1, allowUncheck: true }, { onChange: onChangeFn }); + wrapper.find('.t-radio').trigger('click'); + await wrapper.vm.$nextTick(); + expect(onChangeFn).toHaveBeenCalled(); + expect(onChangeFn.mock.calls[0][0]).toBe(undefined); + expect(onChangeFn.mock.calls[0][1].e.type).toBe('click'); + }); + + it(':defaultValue', () => { + const defaultValue = ref('1'); + const wrapper = mount(() => ( + + 选项一 + 选项二 + + )); + const inputList = wrapper.findAll('.t-radio'); + expect(inputList[0].classes()).toContain('t-is-checked'); + expect(inputList[1].classes()).not.toContain('t-is-checked'); + }); + + it(':disabled with options', () => { + const wrapper = getRadioGroupDefaultMount({ disabled: true }); + expect(wrapper.findAll('.t-radio.t-is-disabled').length).toBe(4); + expect(wrapper.element).toMatchSnapshot(); + }); + + it(':disabled with children', () => { + const wrapper = getRadioGroupKidsMount({ disabled: true }); + expect(wrapper.findAll('.t-radio.t-is-disabled').length).toBe(4); + expect(wrapper.element).toMatchSnapshot(); + }); + + it(':disabled with children (JSX)', () => { const wrapper = mount(() => ( 选项一 @@ -325,7 +217,37 @@ describe('RadioGroup', () => { }); }); - it(':name', () => { + it(':disabled prevents onChange with options', async () => { + const onChangeFn = vi.fn(); + const wrapper = getRadioGroupDefaultMount({ disabled: true }, { onChange: onChangeFn }); + wrapper.find('.t-radio').trigger('click'); + await wrapper.vm.$nextTick(); + expect(onChangeFn).not.toHaveBeenCalled(); + }); + + it(':disabled prevents onChange with children', async () => { + const onChangeFn = vi.fn(); + const wrapper = getRadioGroupKidsMount({ disabled: true }, { onChange: onChangeFn }); + wrapper.find('.t-radio').trigger('click'); + await wrapper.vm.$nextTick(); + expect(onChangeFn).not.toHaveBeenCalled(); + }); + + it(':name with options', () => { + const wrapper = getRadioGroupDefaultMount({ name: 'custom-radio-name' }); + const domWrapper = wrapper.find('input'); + expect(domWrapper.attributes('name')).toBe('custom-radio-name'); + expect(wrapper.element).toMatchSnapshot(); + }); + + it(':name with children', () => { + const wrapper = getRadioGroupKidsMount({ name: 'custom-radio-name' }); + const domWrapper = wrapper.find('input'); + expect(domWrapper.attributes('name')).toBe('custom-radio-name'); + expect(wrapper.element).toMatchSnapshot(); + }); + + it(':name with children (JSX)', () => { const wrapper = mount(() => ( 选项一 @@ -338,32 +260,22 @@ describe('RadioGroup', () => { }); }); - it(':size', () => { - const sizeList = ['small', 'large']; - sizeList.forEach((size) => { - const wrapper = mount(() => ( - - 选项一 - 选项二 - - )); - expect(wrapper.find('.t-radio-group').classes()).toContain(`t-size-${size.slice(0, 1)}`); - }); + it(':options render radios', () => { + const wrapper = getRadioGroupDefaultMount(); + expect(wrapper.findAll('.t-radio').length).toBe(4); }); - it(':defaultValue', () => { - const defaultValue = ref('1'); - const wrapper = mount(() => ( - - 选项一 - 选项二 - - )); - const inputList = wrapper.findAll('.t-radio'); - expect(inputList[0].classes()).toContain('t-is-checked'); - expect(inputList[1].classes()).not.toContain('t-is-checked'); + it(':options with custom node', () => { + const wrapper = getRadioGroupDefaultMount(); + expect(wrapper.find('.custom-node').exists()).toBeTruthy(); + }); + + it(':options with disabled item', () => { + const wrapper = getRadioGroupDefaultMount(); + expect(wrapper.findAll('.t-radio.t-is-disabled').length).toBe(1); }); - it(':options', () => { + + it(':options with data', () => { const options = [ { value: '1', @@ -383,51 +295,198 @@ describe('RadioGroup', () => { expect(inputList[1].classes()).not.toContain('t-is-checked'); }); - it(':variant', () => { - // const variantList = ['outline', 'primary-filled', 'default-filled']; - const wrapper1 = mount(() => ( - + it(':size', () => { + const sizeList = ['small', 'large']; + sizeList.forEach((size) => { + const wrapper = mount(() => ( + + 选项一 + 选项二 + + )); + expect(wrapper.find('.t-radio-group').classes()).toContain(`t-size-${size.slice(0, 1)}`); + }); + }); + + it(':theme[radio]', () => { + const wrapper = mount(() => ( + 选项一 选项二 )); - expect(wrapper1.find('.t-radio-group').classes()).toContain(`t-radio-group__outline`); - const wrapper2 = mount(() => ( - + expect(wrapper.findComponent(Radio)).toBeTruthy(); + }); + + it(':theme[button]', () => { + const wrapper = mount(() => ( + 选项一 选项二 )); - expect(wrapper2.find('.t-radio-group').classes()).toContain(`t-radio-group--primary-filled`); - const wrapper3 = mount(() => ( - + expect(wrapper.findComponent(RadioButton)).toBeTruthy(); + }); + + it(':theme[button] with options renders RadioButton', () => { + const options = [ + { value: '1', label: '选项一' }, + { value: '2', label: '选项二' }, + { value: '3', label: '选项三' }, + ]; + const wrapper = mount(() => ); + const radioButtons = wrapper.findAllComponents(RadioButton); + expect(radioButtons.length).toBe(3); + expect(radioButtons[0].classes()).toContain('t-is-checked'); + }); + + it(':theme[radio] with options renders Radio', () => { + const options = [ + { value: '1', label: '选项一' }, + { value: '2', label: '选项二' }, + ]; + const wrapper = mount(() => ); + const radios = wrapper.findAllComponents(Radio); + expect(radios.length).toBe(2); + }); + + it(':options with number or string values', () => { + const options = [1, 2, 'three', 4]; + const wrapper = mount(() => ); + const radios = wrapper.findAll('.t-radio'); + expect(radios.length).toBe(4); + expect(radios[1].classes()).toContain('t-is-checked'); + expect(radios[1].text()).toBe('2'); + }); + + it(':variant outline does not render background block', () => { + const wrapper = mount(() => ( + + 选项一 + 选项二 + + )); + expect(wrapper.find('.t-radio-group__bg-block').exists()).toBeFalsy(); + }); + + it(':value with options', () => { + const wrapper = getRadioGroupDefaultMount({ value: '2' }); + const domWrapper = wrapper.find('.t-radio.t-is-checked input'); + expect(domWrapper.element.value).toBe('2'); + }); + + it(':value with children', () => { + const wrapper = getRadioGroupKidsMount({ value: '2' }); + const domWrapper = wrapper.find('.t-radio.t-is-checked input'); + expect(domWrapper.element.value).toBe('2'); + }); + + it(':variant[outline]', () => { + const wrapper = mount(); + expect(wrapper.classes('t-radio-group__outline')).toBeTruthy(); + }); + + it(':variant[primary-filled]', () => { + const wrapper = mount(); + expect(wrapper.classes('t-radio-group--primary-filled')).toBeTruthy(); + }); + + it(':variant[default-filled]', () => { + const wrapper = mount(); + expect(wrapper.classes('t-radio-group--filled')).toBeTruthy(); + }); + + it(':variant with children (outline)', () => { + const wrapper = mount(() => ( + 选项一 选项二 )); - expect(wrapper3.find('.t-radio-group').classes()).toContain(`t-radio-group--filled`); + expect(wrapper.find('.t-radio-group').classes()).toContain(`t-radio-group__outline`); }); - it(':theme', () => { + + it(':variant with children (primary-filled)', () => { const wrapper = mount(() => ( - + 选项一 选项二 )); - expect(wrapper.findComponent(Radio)).toBeTruthy(); - const wrapper2 = mount(() => ( - + expect(wrapper.find('.t-radio-group').classes()).toContain(`t-radio-group--primary-filled`); + }); + + it(':variant with children (default-filled)', () => { + const wrapper = mount(() => ( + 选项一 选项二 )); + expect(wrapper.find('.t-radio-group').classes()).toContain(`t-radio-group--filled`); + }); - expect(wrapper2.findComponent(RadioButton)).toBeTruthy(); + it(':variant filled bar style calculation', async () => { + const value = ref('1'); + const wrapper = mount(() => ( + + 选项一 + 选项二 + + )); + await nextTick(); + const bgBlock = wrapper.find('.t-radio-group__bg-block'); + expect(bgBlock.exists()).toBeTruthy(); + + // Change value to trigger bar style recalculation + value.value = '2'; + await nextTick(); + expect(bgBlock.exists()).toBeTruthy(); }); }); - describe(':events', () => { - it(':onChange', async () => { + describe('events', () => { + it(':onChange with options (value: 2)', async () => { + const onChangeFn = vi.fn(); + const wrapper = getRadioGroupDefaultMount({ value: 2 }, { onChange: onChangeFn }); + wrapper.find('.t-radio').trigger('click'); + await wrapper.vm.$nextTick(); + expect(onChangeFn).toHaveBeenCalled(); + expect(onChangeFn.mock.calls[0][0]).toBe(1); + expect(onChangeFn.mock.calls[0][1].e.type).toBe('click'); + }); + + it(':onChange with options (empty value)', async () => { + const onChangeFn = vi.fn(); + const wrapper = getRadioGroupDefaultMount({}, { onChange: onChangeFn }); + wrapper.find('.t-radio').trigger('click'); + await wrapper.vm.$nextTick(); + expect(onChangeFn).toHaveBeenCalled(); + expect(onChangeFn.mock.calls[0][0]).toBe(1); + expect(onChangeFn.mock.calls[0][1].e.type).toBe('click'); + }); + + it(':onChange with children (value: 2)', async () => { + const onChangeFn = vi.fn(); + const wrapper = getRadioGroupKidsMount({ value: 2 }, { onChange: onChangeFn }); + wrapper.find('.t-radio').trigger('click'); + await wrapper.vm.$nextTick(); + expect(onChangeFn).toHaveBeenCalled(); + expect(onChangeFn.mock.calls[0][0]).toBe(1); + expect(onChangeFn.mock.calls[0][1].e.type).toBe('click'); + }); + + it(':onChange with children (empty value)', async () => { + const onChangeFn = vi.fn(); + const wrapper = getRadioGroupKidsMount({}, { onChange: onChangeFn }); + wrapper.find('.t-radio').trigger('click'); + await wrapper.vm.$nextTick(); + expect(onChangeFn).toHaveBeenCalled(); + expect(onChangeFn.mock.calls[0][0]).toBe(1); + expect(onChangeFn.mock.calls[0][1].e.type).toBe('click'); + }); + + it(':onChange with v-model', async () => { const defaultValue = ref('1'); const fn = vi.fn(); const wrapper = mount(() => ( @@ -445,3 +504,148 @@ describe('RadioGroup', () => { }); }); }); + +describe('RadioButton', () => { + describe('props', () => { + it(':defaultChecked', () => { + const wrapper = mount(Button); + expect(wrapper.text()).toBe('Button'); + }); + + it(':disabled', () => { + const wrapper = mount(Disabled Button); + expect(wrapper.find('.t-radio-button').classes()).toContain('t-is-disabled'); + }); + }); +}); + +describe('RadioGroup Keyboard', () => { + it('keyboard Enter key to select radio', async () => { + const wrapper = mount(() => ( + + 选项一 + 选项二 + 选项三 + + )); + await nextTick(); + + const radioList = wrapper.findAll('.t-radio'); + await radioList[1].trigger('keydown', { key: 'Enter' }); + await nextTick(); + expect(radioList[1].classes()).toContain('t-is-checked'); + }); + + it('keyboard with allowUncheck', async () => { + const wrapper = mount(() => ( + + 选项一 + 选项二 + + )); + await nextTick(); + + const radioList = wrapper.findAll('.t-radio'); + expect(radioList[0].classes()).toContain('t-is-checked'); + + // Press Enter on checked radio with allowUncheck should uncheck it + await radioList[0].trigger('keydown', { key: 'Enter' }); + await nextTick(); + expect(radioList[0].classes()).not.toContain('t-is-checked'); + }); + + it('keyboard with number value', async () => { + const wrapper = mount(() => ( + + 选项一 + 选项二 + + )); + await nextTick(); + + const radioList = wrapper.findAll('.t-radio'); + await radioList[0].trigger('keydown', { key: 'Enter' }); + await nextTick(); + expect(radioList[0].classes()).toContain('t-is-checked'); + }); + + it('keyboard with boolean value', async () => { + const wrapper = mount(() => ( + + True + False + + )); + await nextTick(); + + const radioList = wrapper.findAll('.t-radio'); + await radioList[0].trigger('keydown', { key: 'Enter' }); + await nextTick(); + expect(radioList[0].classes()).toContain('t-is-checked'); + }); + + it('keyboard with string value', async () => { + const wrapper = mount(() => ( + + 选项一 + 选项二 + + )); + await nextTick(); + + const radioList = wrapper.findAll('.t-radio'); + await radioList[1].trigger('keydown', { key: 'Enter' }); + await nextTick(); + expect(radioList[1].classes()).toContain('t-is-checked'); + }); + + it('keyboard with quoted string value', async () => { + const fn = vi.fn(); + const wrapper = mount(() => ( + + Quoted + Normal + + )); + await nextTick(); + + const radioList = wrapper.findAll('.t-radio'); + await radioList[0].trigger('keydown', { key: 'Enter' }); + await nextTick(); + expect(fn).toHaveBeenCalled(); + }); + + it('keyboard event should not trigger when target has no input', async () => { + const wrapper = mount(() => ( + + 选项一 + 选项二 + + )); + await nextTick(); + + // Trigger keydown on the group itself, not on radio + await wrapper.find('.t-radio-group').trigger('keydown', { key: 'Enter' }); + await nextTick(); + + // Value should remain unchanged + const radioList = wrapper.findAll('.t-radio'); + expect(radioList[0].classes()).toContain('t-is-checked'); + }); + + it('keyboard with non-checked keys should not trigger', async () => { + const fn = vi.fn(); + const wrapper = mount(() => ( + + 选项一 + 选项二 + + )); + await nextTick(); + + const radioList = wrapper.findAll('.t-radio'); + await radioList[0].trigger('keydown', { key: 'ArrowDown' }); + await nextTick(); + expect(fn).not.toHaveBeenCalled(); + }); +});