diff --git a/frontend/src/components/posts/PostCard.vue b/frontend/src/components/posts/PostCard.vue index a45f0a5..29aa76d 100644 --- a/frontend/src/components/posts/PostCard.vue +++ b/frontend/src/components/posts/PostCard.vue @@ -1,29 +1,18 @@ - - diff --git a/frontend/src/stores/modal.js b/frontend/src/stores/modal.js index 05876d0..3859b61 100644 --- a/frontend/src/stores/modal.js +++ b/frontend/src/stores/modal.js @@ -18,9 +18,9 @@ export const useModalStore = defineStore('modal', () => { const inflight = useInflightToken() // Post-scoped cycle. When set, prev/next cycles within this array - // (used by PostCard's expanded-mosaic PostImageGrid clicks). When - // null, prev/next falls back to current.value.neighbors (the - // gallery-store-driven /api/gallery/image/ neighbors). + // (used by PostCard image clicks — the modal is scoped to that post's + // images). When null, prev/next falls back to current.value.neighbors + // (the gallery-store-driven /api/gallery/image/ neighbors). const postImageIds = ref(null) const postImageIndex = ref(0) diff --git a/frontend/test/components/postCard.spec.js b/frontend/test/components/postCard.spec.js index 1e39004..3625ca1 100644 --- a/frontend/test/components/postCard.spec.js +++ b/frontend/test/components/postCard.spec.js @@ -1,26 +1,61 @@ // @vitest-environment happy-dom -import { describe, it, expect } from 'vitest' +import { describe, it, expect, vi } from 'vitest' +import { flushPromises } from '@vue/test-utils' import PostCard from '../../src/components/posts/PostCard.vue' +import { useModalStore } from '../../src/stores/modal.js' import { freshPinia, mountComponent } from '../support/mountComponent.js' +const now = new Date().toISOString() +const BASE = { + id: 1, external_post_id: 'P1', + post_title: 'Hello World', + post_url: 'https://x/1', post_date: now, downloaded_at: now, + artist: { id: 1, name: 'Sabu', slug: 'sabu' }, + source: { id: 1, platform: 'subscribestar' }, + thumbnails: [], thumbnails_more: 0, attachments: [], +} + describe('PostCard', () => { it('renders the HTML-stripped title and the artist', () => { - const pinia = freshPinia() - const now = new Date().toISOString() - const post = { - id: 1, external_post_id: 'P1', - post_title: 'Hello World', - post_url: 'https://x/1', post_date: now, downloaded_at: now, - description_plain: 'a description', - artist: { id: 1, name: 'Sabu', slug: 'sabu' }, - source: { id: 1, platform: 'subscribestar' }, - thumbnails: [], thumbnails_more: 0, attachments: [], - } - const w = mountComponent(PostCard, { props: { post }, pinia }) + const post = { ...BASE, description_plain: 'a description' } + const w = mountComponent(PostCard, { props: { post }, pinia: freshPinia() }) const t = w.text() expect(t).toContain('Hello World') // plain title expect(t).not.toContain('') // tags stripped expect(t).toContain('Sabu') // artist (RouterLink slot) }) + + it('shows a "Show more" toggle only when the description is truncated', () => { + const truncated = mountComponent(PostCard, { + props: { post: { ...BASE, description_plain: 'lots of text…', description_truncated: true } }, + pinia: freshPinia(), + }) + expect(truncated.text()).toContain('Show more') + + const full = mountComponent(PostCard, { + props: { post: { ...BASE, description_plain: 'short', description_truncated: false } }, + pinia: freshPinia(), + }) + expect(full.text()).not.toContain('Show more') + }) + + it('clicking an image opens the post-scoped modal (never expands the card)', async () => { + const pinia = freshPinia() + const modal = useModalStore() + const openSpy = vi.spyOn(modal, 'open').mockResolvedValue() + const post = { + ...BASE, + description_plain: 'd', + thumbnails: [ + { image_id: 10, thumbnail_url: '/a' }, + { image_id: 11, thumbnail_url: '/b' }, + ], + thumbnails_more: 0, + } + const w = mountComponent(PostCard, { props: { post }, pinia }) + await w.find('.fc-post-card__hero').trigger('click') + await flushPromises() + expect(openSpy).toHaveBeenCalledWith(10, { postImageIds: [10, 11] }) + }) })