import { beforeEach, describe, expect, it } from 'vitest' import NumberField from './story/_NumberField.vue' import { axe } from 'vitest-axe' import type { NumberFieldRootProps } from './NumberFieldRoot.vue' import userEvent from '@testing-library/user-event' import { fireEvent, render } from '@testing-library/vue' import { useKbd } from '@/shared' import { mount } from '@vue/test-utils' import { handleSubmit } from '@/test' function setup(props?: NumberFieldRootProps) { const user = userEvent.setup() const returned = render(NumberField, { props }) const root = returned.getByTestId('root') const input = returned.getByTestId('input') as HTMLInputElement const label = returned.getByTestId('label') const increment = returned.getByTestId('increment') const decrement = returned.getByTestId('decrement') expect(root).toBeVisible() return { ...returned, user, root, input, label, increment, decrement } } const kbd = useKbd() describe('numberField', () => { it('should pass axe accessibility tests', async () => { const { root } = setup() expect(await axe(root)).toHaveNoViolations() }) it('should show a default value if provided', async () => { const { input } = setup({ defaultValue: 5 }) expect(input.value).toBe('5') }) it('should show modelValue if provided', async () => { const { input } = setup({ modelValue: 10 }) expect(input.value).toBe('10') }) it('should show negative sign if less than 0', async () => { const { input } = setup({ modelValue: -10 }) expect(input.value).toBe('-10') }) it('should restart from 0 when clearing the value', async () => { const { input, increment } = setup({ defaultValue: 5 }) await userEvent.clear(input) expect(input.value).toBe('') await userEvent.click(document.body) await userEvent.click(increment) expect(input.value).toBe('0') }) it('should increase and decrease based on default step', async () => { const { input, increment, decrement } = setup({ defaultValue: 10 }) expect(input.value).toBe('10') await userEvent.click(increment) expect(input.value).toBe('11') await userEvent.click(increment) expect(input.value).toBe('12') await userEvent.click(decrement) expect(input.value).toBe('11') await userEvent.click(decrement) expect(input.value).toBe('10') }) it('should increase and decrease based on given step', async () => { const { input, increment, decrement } = setup({ defaultValue: 0, step: 3 }) expect(input.value).toBe('0') await userEvent.click(increment) expect(input.value).toBe('3') await userEvent.click(increment) expect(input.value).toBe('6') await userEvent.click(decrement) expect(input.value).toBe('3') await userEvent.click(decrement) expect(input.value).toBe('0') }) it('should increase and decrease based on keyboard navigation on input', async () => { const { input } = setup({ defaultValue: 0, min: 0, max: 10 }) await fireEvent.keyDown(input, { key: kbd.ARROW_UP }) expect(input.value).toBe('1') await fireEvent.keyDown(input, { key: kbd.ARROW_DOWN }) expect(input.value).toBe('0') await fireEvent.keyDown(input, { key: kbd.END }) expect(input.value).toBe('10') await fireEvent.keyDown(input, { key: kbd.HOME }) expect(input.value).toBe('0') }) it('should not be changed when disabled', async () => { const { root, input, increment, decrement } = setup({ defaultValue: 0, disabled: true }) expect(root.getAttribute('data-disabled')).toBe('') expect(input.getAttribute('data-disabled')).toBe('') await fireEvent.keyDown(input, { key: kbd.ARROW_UP }) expect(input.value).toBe('0') await fireEvent.keyDown(input, { key: kbd.ARROW_DOWN }) expect(input.value).toBe('0') await userEvent.click(increment) expect(input.value).toBe('0') await userEvent.click(decrement) expect(input.value).toBe('0') }) describe('with different formatOptions', () => { it('should show decimal point', async () => { const { input } = setup({ defaultValue: 10, formatOptions: { signDisplay: 'exceptZero', minimumFractionDigits: 1, }, }) expect(input.value).toBe('+10.0') }) it('should show percentage', async () => { const { input } = setup({ defaultValue: 0.05, step: 0.01, formatOptions: { style: 'percent', }, }) expect(input.value).toBe('5%') }) it('should show currency', async () => { const { input } = setup({ defaultValue: 5, formatOptions: { style: 'currency', currency: 'EUR', currencyDisplay: 'code', currencySign: 'accounting', }, }) expect(input.value).toBe('EUR 5.00') }) it('should show units', async () => { const { input } = setup({ defaultValue: 5, formatOptions: { style: 'unit', unit: 'inch', unitDisplay: 'long', }, }) expect(input.value).toBe('5 inches') }) it('should change format based on reactive options', async () => { const { input, rerender } = setup({ defaultValue: 5, formatOptions: { style: 'currency', currency: 'EUR', currencyDisplay: 'code', currencySign: 'accounting', }, }) expect(input.value).toBe('EUR 5.00') await rerender({ defaultValue: 5, formatOptions: { style: 'currency', currency: 'USD', currencyDisplay: 'code', currencySign: 'accounting', }, }) expect(input.value).toBe('USD 5.00') }) }) describe('given min/max with step condition', () => { it('should compute min with step correctly', async () => { const { input } = setup({ min: 2, step: 3 }) expect(input.value).toBe('') await fireEvent.keyDown(input, { key: kbd.ARROW_UP }) expect(input.value).toBe('2') await fireEvent.keyDown(input, { key: kbd.ARROW_UP }) expect(input.value).toBe('5') await fireEvent.keyDown(input, { key: kbd.ARROW_UP }) expect(input.value).toBe('8') }) it('should compute min-max with step correctly', async () => { const { input } = setup({ min: 2, max: 21, step: 3 }) expect(input.value).toBe('') await fireEvent.keyDown(input, { key: kbd.ARROW_UP }) // 2 await fireEvent.keyDown(input, { key: kbd.ARROW_UP }) // 5 await fireEvent.keyDown(input, { key: kbd.ARROW_UP }) // 8 await fireEvent.keyDown(input, { key: kbd.ARROW_UP }) // 11 await fireEvent.keyDown(input, { key: kbd.ARROW_UP }) // 14 await fireEvent.keyDown(input, { key: kbd.ARROW_UP }) // 17 await fireEvent.keyDown(input, { key: kbd.ARROW_UP }) // 20 expect(input.value).toBe('20') await fireEvent.keyDown(input, { key: kbd.ARROW_UP }) // 20 (max) expect(input.value).toBe('20') }) }) describe('given setting the input value manually', async () => { it('should it increase/decrease the value appropriately', async () => { const { input, increment, decrement } = setup({ defaultValue: 6 }) input.value = '100' await userEvent.click(increment) expect(input.value).toBe('101') input.value = '100' await userEvent.click(decrement) expect(input.value).toBe('99') input.value = '' await userEvent.click(decrement) expect(input.value).toBe('0') input.value = '0' await userEvent.click(decrement) expect(input.value).toBe('-1') }) }) describe('given setting the input value manually and keydown enter', async () => { it('should it update the value appropriately', async () => { const { input } = setup({ defaultValue: 6, formatOptions: { style: 'currency', currency: 'EUR', currencyDisplay: 'code', currencySign: 'accounting', }, }) input.value = '7' expect(input.value).toBe('7') await fireEvent.keyDown(input, { key: kbd.ENTER }) expect(input.value).toBe('EUR 7.00') }) }) }) describe('given checkbox in a form', async () => { const wrapper = mount({ props: ['handleSubmit'], components: { NumberField }, template: '
', }, { props: { handleSubmit }, }) it('should have hidden input field', async () => { expect(wrapper.find('[type="text"]').exists()).toBe(true) }) describe('after clicking submit button', () => { beforeEach(async () => { await wrapper.find('form').trigger('submit') }) it('should trigger submit once', () => { expect(handleSubmit).toHaveBeenCalledTimes(1) expect(handleSubmit.mock.results[0].value).toStrictEqual({ test: '5' }) }) }) describe('after changing value and click submit button again', () => { beforeEach(async () => { const increment = wrapper.find('[data-testid="increment"]').element await userEvent.click(increment) await wrapper.find('form').trigger('submit') }) it('should trigger submit once', () => { expect(handleSubmit).toHaveBeenCalledTimes(2) expect(handleSubmit.mock.results[1].value).toStrictEqual({ test: '6' }) }) }) })