import { fireEvent, render, screen, waitFor } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' import OrgDetail from './OrgDetail' import type { Org } from '../api/client' const mocks = vi.hoisted(() => ({ updateContact: vi.fn(), provisionChannels: vi.fn(), })) vi.mock('../api/client', async (importOriginal) => ({ ...(await importOriginal()), updateContact: mocks.updateContact, provisionChannels: mocks.provisionChannels, })) function org(overrides: Partial = {}): Org { return { id: 1, orgNo: '001', orgName: '국제방송교류재단', status: 'INFO_PENDING', deptName: null, managerName: null, managerTitle: null, managerPhone: null, managerEmail: null, channelIdMj: null, channelIdLaw: null, ...overrides, } } beforeEach(() => { mocks.updateContact.mockReset() mocks.provisionChannels.mockReset() }) describe('OrgDetail', () => { it('정보 대기 상태면 채널 생성 버튼이 비활성이다', () => { render( {}} />) expect(screen.getByRole('button', { name: '채널 생성' })).toBeDisabled() }) it('생성 가능 상태면 채널 생성 버튼이 활성이다', () => { render( {}} />) expect(screen.getByRole('button', { name: '채널 생성' })).toBeEnabled() }) it('담당자 정보를 저장하면 API를 호출한다', async () => { mocks.updateContact.mockResolvedValue(org({ status: 'READY' })) const onChanged = vi.fn() render() fireEvent.change(screen.getByLabelText('부서명'), { target: { value: '데이터정보화팀' } }) fireEvent.change(screen.getByLabelText('담당자명'), { target: { value: '송민지' } }) fireEvent.change(screen.getByLabelText('직급/직함'), { target: { value: '과장' } }) fireEvent.change(screen.getByLabelText('연락처'), { target: { value: '02-3475-5434' } }) fireEvent.change(screen.getByLabelText('이메일'), { target: { value: 'ming@arirang.com' } }) fireEvent.click(screen.getByRole('button', { name: '정보 저장' })) await waitFor(() => { expect(mocks.updateContact).toHaveBeenCalledWith(1, { deptName: '데이터정보화팀', managerName: '송민지', managerTitle: '과장', managerPhone: '02-3475-5434', managerEmail: 'ming@arirang.com', }) expect(onChanged).toHaveBeenCalled() }) }) it('채널 생성은 확인 다이얼로그에 만들어질 채널 2개를 보여준다', () => { render( {}} />) fireEvent.click(screen.getByRole('button', { name: '채널 생성' })) expect(screen.getByText('001_국제방송교류재단 (문정원)')).toBeTruthy() expect(screen.getByText('001_국제방송교류재단 (법률검토)')).toBeTruthy() expect(mocks.provisionChannels).not.toHaveBeenCalled() }) it('확인을 눌러야 채널 생성 API가 호출된다', async () => { mocks.provisionChannels.mockResolvedValue({ mj: 'CREATED', law: 'CREATED', message: '' }) render( {}} />) fireEvent.click(screen.getByRole('button', { name: '채널 생성' })) fireEvent.click(screen.getByRole('button', { name: '생성' })) await waitFor(() => expect(mocks.provisionChannels).toHaveBeenCalledWith(1)) }) it('부분 실패하면 재시도 안내를 보여준다', async () => { mocks.provisionChannels.mockResolvedValue({ mj: 'CREATED', law: 'FAILED', message: '[법률검토] 서버 오류', }) render( {}} />) fireEvent.click(screen.getByRole('button', { name: '채널 생성' })) fireEvent.click(screen.getByRole('button', { name: '생성' })) await waitFor(() => { expect(screen.getByText(/법률검토 채널 생성에 실패/)).toBeTruthy() expect(screen.getByText(/다시 눌러도 안전/)).toBeTruthy() }) }) it('양쪽 다 실패하면 문정원 채널이 살아있다는 잘못된 안내를 보여주지 않는다', async () => { mocks.provisionChannels.mockResolvedValue({ mj: 'FAILED', law: 'FAILED', message: '[문정원] 서버 오류\n[법률검토] 서버 오류', }) render( {}} />) fireEvent.click(screen.getByRole('button', { name: '채널 생성' })) fireEvent.click(screen.getByRole('button', { name: '생성' })) await waitFor(() => { expect(screen.getByText(/문정원 채널.*법률검토 채널.*모두 생성에 실패/)).toBeTruthy() }) expect(screen.queryByText(/문정원 채널은 그대로 남아 있으니/)).toBeNull() }) it('문정원만 실패하면 법률검토 채널이 살아있다는 안내를 보여준다', async () => { mocks.provisionChannels.mockResolvedValue({ mj: 'FAILED', law: 'CREATED', message: '[문정원] 서버 오류', }) render( {}} />) fireEvent.click(screen.getByRole('button', { name: '채널 생성' })) fireEvent.click(screen.getByRole('button', { name: '생성' })) await waitFor(() => { expect(screen.getByText(/문정원 채널 생성에 실패/)).toBeTruthy() expect(screen.getByText(/법률검토 채널은 그대로 남아 있으니/)).toBeTruthy() }) }) it('채널 생성이 진행 중이면 생성 버튼과 채널 생성 버튼이 비활성화된다', async () => { let resolvePromise: (value: { mj: string; law: string; message: string }) => void = () => {} mocks.provisionChannels.mockReturnValue( new Promise((resolve) => { resolvePromise = resolve }), ) render( {}} />) fireEvent.click(screen.getByRole('button', { name: '채널 생성' })) fireEvent.click(screen.getByRole('button', { name: '생성' })) await waitFor(() => { expect(screen.getByRole('button', { name: '생성' })).toBeDisabled() }) expect(screen.getByRole('button', { name: '채널 생성' })).toBeDisabled() resolvePromise({ mj: 'CREATED', law: 'CREATED', message: '' }) await waitFor(() => { expect(screen.getByRole('button', { name: '채널 생성' })).toBeEnabled() }) }) it('필수 항목이 비어있으면 정보 저장 버튼이 비활성화된다', () => { render( {}} />) expect(screen.getByRole('button', { name: '정보 저장' })).toBeDisabled() }) it('직급/직함이 비어있어도 나머지 필수 항목만 채우면 정보 저장 버튼이 활성화된다', () => { render( {}} />) fireEvent.change(screen.getByLabelText('부서명'), { target: { value: '데이터정보화팀' } }) fireEvent.change(screen.getByLabelText('담당자명'), { target: { value: '송민지' } }) fireEvent.change(screen.getByLabelText('연락처'), { target: { value: '02-3475-5434' } }) fireEvent.change(screen.getByLabelText('이메일'), { target: { value: 'ming@arirang.com' } }) expect(screen.getByRole('button', { name: '정보 저장' })).toBeEnabled() }) it('reload로 org 객체가 새로 만들어져도 같은 기관이면 결과가 지워지지 않는다', async () => { mocks.provisionChannels.mockResolvedValue({ mj: 'CREATED', law: 'FAILED', message: '[법률검토] 서버 오류', }) const { rerender } = render( {}} />) fireEvent.click(screen.getByRole('button', { name: '채널 생성' })) fireEvent.click(screen.getByRole('button', { name: '생성' })) await waitFor(() => { expect(screen.getByText(/법률검토 채널 생성에 실패/)).toBeTruthy() }) // App.reload()가 만드는, id는 같지만 identity가 다른 새 객체를 흉내낸다. rerender( {}} />, ) expect(screen.getByText(/법률검토 채널 생성에 실패/)).toBeTruthy() }) it('다른 기관으로 전환하면 이전 결과와 입력값이 초기화된다', async () => { mocks.provisionChannels.mockResolvedValue({ mj: 'CREATED', law: 'FAILED', message: '[법률검토] 서버 오류', }) const { rerender } = render( {}} />) fireEvent.click(screen.getByRole('button', { name: '채널 생성' })) fireEvent.click(screen.getByRole('button', { name: '생성' })) await waitFor(() => { expect(screen.getByText(/법률검토 채널 생성에 실패/)).toBeTruthy() }) rerender( {}} />) expect(screen.queryByText(/법률검토 채널 생성에 실패/)).toBeNull() }) })