test(web): increase frontend test coverage to ~70% page coverage
- Fix vitest config to include [locale] directory tests (was excluded)
- Fix register.spec.tsx: use getByRole('heading') to avoid duplicate text match
- Fix search.spec.tsx: add QueryClientProvider wrapper and mock saved searches hook
- Add 12 new page test files covering dashboard, admin, public, and OAuth pages:
- dashboard (main, profile, payments, subscription, KYC)
- admin (dashboard, users)
- public (landing, pricing)
- analytics
- OAuth callbacks (Google, Zalo)
- 29 test files, 174 tests, 16/23 pages covered (69.6%)
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
/* eslint-disable import-x/order */
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
vi.mock('lucide-react', () => ({
|
||||
Users: () => <span data-testid="icon-users" />,
|
||||
Home: () => <span data-testid="icon-home" />,
|
||||
ClipboardCheck: () => <span data-testid="icon-clipboard" />,
|
||||
Clock: () => <span data-testid="icon-clock" />,
|
||||
UserCheck: () => <span data-testid="icon-usercheck" />,
|
||||
ShieldCheck: () => <span data-testid="icon-shieldcheck" />,
|
||||
ArrowUpRight: () => <span data-testid="icon-arrowup" />,
|
||||
ArrowDownRight: () => <span data-testid="icon-arrowdown" />,
|
||||
TrendingUp: () => <span data-testid="icon-trending" />,
|
||||
RefreshCw: () => <span data-testid="icon-refresh" />,
|
||||
}));
|
||||
|
||||
vi.mock('@/lib/admin-api', () => ({
|
||||
adminApi: {
|
||||
getDashboardStats: vi.fn().mockResolvedValue({
|
||||
totalUsers: 5000,
|
||||
totalListings: 1200,
|
||||
activeListings: 800,
|
||||
pendingModerationCount: 15,
|
||||
totalAgents: 300,
|
||||
verifiedAgents: 150,
|
||||
totalTransactions: 4500,
|
||||
newUsersLast30Days: 200,
|
||||
newListingsLast30Days: 50,
|
||||
}),
|
||||
getRevenueStats: vi.fn().mockResolvedValue([
|
||||
{ period: '2025-11', totalRevenue: 50000000, subscriptionRevenue: 30000000, listingFeeRevenue: 20000000, transactionCount: 100 },
|
||||
{ period: '2025-12', totalRevenue: 65000000, subscriptionRevenue: 40000000, listingFeeRevenue: 25000000, transactionCount: 130 },
|
||||
]),
|
||||
},
|
||||
}));
|
||||
|
||||
import AdminDashboardPage from '../page';
|
||||
|
||||
describe('AdminDashboardPage', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('renders admin dashboard heading', async () => {
|
||||
render(<AdminDashboardPage />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Dashboard')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders stat cards with data', async () => {
|
||||
render(<AdminDashboardPage />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Tổng người dùng')).toBeInTheDocument();
|
||||
expect(screen.getByText('Tổng tin đăng')).toBeInTheDocument();
|
||||
expect(screen.getByText('Tin đang hoạt động')).toBeInTheDocument();
|
||||
expect(screen.getByText('Chờ kiểm duyệt')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders revenue chart section', async () => {
|
||||
render(<AdminDashboardPage />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Doanh thu 6 tháng gần nhất')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders refresh button', async () => {
|
||||
render(<AdminDashboardPage />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('button', { name: /làm mới/i })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
97
apps/web/app/[locale]/(admin)/admin/__tests__/users.spec.tsx
Normal file
97
apps/web/app/[locale]/(admin)/admin/__tests__/users.spec.tsx
Normal file
@@ -0,0 +1,97 @@
|
||||
/* eslint-disable import-x/order */
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
vi.mock('lucide-react', () => ({
|
||||
Search: () => <span data-testid="icon-search" />,
|
||||
RefreshCw: () => <span data-testid="icon-refresh" />,
|
||||
ChevronLeft: () => <span data-testid="icon-left" />,
|
||||
ChevronRight: () => <span data-testid="icon-right" />,
|
||||
UserX: () => <span data-testid="icon-userx" />,
|
||||
UserCheck: () => <span data-testid="icon-usercheck" />,
|
||||
Eye: () => <span data-testid="icon-eye" />,
|
||||
X: () => <span data-testid="icon-x" />,
|
||||
}));
|
||||
|
||||
vi.mock('@/lib/admin-api', () => ({
|
||||
adminApi: {
|
||||
getUsers: vi.fn().mockResolvedValue({
|
||||
data: [
|
||||
{
|
||||
id: 'u1',
|
||||
fullName: 'Nguyen Van A',
|
||||
phone: '0912345678',
|
||||
email: 'a@test.com',
|
||||
role: 'BUYER',
|
||||
kycStatus: 'NONE',
|
||||
isActive: true,
|
||||
createdAt: '2024-01-01T00:00:00.000Z',
|
||||
},
|
||||
{
|
||||
id: 'u2',
|
||||
fullName: 'Tran Thi B',
|
||||
phone: '0987654321',
|
||||
email: null,
|
||||
role: 'AGENT',
|
||||
kycStatus: 'VERIFIED',
|
||||
isActive: true,
|
||||
createdAt: '2024-02-01T00:00:00.000Z',
|
||||
},
|
||||
],
|
||||
total: 2,
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
}),
|
||||
getUserDetail: vi.fn(),
|
||||
banUser: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
import AdminUsersPage from '../users/page';
|
||||
|
||||
describe('AdminUsersPage', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('renders user management heading', async () => {
|
||||
render(<AdminUsersPage />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Quản lý người dùng')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders user list', async () => {
|
||||
render(<AdminUsersPage />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Nguyen Van A')).toBeInTheDocument();
|
||||
expect(screen.getByText('Tran Thi B')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders search input', async () => {
|
||||
render(<AdminUsersPage />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByPlaceholderText(/tìm theo tên/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders role filter', async () => {
|
||||
render(<AdminUsersPage />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Tất cả vai trò')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders empty detail panel placeholder', async () => {
|
||||
render(<AdminUsersPage />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/chọn người dùng để xem chi tiết/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user