import React from 'react';
import { vi, describe, it, expect, test, beforeEach } from 'vitest';
import userEvent from '@testing-library/user-event';
import { renderHook, act, render, screen, waitFor } from '@testing-library/react';
import { type StockSource } from '../core/api/types/stockOperation/StockSource';
import { type Concept } from '../core/api/types/concept/Concept';
import { useConcept } from '../stock-lookups/stock-lookups.resource';
import useStockSourcesPage from './stock-sources-items-table.resource';
import StockSourcesItems from './stock-sources-items-table.component';
const mockUseStockSourcesPage = vi.mocked(useStockSourcesPage);
const mockUseConcept = vi.mocked(useConcept);
vi.mock('./stock-sources-items-table.resource', () => ({
default: vi.fn(),
useStockSourcesPage: vi.fn(),
}));
vi.mock('../stock-lookups/stock-lookups.resource', () => ({
useConcept: vi.fn(),
}));
describe('StockSourcesItems', () => {
const mockFilter = {};
const mockItems = {
results: [
{
uuid: '1',
name: 'Community',
acronym: 'Community',
sourceType: { display: 'Donation' },
creator: null,
dateCreated: null,
changedBy: null,
dateChanged: null,
retired: false,
dateRetired: null,
retiredBy: null,
retireReason: null,
},
] as unknown as StockSource[],
totalCount: 1,
};
beforeEach(() => {
mockUseConcept.mockReturnValue({
items: {
uuid: '1',
display: 'Source Types',
answers: [
{ uuid: '1', display: 'All' },
{ uuid: '2', display: 'Internal' },
{ uuid: '3', display: 'External' },
] as Concept[],
} as Concept,
isLoading: false,
error: null,
});
});
it('should return initial values', () => {
const mockFilter = {};
const mockItems = {
results: [{ name: 'Community', acronym: 'Community', sourceType: { display: 'Donation' } }],
totalCount: 1,
};
mockUseStockSourcesPage.mockReturnValue({
items: mockItems.results as unknown as StockSource[],
isLoading: false,
totalItems: mockItems.totalCount,
currentPage: 1,
pageSizes: [10, 20, 50],
goTo: vi.fn(),
currentPageSize: 10,
setPageSize: vi.fn(),
error: null,
paginatedItems: mockItems.results as unknown as StockSource[],
tableHeaders: [],
});
const { result } = renderHook(() => useStockSourcesPage(mockFilter));
expect(result.current.items).toEqual(mockItems.results);
expect(result.current.totalItems).toBe(mockItems.totalCount);
expect(result.current.currentPageSize).toBe(10);
expect(result.current.isLoading).toBe(false);
expect(result.current.error).toBe(null);
});
it('should update current page size', () => {
let currentPageSize = 10; // Track page size with a local variable
mockUseStockSourcesPage.mockReturnValue({
items: mockItems.results,
isLoading: false,
totalItems: mockItems.totalCount,
currentPage: 1,
pageSizes: [10, 20, 50],
goTo: vi.fn(),
currentPageSize,
setPageSize: vi.fn((size: number) => {
currentPageSize = size;
}),
error: null,
paginatedItems: mockItems.results,
tableHeaders: [],
});
const { result } = renderHook(() => useStockSourcesPage(mockFilter));
act(() => {
result.current.setPageSize(20);
});
expect(currentPageSize).toBe(20); // Check the local variable instead
});
it('should handle loading and error states', () => {
mockUseStockSourcesPage.mockReturnValue({
items: [] as unknown as StockSource[],
isLoading: true,
error: null,
totalItems: 0,
currentPage: 1,
currentPageSize: 10,
pageSizes: [10, 20, 50],
goTo: vi.fn(),
setPageSize: vi.fn(),
paginatedItems: [] as unknown as StockSource[],
tableHeaders: [],
});
const { result } = renderHook(() => useStockSourcesPage(mockFilter));
expect(result.current.isLoading).toBe(true);
expect(result.current.error).toBe(null);
expect(result.current.totalItems).toBe(0);
});
test('renders loading state when data is being fetched', () => {
mockUseStockSourcesPage.mockReturnValue({
items: [],
isLoading: true,
totalItems: 0,
tableHeaders: [],
currentPage: 1,
pageSizes: [10, 20, 50],
goTo: vi.fn(),
currentPageSize: 10,
setPageSize: vi.fn(),
paginatedItems: [],
error: null,
});
render();
expect(screen.getByRole('progressbar')).toBeInTheDocument();
});
test('renders data table when data is loaded', () => {
const mockItems = [
{
uuid: '1234',
name: 'Source A',
acronym: 'SA',
sourceType: { display: 'Internal' },
},
];
mockUseStockSourcesPage.mockReturnValue({
items: mockItems as unknown as StockSource[],
isLoading: false,
totalItems: 1,
tableHeaders: [
{ id: 0, key: 'name', header: 'Name' },
{ id: 1, key: 'sourceType', header: 'Source Type' },
],
currentPage: 1,
pageSizes: [10, 20, 50],
goTo: vi.fn(),
currentPageSize: 10,
setPageSize: vi.fn(),
paginatedItems: mockItems as unknown as StockSource[],
error: null,
});
render();
expect(screen.getByText(/source a/i)).toBeInTheDocument();
expect(screen.getByText(/internal/i)).toBeInTheDocument();
});
test('filters data based on source type', async () => {
const user = userEvent.setup();
const mockItems = [
{
uuid: '1234',
name: 'Source A',
acronym: 'SA',
sourceType: { display: 'Internal' },
},
{
uuid: '5678',
name: 'Source B',
acronym: 'SB',
sourceType: { display: 'External' },
},
];
mockUseStockSourcesPage.mockReturnValue({
items: mockItems as unknown as StockSource[],
isLoading: false,
totalItems: 2,
tableHeaders: [
{ id: 0, key: 'name', header: 'Name' },
{ id: 1, key: 'sourceType', header: 'Source Type' },
],
currentPage: 1,
pageSizes: [10, 20, 50],
goTo: vi.fn(),
currentPageSize: 10,
setPageSize: vi.fn(),
paginatedItems: mockItems as unknown as StockSource[],
error: null,
});
// Mock the concept data for the filter
mockUseConcept.mockReturnValue({
items: {
uuid: '1',
display: 'Source Types',
answers: [
{ uuid: '1', display: 'All' },
{ uuid: '2', display: 'Internal' },
{ uuid: '3', display: 'External' },
] as Concept[],
} as Concept,
isLoading: false,
error: null,
});
render();
// Initial state - both sources should be visible
expect(screen.getByText(/source a/i)).toBeInTheDocument();
expect(screen.getByText(/source b/i)).toBeInTheDocument();
// Find and click the source type filter dropdown
const filterDropdown = screen.getByRole('combobox', { name: /select source type/i });
await user.click(filterDropdown);
// Select "Internal" from the dropdown
const internalOption = screen.getByRole('option', { name: /internal/i });
await user.click(internalOption);
// Wait for Source B to disappear
await waitFor(() => {
expect(screen.queryByText(/source b/i)).not.toBeInTheDocument();
});
// Verify Source A is still visible
expect(screen.getByText(/source a/i)).toBeInTheDocument();
});
test('renders a message when no stock sources are available', () => {
mockUseStockSourcesPage.mockReturnValue({
items: [],
isLoading: false,
totalItems: 0,
tableHeaders: [],
currentPage: 1,
pageSizes: [10, 20, 50],
goTo: vi.fn(),
currentPageSize: 10,
setPageSize: vi.fn(),
paginatedItems: [],
error: null,
});
render();
expect(screen.getByText(/no stock sources to display/i)).toBeInTheDocument();
expect(screen.getByText(/check the filters above/i)).toBeInTheDocument();
});
test('pagination works as expected', async () => {
const user = userEvent.setup();
const mockGoTo = vi.fn();
const mockSetPageSize = vi.fn();
mockUseStockSourcesPage.mockReturnValue({
items: [],
isLoading: false,
totalItems: 20,
tableHeaders: [],
currentPage: 1,
pageSizes: [10, 20, 50],
goTo: mockGoTo,
currentPageSize: 10,
setPageSize: mockSetPageSize,
paginatedItems: [],
error: null,
});
render();
const nextPageButton = screen.getByLabelText(/next page/i);
await user.click(nextPageButton);
expect(mockGoTo).toHaveBeenCalledWith(2);
});
});