feat(tags): fandom-edit UI in tags directory + image modal
Adds the missing UI to change a character tag's fandom, in both places:
- FandomSetDialog (shared): pick an existing fandom, create a new one, or
clear it; on a name collision in the target fandom it surfaces a merge
confirmation and resolves via setFandom(merge:true). Reuses the tags
store's fandom cache.
- TagCard kebab gains "Set fandom…" for character tags (→ TagsView opens
the dialog, reloads on success).
- TagPanel chip kebab gains "Set fandom…" for character tags (→ reloads the
modal's tag list on success).
- tags store: setFandom(tagId, fandomId, {merge}) action + test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
|
||||
import { setActivePinia, createPinia } from 'pinia'
|
||||
import { useTagStore } from '../src/stores/tags.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)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
describe('tags store: setFandom', () => {
|
||||
beforeEach(() => setActivePinia(createPinia()))
|
||||
afterEach(() => vi.restoreAllMocks())
|
||||
|
||||
it('PATCHes fandom_id, and null to clear', async () => {
|
||||
const s = useTagStore()
|
||||
const calls = []
|
||||
stubFetch((url, init) => {
|
||||
calls.push({ url, init })
|
||||
return { status: 200, body: { id: 5, name: 'Ichigo', kind: 'character', fandom_id: 9 } }
|
||||
})
|
||||
const body = await s.setFandom(5, 9)
|
||||
expect(body.fandom_id).toBe(9)
|
||||
const last = calls.at(-1)
|
||||
expect(last.url).toContain('/api/tags/5')
|
||||
expect(last.init.method).toBe('PATCH')
|
||||
expect(JSON.parse(last.init.body)).toEqual({ fandom_id: 9 })
|
||||
|
||||
await s.setFandom(5, null)
|
||||
expect(JSON.parse(calls.at(-1).init.body)).toEqual({ fandom_id: null })
|
||||
})
|
||||
|
||||
it('sends merge: true when requested', async () => {
|
||||
const s = useTagStore()
|
||||
const calls = []
|
||||
stubFetch((url, init) => {
|
||||
calls.push({ url, init })
|
||||
return { status: 200, body: { id: 2 } }
|
||||
})
|
||||
await s.setFandom(7, 3, { merge: true })
|
||||
expect(JSON.parse(calls.at(-1).init.body)).toEqual({ fandom_id: 3, merge: true })
|
||||
})
|
||||
|
||||
it('throws ApiError carrying the 409 collision body', async () => {
|
||||
const s = useTagStore()
|
||||
stubFetch(() => ({
|
||||
status: 409,
|
||||
body: { error: 'exists', target: { id: 42, name: 'Renji' } },
|
||||
}))
|
||||
await expect(s.setFandom(1, 2)).rejects.toMatchObject({
|
||||
status: 409,
|
||||
body: { target: { id: 42 } },
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user