diff --git a/frontend/src/stores/gallery.js b/frontend/src/stores/gallery.js index 10d3208..0c4ca77 100644 --- a/frontend/src/stores/gallery.js +++ b/frontend/src/stores/gallery.js @@ -10,7 +10,7 @@ export const useGalleryStore = defineStore('gallery', () => { const nextCursor = ref(null) const loading = ref(false) const error = ref(null) - const filter = ref({ tag_id: null }) + const filter = ref({ tag_id: null, post_id: null }) const timelineBuckets = ref([]) const timelineLoading = ref(false) @@ -30,8 +30,7 @@ export const useGalleryStore = defineStore('gallery', () => { error.value = null const myId = ++inflightId try { - const params = { limit: 50 } - if (filter.value.tag_id) params.tag_id = filter.value.tag_id + const params = { limit: 50, ...activeFilterParam() } if (nextCursor.value) params.cursor = nextCursor.value const body = await api.get('/api/gallery/scroll', { params }) if (myId !== inflightId) return // stale response @@ -48,7 +47,7 @@ export const useGalleryStore = defineStore('gallery', () => { async function loadTimeline() { timelineLoading.value = true try { - const params = filter.value.tag_id ? { tag_id: filter.value.tag_id } : {} + const params = activeFilterParam() timelineBuckets.value = await api.get('/api/gallery/timeline', { params }) } finally { timelineLoading.value = false @@ -56,8 +55,7 @@ export const useGalleryStore = defineStore('gallery', () => { } async function jumpTo(year, month) { - const params = { year, month } - if (filter.value.tag_id) params.tag_id = filter.value.tag_id + const params = { year, month, ...activeFilterParam() } const body = await api.get('/api/gallery/jump', { params }) if (body.cursor) { images.value = [] @@ -67,8 +65,22 @@ export const useGalleryStore = defineStore('gallery', () => { } } + function activeFilterParam() { + if (filter.value.tag_id) return { tag_id: filter.value.tag_id } + if (filter.value.post_id) return { post_id: filter.value.post_id } + return {} + } + function setTagFilter(tagId) { filter.value.tag_id = tagId + filter.value.post_id = null + loadInitial() + loadTimeline() + } + + function setPostFilter(postId) { + filter.value.post_id = postId + filter.value.tag_id = null loadInitial() loadTimeline() } @@ -79,7 +91,7 @@ export const useGalleryStore = defineStore('gallery', () => { return { images, dateGroups, hasMore, isEmpty, loading, error, filter, timelineBuckets, timelineLoading, - loadInitial, loadMore, loadTimeline, jumpTo, setTagFilter + loadInitial, loadMore, loadTimeline, jumpTo, setTagFilter, setPostFilter } }) diff --git a/frontend/test/gallery.spec.js b/frontend/test/gallery.spec.js new file mode 100644 index 0000000..4fb866a --- /dev/null +++ b/frontend/test/gallery.spec.js @@ -0,0 +1,52 @@ +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' +import { setActivePinia, createPinia } from 'pinia' +import { useGalleryStore } from '../src/stores/gallery.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)) + } + }) +} + +const EMPTY = { images: [], date_groups: [], next_cursor: null } + +describe('gallery store: tag/post filter exclusivity', () => { + beforeEach(() => setActivePinia(createPinia())) + afterEach(() => vi.restoreAllMocks()) + + it('setPostFilter sets post_id and clears tag_id', () => { + const s = useGalleryStore() + stubFetch(() => ({ status: 200, body: EMPTY })) + s.setTagFilter(3) + expect(s.filter.tag_id).toBe(3) + s.setPostFilter(7) + expect(s.filter.post_id).toBe(7) + expect(s.filter.tag_id).toBe(null) + }) + + it('setTagFilter clears post_id', () => { + const s = useGalleryStore() + stubFetch(() => ({ status: 200, body: EMPTY })) + s.setPostFilter(7) + s.setTagFilter(3) + expect(s.filter.tag_id).toBe(3) + expect(s.filter.post_id).toBe(null) + }) + + it('loadMore sends exactly the active filter param', async () => { + const s = useGalleryStore() + const urls = [] + stubFetch((url) => { urls.push(url); return { status: 200, body: EMPTY } }) + s.setPostFilter(7) + await s.loadMore() + const scrollCall = urls.filter(u => u.includes('/api/gallery/scroll')).pop() + expect(scrollCall).toContain('post_id=7') + expect(scrollCall).not.toContain('tag_id=') + }) +})