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>
166 lines
4.6 KiB
TypeScript
166 lines
4.6 KiB
TypeScript
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
|
import { setActivePinia, createPinia } from 'pinia'
|
|
import { useImagesStore } from '@/stores/images'
|
|
import type { Image } from '@/types'
|
|
|
|
const makeImage = (overrides: Partial<Image> = {}): Image => ({
|
|
id: 1,
|
|
originalFilename: 'photo.jpg',
|
|
thumbnailUrl: '/thumb/1.jpg',
|
|
originalUrl: '/orig/1.jpg',
|
|
uploadedAt: '2026-01-01T00:00:00Z',
|
|
approvedDeviceIds: [],
|
|
cropParams: null,
|
|
stickerState: null,
|
|
...overrides,
|
|
})
|
|
|
|
describe('images 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
|
|
})
|
|
|
|
it('fetchImages success populates images and clears loading', async () => {
|
|
const mockImages = [makeImage(), makeImage({ id: 2 })]
|
|
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
json: () => Promise.resolve(mockImages),
|
|
}))
|
|
|
|
const store = useImagesStore()
|
|
await store.fetchImages()
|
|
|
|
expect(store.images).toEqual(mockImages)
|
|
expect(store.loading).toBe(false)
|
|
expect(store.error).toBeNull()
|
|
})
|
|
|
|
it('fetchImages network error sets error state', async () => {
|
|
vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('Net error')))
|
|
|
|
const store = useImagesStore()
|
|
await store.fetchImages()
|
|
|
|
expect(store.images).toEqual([])
|
|
expect(store.error).toBe('Net error')
|
|
expect(store.loading).toBe(false)
|
|
})
|
|
|
|
it('uploadImage prepends to images list on success', async () => {
|
|
const existing = makeImage({ id: 1 })
|
|
const newImage = makeImage({ id: 2 })
|
|
|
|
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
json: () => Promise.resolve(newImage),
|
|
}))
|
|
|
|
const store = useImagesStore()
|
|
store.images = [existing]
|
|
|
|
const file = new File(['data'], 'photo.jpg', { type: 'image/jpeg' })
|
|
const result = await store.uploadImage(file)
|
|
|
|
expect(result).toEqual(newImage)
|
|
expect(store.images[0]).toEqual(newImage)
|
|
expect(store.images).toHaveLength(2)
|
|
})
|
|
|
|
it('uploadImage throws with error message on failure', async () => {
|
|
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
|
|
ok: false,
|
|
json: () => Promise.resolve({ error: 'File too large' }),
|
|
}))
|
|
|
|
const store = useImagesStore()
|
|
const file = new File(['data'], 'photo.jpg', { type: 'image/jpeg' })
|
|
|
|
await expect(store.uploadImage(file)).rejects.toThrow('File too large')
|
|
})
|
|
|
|
it('deleteImage removes image from list', async () => {
|
|
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({ ok: true }))
|
|
|
|
const store = useImagesStore()
|
|
store.images = [makeImage({ id: 1 }), makeImage({ id: 2 })]
|
|
|
|
await store.deleteImage(1)
|
|
|
|
expect(store.images).toHaveLength(1)
|
|
expect(store.images[0].id).toBe(2)
|
|
})
|
|
|
|
it('deleteImage throws on failure', async () => {
|
|
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({ ok: false }))
|
|
|
|
const store = useImagesStore()
|
|
store.images = [makeImage()]
|
|
|
|
await expect(store.deleteImage(1)).rejects.toThrow('Delete failed')
|
|
})
|
|
|
|
it('setApproval updates image in list', async () => {
|
|
const original = makeImage({ id: 1, approvedDeviceIds: [] })
|
|
const updated = makeImage({ id: 1, approvedDeviceIds: [42] })
|
|
|
|
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
json: () => Promise.resolve(updated),
|
|
}))
|
|
|
|
const store = useImagesStore()
|
|
store.images = [original]
|
|
|
|
await store.setApproval(1, 42, true)
|
|
|
|
expect(store.images[0].approvedDeviceIds).toEqual([42])
|
|
})
|
|
|
|
it('fetchPendingCount stores the count', async () => {
|
|
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
json: () => Promise.resolve({ count: 5 }),
|
|
}))
|
|
|
|
const store = useImagesStore()
|
|
await store.fetchPendingCount()
|
|
|
|
expect(store.pendingCount).toBe(5)
|
|
})
|
|
|
|
it('approveShared decrements pendingCount', async () => {
|
|
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
json: () => Promise.resolve({ id: 1, status: 'approved' }),
|
|
}))
|
|
|
|
const store = useImagesStore()
|
|
store.pendingCount = 3
|
|
|
|
await store.approveShared(1, [42])
|
|
|
|
expect(store.pendingCount).toBe(2)
|
|
})
|
|
|
|
it('declineShared decrements pendingCount', async () => {
|
|
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
json: () => Promise.resolve({ id: 1, status: 'declined' }),
|
|
}))
|
|
|
|
const store = useImagesStore()
|
|
store.pendingCount = 2
|
|
|
|
await store.declineShared(1)
|
|
|
|
expect(store.pendingCount).toBe(1)
|
|
})
|
|
})
|