このコミットが含まれているのは:
@@ -0,0 +1,204 @@
|
||||
import { fireEvent, screen, waitFor } 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,
|
||||
buildTheatre,
|
||||
buildTheatreComment,
|
||||
buildTheatreInfo,
|
||||
buildTheatrePostSelectionWeights,
|
||||
buildTheatreProgramme,
|
||||
buildUser } from '@/test/factories'
|
||||
import { renderWithProviders } from '@/test/render'
|
||||
|
||||
import type { ReactNode } from 'react'
|
||||
|
||||
const api = vi.hoisted (() => ({
|
||||
apiDelete: vi.fn (),
|
||||
apiGet: vi.fn (),
|
||||
apiPatch: vi.fn (),
|
||||
apiPost: vi.fn (),
|
||||
apiPut: vi.fn (),
|
||||
isApiError: vi.fn (() => false),
|
||||
}))
|
||||
|
||||
const postsApi = vi.hoisted (() => ({
|
||||
fetchPost: vi.fn (),
|
||||
}))
|
||||
|
||||
const dialogue = vi.hoisted (() => ({
|
||||
confirm: vi.fn (),
|
||||
}))
|
||||
|
||||
vi.mock ('@/lib/api', () => api)
|
||||
vi.mock ('@/lib/posts', () => postsApi)
|
||||
vi.mock ('@/components/dialogues/DialogueProvider', () => ({
|
||||
useDialogue: () => dialogue,
|
||||
}))
|
||||
vi.mock ('@/components/PostEmbed', () => ({
|
||||
default: ({ post }: {
|
||||
post: { title: string | null; url: string }
|
||||
}) => <div>Embed:{post.title || post.url}</div>,
|
||||
}))
|
||||
vi.mock ('@/components/PostEditForm', () => ({
|
||||
default: () => <div>Post edit form</div>,
|
||||
}))
|
||||
vi.mock ('framer-motion', () => ({
|
||||
motion: {
|
||||
aside: ({ children }: { children?: ReactNode }) => <aside>{children}</aside>,
|
||||
div: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
|
||||
main: ({ children }: { children?: ReactNode }) => <main>{children}</main>,
|
||||
},
|
||||
}))
|
||||
|
||||
const currentPost = buildPost ({
|
||||
id: 10,
|
||||
title: '上映中の投稿',
|
||||
url: 'https://www.nicovideo.jp/watch/sm10',
|
||||
})
|
||||
const theatre = buildTheatre ({ id: 7, name: '上映室' })
|
||||
const programme = buildTheatreProgramme ({
|
||||
theatreId: 7,
|
||||
position: 3,
|
||||
post: currentPost,
|
||||
})
|
||||
const weights = buildTheatrePostSelectionWeights ({
|
||||
lightestPosts: [{
|
||||
post: currentPost,
|
||||
penalty: 2,
|
||||
weight: 0.5,
|
||||
tags: [],
|
||||
}],
|
||||
})
|
||||
|
||||
const renderPage = (user = buildUser ({ id: 1, role: 'member' })) =>
|
||||
renderWithProviders (
|
||||
<Routes>
|
||||
<Route path="/theatres/:id" element={<TheatreDetailPage user={user}/>}/>
|
||||
</Routes>,
|
||||
{ route: '/theatres/7' },
|
||||
)
|
||||
|
||||
const mockDefaultApi = () => {
|
||||
api.apiGet.mockImplementation ((path: string) => {
|
||||
switch (path)
|
||||
{
|
||||
case '/theatres/7':
|
||||
return Promise.resolve (theatre)
|
||||
|
||||
case '/theatres/7/comments':
|
||||
return Promise.resolve ([
|
||||
buildTheatreComment ({
|
||||
theatreId: 7,
|
||||
no: 2,
|
||||
user: { id: 1, name: 'tester' },
|
||||
content: '視聴コメント',
|
||||
}),
|
||||
])
|
||||
|
||||
case '/theatres/7/programmes':
|
||||
return Promise.resolve ([programme])
|
||||
|
||||
case '/theatres/7/post_selection_weights':
|
||||
return Promise.resolve (weights)
|
||||
|
||||
default:
|
||||
return Promise.reject (new Error (`Unexpected GET ${ path }`))
|
||||
}
|
||||
})
|
||||
|
||||
api.apiPut.mockImplementation ((path: string) => {
|
||||
switch (path)
|
||||
{
|
||||
case '/theatres/7/watching':
|
||||
return Promise.resolve (buildTheatreInfo ({
|
||||
postId: currentPost.id,
|
||||
postStartedAt: '2026-01-02T03:04:05.000Z',
|
||||
postElapsedMs: 1_000,
|
||||
watchingUsers: [{ id: 1, name: 'tester' }],
|
||||
skipVote: {
|
||||
votesCount: 0,
|
||||
requiredCount: 2,
|
||||
watchingUsersCount: 1,
|
||||
voted: false,
|
||||
},
|
||||
}))
|
||||
|
||||
case '/theatres/7/skip_vote':
|
||||
return Promise.resolve (buildTheatreInfo ({
|
||||
postId: currentPost.id,
|
||||
postStartedAt: '2026-01-02T03:04:05.000Z',
|
||||
postElapsedMs: 2_000,
|
||||
watchingUsers: [{ id: 1, name: 'tester' }],
|
||||
skipVote: {
|
||||
votesCount: 1,
|
||||
requiredCount: 2,
|
||||
watchingUsersCount: 1,
|
||||
voted: true,
|
||||
},
|
||||
}))
|
||||
|
||||
default:
|
||||
return Promise.reject (new Error (`Unexpected PUT ${ path }`))
|
||||
}
|
||||
})
|
||||
|
||||
api.apiDelete.mockResolvedValue (undefined)
|
||||
api.apiPatch.mockResolvedValue (undefined)
|
||||
api.apiPost.mockResolvedValue (undefined)
|
||||
postsApi.fetchPost.mockResolvedValue (currentPost)
|
||||
dialogue.confirm.mockResolvedValue (true)
|
||||
}
|
||||
|
||||
describe ('TheatreDetailPage', () => {
|
||||
beforeEach (() => {
|
||||
vi.clearAllMocks ()
|
||||
mockDefaultApi ()
|
||||
})
|
||||
|
||||
it ('loads theatre state, comments, current post, programme history, and weights', async () => {
|
||||
renderPage ()
|
||||
|
||||
expect (await screen.findByText ('上映会場『上映室』')).toBeInTheDocument ()
|
||||
expect (await screen.findByText ('Embed:上映中の投稿')).toBeInTheDocument ()
|
||||
expect (screen.getAllByText ('視聴コメント')[0]).toBeInTheDocument ()
|
||||
expect (screen.getAllByText ('上映中の投稿')[0]).toBeInTheDocument ()
|
||||
expect (screen.getByText ('penalty 2')).toBeInTheDocument ()
|
||||
|
||||
await waitFor (() => {
|
||||
expect (postsApi.fetchPost).toHaveBeenCalledWith ('10')
|
||||
})
|
||||
})
|
||||
|
||||
it ('votes to skip the current post', async () => {
|
||||
renderPage ()
|
||||
|
||||
await screen.findByText ('Embed:上映中の投稿')
|
||||
|
||||
fireEvent.click (screen.getByRole ('button', { name: 'スキップ 0 / 2' }))
|
||||
|
||||
await waitFor (() => {
|
||||
expect (api.apiPut).toHaveBeenCalledWith (
|
||||
'/theatres/7/skip_vote',
|
||||
{ post_id: 10 },
|
||||
)
|
||||
})
|
||||
expect (await screen.findByRole ('button', { name: 'スキップ取消 1 / 2' }))
|
||||
.toBeInTheDocument ()
|
||||
})
|
||||
|
||||
it ('deletes an owned comment after confirmation', async () => {
|
||||
renderPage ()
|
||||
|
||||
fireEvent.click ((await screen.findAllByLabelText ('コメントを削除'))[0])
|
||||
|
||||
await waitFor (() => {
|
||||
expect (dialogue.confirm).toHaveBeenCalled ()
|
||||
})
|
||||
await waitFor (() => {
|
||||
expect (api.apiDelete).toHaveBeenCalledWith ('/theatres/7/comments/2')
|
||||
})
|
||||
expect (await screen.findAllByText ('削除されました.')).toHaveLength (2)
|
||||
})
|
||||
})
|
||||
@@ -1,4 +1,13 @@
|
||||
import type { Material, Post, Tag, User, WikiPage } from '@/types'
|
||||
import type { Material,
|
||||
Post,
|
||||
Tag,
|
||||
Theatre,
|
||||
TheatreComment,
|
||||
TheatreInfo,
|
||||
TheatrePostSelectionWeights,
|
||||
TheatreProgramme,
|
||||
User,
|
||||
WikiPage } from '@/types'
|
||||
|
||||
export const buildTag = (overrides: Partial<Tag> = {}): Tag => ({
|
||||
id: 1,
|
||||
@@ -72,3 +81,62 @@ export const buildMaterial = (overrides: Partial<Material> = {}): Material => ({
|
||||
updatedByUser: { id: 2, name: 'updater' },
|
||||
...overrides,
|
||||
})
|
||||
|
||||
export const buildTheatre = (overrides: Partial<Theatre> = {}): Theatre => ({
|
||||
id: 1,
|
||||
name: 'テスト劇場',
|
||||
opensAt: '2026-01-02T03:04:05.000Z',
|
||||
closesAt: null,
|
||||
createdByUser: { id: 1, name: 'creator' },
|
||||
createdAt: '2026-01-02T03:04:05.000Z',
|
||||
updatedAt: '2026-01-03T03:04:05.000Z',
|
||||
...overrides,
|
||||
})
|
||||
|
||||
export const buildTheatreInfo = (
|
||||
overrides: Partial<TheatreInfo> = {},
|
||||
): TheatreInfo => ({
|
||||
hostFlg: false,
|
||||
postId: null,
|
||||
postStartedAt: null,
|
||||
postElapsedMs: null,
|
||||
watchingUsers: [],
|
||||
skipVote: {
|
||||
votesCount: 0,
|
||||
requiredCount: 1,
|
||||
watchingUsersCount: 0,
|
||||
voted: false,
|
||||
},
|
||||
...overrides,
|
||||
})
|
||||
|
||||
export const buildTheatreComment = (
|
||||
overrides: Partial<TheatreComment> = {},
|
||||
): TheatreComment => ({
|
||||
theatreId: 1,
|
||||
no: 1,
|
||||
deleted: false,
|
||||
user: { id: 1, name: 'tester' },
|
||||
content: 'テストコメント',
|
||||
createdAt: '2026-01-02T03:04:05.000Z',
|
||||
...overrides,
|
||||
} as TheatreComment)
|
||||
|
||||
export const buildTheatreProgramme = (
|
||||
overrides: Partial<TheatreProgramme> = {},
|
||||
): TheatreProgramme => ({
|
||||
theatreId: 1,
|
||||
position: 1,
|
||||
post: buildPost (),
|
||||
createdAt: '2026-01-02T03:04:05.000Z',
|
||||
...overrides,
|
||||
})
|
||||
|
||||
export const buildTheatrePostSelectionWeights = (
|
||||
overrides: Partial<TheatrePostSelectionWeights> = {},
|
||||
): TheatrePostSelectionWeights => ({
|
||||
tagPenalties: [],
|
||||
lightestPosts: [],
|
||||
heaviestPosts: [],
|
||||
...overrides,
|
||||
})
|
||||
|
||||
新しい課題から参照
ユーザをブロックする