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, within } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import WorkMemos from './WorkMemos'
import type { Contact, Org, WorkMemo } from '../api/client'
const mocks = vi.hoisted(() => ({
getMemos: vi.fn(),
createMemo: vi.fn(),
deleteMemo: vi.fn(),
}))
vi.mock('../api/client', async (importOriginal) => ({
...(await importOriginal<typeof import('../api/client')>()),
getMemos: mocks.getMemos,
createMemo: mocks.createMemo,
deleteMemo: mocks.deleteMemo,
}))
function contact(overrides: Partial<Contact> = {}): Contact {
return {
id: 1,
category: 'APPLICANT',
name: '이름없음',
affiliation: null,
deptName: null,
title: null,
phone: null,
email: null,
...overrides,
}
}
function org(overrides: Partial<Org> = {}): Org {
return {
id: 1,
orgNo: '001',
orgName: '국제방송교류재단',
status: 'ACTIVE',
applicant: contact({
id: 1,
category: 'APPLICANT',
name: '송민지',
affiliation: '국제방송교류재단',
deptName: '데이터정보화팀',
title: '과장',
phone: '02-3475-5434',
email: 'ming@arirang.com',
}),
mj: null,
itn: null,
lawyer: null,
lawyerAssignedDate: null,
channelIdMj: 'chan-mj',
channelIdLaw: 'chan-law',
...overrides,
}
}
function memo(overrides: Partial<WorkMemo> = {}): WorkMemo {
return {
id: 1,
orgId: 1,
contactName: '송민지 과장',
body: '채널 생성 일정 문의',
createdAt: new Date('2026-07-01T10:30:00').getTime(),
...overrides,
}
}
beforeEach(() => {
mocks.getMemos.mockReset()
mocks.createMemo.mockReset()
mocks.deleteMemo.mockReset()
})
describe('WorkMemos', () => {
it('등록된 메모를 담당자와 본문과 함께 보여준다', async () => {
mocks.getMemos.mockResolvedValue([memo()])
render(<WorkMemos org={org()} />)
await waitFor(() => expect(screen.getByRole('listitem')).toBeTruthy())
const item = within(screen.getByRole('listitem'))
expect(item.getByText('송민지 과장')).toBeTruthy()
expect(item.getByText('채널 생성 일정 문의')).toBeTruthy()
})
it('메모가 없으면 안내 문구를 보여준다', async () => {
mocks.getMemos.mockResolvedValue([])
render(<WorkMemos org={org()} />)
await waitFor(() => {
expect(
screen.getByText('등록된 메모가 없습니다. 통화 후 첫 메모를 남겨보세요.'),
).toBeTruthy()
})
})
it('담당자 선택에 기관 담당자와 직접 입력이 나오고, 직접 입력을 고르면 이름 입력칸이 나타난다', async () => {
mocks.getMemos.mockResolvedValue([])
render(<WorkMemos org={org()} />)
await waitFor(() => expect(mocks.getMemos).toHaveBeenCalled())
expect(screen.getByRole('option', { name: '송민지 과장' })).toBeTruthy()
expect(screen.getByRole('option', { name: '직접 입력' })).toBeTruthy()
expect(screen.queryByLabelText('담당자 이름')).toBeNull()
fireEvent.change(screen.getByLabelText('담당자'), { target: { value: '__custom__' } })
expect(screen.getByLabelText('담당자 이름')).toBeTruthy()
})
it('신청기관 담당자, 문정원 담당자, 아이티앤 담당자, 담당 변호사가 모두 있으면 넷 다 선택 옵션으로 나온다', async () => {
mocks.getMemos.mockResolvedValue([])
render(
<WorkMemos
org={org({
mj: contact({ id: 2, category: 'MJ', name: '김문정' }),
itn: contact({ id: 4, category: 'ITN', name: '박아이티' }),
lawyer: contact({ id: 3, category: 'LAWYER', name: '이변호' }),
})}
/>,
)
await waitFor(() => expect(mocks.getMemos).toHaveBeenCalled())
expect(screen.getByRole('option', { name: '송민지 과장' })).toBeTruthy()
expect(screen.getByRole('option', { name: '김문정' })).toBeTruthy()
expect(screen.getByRole('option', { name: '박아이티' })).toBeTruthy()
expect(screen.getByRole('option', { name: '이변호' })).toBeTruthy()
expect(screen.getByRole('option', { name: '직접 입력' })).toBeTruthy()
})
it('메모 내용이 비어 있으면 등록 버튼이 비활성이고, 입력하면 활성화된다', async () => {
mocks.getMemos.mockResolvedValue([])
render(<WorkMemos org={org()} />)
await waitFor(() => expect(mocks.getMemos).toHaveBeenCalled())
const submit = screen.getByRole('button', { name: '등록' })
expect(submit).toBeDisabled()
fireEvent.change(screen.getByLabelText('메모 내용'), { target: { value: '통화 내용' } })
expect(submit).not.toBeDisabled()
})
it('등록을 누르면 선택된 담당자와 본문으로 createMemo를 호출하고 본문을 비운다', async () => {
mocks.getMemos.mockResolvedValue([])
mocks.createMemo.mockResolvedValue(memo({ id: 2, body: '통화 내용' }))
render(<WorkMemos org={org()} />)
await waitFor(() => expect(mocks.getMemos).toHaveBeenCalled())
fireEvent.change(screen.getByLabelText('메모 내용'), { target: { value: '통화 내용' } })
fireEvent.click(screen.getByRole('button', { name: '등록' }))
await waitFor(() => {
expect(mocks.createMemo).toHaveBeenCalledWith(1, {
contactName: '송민지 과장',
body: '통화 내용',
})
})
await waitFor(() => {
expect((screen.getByLabelText('메모 내용') as HTMLTextAreaElement).value).toBe('')
})
})
it('삭제를 누르면 확인을 물어보고, 확인하면 deleteMemo를 호출한다', async () => {
mocks.getMemos.mockResolvedValue([memo()])
mocks.deleteMemo.mockResolvedValue(undefined)
const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(true)
render(<WorkMemos org={org()} />)
await waitFor(() => expect(screen.getByText('채널 생성 일정 문의')).toBeTruthy())
fireEvent.click(screen.getByRole('button', { name: '삭제' }))
expect(confirmSpy).toHaveBeenCalledWith('이 메모를 삭제할까요?')
await waitFor(() => expect(mocks.deleteMemo).toHaveBeenCalledWith(1, 1))
confirmSpy.mockRestore()
})
it('삭제 확인을 취소하면 deleteMemo를 호출하지 않는다', async () => {
mocks.getMemos.mockResolvedValue([memo()])
const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(false)
render(<WorkMemos org={org()} />)
await waitFor(() => expect(screen.getByText('채널 생성 일정 문의')).toBeTruthy())
fireEvent.click(screen.getByRole('button', { name: '삭제' }))
expect(confirmSpy).toHaveBeenCalled()
expect(mocks.deleteMemo).not.toHaveBeenCalled()
confirmSpy.mockRestore()
})
})