- Raise use-swipe-gesture mutation score to 66.13% (target 65%+) - Maintain use-reduced-motion mutation score at 76.32% (target 50%+) - Fix use-reduced-motion.ts ESLint set-state-in-effect warning - Add UJ-10 deep searcher journey (category browse → article read → content discovery) - Add 40 new test files (analytics, detail, sections, ui, lib components) - Update test-strategy-plan.md to v2.0 (sync test count to 1591) - Sync README.md with final release metrics Quality gates: TS 0 errors, ESLint 0 errors, 121 suites / 1591 tests passed
209 lines
6.4 KiB
TypeScript
209 lines
6.4 KiB
TypeScript
// @ts-nocheck
|
|
import { describe, it, expect, jest } from '@jest/globals';
|
|
import { render, screen, act } from '@testing-library/react';
|
|
import '@testing-library/jest-dom';
|
|
import { LoadingState, Spinner, PageLoader } from './loading-state';
|
|
|
|
// ─── Mocks ───────────────────────────────────────────────────────────────
|
|
|
|
jest.mock('framer-motion', () => ({
|
|
motion: {
|
|
div: ({ children, className, ...props }: any) => (
|
|
<div className={className} {...props}>{children}</div>
|
|
),
|
|
},
|
|
}));
|
|
|
|
jest.mock('@/components/ui/skeleton', () => ({
|
|
Skeleton: ({ className }: any) => <div className={className} data-testid="skeleton" />,
|
|
SkeletonHero: () => <div data-testid="skeleton-hero" />,
|
|
SkeletonList: ({ items }: any) => <div data-testid="skeleton-list" data-items={items} />,
|
|
SkeletonCard: () => <div data-testid="skeleton-card" />,
|
|
SkeletonForm: ({ fields }: any) => <div data-testid="skeleton-form" data-fields={fields} />,
|
|
}));
|
|
|
|
// ─── Tests ───────────────────────────────────────────────────────────────
|
|
|
|
describe('Spinner', () => {
|
|
it('should render with default size', () => {
|
|
render(<Spinner />);
|
|
const svg = document.querySelector('svg');
|
|
expect(svg).toBeInTheDocument();
|
|
expect(svg).toHaveAttribute('role', 'status');
|
|
});
|
|
|
|
it('should render with small size', () => {
|
|
render(<Spinner size="sm" />);
|
|
const svg = document.querySelector('svg');
|
|
expect(svg?.getAttribute('class')).toContain('w-4');
|
|
});
|
|
|
|
it('should render with large size', () => {
|
|
render(<Spinner size="lg" />);
|
|
const svg = document.querySelector('svg');
|
|
expect(svg?.getAttribute('class')).toContain('w-12');
|
|
});
|
|
|
|
it('should apply custom className', () => {
|
|
render(<Spinner className="custom-spinner" />);
|
|
const svg = document.querySelector('svg');
|
|
expect(svg?.getAttribute('class')).toContain('custom-spinner');
|
|
});
|
|
});
|
|
|
|
describe('PageLoader', () => {
|
|
it('should render when loading', () => {
|
|
render(<PageLoader isLoading={true} />);
|
|
expect(screen.getByText('正在加载...')).toBeInTheDocument();
|
|
expect(screen.getByRole('alertdialog')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should render custom message', () => {
|
|
render(<PageLoader isLoading={true} message="请稍候..." />);
|
|
expect(screen.getByText('请稍候...')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should not render when not loading', () => {
|
|
const { container } = render(<PageLoader isLoading={false} />);
|
|
expect(container.innerHTML).toBe('');
|
|
});
|
|
});
|
|
|
|
describe('LoadingState', () => {
|
|
beforeEach(() => {
|
|
jest.useFakeTimers();
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.useRealTimers();
|
|
});
|
|
|
|
it('should render children when not loading', () => {
|
|
render(
|
|
<LoadingState isLoading={false}>
|
|
<div data-testid="content">Content</div>
|
|
</LoadingState>
|
|
);
|
|
expect(screen.getByTestId('content')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should show skeleton after delay when loading', () => {
|
|
// Mount as not loading first, then switch to loading
|
|
const { rerender } = render(
|
|
<LoadingState isLoading={false}>
|
|
<div data-testid="content">Content</div>
|
|
</LoadingState>
|
|
);
|
|
expect(screen.getByTestId('content')).toBeInTheDocument();
|
|
|
|
rerender(
|
|
<LoadingState isLoading={true}>
|
|
<div data-testid="content">Content</div>
|
|
</LoadingState>
|
|
);
|
|
// Before delay, skeleton should not be shown
|
|
expect(screen.queryByRole('status')).not.toBeInTheDocument();
|
|
|
|
// After delay, skeleton should appear
|
|
act(() => {
|
|
jest.advanceTimersByTime(150);
|
|
});
|
|
expect(screen.getByRole('status')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should render hero variant skeleton', () => {
|
|
const { rerender } = render(
|
|
<LoadingState isLoading={false} variant="hero">
|
|
<div>Content</div>
|
|
</LoadingState>
|
|
);
|
|
rerender(
|
|
<LoadingState isLoading={true} variant="hero">
|
|
<div>Content</div>
|
|
</LoadingState>
|
|
);
|
|
act(() => {
|
|
jest.advanceTimersByTime(150);
|
|
});
|
|
expect(screen.getByRole('status')).toBeInTheDocument();
|
|
expect(screen.getByTestId('skeleton-hero')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should render list variant skeleton', () => {
|
|
const { rerender } = render(
|
|
<LoadingState isLoading={false} variant="list">
|
|
<div>Content</div>
|
|
</LoadingState>
|
|
);
|
|
rerender(
|
|
<LoadingState isLoading={true} variant="list">
|
|
<div>Content</div>
|
|
</LoadingState>
|
|
);
|
|
act(() => {
|
|
jest.advanceTimersByTime(150);
|
|
});
|
|
expect(screen.getByRole('status')).toBeInTheDocument();
|
|
expect(screen.getByTestId('skeleton-list')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should render card variant skeleton', () => {
|
|
const { rerender } = render(
|
|
<LoadingState isLoading={false} variant="card">
|
|
<div>Content</div>
|
|
</LoadingState>
|
|
);
|
|
rerender(
|
|
<LoadingState isLoading={true} variant="card">
|
|
<div>Content</div>
|
|
</LoadingState>
|
|
);
|
|
act(() => {
|
|
jest.advanceTimersByTime(150);
|
|
});
|
|
expect(screen.getByRole('status')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should render form variant skeleton', () => {
|
|
const { rerender } = render(
|
|
<LoadingState isLoading={false} variant="form">
|
|
<div>Content</div>
|
|
</LoadingState>
|
|
);
|
|
rerender(
|
|
<LoadingState isLoading={true} variant="form">
|
|
<div>Content</div>
|
|
</LoadingState>
|
|
);
|
|
act(() => {
|
|
jest.advanceTimersByTime(150);
|
|
});
|
|
expect(screen.getByRole('status')).toBeInTheDocument();
|
|
expect(screen.getByTestId('skeleton-form')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should render children when loading stops', () => {
|
|
const { rerender } = render(
|
|
<LoadingState isLoading={false}>
|
|
<div data-testid="content">Content</div>
|
|
</LoadingState>
|
|
);
|
|
|
|
rerender(
|
|
<LoadingState isLoading={true}>
|
|
<div data-testid="content">Content</div>
|
|
</LoadingState>
|
|
);
|
|
act(() => {
|
|
jest.advanceTimersByTime(150);
|
|
});
|
|
expect(screen.getByRole('status')).toBeInTheDocument();
|
|
|
|
rerender(
|
|
<LoadingState isLoading={false}>
|
|
<div data-testid="content">Content</div>
|
|
</LoadingState>
|
|
);
|
|
expect(screen.getByTestId('content')).toBeInTheDocument();
|
|
});
|
|
}); |