import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen, fireEvent, waitFor } from '@testing-library/react'; vi.mock('../../../api', () => ({ safetyCheck: vi.fn(), budgetCalc: vi.fn() })); import { safetyCheck, budgetCalc } from '../../../api'; import ToolsTab from './ToolsTab.jsx'; beforeEach(() => vi.clearAllMocks()); describe('ToolsTab', () => { it('안전마진·예산 카드 헤딩과 disclaimer 안내를 렌더', () => { render(); expect(screen.getByText('안전마진 체커')).toBeInTheDocument(); expect(screen.getByText('예산 계산기')).toBeInTheDocument(); // 제출 버튼 존재 expect(screen.getAllByRole('button', { name: /판정|계산/ }).length).toBeGreaterThan(0); }); it('안전마진 제출 → tier·비율·disclaimer 렌더 (객체 아님)', async () => { safetyCheck.mockResolvedValue({ median: 45000, ratio: 0.72, tier: '안전', sample: 7, is_toheo: false, disclaimer: '등기부 선순위·근사치 주의' }); render(); fireEvent.change(screen.getByPlaceholderText(/면적/), { target: { value: '84' } }); fireEvent.change(screen.getByPlaceholderText(/금액/), { target: { value: '45000' } }); fireEvent.click(screen.getByRole('button', { name: '판정' })); await waitFor(() => expect(screen.getByText(/등기부 선순위/)).toBeInTheDocument()); // "안전마진 체커" 헤딩과 구분하기 위해 "마진"이 뒤따르지 않는 "안전"만 매칭 expect(screen.getByText(/안전(?!마진)/)).toBeInTheDocument(); }); it('예산 제출 → 전세/매매 금액·disclaimer 렌더 (중첩 객체 필드별 렌더)', async () => { budgetCalc.mockResolvedValue({ jeonse: { loan_limit: 30000, max_deposit: 40000, notes: '' }, purchase: { ltv_pct: 70, loan_cap: 50000, max_price: 90000, dsr_note: 'DSR 40%', regulation_flags: ['규제지역'] }, region: { is_toheo: true, is_regulated: true, notes: '토허 구역' }, disclaimer: '정책 수치는 근사치입니다', }); render(); fireEvent.change(screen.getByPlaceholderText(/자기자본/), { target: { value: '20000' } }); fireEvent.click(screen.getByRole('button', { name: '계산' })); await waitFor(() => expect(screen.getByText(/정책 수치는 근사치/)).toBeInTheDocument()); // purchase.regulation_flags 와 region.is_regulated 플래그 둘 다 "규제지역" 텍스트를 렌더함 expect(screen.getAllByText('규제지역').length).toBeGreaterThan(0); }); });