4002ff9fbf
CI / test (push) Has been cancelled
Web app: new entities (Image, RenderedAsset, SharedImage, Token, DeviceImageHistory), enums, repositories, controllers, message handlers, migrations, tests, frontend upload/library/sticker UI, Vue components. Firmware: EPD background screen binaries + gen scripts, setup_bg header. Infra: ddev config, test bundle, gitignore coverage dir. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
159 lines
4.5 KiB
TypeScript
159 lines
4.5 KiB
TypeScript
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
|
import { setActivePinia, createPinia } from 'pinia'
|
|
import { useDevicesStore } from '@/stores/devices'
|
|
import type { Device } from '@/types'
|
|
|
|
const makeDevice = (overrides: Partial<Device> = {}): Device => ({
|
|
id: 1,
|
|
mac: 'AA:BB:CC:DD:EE:FF',
|
|
name: 'Living Room',
|
|
orientation: 'landscape',
|
|
rotationIntervalMinutes: 60,
|
|
wakeHour: null,
|
|
timezone: 'America/Chicago',
|
|
uniquenessWindow: 30,
|
|
linkedAt: '2026-01-01T00:00:00Z',
|
|
lastSeenAt: null,
|
|
lockedImageId: null,
|
|
...overrides,
|
|
})
|
|
|
|
describe('devices store', () => {
|
|
beforeEach(() => {
|
|
vi.unstubAllGlobals()
|
|
vi.restoreAllMocks()
|
|
setActivePinia(createPinia())
|
|
})
|
|
|
|
afterEach(() => {
|
|
// unstubAllGlobals is handled in beforeEach so stubs don't leak
|
|
// even if a test throws before afterEach runs
|
|
})
|
|
|
|
// DS-01
|
|
it('fetchDevices success populates devices and clears loading', async () => {
|
|
const mockDevices = [makeDevice()]
|
|
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
json: () => Promise.resolve(mockDevices),
|
|
}))
|
|
|
|
const store = useDevicesStore()
|
|
await store.fetchDevices()
|
|
|
|
expect(store.devices).toEqual(mockDevices)
|
|
expect(store.loading).toBe(false)
|
|
expect(store.error).toBeNull()
|
|
})
|
|
|
|
// DS-02
|
|
it('fetchDevices network error sets error state', async () => {
|
|
vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('Network failure')))
|
|
|
|
const store = useDevicesStore()
|
|
await store.fetchDevices()
|
|
|
|
expect(store.devices).toEqual([])
|
|
expect(store.loading).toBe(false)
|
|
expect(store.error).toBe('Network failure')
|
|
})
|
|
|
|
// DS-02b — non-ok response
|
|
it('fetchDevices non-ok response sets error state', async () => {
|
|
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({ ok: false }))
|
|
|
|
const store = useDevicesStore()
|
|
await store.fetchDevices()
|
|
|
|
expect(store.error).toBe('Failed to load devices')
|
|
expect(store.loading).toBe(false)
|
|
})
|
|
|
|
// DS-03
|
|
it('updateDevice patches local array entry', async () => {
|
|
const original = makeDevice({ id: 1, name: 'Old Name' })
|
|
const updated = makeDevice({ id: 1, name: 'New Name' })
|
|
|
|
const store = useDevicesStore()
|
|
store.devices = [original]
|
|
|
|
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
json: () => Promise.resolve(updated),
|
|
}))
|
|
|
|
const result = await store.updateDevice(1, { name: 'New Name' })
|
|
|
|
expect(result.name).toBe('New Name')
|
|
expect(store.devices[0].name).toBe('New Name')
|
|
})
|
|
|
|
// DS-03b — updateDevice throws on failure
|
|
it('updateDevice throws on non-ok response', async () => {
|
|
const store = useDevicesStore()
|
|
store.devices = [makeDevice()]
|
|
|
|
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({ ok: false }))
|
|
|
|
await expect(store.updateDevice(1, { name: 'x' })).rejects.toThrow('Failed to update device')
|
|
})
|
|
|
|
// DS-04
|
|
it('lockImage sets lockedImageId on local device', async () => {
|
|
const device = makeDevice({ id: 1, lockedImageId: null })
|
|
const locked = makeDevice({ id: 1, lockedImageId: 42 })
|
|
|
|
const store = useDevicesStore()
|
|
store.devices = [device]
|
|
|
|
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
json: () => Promise.resolve(locked),
|
|
}))
|
|
|
|
const result = await store.lockImage(1, 42)
|
|
|
|
expect(result.lockedImageId).toBe(42)
|
|
expect(store.devices[0].lockedImageId).toBe(42)
|
|
})
|
|
|
|
// DS-05
|
|
it('unlockImage clears lockedImageId', async () => {
|
|
const device = makeDevice({ id: 1, lockedImageId: 42 })
|
|
const unlocked = makeDevice({ id: 1, lockedImageId: null })
|
|
|
|
const store = useDevicesStore()
|
|
store.devices = [device]
|
|
|
|
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
json: () => Promise.resolve(unlocked),
|
|
}))
|
|
|
|
const result = await store.unlockImage(1)
|
|
|
|
expect(result.lockedImageId).toBeNull()
|
|
expect(store.devices[0].lockedImageId).toBeNull()
|
|
})
|
|
|
|
// DS-05b — lockImage throws on failure
|
|
it('lockImage throws on non-ok response', async () => {
|
|
const store = useDevicesStore()
|
|
store.devices = [makeDevice()]
|
|
|
|
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({ ok: false }))
|
|
|
|
await expect(store.lockImage(1, 42)).rejects.toThrow('Failed to lock image')
|
|
})
|
|
|
|
// DS-05c — unlockImage throws on failure
|
|
it('unlockImage throws on non-ok response', async () => {
|
|
const store = useDevicesStore()
|
|
store.devices = [makeDevice()]
|
|
|
|
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({ ok: false }))
|
|
|
|
await expect(store.unlockImage(1)).rejects.toThrow('Failed to unlock')
|
|
})
|
|
})
|