File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
import { act, render, screen, waitFor } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import ChannelPosts from './ChannelPosts'
import type { Org, PostView } from '../api/client'
const mocks = vi.hoisted(() => ({
getPosts: vi.fn(),
}))
vi.mock('../api/client', async (importOriginal) => ({
...(await importOriginal<typeof import('../api/client')>()),
getPosts: mocks.getPosts,
}))
function org(overrides: Partial<Org> = {}): Org {
return {
id: 1,
orgNo: '001',
orgName: '국제방송교류재단',
status: 'ACTIVE',
deptName: null,
managerName: null,
managerTitle: null,
managerPhone: null,
managerEmail: null,
channelIdMj: 'chan-mj',
channelIdLaw: 'chan-law',
...overrides,
}
}
function post(overrides: Partial<PostView> = {}): PostView {
return {
id: 'p1',
user: '송민지',
message: '안녕하세요',
createAt: Date.now(),
system: false,
files: [],
...overrides,
}
}
beforeEach(() => {
mocks.getPosts.mockReset()
vi.useRealTimers()
})
describe('ChannelPosts', () => {
it('메시지를 작성자와 본문과 함께 보여준다', async () => {
mocks.getPosts.mockResolvedValue([post()])
render(<ChannelPosts org={org()} />)
await waitFor(() => {
expect(screen.getByText('송민지')).toBeTruthy()
expect(screen.getByText('안녕하세요')).toBeTruthy()
})
})
it('시스템 메시지는 회색 중앙 정렬 한 줄로 보여준다', async () => {
mocks.getPosts.mockResolvedValue([post({ system: true, message: '님이 입장했습니다.' })])
render(<ChannelPosts org={org()} />)
await waitFor(() => {
const line = screen.getByText('님이 입장했습니다.')
expect(line.className).toContain('text-gray-400')
expect(line.className).toContain('text-center')
})
})
it('3초마다 폴링해서 게시글을 다시 조회한다', async () => {
vi.useFakeTimers()
mocks.getPosts.mockResolvedValue([])
render(<ChannelPosts org={org()} />)
await act(async () => {
await vi.advanceTimersByTimeAsync(0)
})
expect(mocks.getPosts).toHaveBeenCalledTimes(1)
await act(async () => {
await vi.advanceTimersByTimeAsync(3000)
})
expect(mocks.getPosts).toHaveBeenCalledTimes(2)
vi.useRealTimers()
})
it('양쪽 채널이 모두 없으면 안내 문구를 보여주고 API를 호출하지 않는다', () => {
render(<ChannelPosts org={org({ channelIdMj: null, channelIdLaw: null })} />)
expect(screen.getByText('채널이 아직 생성되지 않았습니다.')).toBeTruthy()
expect(mocks.getPosts).not.toHaveBeenCalled()
})
it('법률검토 채널이 없으면 법률검토 칩이 비활성화된다', async () => {
mocks.getPosts.mockResolvedValue([])
render(<ChannelPosts org={org({ channelIdLaw: null })} />)
expect(screen.getByRole('button', { name: '법률검토' })).toBeDisabled()
await waitFor(() => expect(mocks.getPosts).toHaveBeenCalled())
})
it('같은 사람이 5분 안에 연달아 쓴 메시지는 이름과 아바타를 한 번만 보여준다', async () => {
const now = Date.now()
mocks.getPosts.mockResolvedValue([
post({ id: 'p1', createAt: now }),
post({ id: 'p2', message: '이어서 씁니다', createAt: now + 1000 }),
])
render(<ChannelPosts org={org()} />)
await waitFor(() => expect(screen.getByText('이어서 씁니다')).toBeTruthy())
expect(screen.getAllByText('송민지')).toHaveLength(1)
})
it('첨부파일 칩의 링크는 다운로드 엔드포인트를 가리킨다', async () => {
mocks.getPosts.mockResolvedValue([
post({ files: [{ id: 'f1', name: '보고서.pdf', size: 1200 }] }),
])
render(<ChannelPosts org={org()} />)
await waitFor(() => {
const link = screen.getByText('보고서.pdf').closest('a')
expect(link).toHaveAttribute('href', '/api/files/f1')
})
})
})