import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' import { setActivePinia, createPinia } from 'pinia' import { useSuggestionsStore } from '../src/stores/suggestions.js' vi.mock('../src/utils/toast.js', () => ({ toast: vi.fn() })) 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)), } }) } const sugg = (over = {}) => ({ canonical_tag_id: 7, display_name: 'Ichigo', category: 'character', score: 0.9, source: 'head', creates_new_tag: false, rejected: false, ...over, }) // Seed the store through its own load/loadAll so currentImageId is set the // way the panel sets it (accept() derives the image from it). async function seed(store, byCategory) { stubFetch(() => ({ status: 200, body: { by_category: byCategory } })) await store.load(1) await store.loadAll(1) } describe('suggestions store: findPending (manual add == accept, 2026-07-03)', () => { beforeEach(() => setActivePinia(createPinia())) afterEach(() => vi.restoreAllMocks()) it('matches by canonical tag id', async () => { const s = useSuggestionsStore() await seed(s, { character: [sugg()] }) expect(s.findPending('general', 'unrelated', 7)?.display_name).toBe('Ichigo') }) it('matches raw suggestions by kind + name, case-insensitively', async () => { const s = useSuggestionsStore() await seed(s, { general: [sugg({ canonical_tag_id: null, display_name: 'Holding Sword', category: 'general', creates_new_tag: true, })], }) expect(s.findPending('general', 'holding sword')).toBeTruthy() // Kind must match: same name under another kind is a different tag. expect(s.findPending('character', 'holding sword')).toBeNull() }) it('skips rejected rows and returns null when nothing matches', async () => { const s = useSuggestionsStore() await seed(s, { character: [sugg({ rejected: true })] }) expect(s.findPending('character', 'Ichigo', 7)).toBeNull() expect(s.findPending('character', 'Rukia')).toBeNull() }) }) describe('suggestions store: accept with a known tag id', () => { beforeEach(() => setActivePinia(createPinia())) afterEach(() => vi.restoreAllMocks()) it('POSTs only the accept endpoint (no tag create) and drops the row', async () => { const s = useSuggestionsStore() await seed(s, { character: [sugg()] }) const calls = [] stubFetch((url, init) => { calls.push({ url, method: init?.method }) return { status: 200, body: { accepted: true, tag_id: 7 } } }) // TagPanel routes a manual pick that matches a pending suggestion through // accept, filling canonical_tag_id from the picked hit when the // suggestion is raw — no /api/tags create must fire in that case. await s.accept({ ...s.byCategory.character[0], canonical_tag_id: 7 }) expect(calls).toHaveLength(1) expect(calls[0].url).toContain('/api/images/1/suggestions/accept') expect(s.byCategory.character).toHaveLength(0) expect(s.allByCategory.character).toHaveLength(0) }) })