diff --git a/frontend/src/pages/theatres/TheatreDetailPage.test.tsx b/frontend/src/pages/theatres/TheatreDetailPage.test.tsx index 7adab24..7f7bf12 100644 --- a/frontend/src/pages/theatres/TheatreDetailPage.test.tsx +++ b/frontend/src/pages/theatres/TheatreDetailPage.test.tsx @@ -1,9 +1,10 @@ -import { act, fireEvent, screen, waitFor } from '@testing-library/react' +import { act, fireEvent, screen, waitFor, within } from '@testing-library/react' import { Route, Routes } from 'react-router-dom' import { beforeEach, describe, expect, it, vi } from 'vitest' import TheatreDetailPage from '@/pages/theatres/TheatreDetailPage' import { buildPost, + buildTag, buildTheatre, buildTheatreComment, buildTheatreInfo, @@ -96,6 +97,9 @@ const renderPage = (user = buildUser ({ id: 1, role: 'member' })) => { route: '/theatres/7' }, ) +const tagSection = (): HTMLElement => + screen.getAllByRole ('heading', { name: 'タグ' })[0].closest ('section')! + const mockDefaultApi = () => { api.apiGet.mockImplementation ((path: string) => { switch (path) @@ -245,6 +249,54 @@ describe ('TheatreDetailPage', () => { expect (postEmbed.seek).not.toHaveBeenCalledWith (0) }) + it ('shows child tags from the post tag tree in both vertical and horizontal layouts', async () => { + const childTag = buildTag ({ id: 12, name: '子タグ', category: 'general' }) + const parentTag = buildTag ({ + id: 11, + name: '親タグ', + category: 'general', + children: [childTag], + }) + const duplicateParentTag = buildTag ({ + id: 13, + name: '別親タグ', + category: 'general', + children: [childTag], + }) + + postsApi.fetchPost.mockResolvedValueOnce (buildPost ({ + ...currentPost, + tags: [parentTag, duplicateParentTag], + })) + + renderPage () + + await screen.findByText ('Embed:上映中の投稿') + + expect (within (tagSection ()).getByRole ('link', { name: '親タグ' })) + .toBeInTheDocument () + expect (within (tagSection ()).getByRole ('link', { name: '別親タグ' })) + .toBeInTheDocument () + expect (within (tagSection ()).getByRole ('link', { name: '子タグ' })) + .toBeInTheDocument () + expect (within (tagSection ()).getAllByRole ('link', { name: '子タグ' })) + .toHaveLength (1) + + fireEvent.click (screen.getByRole ('button', { name: '2 列 A 型' })) + fireEvent.click (screen.getAllByRole ('button', { name: '横並び' })[0]) + + await waitFor (() => { + expect (within (tagSection ()).getByRole ('link', { name: '親タグ' })) + .toBeInTheDocument () + expect (within (tagSection ()).getByRole ('link', { name: '別親タグ' })) + .toBeInTheDocument () + expect (within (tagSection ()).getByRole ('link', { name: '子タグ' })) + .toBeInTheDocument () + expect (within (tagSection ()).getAllByRole ('link', { name: '子タグ' })) + .toHaveLength (1) + }) + }) + it ('does not advance host post while video length is unknown', async () => { api.apiPut.mockImplementation ((path: string) => { switch (path) diff --git a/frontend/src/pages/theatres/TheatreDetailPage.tsx b/frontend/src/pages/theatres/TheatreDetailPage.tsx index f3f27aa..a52169f 100644 --- a/frontend/src/pages/theatres/TheatreDetailPage.tsx +++ b/frontend/src/pages/theatres/TheatreDetailPage.tsx @@ -102,10 +102,33 @@ const compareTagName = (a: Tag, b: Tag): number => a.name === b.name ? 0 : (a.name < b.name ? -1 : 1) -const tagsByCategory = (tags: Tag[]): Partial> => { - const grouped: Partial> = { } +const flattenTags = (tags: Tag[]): Tag[] => { + const flattened: Tag[] = [] + const seen = new Set () + + const visit = (tag: Tag) => { + if (seen.has (tag.id)) + return + + seen.add (tag.id) + flattened.push (tag) + + for (const child of tag.children ?? []) + visit (child) + } for (const tag of tags) + visit (tag) + + return flattened +} + + +const tagsByCategory = (tags: Tag[]): Partial> => { + const flattenedTags = flattenTags (tags) + const grouped: Partial> = { } + + for (const tag of flattenedTags) { grouped[tag.category] ??= [] grouped[tag.category]!.push (tag)