import { describe, expect, it, vi } from 'vitest' import { mount } from '@vue/test-utils' import UnitInput from '../../components/UnitInput.vue' import type { UnitOption } from '../../types' const volume: UnitOption[] = [ { value: 'nL', label: 'nL', factor: 1e-9 }, { value: 'µL', label: 'µL', factor: 1e-6 }, { value: 'mL', label: 'mL', factor: 1e-3 }, { value: 'L', label: 'L', factor: 1 }, ] function mountVolume(props: Record = {}) { return mount(UnitInput, { props: { modelValue: 250, unit: 'µL', units: volume, ...props }, attachTo: document.body, global: { stubs: { teleport: true } }, }) } async function openMenu(wrapper: ReturnType) { await wrapper.find('.mint-unit-input__unit').trigger('click') } describe('UnitInput unit segment (4b)', () => { it('should teleport the unit menu to the document body when opened', async () => { const wrapper = mount(UnitInput, { props: { modelValue: 250, unit: 'µL', units: volume }, attachTo: document.body, }) await wrapper.find('.mint-unit-input__unit').trigger('click') expect(document.body.querySelector(':scope > .mint-unit-input__menu')).not.toBeNull() wrapper.unmount() }) it('should render the unit as a listbox button when several units are given', () => { const wrapper = mountVolume() expect(wrapper.find('button.mint-unit-input__unit').attributes('aria-haspopup')).toBe('listbox') }) it('should show the current unit label in the segment when mounted', () => { const wrapper = mountVolume() expect(wrapper.find('.mint-unit-input__unit').text()).toBe('µL') }) it('should show a chevron when the unit is selectable', () => { const wrapper = mountVolume() expect(wrapper.find('.mint-unit-input__chevron').exists()).toBe(true) }) it('should render a static segment without chevron when only one unit is given', () => { const wrapper = mountVolume({ units: [volume[1]] }) expect(wrapper.find('.mint-unit-input__chevron').exists()).toBe(false) }) it('should list units as menu items when the segment is clicked', async () => { const wrapper = mountVolume() await openMenu(wrapper) expect(wrapper.findAll('.mint-unit-input__menu .mint-menu-item')).toHaveLength(4) }) it('should preview the converted value next to each unit when factors are set', async () => { const wrapper = mountVolume() await openMenu(wrapper) expect(wrapper.findAll('.mint-unit-input__menu .mint-menu-item__meta')[2].text()).toBe('0.25') }) it('should mark the current unit as selected when the menu is open', async () => { const wrapper = mountVolume() await openMenu(wrapper) expect(wrapper.findAll('[role="option"]')[1].attributes('aria-selected')).toBe('true') }) it('should emit update:unit when a unit is chosen', async () => { const wrapper = mountVolume() await openMenu(wrapper) await wrapper.findAll('[role="option"]')[2].trigger('click') expect(wrapper.emitted('update:unit')?.[0]).toEqual(['mL']) }) it('should close the menu when a unit is chosen', async () => { const wrapper = mountVolume() await openMenu(wrapper) await wrapper.findAll('[role="option"]')[2].trigger('click') expect(wrapper.find('.mint-unit-input__menu').exists()).toBe(false) }) it('should not let Escape reach the document when it closes the unit menu', async () => { const wrapper = mountVolume() await openMenu(wrapper) const onDocumentKeydown = vi.fn() document.addEventListener('keydown', onDocumentKeydown) await wrapper.find('.mint-unit-input__menu').trigger('keydown', { key: 'Escape' }) document.removeEventListener('keydown', onDocumentKeydown) expect(onDocumentKeydown).not.toHaveBeenCalled() }) it('should convert the value when convertOnUnitChange is set', async () => { const wrapper = mountVolume({ convertOnUnitChange: true }) await openMenu(wrapper) await wrapper.findAll('[role="option"]')[2].trigger('click') expect(wrapper.emitted('update:modelValue')?.[0]).toEqual([0.25]) }) it('should not preview units from another group when groups differ', async () => { const units = [ { value: 'mM', label: 'mM', factor: 1e-3, group: 'Molar' }, { value: 'mg/mL', label: 'mg/mL', factor: 1e-3, group: 'Mass' }, ] const wrapper = mountVolume({ unit: 'mM', units }) await openMenu(wrapper) expect(wrapper.findAll('.mint-unit-input__menu .mint-menu-item__meta')).toHaveLength(1) }) it('should render group labels when units carry groups', async () => { const wrapper = mountVolume({ units: volume.map(u => ({ ...u, group: 'Volume' })) }) await openMenu(wrapper) expect(wrapper.find('.mint-unit-input__menu-group').text()).toBe('Volume') }) }) describe('UnitInput conversion hint (4b)', () => { it('should show the neighbour unit nearest to 1–1000 when the value has a factor', () => { const wrapper = mountVolume() expect(wrapper.find('.mint-unit-input__hint').text()).toBe('≈ 0.25 mL') }) it('should step down a unit when the value is below 1', () => { const wrapper = mountVolume({ modelValue: 0.5 }) expect(wrapper.find('.mint-unit-input__hint').text()).toBe('≈ 500 nL') }) it('should step up a unit when the value is 1000 or more', () => { const wrapper = mountVolume({ modelValue: 1500 }) expect(wrapper.find('.mint-unit-input__hint').text()).toBe('≈ 1.5 mL') }) it('should hide the hint when units carry no factor', () => { const wrapper = mountVolume({ units: volume.map(({ value, label }) => ({ value, label })) }) expect(wrapper.find('.mint-unit-input__hint').exists()).toBe(false) }) it('should hide the hint when the value is empty', () => { const wrapper = mountVolume({ modelValue: undefined }) expect(wrapper.find('.mint-unit-input__hint').exists()).toBe(false) }) })