// Tests for useTaskImages composable. Public seam: the factory function and // the reactive contract it returns. Pure-logic behaviour (computeds, mark/ // unmark, cancel) is tested without mocks; the network-touching methods // (load / getContentUrl / commit) live in a separate file below. // // Why a separate file: keeping network and pure tests apart means a failure // points to one of two things only — "logic wrong" vs "wire wrong" — and // lets us run the pure slice under jsdom with no axios mocks at all. import { describe, it, expect, beforeEach } from 'vitest' import { nextTick } from 'vue' import { useTaskImages } from '@/composables/useTaskImages.js' describe('useTaskImages — pure state', () => { let img beforeEach(() => { img = useTaskImages() }) it('starts with empty images / pendingImages / pendingDeletes', () => { expect(img.images.value).toEqual([]) expect(img.pendingImages.value).toEqual([]) expect(img.pendingDeletes.value).toEqual([]) }) it('totalCount counts images minus pendingDeletes plus pendingImages', () => { img.images.value = [{ id: 1 }, { id: 2 }, { id: 3 }] img.pendingDeletes.value = [2] img.pendingImages.value = [{ localId: 'a' }] expect(img.totalCount.value).toBe(3) }) it('canAddMore is true below 20, false at 20', () => { // 19 images → can add (totalCount 19 < 20) img.images.value = Array.from({ length: 19 }, (_, i) => ({ id: i + 1 })) expect(img.canAddMore.value).toBe(true) // 20 images → full img.images.value = Array.from({ length: 20 }, (_, i) => ({ id: i + 1 })) expect(img.canAddMore.value).toBe(false) // 20 images + 1 pending → still over → can't add more img.pendingImages.value = [{ localId: 'a' }] expect(img.canAddMore.value).toBe(false) // 20 images, mark 1 delete → 19 effective → can add again img.pendingImages.value = [] img.pendingDeletes.value = [1] expect(img.canAddMore.value).toBe(true) }) it('markDelete adds id, unmarkDelete removes it', () => { img.markDelete(42) expect(img.pendingDeletes.value).toEqual([42]) img.markDelete(42) // idempotent expect(img.pendingDeletes.value).toEqual([42]) img.unmarkDelete(42) expect(img.pendingDeletes.value).toEqual([]) }) it('cancelPending removes by localId', () => { img.pendingImages.value = [ { localId: 'a' }, { localId: 'b' }, { localId: 'c' }, ] img.cancelPending('b') expect(img.pendingImages.value.map((p) => p.localId)).toEqual(['a', 'c']) }) it('reset clears images, pending, cache', () => { img.images.value = [{ id: 1 }] img.pendingImages.value = [{ localId: 'a' }] img.pendingDeletes.value = [1] img.reset() expect(img.images.value).toEqual([]) expect(img.pendingImages.value).toEqual([]) expect(img.pendingDeletes.value).toEqual([]) }) it('load sets isLoading true during the call and false after', async () => { // stub the underlying network call via the composable's public surface — // here we just confirm the loading flag flips. Network behavior is in // the next describe block. img.isLoading.value = false await img.load(null) // null taskId should not throw — handled gracefully await nextTick() expect(img.isLoading.value).toBe(false) }) })