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>
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
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)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user