import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' import { setActivePinia, createPinia } from 'pinia' import { useSeriesManageStore, moveItem } from '../src/stores/seriesManage.js' function stubFetch(handler) { globalThis.fetch = vi.fn(async (url, init) => { const { status, body } = handler(url, init) return { ok: status >= 200 && status < 300, status, statusText: String(status), text: async () => (body == null ? '' : JSON.stringify(body)) } }) } // FC-6.x: a flat page run + cosmetic chapter dividers. const SERIES_BODY = { series: { id: 7, name: 'V' }, pages: [{ image_id: 1, page_number: 1, thumbnail_url: 't', source_post: null }], dividers: [ { id: 1, anchor_image_id: 1, page_number: 1, title: null, stated_part: null } ], part_gaps: [] } describe('seriesManage', () => { beforeEach(() => setActivePinia(createPinia())) afterEach(() => vi.restoreAllMocks()) it('moveItem reorders immutably', () => { expect(moveItem([1, 2, 3, 4], 3, 0)).toEqual([4, 1, 2, 3]) expect(moveItem([1, 2, 3], 0, 2)).toEqual([2, 3, 1]) }) it('load fetches the flat pages + dividers + series', async () => { const s = useSeriesManageStore() stubFetch(() => ({ status: 200, body: SERIES_BODY })) await s.load(7) expect(s.series).toEqual({ id: 7, name: 'V' }) expect(s.pages.map(p => p.image_id)).toEqual([1]) expect(s.dividers.map(d => d.id)).toEqual([1]) expect(s.pageCount).toBe(1) }) it('setPageNumber posts the image + number to /pages/number', async () => { const s = useSeriesManageStore() s.tagId = 7 const calls = [] stubFetch((url, init) => { calls.push({ url, body: init.body ? JSON.parse(init.body) : null }) if (url.includes('/pages/number')) return { status: 200, body: { ok: true } } return { status: 200, body: SERIES_BODY } }) await s.setPageNumber(42, 5) const r = calls.find(c => c.url.includes('/pages/number')) expect(r.url).toContain('/api/series/7/pages/number') expect(r.body).toEqual({ image_id: 42, page_number: 5 }) }) it('gapAfter looks up a gap by its after_image_id', async () => { const s = useSeriesManageStore() stubFetch(() => ({ status: 200, body: { ...SERIES_BODY, gaps: [{ after_image_id: 1, before_image_id: 9, from: 2, to: 3 }] } })) await s.load(7) expect(s.gapAfter(1)).toEqual({ after_image_id: 1, before_image_id: 9, from: 2, to: 3 }) expect(s.gapAfter(99)).toBeNull() }) it('createDivider posts the anchor image + labels', async () => { const s = useSeriesManageStore() s.tagId = 7 const calls = [] stubFetch((url, init) => { calls.push({ url, method: init.method, body: init.body ? JSON.parse(init.body) : null }) if (init.method === 'POST' && url.endsWith('/chapters')) return { status: 200, body: { id: 2, anchor_image_id: 5 } } return { status: 200, body: SERIES_BODY } }) await s.createDivider(5, { title: 'X', statedPart: 2 }) const c = calls.find(x => x.method === 'POST' && x.url.endsWith('/chapters')) expect(c.url).toContain('/api/series/7/chapters') expect(c.body).toEqual({ anchor_image_id: 5, title: 'X', stated_part: 2 }) }) it('setDividerPart patches stated_part on the divider', async () => { const s = useSeriesManageStore() s.tagId = 7 const calls = [] stubFetch((url, init) => { calls.push({ url, method: init.method, body: init.body ? JSON.parse(init.body) : null }) if (init.method === 'PATCH') return { status: 200, body: { ok: true } } return { status: 200, body: SERIES_BODY } }) await s.setDividerPart(1, 2) const p = calls.find(c => c.method === 'PATCH') expect(p.url).toContain('/api/series/7/chapters/1') expect(p.body).toEqual({ stated_part: 2 }) }) it('load surfaces part_gaps and partGapAfter looks them up', async () => { const s = useSeriesManageStore() stubFetch(() => ({ status: 200, body: { ...SERIES_BODY, part_gaps: [{ after_divider_id: 1, start: 2, end: 2 }] } })) await s.load(7) expect(s.partGaps).toHaveLength(1) expect(s.partGapAfter(1)).toEqual({ after_divider_id: 1, start: 2, end: 2 }) expect(s.partGapAfter(99)).toBeNull() }) it('dividerAt finds a divider by its anchor image', async () => { const s = useSeriesManageStore() stubFetch(() => ({ status: 200, body: SERIES_BODY })) await s.load(7) expect(s.dividerAt(1)?.id).toBe(1) expect(s.dividerAt(99)).toBeNull() }) it('addSelected posts the selection (no chapter) then clears', async () => { const s = useSeriesManageStore() s.tagId = 7 s.pickerSelection = [9, 10] const calls = [] stubFetch((url, init) => { calls.push({ url, body: init.body ? JSON.parse(init.body) : null }) if (url.endsWith('/pages') && init.method === 'POST') return { status: 200, body: { added_count: 2 } } return { status: 200, body: SERIES_BODY } }) await s.addSelected() const add = calls.find(c => c.url.endsWith('/api/series/7/pages')) expect(add.body).toEqual({ image_ids: [9, 10] }) expect(s.pickerSelection).toEqual([]) }) })