claude 1 - fixes : -/
All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 24m33s

This commit is contained in:
2026-01-08 22:30:21 -08:00
parent 62470e7661
commit 74a5ca6331
14 changed files with 509 additions and 456 deletions

View File

@@ -37,7 +37,7 @@ export const useAddWatchedItemMutation = () => {
return useMutation({
mutationFn: async ({ itemName, category }: AddWatchedItemParams) => {
const response = await apiClient.addWatchedItem(itemName, category);
const response = await apiClient.addWatchedItem(itemName, category ?? '');
if (!response.ok) {
const error = await response.json().catch(() => ({

View File

@@ -1,18 +1,7 @@
// src/hooks/queries/useActivityLogQuery.ts
import { useQuery } from '@tanstack/react-query';
import * as apiClient from '../../services/apiClient';
interface ActivityLogEntry {
activity_log_id: number;
user_id: string;
action: string;
entity_type: string | null;
entity_id: number | null;
details: any;
ip_address: string | null;
user_agent: string | null;
created_at: string;
}
import { fetchActivityLog } from '../../services/apiClient';
import type { ActivityLogItem } from '../../types';
/**
* Query hook for fetching the admin activity log.
@@ -33,8 +22,8 @@ interface ActivityLogEntry {
export const useActivityLogQuery = (limit: number = 20, offset: number = 0) => {
return useQuery({
queryKey: ['activity-log', { limit, offset }],
queryFn: async (): Promise<ActivityLogEntry[]> => {
const response = await apiClient.fetchActivityLog(limit, offset);
queryFn: async (): Promise<ActivityLogItem[]> => {
const response = await fetchActivityLog(limit, offset);
if (!response.ok) {
const error = await response.json().catch(() => ({

View File

@@ -1,6 +1,6 @@
// src/hooks/queries/useApplicationStatsQuery.ts
import { useQuery } from '@tanstack/react-query';
import { apiClient, AppStats } from '../../services/apiClient';
import { getApplicationStats, AppStats } from '../../services/apiClient';
/**
* Query hook for fetching application-wide statistics (admin feature).
@@ -21,7 +21,7 @@ export const useApplicationStatsQuery = () => {
return useQuery({
queryKey: ['application-stats'],
queryFn: async (): Promise<AppStats> => {
const response = await apiClient.getApplicationStats();
const response = await getApplicationStats();
if (!response.ok) {
const error = await response.json().catch(() => ({

View File

@@ -1,6 +1,6 @@
// src/hooks/queries/useCategoriesQuery.ts
import { useQuery } from '@tanstack/react-query';
import { apiClient } from '../../services/apiClient';
import { fetchCategories } from '../../services/apiClient';
import type { Category } from '../../types';
/**
@@ -16,7 +16,7 @@ export const useCategoriesQuery = () => {
return useQuery({
queryKey: ['categories'],
queryFn: async (): Promise<Category[]> => {
const response = await apiClient.fetchCategories();
const response = await fetchCategories();
if (!response.ok) {
const error = await response.json().catch(() => ({

View File

@@ -1,6 +1,6 @@
// src/hooks/queries/useSuggestedCorrectionsQuery.ts
import { useQuery } from '@tanstack/react-query';
import { apiClient } from '../../services/apiClient';
import { getSuggestedCorrections } from '../../services/apiClient';
import type { SuggestedCorrection } from '../../types';
/**
@@ -16,7 +16,7 @@ export const useSuggestedCorrectionsQuery = () => {
return useQuery({
queryKey: ['suggested-corrections'],
queryFn: async (): Promise<SuggestedCorrection[]> => {
const response = await apiClient.getSuggestedCorrections();
const response = await getSuggestedCorrections();
if (!response.ok) {
const error = await response.json().catch(() => ({

View File

@@ -4,15 +4,15 @@ import { renderHook, act } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { useFlyers } from './useFlyers';
import { FlyersProvider } from '../providers/FlyersProvider';
import { useInfiniteQuery } from './useInfiniteQuery';
import { useFlyersQuery } from './queries/useFlyersQuery';
import type { Flyer } from '../types';
import { createMockFlyer } from '../tests/utils/mockFactories';
// 1. Mock the useInfiniteQuery hook, which is the dependency of our FlyersProvider.
vi.mock('./useInfiniteQuery');
// 1. Mock the useFlyersQuery hook, which is the dependency of our FlyersProvider.
vi.mock('./queries/useFlyersQuery');
// 2. Create a typed mock of the hook for type safety and autocompletion.
const mockedUseInfiniteQuery = vi.mocked(useInfiniteQuery);
const mockedUseFlyersQuery = vi.mocked(useFlyersQuery);
// 3. A simple wrapper component that renders our provider.
// This is necessary because the useFlyers hook needs to be a child of FlyersProvider.
@@ -22,7 +22,6 @@ const wrapper = ({ children }: { children: ReactNode }) => (
describe('useFlyers Hook and FlyersProvider', () => {
// Create mock functions that we can spy on to see if they are called.
const mockFetchNextPage = vi.fn();
const mockRefetch = vi.fn();
beforeEach(() => {
@@ -46,16 +45,32 @@ describe('useFlyers Hook and FlyersProvider', () => {
it('should return the initial loading state correctly', () => {
// Arrange: Configure the mocked hook to return a loading state.
mockedUseInfiniteQuery.mockReturnValue({
data: [],
mockedUseFlyersQuery.mockReturnValue({
data: undefined,
isLoading: true,
error: null,
fetchNextPage: mockFetchNextPage,
hasNextPage: false,
refetch: mockRefetch,
isRefetching: false,
isFetchingNextPage: false,
});
// TanStack Query properties (partial mock)
status: 'pending',
fetchStatus: 'fetching',
isPending: true,
isSuccess: false,
isError: false,
isFetched: false,
isFetchedAfterMount: false,
isStale: false,
isPlaceholderData: false,
dataUpdatedAt: 0,
errorUpdatedAt: 0,
failureCount: 0,
failureReason: null,
errorUpdateCount: 0,
isInitialLoading: true,
isLoadingError: false,
isRefetchError: false,
promise: Promise.resolve([]),
} as any);
// Act: Render the hook within the provider wrapper.
const { result } = renderHook(() => useFlyers(), { wrapper });
@@ -66,7 +81,7 @@ describe('useFlyers Hook and FlyersProvider', () => {
expect(result.current.flyersError).toBeNull();
});
it('should return flyers data and hasNextPage on successful fetch', () => {
it('should return flyers data on successful fetch', () => {
// Arrange: Mock a successful data fetch.
const mockFlyers: Flyer[] = [
createMockFlyer({
@@ -77,16 +92,31 @@ describe('useFlyers Hook and FlyersProvider', () => {
created_at: '2024-01-01',
}),
];
mockedUseInfiniteQuery.mockReturnValue({
mockedUseFlyersQuery.mockReturnValue({
data: mockFlyers,
isLoading: false,
error: null,
fetchNextPage: mockFetchNextPage,
hasNextPage: true,
refetch: mockRefetch,
isRefetching: false,
isFetchingNextPage: false,
});
status: 'success',
fetchStatus: 'idle',
isPending: false,
isSuccess: true,
isError: false,
isFetched: true,
isFetchedAfterMount: true,
isStale: false,
isPlaceholderData: false,
dataUpdatedAt: Date.now(),
errorUpdatedAt: 0,
failureCount: 0,
failureReason: null,
errorUpdateCount: 0,
isInitialLoading: false,
isLoadingError: false,
isRefetchError: false,
promise: Promise.resolve(mockFlyers),
} as any);
// Act
const { result } = renderHook(() => useFlyers(), { wrapper });
@@ -94,22 +124,38 @@ describe('useFlyers Hook and FlyersProvider', () => {
// Assert
expect(result.current.isLoadingFlyers).toBe(false);
expect(result.current.flyers).toEqual(mockFlyers);
expect(result.current.hasNextFlyersPage).toBe(true);
// Note: hasNextFlyersPage is always false now since we're not using infinite query
expect(result.current.hasNextFlyersPage).toBe(false);
});
it('should return an error state if the fetch fails', () => {
// Arrange: Mock a failed data fetch.
const mockError = new Error('Failed to fetch');
mockedUseInfiniteQuery.mockReturnValue({
data: [],
mockedUseFlyersQuery.mockReturnValue({
data: undefined,
isLoading: false,
error: mockError,
fetchNextPage: mockFetchNextPage,
hasNextPage: false,
refetch: mockRefetch,
isRefetching: false,
isFetchingNextPage: false,
});
status: 'error',
fetchStatus: 'idle',
isPending: false,
isSuccess: false,
isError: true,
isFetched: true,
isFetchedAfterMount: true,
isStale: false,
isPlaceholderData: false,
dataUpdatedAt: 0,
errorUpdatedAt: Date.now(),
failureCount: 1,
failureReason: mockError,
errorUpdateCount: 1,
isInitialLoading: false,
isLoadingError: true,
isRefetchError: false,
promise: Promise.resolve(undefined),
} as any);
// Act
const { result } = renderHook(() => useFlyers(), { wrapper });
@@ -120,41 +166,33 @@ describe('useFlyers Hook and FlyersProvider', () => {
expect(result.current.flyersError).toBe(mockError);
});
it('should call fetchNextFlyersPage when the context function is invoked', () => {
// Arrange
mockedUseInfiniteQuery.mockReturnValue({
data: [],
isLoading: false,
error: null,
hasNextPage: true,
isRefetching: false,
isFetchingNextPage: false,
fetchNextPage: mockFetchNextPage, // Pass the mock function
refetch: mockRefetch,
});
const { result } = renderHook(() => useFlyers(), { wrapper });
// Act: Use `act` to wrap state updates.
act(() => {
result.current.fetchNextFlyersPage();
});
// Assert
expect(mockFetchNextPage).toHaveBeenCalledTimes(1);
});
it('should call refetchFlyers when the context function is invoked', () => {
// Arrange
mockedUseInfiniteQuery.mockReturnValue({
mockedUseFlyersQuery.mockReturnValue({
data: [],
isLoading: false,
error: null,
hasNextPage: false,
isRefetching: false,
isFetchingNextPage: false,
fetchNextPage: mockFetchNextPage,
refetch: mockRefetch,
});
isRefetching: false,
status: 'success',
fetchStatus: 'idle',
isPending: false,
isSuccess: true,
isError: false,
isFetched: true,
isFetchedAfterMount: true,
isStale: false,
isPlaceholderData: false,
dataUpdatedAt: Date.now(),
errorUpdatedAt: 0,
failureCount: 0,
failureReason: null,
errorUpdateCount: 0,
isInitialLoading: false,
isLoadingError: false,
isRefetchError: false,
promise: Promise.resolve([]),
} as any);
const { result } = renderHook(() => useFlyers(), { wrapper });
// Act
@@ -165,4 +203,40 @@ describe('useFlyers Hook and FlyersProvider', () => {
// Assert
expect(mockRefetch).toHaveBeenCalledTimes(1);
});
it('should have fetchNextFlyersPage as a no-op (infinite scroll not implemented)', () => {
// Arrange
mockedUseFlyersQuery.mockReturnValue({
data: [],
isLoading: false,
error: null,
refetch: mockRefetch,
isRefetching: false,
status: 'success',
fetchStatus: 'idle',
isPending: false,
isSuccess: true,
isError: false,
isFetched: true,
isFetchedAfterMount: true,
isStale: false,
isPlaceholderData: false,
dataUpdatedAt: Date.now(),
errorUpdatedAt: 0,
failureCount: 0,
failureReason: null,
errorUpdateCount: 0,
isInitialLoading: false,
isLoadingError: false,
isRefetchError: false,
promise: Promise.resolve([]),
} as any);
const { result } = renderHook(() => useFlyers(), { wrapper });
// Act & Assert: fetchNextFlyersPage should exist but be a no-op
expect(result.current.fetchNextFlyersPage).toBeDefined();
expect(typeof result.current.fetchNextFlyersPage).toBe('function');
// Calling it should not throw
expect(() => result.current.fetchNextFlyersPage()).not.toThrow();
});
});