import { render, screen, waitFor } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' import { CodeProvider } from './CodeProvider' import { TEST_CODES, withCodes } from './fixtures' import { useStages } from './stage' import { badgeClass, barClass } from './tone' import { useCodeLabel, useCodes, useScopedCodes, useSplitByFirstRow } from './useCodes' const mocks = vi.hoisted(() => ({ getCodes: vi.fn() })) vi.mock('../api/client', async (importOriginal) => ({ ...(await importOriginal()), getCodes: mocks.getCodes, })) beforeEach(() => { mocks.getCodes.mockReset() }) describe('useCodes', () => { it('그룹의 선택지를 정렬순서대로 준다', () => { function Probe() { return {useCodes('REVIEW_RESULT').map((c) => c.label).join('|')} } render(withCodes()) expect( screen.getByText('신유형 개방|계약서 등 재확인|권리처리 추진|개방불가|권리처리 추진 미희망'), ).toBeTruthy() }) it('없는 그룹이면 빈 배열이라 화면이 죽지 않는다', () => { function Probe() { return {useCodes('NO_SUCH_GROUP').length} } render(withCodes()) expect(screen.getByTestId('n').textContent).toBe('0') }) it('저장된 값의 표시명을 준다', () => { function Probe() { return {useCodeLabel('CONTACT_CATEGORY', 'MJ')} } render(withCodes()) // 엑셀에는 '주관기관'으로 오지만 화면에는 '문정원'으로 나와야 한다. expect(screen.getByText('문정원')).toBeTruthy() }) it('코드표에 없는 값이면 값 자체를 보여준다', () => { function Probe() { return {useCodeLabel('CONTACT_METHOD', '문자')} } render(withCodes()) // 과거 데이터가 빈칸으로 사라지는 것보다 원문이라도 보이는 편이 낫다. expect(screen.getByText('문자')).toBeTruthy() }) }) describe('공공누리 유형의 사용처 구분', () => { function ScopeProbe({ scope }: { scope: string }) { return {useScopedCodes('KOGL_TYPE', scope).map((c) => c.code).join(',')} } it('변호사 판정에는 보류가 있다', () => { render(withCodes()) expect(screen.getByText('0유형,1유형,2유형,3유형,4유형,개방불가,보류')).toBeTruthy() }) it('권리처리 판정에는 보류가 없다', () => { render(withCodes()) expect(screen.getByText('0유형,1유형,2유형,3유형,4유형,개방불가')).toBeTruthy() }) it('기존 부착 유형에는 조사표에서 온 값이 들어간다', () => { render(withCodes()) expect(screen.getByText('0유형,1유형,2유형,3유형,4유형,유형 확인 필요,제4유형')).toBeTruthy() }) it('라디오 첫 줄과 둘째 줄을 나눈다', () => { function Probe() { const { first, rest } = useSplitByFirstRow('KOGL_TYPE', 'review') return ( <> {first.map((c) => c.code).join(',')} {rest.map((c) => c.code).join(',')} ) } render(withCodes()) expect(screen.getByTestId('first').textContent).toBe('0유형,개방불가,보류') expect(screen.getByTestId('rest').textContent).toBe('1유형,2유형,3유형,4유형') }) }) describe('useStages', () => { function StageProbe() { const stages = useStages() return ( <> {stages.count} {stages.label(13)} {stages.symbol(13)} {stages.symbol(1)} {stages.groupKey(5)} {stages.groupTone('assign')} {stages.progressOf(1)} {stages.progressOf(7)} {stages.progressOf(13)} {stages.kpiThreshold('completed')} {String(stages.countsToward('completed', 12))} {String(stages.countsToward('completed', 13))} ) } it('단계 수와 라벨이 코드표에서 온다', () => { render(withCodes()) expect(screen.getByTestId('count').textContent).toBe('13') expect(screen.getByTestId('label13').textContent).toBe('보고서 제출 완료') }) it('원문자는 번호에서 계산한다', () => { render(withCodes()) expect(screen.getByTestId('symbol1').textContent).toBe('①') expect(screen.getByTestId('symbol13').textContent).toBe('⑬') }) it('진행상태는 1단계만 신청이고 13단계만 완료다', () => { render(withCodes()) expect(screen.getByTestId('progress1').textContent).toBe('applied') expect(screen.getByTestId('progress7').textContent).toBe('active') expect(screen.getByTestId('progress13').textContent).toBe('done') }) it('완료 KPI는 보고서가 올라온 13단계부터 센다', () => { render(withCodes()) expect(screen.getByTestId('completed-from').textContent).toBe('13') expect(screen.getByTestId('counts12').textContent).toBe('false') expect(screen.getByTestId('counts13').textContent).toBe('true') }) it('업무구간과 색이 코드표에서 온다', () => { render(withCodes()) expect(screen.getByTestId('group5').textContent).toBe('check') expect(screen.getByTestId('tone-assign').textContent).toBe('indigo') }) }) describe('tone', () => { it('의미 토큰을 실제 클래스로 바꾼다', () => { expect(badgeClass('emerald')).toBe('bg-emerald-50 text-emerald-700') expect(barClass('indigo')).toBe('bg-indigo-400') }) it('모르는 토큰은 회색으로 떨어뜨린다', () => { // 코드 관리 화면에서 오타가 들어가도 색만 밋밋해질 뿐 항목이 사라지면 안 된다. expect(badgeClass('없는색')).toBe('bg-gray-100 text-gray-700') expect(barClass(null)).toBe('bg-gray-300') }) }) describe('CodeProvider', () => { it('서버에서 선택지를 받아 아래 화면에 넘긴다', async () => { mocks.getCodes.mockResolvedValue(TEST_CODES) function Probe() { return {useCodes('PROCESS_STATUS').map((c) => c.label).join(',')} } render( , ) await waitFor(() => expect(screen.getByText('미처리,처리완료')).toBeTruthy()) }) it('선택지를 못 받으면 화면을 그리지 않고 오류를 보여준다', async () => { mocks.getCodes.mockRejectedValue(new Error('서버 오류')) function Probe() { return 그려지면 안 됨 } render( , ) // 빈 드롭다운으로 저장하면 값이 지워지므로 아예 안 그리는 편이 안전하다. await waitFor(() => expect(screen.getByText(/선택지 정보를 불러오지 못했습니다/)).toBeTruthy()) expect(screen.queryByText('그려지면 안 됨')).toBeNull() }) })