feat(frontend): tagDirectory rename and merge store actions
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -45,6 +45,38 @@ export const useTagDirectoryStore = defineStore('tagDirectory', () => {
|
||||
function setKind(k) { kind.value = k; reset() }
|
||||
function setQuery(text) { q.value = text; reset() }
|
||||
|
||||
async function rename(id, name) {
|
||||
try {
|
||||
const body = await api.patch(`/api/tags/${id}`, { body: { name } })
|
||||
const card = cards.value.find((c) => c.id === id)
|
||||
if (card) card.name = body.name
|
||||
return { tag: body }
|
||||
} catch (e) {
|
||||
if (e.status === 409 && e.body && e.body.target) {
|
||||
const card = cards.value.find((c) => c.id === id)
|
||||
return {
|
||||
collision: {
|
||||
sourceId: id,
|
||||
sourceName: card ? card.name : '',
|
||||
target: e.body.target,
|
||||
sourceImageCount: e.body.source_image_count,
|
||||
willAlias: e.body.will_alias
|
||||
}
|
||||
}
|
||||
}
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
async function merge(sourceId, targetId) {
|
||||
const result = await api.post(`/api/tags/${sourceId}/merge`, {
|
||||
body: { target_id: targetId }
|
||||
})
|
||||
const idx = cards.value.findIndex((c) => c.id === sourceId)
|
||||
if (idx !== -1) cards.value.splice(idx, 1)
|
||||
return result
|
||||
}
|
||||
|
||||
const hasMore = computed(() => !started || nextCursor.value !== null)
|
||||
const isEmpty = computed(
|
||||
() => !loading.value && cards.value.length === 0 && error.value === null
|
||||
@@ -52,6 +84,6 @@ export const useTagDirectoryStore = defineStore('tagDirectory', () => {
|
||||
|
||||
return {
|
||||
cards, loading, error, kind, q, hasMore, isEmpty,
|
||||
loadMore, reset, setKind, setQuery
|
||||
loadMore, reset, setKind, setQuery, rename, merge
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
|
||||
import { setActivePinia, createPinia } from 'pinia'
|
||||
import { useTagDirectoryStore } from '../src/stores/tagDirectory.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('tagDirectory store: rename / merge', () => {
|
||||
beforeEach(() => setActivePinia(createPinia()))
|
||||
afterEach(() => { vi.restoreAllMocks() })
|
||||
|
||||
it('rename success patches the card name in place', async () => {
|
||||
const store = useTagDirectoryStore()
|
||||
store.cards.push({ id: 1, name: 'old', kind: 'general' })
|
||||
stubFetch(() => ({ status: 200, body: { id: 1, name: 'new', kind: 'general' } }))
|
||||
const res = await store.rename(1, 'new')
|
||||
expect(res.collision).toBeUndefined()
|
||||
expect(store.cards[0].name).toBe('new')
|
||||
})
|
||||
|
||||
it('rename 409 resolves a collision object (does not throw)', async () => {
|
||||
const store = useTagDirectoryStore()
|
||||
store.cards.push({ id: 7, name: 'dupe', kind: 'general' })
|
||||
stubFetch(() => ({
|
||||
status: 409,
|
||||
body: {
|
||||
error: 'exists',
|
||||
target: { id: 2, name: 'Canon' },
|
||||
source_image_count: 5,
|
||||
will_alias: true
|
||||
}
|
||||
}))
|
||||
const res = await store.rename(7, 'Canon')
|
||||
expect(res.collision).toEqual({
|
||||
sourceId: 7,
|
||||
sourceName: 'dupe',
|
||||
target: { id: 2, name: 'Canon' },
|
||||
sourceImageCount: 5,
|
||||
willAlias: true
|
||||
})
|
||||
expect(store.cards[0].name).toBe('dupe') // unchanged
|
||||
})
|
||||
|
||||
it('merge removes the source card optimistically', async () => {
|
||||
const store = useTagDirectoryStore()
|
||||
store.cards.push({ id: 7, name: 'dupe' }, { id: 2, name: 'Canon' })
|
||||
stubFetch(() => ({
|
||||
status: 200,
|
||||
body: {
|
||||
target: { id: 2, name: 'Canon', kind: 'general' },
|
||||
merged_count: 3,
|
||||
alias_created: true,
|
||||
source_deleted: true
|
||||
}
|
||||
}))
|
||||
const res = await store.merge(7, 2)
|
||||
expect(store.cards.map(c => c.id)).toEqual([2])
|
||||
expect(res.merged_count).toBe(3)
|
||||
})
|
||||
|
||||
it('rename non-409 error rethrows', async () => {
|
||||
const store = useTagDirectoryStore()
|
||||
store.cards.push({ id: 1, name: 'x' })
|
||||
stubFetch(() => ({ status: 400, body: { error: 'bad' } }))
|
||||
await expect(store.rename(1, '')).rejects.toThrow()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user