test(web): add component tests for 10 untested frontend components (GOO-54)
Cover critical-path and feature components that were missing tests: - charts: district-heatmap - chuyen-nhuong: detail-client, transfer-wizard-client - du-an: detail-client, project-ai-advice-card, project-map - khu-cong-nghiep: detail-client, listing-search-client, park-compare-client, park-map All 49 new tests pass with Vitest + React Testing Library. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
vi.mock('@/lib/chuyen-nhuong-api', () => ({
|
||||
CATEGORY_LABELS: { FURNITURE: 'Nội thất', APPLIANCE: 'Thiết bị', OFFICE_EQUIPMENT: 'Văn phòng', KITCHEN: 'Bếp', PREMISES: 'Mặt bằng', FULL_UNIT: 'Trọn gói' },
|
||||
CATEGORY_ICONS: {
|
||||
FURNITURE: () => <span data-testid="icon-furniture" />,
|
||||
APPLIANCE: () => <span data-testid="icon-appliance" />,
|
||||
OFFICE_EQUIPMENT: () => <span data-testid="icon-office" />,
|
||||
KITCHEN: () => <span data-testid="icon-kitchen" />,
|
||||
PREMISES: () => <span data-testid="icon-premises" />,
|
||||
FULL_UNIT: () => <span data-testid="icon-full" />,
|
||||
},
|
||||
STATUS_LABELS: { DRAFT: 'Nháp', PENDING_REVIEW: 'Chờ duyệt', ACTIVE: 'Đang đăng', RESERVED: 'Đã đặt cọc', SOLD: 'Đã bán', EXPIRED: 'Hết hạn', REJECTED: 'Từ chối' },
|
||||
CONDITION_LABELS: { NEW: 'Mới', LIKE_NEW: 'Như mới', GOOD: 'Tốt', FAIR: 'Trung bình', WORN: 'Cũ' },
|
||||
CONDITION_COLORS: { NEW: 'bg-green-100 text-green-800', LIKE_NEW: 'bg-blue-100 text-blue-800', GOOD: 'bg-emerald-100 text-emerald-800', FAIR: 'bg-amber-100 text-amber-800', WORN: 'bg-red-100 text-red-800' },
|
||||
}));
|
||||
|
||||
import { ChuyenNhuongDetailClient } from '../chuyen-nhuong-detail-client';
|
||||
|
||||
const listing = {
|
||||
id: 't1',
|
||||
sellerId: 'u1',
|
||||
category: 'FURNITURE' as const,
|
||||
status: 'ACTIVE' as const,
|
||||
title: 'Bộ nội thất văn phòng',
|
||||
description: 'Mô tả chi tiết',
|
||||
address: '123 Nguyễn Huệ',
|
||||
ward: 'Bến Nghé',
|
||||
district: 'Quận 1',
|
||||
city: 'Hồ Chí Minh',
|
||||
askingPriceVND: '15000000',
|
||||
aiEstimatePriceVND: '14000000',
|
||||
aiConfidence: 0.85,
|
||||
isNegotiable: true,
|
||||
areaM2: null,
|
||||
viewCount: 42,
|
||||
saveCount: 10,
|
||||
inquiryCount: 5,
|
||||
contactName: 'Nguyễn Văn A',
|
||||
contactPhone: '0912345678',
|
||||
items: [
|
||||
{ id: 'i1', name: 'Bàn', brand: null, modelName: null, category: 'FURNITURE' as const, condition: 'GOOD' as const, purchaseYear: 2022, originalPriceVND: 5000000, askingPriceVND: '3000000', aiEstimatePriceVND: null, quantity: 1, notes: null },
|
||||
],
|
||||
businessType: 'Quán cà phê',
|
||||
monthlyRentVND: '20000000',
|
||||
depositMonths: 3,
|
||||
remainingLeaseMo: 18,
|
||||
footTraffic: 'Cao',
|
||||
pricingSource: 'MANUAL' as const,
|
||||
} as never;
|
||||
|
||||
describe('ChuyenNhuongDetailClient', () => {
|
||||
it('renders listing title', () => {
|
||||
render(<ChuyenNhuongDetailClient listing={listing} />);
|
||||
expect(screen.getByText('Bộ nội thất văn phòng')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders status badge', () => {
|
||||
render(<ChuyenNhuongDetailClient listing={listing} />);
|
||||
expect(screen.getByText('Đang đăng')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders negotiable badge', () => {
|
||||
render(<ChuyenNhuongDetailClient listing={listing} />);
|
||||
expect(screen.getByText('Thương lượng')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders asking price', () => {
|
||||
render(<ChuyenNhuongDetailClient listing={listing} />);
|
||||
// formatVND uses Intl.NumberFormat('vi-VN')
|
||||
expect(screen.getAllByText(/15\.000\.000/).length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('renders AI confidence', () => {
|
||||
render(<ChuyenNhuongDetailClient listing={listing} />);
|
||||
expect(screen.getByText('85%')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders contact info', () => {
|
||||
render(<ChuyenNhuongDetailClient listing={listing} />);
|
||||
expect(screen.getByText('Nguyễn Văn A')).toBeInTheDocument();
|
||||
expect(screen.getByText('0912345678')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders business info section', () => {
|
||||
render(<ChuyenNhuongDetailClient listing={listing} />);
|
||||
expect(screen.getByText('Thông tin kinh doanh')).toBeInTheDocument();
|
||||
expect(screen.getByText('Quán cà phê')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders items table heading', () => {
|
||||
render(<ChuyenNhuongDetailClient listing={listing} />);
|
||||
expect(screen.getByText(/Danh sách vật phẩm/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,89 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
vi.mock('next/navigation', () => ({
|
||||
useRouter: () => ({ push: vi.fn() }),
|
||||
}));
|
||||
|
||||
vi.mock('@/lib/chuyen-nhuong-api', () => ({
|
||||
CATEGORY_LABELS: { FURNITURE: 'Nội thất', APPLIANCE: 'Thiết bị', OFFICE_EQUIPMENT: 'Văn phòng', KITCHEN: 'Bếp', PREMISES: 'Mặt bằng', FULL_UNIT: 'Trọn gói' },
|
||||
CATEGORY_ICONS: {
|
||||
FURNITURE: () => <span data-testid="icon-furniture" />,
|
||||
APPLIANCE: () => <span data-testid="icon-appliance" />,
|
||||
OFFICE_EQUIPMENT: () => <span data-testid="icon-office" />,
|
||||
KITCHEN: () => <span data-testid="icon-kitchen" />,
|
||||
PREMISES: () => <span data-testid="icon-premises" />,
|
||||
FULL_UNIT: () => <span data-testid="icon-full" />,
|
||||
},
|
||||
CONDITION_LABELS: { NEW: 'Mới', LIKE_NEW: 'Như mới', GOOD: 'Tốt', FAIR: 'Trung bình', WORN: 'Cũ' },
|
||||
transferApi: { estimate: vi.fn(), create: vi.fn() },
|
||||
}));
|
||||
|
||||
vi.mock('@/lib/transfer-wizard-store', () => {
|
||||
const state = {
|
||||
currentStep: 0,
|
||||
category: null as string | null,
|
||||
items: [] as unknown[],
|
||||
title: '',
|
||||
description: '',
|
||||
address: '',
|
||||
district: '',
|
||||
city: '',
|
||||
askingPriceVND: 0,
|
||||
pricingSource: 'MANUAL',
|
||||
isNegotiable: false,
|
||||
aiEstimate: null,
|
||||
isEstimating: false,
|
||||
setCategory: vi.fn(),
|
||||
setStep: vi.fn(),
|
||||
addItem: vi.fn(),
|
||||
removeItem: vi.fn(),
|
||||
setAiEstimate: vi.fn(),
|
||||
setIsEstimating: vi.fn(),
|
||||
setListingDetails: vi.fn(),
|
||||
reset: vi.fn(),
|
||||
};
|
||||
return {
|
||||
useTransferWizardStore: () => state,
|
||||
};
|
||||
});
|
||||
|
||||
import { TransferWizardClient } from '../transfer-wizard-client';
|
||||
|
||||
describe('TransferWizardClient', () => {
|
||||
it('renders wizard title', () => {
|
||||
render(<TransferWizardClient />);
|
||||
expect(screen.getByText('Đăng tin chuyển nhượng')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders step indicators (4 steps)', () => {
|
||||
render(<TransferWizardClient />);
|
||||
expect(screen.getByText('1')).toBeInTheDocument();
|
||||
expect(screen.getByText('2')).toBeInTheDocument();
|
||||
expect(screen.getByText('3')).toBeInTheDocument();
|
||||
expect(screen.getByText('4')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders current step label "Danh mục" for step 0', () => {
|
||||
render(<TransferWizardClient />);
|
||||
expect(screen.getByText('Danh mục')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders category selection buttons', () => {
|
||||
render(<TransferWizardClient />);
|
||||
expect(screen.getByText('Nội thất')).toBeInTheDocument();
|
||||
expect(screen.getByText('Thiết bị')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders navigation buttons', () => {
|
||||
render(<TransferWizardClient />);
|
||||
expect(screen.getByText(/Quay lại/)).toBeInTheDocument();
|
||||
expect(screen.getByText(/Tiếp theo/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('disables back button on first step', () => {
|
||||
render(<TransferWizardClient />);
|
||||
const backBtn = screen.getByText(/Quay lại/).closest('button');
|
||||
expect(backBtn).toBeDisabled();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user