import { describe, it, expect, vi } from 'vitest' import { mount } from '@vue/test-utils' import DevicePicker from '@/components/DevicePicker.vue' import type { Device } from '@/types' // Stub child components DevicePicker wraps vi.mock('@/components/BaseBottomSheet.vue', () => ({ default: { name: 'BaseBottomSheet', template: '
', props: ['modelValue', 'label'], emits: ['update:modelValue'], }, })) vi.mock('@/components/BaseButton.vue', () => ({ default: { name: 'BaseButton', template: '', props: ['variant', 'disabled'], emits: ['click'], }, })) const makeDevice = (overrides: Partial = {}): Device => ({ id: 1, mac: 'AA:BB:CC:DD:EE:FF', name: 'Living Room', orientation: 'landscape', rotationIntervalMinutes: 60, wakeTimes: [], timezone: 'America/Chicago', uniquenessWindow: 30, rotationMode: 'oldest_upload', prioritizeNeverShown: false, linkedAt: '2026-01-01T00:00:00Z', lastSeenAt: null, nextPollExpectedAt: null, lockedImageId: null, currentImageId: null, ...overrides, }) describe('DevicePicker', () => { const devices = [ makeDevice({ id: 1, name: 'Living Room' }), makeDevice({ id: 2, name: 'Bedroom' }), ] function mountPicker(selected: number[] = []) { return mount(DevicePicker, { props: { modelValue: true, devices, selected, }, }) } // DP-01: Selecting a device emits update:selected with the device added it('checking a device emits update:selected with device id added', async () => { const wrapper = mountPicker([]) const checkboxes = wrapper.findAll('input[type="checkbox"]') // Click the first checkbox (Living Room, id=1) await checkboxes[0].trigger('change') const emitted = wrapper.emitted('update:selected') expect(emitted).toBeTruthy() expect(emitted![0][0]).toEqual([1]) }) // DP-02: Deselecting a device emits update:selected with device id removed it('unchecking a device emits update:selected with device id removed', async () => { // Start with both selected const wrapper = mountPicker([1, 2]) const checkboxes = wrapper.findAll('input[type="checkbox"]') // Click the first checkbox (Living Room, id=1) — it's currently checked, so this deselects await checkboxes[0].trigger('change') const emitted = wrapper.emitted('update:selected') expect(emitted).toBeTruthy() // Should emit [2] — Living Room removed expect(emitted![0][0]).toEqual([2]) }) // DP-03: Checkboxes reflect the selected prop it('checkboxes are checked for ids in selected prop', async () => { const wrapper = mountPicker([2]) const checkboxes = wrapper.findAll('input[type="checkbox"]') expect((checkboxes[0].element as HTMLInputElement).checked).toBe(false) // id=1 not selected expect((checkboxes[1].element as HTMLInputElement).checked).toBe(true) // id=2 selected }) // DP-04: Confirm button disabled when nothing selected it('confirm button is disabled when selected is empty', async () => { const wrapper = mountPicker([]) const btn = wrapper.find('button') expect((btn.element as HTMLButtonElement).disabled).toBe(true) }) // DP-05: Confirm button enabled when at least one device selected it('confirm button is enabled when a device is selected', async () => { const wrapper = mountPicker([1]) const btn = wrapper.find('button') expect((btn.element as HTMLButtonElement).disabled).toBe(false) }) // DP-06: Device names are rendered it('renders all device names', () => { const wrapper = mountPicker([]) expect(wrapper.text()).toContain('Living Room') expect(wrapper.text()).toContain('Bedroom') }) // DP-07: Clicking the enabled confirm button emits 'confirm' it('emits confirm when the confirm button is clicked', async () => { const wrapper = mountPicker([1]) await wrapper.find('button').trigger('click') expect(wrapper.emitted('confirm')).toBeTruthy() }) // DP-08: Confirm label adapts to selection count (singular / plural / none) it('renders the singular confirm label for one selected device', () => { const wrapper = mountPicker([1]) expect(wrapper.find('button').text()).toBe('Add to 1 frame') }) it('renders the plural confirm label for multiple selected devices', () => { const wrapper = mountPicker([1, 2]) expect(wrapper.find('button').text()).toBe('Add to 2 frames') }) it('renders the no-selection confirm label when nothing is picked', () => { const wrapper = mountPicker([]) expect(wrapper.find('button').text()).toBe('Add to frame') }) // DP-09: Uploading state — label changes and button is disabled it('shows uploading label and disables the button while uploading', () => { const wrapper = mount(DevicePicker, { props: { modelValue: true, devices, selected: [1], uploading: true }, }) const btn = wrapper.find('button') expect(btn.text()).toBe('Uploading…') expect((btn.element as HTMLButtonElement).disabled).toBe(true) }) // DP-10: Forwards update:modelValue from the wrapped sheet it('forwards update:modelValue from the wrapped sheet', async () => { const wrapper = mountPicker([]) await wrapper.findComponent({ name: 'BaseBottomSheet' }).vm.$emit('update:modelValue', false) expect(wrapper.emitted('update:modelValue')).toEqual([[false]]) }) })