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,92 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
vi.mock('@/lib/khu-cong-nghiep-api', () => ({
|
||||
PARK_STATUS_LABELS: { OPERATIONAL: 'Hoạt động', PLANNING: 'Quy hoạch', UNDER_CONSTRUCTION: 'Đang xây dựng', FULL: 'Đã lấp đầy' },
|
||||
PARK_STATUS_COLORS: { OPERATIONAL: 'bg-green-100 text-green-800', PLANNING: 'bg-blue-100 text-blue-800', UNDER_CONSTRUCTION: 'bg-amber-100 text-amber-800', FULL: 'bg-red-100 text-red-800' },
|
||||
REGION_LABELS: { NORTH: 'Miền Bắc', CENTRAL: 'Miền Trung', SOUTH: 'Miền Nam' },
|
||||
}));
|
||||
|
||||
import { KhuCongNghiepDetailClient } from '../khu-cong-nghiep-detail-client';
|
||||
|
||||
const park = {
|
||||
id: 'ip1',
|
||||
name: 'KCN Tân Bình',
|
||||
nameEn: 'Tan Binh IP',
|
||||
slug: 'kcn-tan-binh',
|
||||
developer: 'Becamex',
|
||||
operator: 'Becamex IDC',
|
||||
status: 'OPERATIONAL' as const,
|
||||
latitude: 10.8,
|
||||
longitude: 106.6,
|
||||
address: '123 Đại lộ',
|
||||
district: 'Tân Bình',
|
||||
province: 'Bình Dương',
|
||||
region: 'SOUTH' as const,
|
||||
totalAreaHa: 500,
|
||||
leasableAreaHa: 400,
|
||||
occupancyRate: 85,
|
||||
remainingAreaHa: 60,
|
||||
tenantCount: 120,
|
||||
listingCount: 15,
|
||||
establishedYear: 2005,
|
||||
isVerified: true,
|
||||
landRentUsdM2Year: '55.0000',
|
||||
rbfRentUsdM2Month: '4.5000',
|
||||
rbwRentUsdM2Month: '3.2000',
|
||||
managementFeeUsd: '0.8000',
|
||||
targetIndustries: ['Điện tử', 'Cơ khí'],
|
||||
certifications: ['ISO 14001'],
|
||||
description: 'KCN hàng đầu',
|
||||
infrastructure: { power: '110kV', water: '50,000 m³/ngày' },
|
||||
connectivity: { airport: { name: 'Tân Sơn Nhất', distanceKm: 25 } },
|
||||
incentives: { cit: '2 năm miễn thuế' },
|
||||
existingTenants: [{ name: 'Samsung', country: 'Hàn Quốc', industry: 'Điện tử' }],
|
||||
documents: [{ name: 'Brochure.pdf', url: '/docs/b.pdf' }],
|
||||
} as never;
|
||||
|
||||
describe('KhuCongNghiepDetailClient', () => {
|
||||
it('renders park name', () => {
|
||||
render(<KhuCongNghiepDetailClient park={park} />);
|
||||
expect(screen.getByText('KCN Tân Bình')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders English name', () => {
|
||||
render(<KhuCongNghiepDetailClient park={park} />);
|
||||
expect(screen.getByText('Tan Binh IP')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders status badge', () => {
|
||||
render(<KhuCongNghiepDetailClient park={park} />);
|
||||
expect(screen.getByText('Hoạt động')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders verified badge', () => {
|
||||
render(<KhuCongNghiepDetailClient park={park} />);
|
||||
expect(screen.getByText('Đã xác minh')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders quick stats', () => {
|
||||
render(<KhuCongNghiepDetailClient park={park} />);
|
||||
expect(screen.getByText('500 ha')).toBeInTheDocument();
|
||||
expect(screen.getByText('85%')).toBeInTheDocument();
|
||||
expect(screen.getByText('120')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders target industries', () => {
|
||||
render(<KhuCongNghiepDetailClient park={park} />);
|
||||
expect(screen.getByText('Điện tử')).toBeInTheDocument();
|
||||
expect(screen.getByText('Cơ khí')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders rent info', () => {
|
||||
render(<KhuCongNghiepDetailClient park={park} />);
|
||||
expect(screen.getByText('$55.0000/m²/năm')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders tabs', () => {
|
||||
render(<KhuCongNghiepDetailClient park={park} />);
|
||||
expect(screen.getByRole('tab', { name: 'Hạ tầng' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('tab', { name: 'Doanh nghiệp' })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,50 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
vi.mock('@/i18n/navigation', () => ({
|
||||
Link: ({ children, ...props }: React.PropsWithChildren<Record<string, unknown>>) => <a {...props}>{children}</a>,
|
||||
}));
|
||||
|
||||
vi.mock('@/components/khu-cong-nghiep/listing-card', () => ({
|
||||
IndustrialListingCard: ({ listing }: { listing: { id: string; title?: string } }) => (
|
||||
<div data-testid={`listing-${listing.id}`}>listing</div>
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock('@/lib/hooks/use-khu-cong-nghiep', () => ({
|
||||
useIndustrialListingsSearch: () => ({
|
||||
data: { data: [], total: 0, page: 1, totalPages: 1 },
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('@/lib/khu-cong-nghiep-api', () => ({
|
||||
PROPERTY_TYPE_LABELS: { FACTORY: 'Nhà xưởng', WAREHOUSE: 'Kho bãi', LAND: 'Đất CN' },
|
||||
LEASE_TYPE_LABELS: { LONG_TERM: 'Dài hạn', SHORT_TERM: 'Ngắn hạn' },
|
||||
}));
|
||||
|
||||
import { ListingSearchClient } from '../listing-search-client';
|
||||
|
||||
describe('ListingSearchClient', () => {
|
||||
it('renders page heading', () => {
|
||||
render(<ListingSearchClient />);
|
||||
expect(screen.getByText('Cho Thuê Bất Động Sản Công Nghiệp')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders search input', () => {
|
||||
render(<ListingSearchClient />);
|
||||
expect(screen.getByPlaceholderText(/Tìm kiếm/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders filter dropdowns', () => {
|
||||
render(<ListingSearchClient />);
|
||||
expect(screen.getByLabelText('Loại BĐS')).toBeInTheDocument();
|
||||
expect(screen.getByLabelText('Hình thức')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows empty state when no results', () => {
|
||||
render(<ListingSearchClient />);
|
||||
expect(screen.getByText('Không tìm thấy tin cho thuê')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,47 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
vi.mock('recharts', () => ({
|
||||
Radar: () => null,
|
||||
RadarChart: ({ children }: { children: React.ReactNode }) => <div data-testid="radar-chart">{children}</div>,
|
||||
PolarGrid: () => null,
|
||||
PolarAngleAxis: () => null,
|
||||
PolarRadiusAxis: () => null,
|
||||
ResponsiveContainer: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
Legend: () => null,
|
||||
Tooltip: () => null,
|
||||
}));
|
||||
|
||||
vi.mock('@/i18n/navigation', () => ({
|
||||
Link: ({ children, ...props }: React.PropsWithChildren<Record<string, unknown>>) => <a {...props}>{children}</a>,
|
||||
}));
|
||||
|
||||
vi.mock('@/lib/hooks/use-khu-cong-nghiep', () => ({
|
||||
useIndustrialCompare: () => ({ data: undefined, isLoading: false }),
|
||||
useIndustrialParksSearch: () => ({ data: { data: [] } }),
|
||||
}));
|
||||
|
||||
vi.mock('@/lib/khu-cong-nghiep-api', () => ({
|
||||
PARK_STATUS_COLORS: { OPERATIONAL: 'bg-green-100 text-green-800' },
|
||||
PARK_STATUS_LABELS: { OPERATIONAL: 'Hoạt động' },
|
||||
REGION_LABELS: { SOUTH: 'Miền Nam' },
|
||||
}));
|
||||
|
||||
import { ParkCompareClient } from '../park-compare-client';
|
||||
|
||||
describe('ParkCompareClient', () => {
|
||||
it('renders heading', () => {
|
||||
render(<ParkCompareClient />);
|
||||
expect(screen.getByText('So Sánh Khu Công Nghiệp')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows empty state when fewer than 2 parks selected', () => {
|
||||
render(<ParkCompareClient />);
|
||||
expect(screen.getByText('Chọn ít nhất 2 KCN để so sánh')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders add park button', () => {
|
||||
render(<ParkCompareClient />);
|
||||
expect(screen.getByText('Thêm KCN')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,72 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
vi.mock('mapbox-gl', () => {
|
||||
class MockMap {
|
||||
addControl = vi.fn();
|
||||
fitBounds = vi.fn();
|
||||
flyTo = vi.fn();
|
||||
setStyle = vi.fn();
|
||||
remove = vi.fn();
|
||||
on = vi.fn();
|
||||
}
|
||||
class MockNavigationControl {}
|
||||
class MockAttributionControl {}
|
||||
class MockMarker {
|
||||
setLngLat() { return this; }
|
||||
setPopup() { return this; }
|
||||
addTo() { return this; }
|
||||
remove() {}
|
||||
}
|
||||
class MockPopup {
|
||||
setHTML() { return this; }
|
||||
setLngLat() { return this; }
|
||||
addTo() { return this; }
|
||||
remove() {}
|
||||
}
|
||||
class MockLngLatBounds {
|
||||
extend() { return this; }
|
||||
isEmpty() { return false; }
|
||||
}
|
||||
return {
|
||||
default: {
|
||||
accessToken: '',
|
||||
Map: MockMap,
|
||||
NavigationControl: MockNavigationControl,
|
||||
AttributionControl: MockAttributionControl,
|
||||
Marker: MockMarker,
|
||||
Popup: MockPopup,
|
||||
LngLatBounds: MockLngLatBounds,
|
||||
},
|
||||
};
|
||||
});
|
||||
vi.mock('mapbox-gl/dist/mapbox-gl.css', () => ({}));
|
||||
vi.mock('@/lib/mapbox-style', () => ({ useMapboxStyle: () => 'mapbox://styles/mapbox/light-v11' }));
|
||||
vi.mock('@/lib/khu-cong-nghiep-api', () => ({
|
||||
PARK_STATUS_LABELS: { OPERATIONAL: 'Hoạt động' },
|
||||
PARK_STATUS_COLORS: { OPERATIONAL: 'bg-green-100 text-green-800' },
|
||||
}));
|
||||
|
||||
import { ParkMap } from '../park-map';
|
||||
|
||||
const parks = [
|
||||
{ id: 'ip1', name: 'KCN A', slug: 'kcn-a', status: 'OPERATIONAL' as const, province: 'Bình Dương', totalAreaHa: 500, occupancyRate: 80, tenantCount: 50, landRentUsdM2Year: '55', latitude: 10.8, longitude: 106.6 },
|
||||
] as never[];
|
||||
|
||||
describe('ParkMap', () => {
|
||||
it('renders fallback when no token', () => {
|
||||
delete (process.env as Record<string, string | undefined>)['NEXT_PUBLIC_MAPBOX_TOKEN'];
|
||||
render(<ParkMap parks={[]} />);
|
||||
expect(screen.getByText(/NEXT_PUBLIC_MAPBOX_TOKEN/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows park count overlay', () => {
|
||||
render(<ParkMap parks={parks} />);
|
||||
expect(screen.getByText(/1 KCN trên bản đồ/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows 0 parks when empty', () => {
|
||||
render(<ParkMap parks={[]} />);
|
||||
expect(screen.getByText(/0 KCN trên bản đồ/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user