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 { fireEvent, render, screen, waitFor } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import Timeline from './Timeline'
import { withCodes } from '../codes/fixtures'
import type { Org, TimelineEvent } from '../api/client'
const mocks = vi.hoisted(() => ({
getTimeline: vi.fn(),
}))
vi.mock('../api/client', async (importOriginal) => ({
...(await importOriginal<typeof import('../api/client')>()),
getTimeline: mocks.getTimeline,
}))
function org(overrides: Partial<Org> = {}): Org {
return {
id: 1,
orgNo: '001',
orgName: '국제방송교류재단',
status: 'ACTIVE',
applicant: null,
mj: null,
itn: null,
lawyer: null,
lawyerAssignedDate: null,
channelIdMj: 'chan-mj',
channelIdLaw: 'chan-law',
stage: 5,
...overrides,
}
}
function event(overrides: Partial<TimelineEvent> = {}): TimelineEvent {
return {
type: 'STAGE',
at: Date.now(),
title: '1',
detail: null,
...overrides,
}
}
beforeEach(() => {
mocks.getTimeline.mockReset()
})
describe('Timeline', () => {
it('불러오는 동안 로딩 문구를 보여준다', () => {
mocks.getTimeline.mockReturnValue(new Promise(() => {}))
render(withCodes(<Timeline org={org()} />))
expect(screen.getByText('불러오는 중…')).toBeTruthy()
})
it('기록이 없으면 빈 상태 문구를 보여준다', async () => {
mocks.getTimeline.mockResolvedValue([])
render(withCodes(<Timeline org={org()} />))
await waitFor(() =>
expect(
screen.getByText('아직 기록이 없습니다. 채널을 만들고 업무를 진행하면 여기에 쌓입니다.'),
).toBeTruthy(),
)
})
it('세 종류 이벤트를 종류별 라벨과 함께 날짜로 묶어서 보여준다', async () => {
const day1 = new Date('2026-07-20T09:00:00').getTime()
const day2Morning = new Date('2026-07-21T08:30:00').getTime()
const day2Afternoon = new Date('2026-07-21T15:00:00').getTime()
mocks.getTimeline.mockResolvedValue([
event({ type: 'FILE', at: day2Afternoon, title: '보고서.pdf', detail: '문정원 · 박아이티' }),
event({ type: 'MEMO', at: day2Morning, title: '송민지', detail: '전화 통화 내용' }),
event({ type: 'STAGE', at: day1, title: '4', detail: null }),
])
render(withCodes(<Timeline org={org()} />))
await waitFor(() => expect(screen.getByText('보고서.pdf')).toBeTruthy())
expect(screen.getAllByText('자료 등록')).toHaveLength(1)
expect(screen.getByText('문정원 · 박아이티')).toBeTruthy()
expect(screen.getByText('업무메모')).toBeTruthy()
expect(screen.getByText('송민지')).toBeTruthy()
expect(screen.getByText('전화 통화 내용')).toBeTruthy()
expect(screen.getByText('단계 변경')).toBeTruthy()
expect(screen.getByText('4. 변호사 배당 단계로 진행')).toBeTruthy()
// 날짜 그룹 헤더 2개(2026-07-21, 2026-07-20)가 최신순으로 보여야 한다.
const headers = screen.getAllByText(/2026-07-2[01]/)
expect(headers[0].textContent).toContain('2026-07-21')
expect(headers[1].textContent).toContain('2026-07-20')
})
it('새로고침 버튼을 누르면 다시 조회한다', async () => {
mocks.getTimeline.mockResolvedValue([])
render(withCodes(<Timeline org={org()} />))
await waitFor(() => expect(mocks.getTimeline).toHaveBeenCalledTimes(1))
fireEvent.click(screen.getByRole('button', { name: '새로고침' }))
await waitFor(() => expect(mocks.getTimeline).toHaveBeenCalledTimes(2))
})
it('조회가 실패하면 오류 메시지를 보여준다', async () => {
mocks.getTimeline.mockRejectedValue(new Error('타임라인을 불러오지 못했습니다.'))
render(withCodes(<Timeline org={org()} />))
await waitFor(() => expect(screen.getByText('타임라인을 불러오지 못했습니다.')).toBeTruthy())
})
})