fix: resolve all ESLint errors and TypeScript compilation errors
- Auto-fixed 712 import ordering errors via `pnpm lint --fix` - Manually fixed 13 remaining ESLint errors: - Prefixed unused vars with _ (mockAdminUser, params) - Removed unused imports (UnauthorizedException, vi, screen) - Moved imports above vi.mock() calls to fix import group ordering - Removed eslint-disable for non-existent rules - Fixed empty object pattern in Playwright fixture - Fixed ~40 TypeScript TS4111 index signature errors in test files: - Used bracket notation for Record<string, unknown> property access - Added missing PropertyMedia fields (id, order, caption) to test data - Fixed pre-existing test failures in rate-limit guard specs: - Added NODE_ENV override to bypass test-mode skip in guard Both `pnpm lint` and `pnpm typecheck` now exit 0 cleanly. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { AddToCompareButton } from '../add-to-compare-button';
|
||||
|
||||
// Mock next-intl
|
||||
vi.mock('next-intl', () => ({
|
||||
useTranslations: () => (key: string) => key,
|
||||
}));
|
||||
|
||||
// Mock comparison store
|
||||
vi.mock('@/lib/comparison-store', () => {
|
||||
let selectedIds: string[] = [];
|
||||
return {
|
||||
useComparisonStore: (selector: (state: unknown) => unknown) => {
|
||||
const state = {
|
||||
isSelected: (id: string) => selectedIds.includes(id),
|
||||
addToCompare: vi.fn((id: string) => { selectedIds.push(id); }),
|
||||
removeFromCompare: vi.fn((id: string) => { selectedIds = selectedIds.filter((i) => i !== id); }),
|
||||
canAdd: () => selectedIds.length < 5,
|
||||
};
|
||||
return selector(state);
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
describe('AddToCompareButton', () => {
|
||||
it('renders add button in full mode', () => {
|
||||
render(<AddToCompareButton listingId="listing-1" />);
|
||||
expect(screen.getByRole('button', { name: 'addToCompare' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders compact button with aria-label', () => {
|
||||
render(<AddToCompareButton listingId="listing-1" compact />);
|
||||
expect(screen.getByRole('button', { name: 'addToCompare' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows add text in full mode', () => {
|
||||
render(<AddToCompareButton listingId="listing-1" />);
|
||||
expect(screen.getByText('addToCompare')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('handles click on compact button', async () => {
|
||||
render(<AddToCompareButton listingId="listing-2" compact />);
|
||||
const btn = screen.getByRole('button');
|
||||
await userEvent.click(btn);
|
||||
// Should not throw - click handler stops propagation
|
||||
});
|
||||
|
||||
it('handles click on full button', async () => {
|
||||
render(<AddToCompareButton listingId="listing-3" />);
|
||||
const btn = screen.getByRole('button', { name: 'addToCompare' });
|
||||
await userEvent.click(btn);
|
||||
// Should not throw
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,99 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { describe, expect, it, vi, beforeEach } from 'vitest';
|
||||
import { CompareFloatingBar } from '../compare-floating-bar';
|
||||
|
||||
// Mock next-intl
|
||||
vi.mock('next-intl', () => ({
|
||||
useTranslations: () => (key: string, params?: Record<string, unknown>) => {
|
||||
const translations: Record<string, string> = {
|
||||
selected: `${params?.['count'] ?? 0} of ${params?.['max'] ?? 5} selected`,
|
||||
removeItem: 'Remove item',
|
||||
clearAll: 'Clear all',
|
||||
compareNow: 'Compare now',
|
||||
needMore: 'Need more',
|
||||
};
|
||||
return translations[key] ?? key;
|
||||
},
|
||||
}));
|
||||
|
||||
// Mock i18n navigation
|
||||
vi.mock('@/i18n/navigation', () => ({
|
||||
Link: ({ children, href }: { children: React.ReactNode; href: string }) => (
|
||||
<a href={href}>{children}</a>
|
||||
),
|
||||
}));
|
||||
|
||||
const mockStoreState = {
|
||||
selectedIds: [] as string[],
|
||||
clearAll: vi.fn(),
|
||||
removeFromCompare: vi.fn(),
|
||||
canCompare: () => false,
|
||||
};
|
||||
|
||||
// Mock comparison store
|
||||
vi.mock('@/lib/comparison-store', () => ({
|
||||
useComparisonStore: (selector: (state: unknown) => unknown) => selector(mockStoreState),
|
||||
MAX_COMPARE: 5,
|
||||
}));
|
||||
|
||||
describe('CompareFloatingBar', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockStoreState.selectedIds = [];
|
||||
mockStoreState.canCompare = () => false;
|
||||
});
|
||||
|
||||
it('renders nothing when no items selected', () => {
|
||||
const { container } = render(<CompareFloatingBar />);
|
||||
expect(container.firstChild).toBeNull();
|
||||
});
|
||||
|
||||
it('renders floating bar when items are selected', () => {
|
||||
mockStoreState.selectedIds = ['listing-1'];
|
||||
render(<CompareFloatingBar />);
|
||||
expect(screen.getByText('1 of 5 selected')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders clear all button', () => {
|
||||
mockStoreState.selectedIds = ['listing-1'];
|
||||
render(<CompareFloatingBar />);
|
||||
expect(screen.getByRole('button', { name: 'Clear all' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls clearAll when clear button is clicked', async () => {
|
||||
mockStoreState.selectedIds = ['listing-1'];
|
||||
render(<CompareFloatingBar />);
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Clear all' }));
|
||||
expect(mockStoreState.clearAll).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('shows disabled compare button when canCompare is false', () => {
|
||||
mockStoreState.selectedIds = ['listing-1'];
|
||||
mockStoreState.canCompare = () => false;
|
||||
render(<CompareFloatingBar />);
|
||||
expect(screen.getByRole('button', { name: /Need more/ })).toBeDisabled();
|
||||
});
|
||||
|
||||
it('shows compare now link when canCompare is true', () => {
|
||||
mockStoreState.selectedIds = ['listing-1', 'listing-2'];
|
||||
mockStoreState.canCompare = () => true;
|
||||
render(<CompareFloatingBar />);
|
||||
expect(screen.getByText('Compare now')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders remove buttons for each selected item', () => {
|
||||
mockStoreState.selectedIds = ['listing-1', 'listing-2'];
|
||||
render(<CompareFloatingBar />);
|
||||
const removeButtons = screen.getAllByRole('button', { name: 'Remove item' });
|
||||
expect(removeButtons).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('calls removeFromCompare when remove button is clicked', async () => {
|
||||
mockStoreState.selectedIds = ['listing-1'];
|
||||
render(<CompareFloatingBar />);
|
||||
const removeBtn = screen.getByRole('button', { name: 'Remove item' });
|
||||
await userEvent.click(removeBtn);
|
||||
expect(mockStoreState.removeFromCompare).toHaveBeenCalledWith('listing-1');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,54 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import type { ComparisonStats } from '@/lib/comparison-store';
|
||||
import { ComparisonStatsBanner } from '../comparison-stats';
|
||||
|
||||
// Mock next-intl
|
||||
vi.mock('next-intl', () => ({
|
||||
useTranslations: () => (key: string, _params?: Record<string, unknown>) => {
|
||||
if (key === 'priceRange') return 'Khoảng giá';
|
||||
if (key === 'areaRange') return 'Khoảng diện tích';
|
||||
if (key === 'pricePerM2Range') return 'Giá/m²';
|
||||
if (key === 'average') return 'Trung bình';
|
||||
return key;
|
||||
},
|
||||
}));
|
||||
|
||||
const mockStats: ComparisonStats = {
|
||||
priceRange: { min: 2_000_000_000, max: 5_000_000_000, avg: 3_500_000_000 },
|
||||
areaRange: { min: 60, max: 120, avg: 90 },
|
||||
pricePerM2Range: { min: 30_000_000, max: 50_000_000, avg: 40_000_000 },
|
||||
};
|
||||
|
||||
describe('ComparisonStatsBanner', () => {
|
||||
it('renders price range card', () => {
|
||||
render(<ComparisonStatsBanner stats={mockStats} />);
|
||||
expect(screen.getByText('Khoảng giá')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders area range card', () => {
|
||||
render(<ComparisonStatsBanner stats={mockStats} />);
|
||||
expect(screen.getByText('Khoảng diện tích')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders price per m² card when data available', () => {
|
||||
render(<ComparisonStatsBanner stats={mockStats} />);
|
||||
expect(screen.getByText('Giá/m²')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders min and max area', () => {
|
||||
render(<ComparisonStatsBanner stats={mockStats} />);
|
||||
expect(screen.getByText(/60.*120 m²/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders average area', () => {
|
||||
render(<ComparisonStatsBanner stats={mockStats} />);
|
||||
expect(screen.getByText(/Trung bình.*90 m²/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('hides price per m² card when data is null', () => {
|
||||
const statsNoM2 = { ...mockStats, pricePerM2Range: null };
|
||||
render(<ComparisonStatsBanner stats={statsNoM2} />);
|
||||
expect(screen.queryByText('Giá/m²')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user