fix(web): resolve lint errors for v0.101 - eslint: add ignores (e2e, scripts, playwright-report, generated types) - eslint: add browser globals, disable react-hooks in stories - fix empty catch blocks (Cart, MarketplacePage, RolesPage, SettingsPage) - fix PlayerExpanded: move useEffect before early return - fix TrackHistory.test: rename type import to avoid no-redeclare
337 lines
8.6 KiB
TypeScript
337 lines
8.6 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import { render, screen, waitFor, fireEvent } from '@testing-library/react';
|
|
import { TrackHistory } from './TrackHistory';
|
|
import {
|
|
getTrackHistory,
|
|
TrackHistoryError,
|
|
} from '@/features/tracks/services/trackHistoryService';
|
|
import type { TrackHistory as TrackHistoryType } from '@/features/tracks/services/trackHistoryService';
|
|
import { useToast } from '@/hooks/useToast';
|
|
|
|
// Mock services (path must match hook: track-history/useTrackHistory imports ../../services/trackHistoryService)
|
|
vi.mock('@/features/tracks/services/trackHistoryService', () => ({
|
|
getTrackHistory: vi.fn(),
|
|
TrackHistoryError: class TrackHistoryError extends Error {
|
|
constructor(
|
|
message: string,
|
|
public code: string,
|
|
public retryable: boolean,
|
|
public originalError?: unknown,
|
|
) {
|
|
super(message);
|
|
this.name = 'TrackHistoryError';
|
|
}
|
|
},
|
|
}));
|
|
vi.mock('@/hooks/useToast');
|
|
|
|
describe('TrackHistory', () => {
|
|
const mockToast = {
|
|
success: vi.fn(),
|
|
error: vi.fn(),
|
|
};
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
vi.mocked(useToast).mockReturnValue(mockToast);
|
|
});
|
|
|
|
const mockHistoryItems: TrackHistoryType[] = [
|
|
{
|
|
id: 1,
|
|
track_id: 123,
|
|
user_id: 1,
|
|
action: 'created',
|
|
old_value: undefined,
|
|
new_value: '{"title": "Test Track"}',
|
|
created_at: '2024-01-01T00:00:00Z',
|
|
},
|
|
{
|
|
id: 2,
|
|
track_id: 123,
|
|
user_id: 1,
|
|
action: 'updated',
|
|
old_value: '{"title": "Test Track"}',
|
|
new_value: '{"title": "Updated Track"}',
|
|
created_at: '2024-01-02T00:00:00Z',
|
|
},
|
|
{
|
|
id: 3,
|
|
track_id: 123,
|
|
user_id: 1,
|
|
action: 'published',
|
|
old_value: undefined,
|
|
new_value: undefined,
|
|
created_at: '2024-01-03T00:00:00Z',
|
|
},
|
|
];
|
|
|
|
it('should render loading state initially', async () => {
|
|
vi.mocked(getTrackHistory).mockImplementation(
|
|
() =>
|
|
new Promise((resolve) =>
|
|
setTimeout(
|
|
() =>
|
|
resolve({
|
|
history: mockHistoryItems,
|
|
total: 3,
|
|
limit: 50,
|
|
offset: 0,
|
|
}),
|
|
100,
|
|
),
|
|
),
|
|
);
|
|
|
|
render(<TrackHistory trackId={123} />);
|
|
|
|
expect(screen.getByRole('status')).toBeInTheDocument();
|
|
await waitFor(() => {
|
|
expect(getTrackHistory).toHaveBeenCalledWith(123, {
|
|
limit: 50,
|
|
offset: 0,
|
|
});
|
|
});
|
|
});
|
|
|
|
it('should display history timeline', async () => {
|
|
vi.mocked(getTrackHistory).mockResolvedValue({
|
|
history: mockHistoryItems,
|
|
total: 3,
|
|
limit: 50,
|
|
offset: 0,
|
|
});
|
|
|
|
render(<TrackHistory trackId={123} />);
|
|
|
|
await waitFor(() => {
|
|
expect(
|
|
screen.getByText('Historique des modifications'),
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
expect(screen.getByText('Créé')).toBeInTheDocument();
|
|
expect(screen.getByText('Modifié')).toBeInTheDocument();
|
|
expect(screen.getByText('Publié')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should display empty state when no history', async () => {
|
|
vi.mocked(getTrackHistory).mockResolvedValue({
|
|
history: [],
|
|
total: 0,
|
|
limit: 50,
|
|
offset: 0,
|
|
});
|
|
|
|
render(<TrackHistory trackId={123} />);
|
|
|
|
await waitFor(() => {
|
|
expect(
|
|
screen.getByText('Aucune modification enregistrée'),
|
|
).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('should display error message on load error', async () => {
|
|
const loadError = new TrackHistoryError('Failed to load', 'SERVER', true);
|
|
vi.mocked(getTrackHistory).mockImplementation(() =>
|
|
Promise.reject(loadError),
|
|
);
|
|
|
|
render(<TrackHistory trackId="123" />);
|
|
|
|
const message = await screen.findByText('Failed to load', {}, { timeout: 3000 });
|
|
expect(message).toBeInTheDocument();
|
|
});
|
|
|
|
it('should display old and new values when available', async () => {
|
|
vi.mocked(getTrackHistory).mockResolvedValue({
|
|
history: [mockHistoryItems[1]], // Updated action with values
|
|
total: 1,
|
|
limit: 50,
|
|
offset: 0,
|
|
});
|
|
|
|
render(<TrackHistory trackId={123} />);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Ancienne valeur:')).toBeInTheDocument();
|
|
expect(screen.getByText('Nouvelle valeur:')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('should handle pagination', async () => {
|
|
const largeHistory: TrackHistory[] = Array.from(
|
|
{ length: 10 },
|
|
(_, i) => ({
|
|
id: i + 1,
|
|
track_id: 123,
|
|
user_id: 1,
|
|
action: 'updated' as const,
|
|
created_at: `2024-01-0${i + 1}T00:00:00Z`,
|
|
}),
|
|
);
|
|
|
|
vi.mocked(getTrackHistory).mockResolvedValue({
|
|
history: largeHistory.slice(0, 5),
|
|
total: 10,
|
|
limit: 5,
|
|
offset: 0,
|
|
});
|
|
|
|
render(<TrackHistory trackId={123} limit={5} />);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText(/Affichage 1 - 5 sur 10/)).toBeInTheDocument();
|
|
});
|
|
|
|
const nextButton = screen.getByText('Suivant');
|
|
expect(nextButton).toBeInTheDocument();
|
|
expect(nextButton).not.toBeDisabled();
|
|
|
|
// Mock next page response
|
|
vi.mocked(getTrackHistory).mockResolvedValue({
|
|
history: largeHistory.slice(5, 10),
|
|
total: 10,
|
|
limit: 5,
|
|
offset: 5,
|
|
});
|
|
|
|
fireEvent.click(nextButton);
|
|
|
|
await waitFor(() => {
|
|
expect(getTrackHistory).toHaveBeenCalledWith(123, {
|
|
limit: 5,
|
|
offset: 5,
|
|
});
|
|
});
|
|
});
|
|
|
|
it('should disable previous button on first page', async () => {
|
|
vi.mocked(getTrackHistory).mockResolvedValue({
|
|
history: mockHistoryItems.slice(0, 2),
|
|
total: 10,
|
|
limit: 2,
|
|
offset: 0,
|
|
});
|
|
|
|
render(<TrackHistory trackId={123} limit={2} />);
|
|
|
|
await waitFor(() => {
|
|
const previousButton = screen.getByText('Précédent');
|
|
expect(previousButton).toBeDisabled();
|
|
});
|
|
});
|
|
|
|
it('should disable next button on last page', async () => {
|
|
vi.mocked(getTrackHistory)
|
|
.mockResolvedValueOnce({
|
|
history: mockHistoryItems.slice(0, 2),
|
|
total: 3,
|
|
limit: 2,
|
|
offset: 0,
|
|
})
|
|
.mockResolvedValueOnce({
|
|
history: mockHistoryItems.slice(2, 3),
|
|
total: 3,
|
|
limit: 2,
|
|
offset: 2,
|
|
});
|
|
|
|
render(<TrackHistory trackId={123} limit={2} />);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText(/Affichage 1 - 2 sur 3/)).toBeInTheDocument();
|
|
});
|
|
|
|
const nextButton = screen.getByText('Suivant');
|
|
fireEvent.click(nextButton);
|
|
|
|
await waitFor(() => {
|
|
const nextButtonAfter = screen.getByText('Suivant');
|
|
expect(nextButtonAfter).toBeDisabled();
|
|
});
|
|
});
|
|
|
|
it('should display all action types correctly', async () => {
|
|
const allActions: TrackHistory[] = [
|
|
{
|
|
id: 1,
|
|
track_id: 123,
|
|
user_id: 1,
|
|
action: 'created',
|
|
created_at: '2024-01-01T00:00:00Z',
|
|
},
|
|
{
|
|
id: 2,
|
|
track_id: 123,
|
|
user_id: 1,
|
|
action: 'updated',
|
|
created_at: '2024-01-02T00:00:00Z',
|
|
},
|
|
{
|
|
id: 3,
|
|
track_id: 123,
|
|
user_id: 1,
|
|
action: 'deleted',
|
|
created_at: '2024-01-03T00:00:00Z',
|
|
},
|
|
{
|
|
id: 4,
|
|
track_id: 123,
|
|
user_id: 1,
|
|
action: 'published',
|
|
created_at: '2024-01-04T00:00:00Z',
|
|
},
|
|
{
|
|
id: 5,
|
|
track_id: 123,
|
|
user_id: 1,
|
|
action: 'unpublished',
|
|
created_at: '2024-01-05T00:00:00Z',
|
|
},
|
|
{
|
|
id: 6,
|
|
track_id: 123,
|
|
user_id: 1,
|
|
action: 'restored',
|
|
created_at: '2024-01-06T00:00:00Z',
|
|
},
|
|
];
|
|
|
|
vi.mocked(getTrackHistory).mockResolvedValue({
|
|
history: allActions,
|
|
total: 6,
|
|
limit: 50,
|
|
offset: 0,
|
|
});
|
|
|
|
render(<TrackHistory trackId={123} />);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Créé')).toBeInTheDocument();
|
|
expect(screen.getByText('Modifié')).toBeInTheDocument();
|
|
expect(screen.getByText('Supprimé')).toBeInTheDocument();
|
|
expect(screen.getByText('Publié')).toBeInTheDocument();
|
|
expect(screen.getByText('Dépublié')).toBeInTheDocument();
|
|
expect(screen.getByText('Restauré')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('should format dates correctly', async () => {
|
|
vi.mocked(getTrackHistory).mockResolvedValue({
|
|
history: [mockHistoryItems[0]],
|
|
total: 1,
|
|
limit: 50,
|
|
offset: 0,
|
|
});
|
|
|
|
render(<TrackHistory trackId={123} />);
|
|
|
|
await waitFor(() => {
|
|
// Check that date is formatted (should contain month name in French)
|
|
const dateElement = screen.getByText(/janvier/i);
|
|
expect(dateElement).toBeInTheDocument();
|
|
});
|
|
});
|
|
});
|