177 lines
5.7 KiB
JavaScript
177 lines
5.7 KiB
JavaScript
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import { renderHook, act, waitFor } from '@testing-library/react';
|
|
|
|
vi.mock('../api/sourceCorpus.js', () => ({
|
|
listSourceCorpora: vi.fn(),
|
|
createSourceCorpus: vi.fn(),
|
|
deleteSourceCorpus: vi.fn(),
|
|
}));
|
|
|
|
import { useSourceCorpus } from './useSourceCorpus.js';
|
|
import * as sourceCorpusApi from '../api/sourceCorpus.js';
|
|
|
|
const sourceCorpora = [
|
|
{ id: 'nb-1', name: 'Research' },
|
|
{ id: 'nb-2', name: 'Personal' },
|
|
];
|
|
|
|
beforeEach(() => {
|
|
vi.resetAllMocks();
|
|
sourceCorpusApi.listSourceCorpora.mockResolvedValue(sourceCorpora);
|
|
sourceCorpusApi.createSourceCorpus.mockImplementation(async (name) => ({
|
|
id: `nb-${Date.now()}`,
|
|
name,
|
|
}));
|
|
sourceCorpusApi.deleteSourceCorpus.mockResolvedValue({});
|
|
});
|
|
|
|
describe('useSourceCorpus', () => {
|
|
it('loads sourceCorpora on mount', async () => {
|
|
const { result } = renderHook(() => useSourceCorpus());
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.sourceCorpora).toEqual(sourceCorpora);
|
|
});
|
|
expect(sourceCorpusApi.listSourceCorpora).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
it('starts with no active sourceCorpus', () => {
|
|
const { result } = renderHook(() => useSourceCorpus());
|
|
expect(result.current.activeSourceCorpus).toBeNull();
|
|
});
|
|
|
|
it('selectSourceCorpus sets the active sourceCorpus', async () => {
|
|
const { result } = renderHook(() => useSourceCorpus());
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.sourceCorpora).toHaveLength(2);
|
|
});
|
|
|
|
act(() => {
|
|
result.current.selectSourceCorpus('nb-2');
|
|
});
|
|
|
|
expect(result.current.activeSourceCorpus).toEqual({ id: 'nb-2', name: 'Personal' });
|
|
});
|
|
|
|
it('selectSourceCorpus sets null for unknown id', async () => {
|
|
const { result } = renderHook(() => useSourceCorpus());
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.sourceCorpora).toHaveLength(2);
|
|
});
|
|
|
|
act(() => {
|
|
result.current.selectSourceCorpus('nb-2');
|
|
});
|
|
expect(result.current.activeSourceCorpus).not.toBeNull();
|
|
|
|
act(() => {
|
|
result.current.selectSourceCorpus('nonexistent');
|
|
});
|
|
expect(result.current.activeSourceCorpus).toBeNull();
|
|
});
|
|
|
|
it('createSourceCorpus calls API and adds to list', async () => {
|
|
const newCorpus = { id: 'nb-new', name: 'New One' };
|
|
sourceCorpusApi.createSourceCorpus.mockResolvedValue(newCorpus);
|
|
|
|
const { result } = renderHook(() => useSourceCorpus());
|
|
await waitFor(() => expect(result.current.sourceCorpora).toHaveLength(2));
|
|
|
|
await act(async () => {
|
|
await result.current.createSourceCorpus('New One');
|
|
});
|
|
|
|
expect(sourceCorpusApi.createSourceCorpus).toHaveBeenCalledWith('New One');
|
|
expect(result.current.sourceCorpora).toHaveLength(3);
|
|
expect(result.current.sourceCorpora[2]).toEqual(newCorpus);
|
|
expect(result.current.activeSourceCorpus).toEqual(newCorpus);
|
|
});
|
|
|
|
it('createSourceCorpus does nothing for empty name', async () => {
|
|
const { result } = renderHook(() => useSourceCorpus());
|
|
await waitFor(() => expect(result.current.sourceCorpora).toHaveLength(2));
|
|
|
|
await act(async () => {
|
|
await result.current.createSourceCorpus('');
|
|
});
|
|
|
|
expect(sourceCorpusApi.createSourceCorpus).not.toHaveBeenCalled();
|
|
expect(result.current.sourceCorpora).toHaveLength(2);
|
|
});
|
|
|
|
it('deleteSourceCorpus removes from list', async () => {
|
|
const { result } = renderHook(() => useSourceCorpus());
|
|
await waitFor(() => expect(result.current.sourceCorpora).toHaveLength(2));
|
|
|
|
await act(async () => {
|
|
await result.current.deleteSourceCorpus('nb-1');
|
|
});
|
|
|
|
expect(sourceCorpusApi.deleteSourceCorpus).toHaveBeenCalledWith('nb-1');
|
|
expect(result.current.sourceCorpora).toHaveLength(1);
|
|
expect(result.current.sourceCorpora[0].id).toBe('nb-2');
|
|
});
|
|
|
|
it('deleteSourceCorpus clears activeSourceCorpus if it was the deleted one', async () => {
|
|
const { result } = renderHook(() => useSourceCorpus());
|
|
await waitFor(() => expect(result.current.sourceCorpora).toHaveLength(2));
|
|
|
|
act(() => {
|
|
result.current.selectSourceCorpus('nb-1');
|
|
});
|
|
expect(result.current.activeSourceCorpus?.id).toBe('nb-1');
|
|
|
|
await act(async () => {
|
|
await result.current.deleteSourceCorpus('nb-1');
|
|
});
|
|
|
|
expect(result.current.activeSourceCorpus).toBeNull();
|
|
});
|
|
|
|
it('deleteSourceCorpus preserves activeSourceCorpus if different one deleted', async () => {
|
|
const { result } = renderHook(() => useSourceCorpus());
|
|
await waitFor(() => expect(result.current.sourceCorpora).toHaveLength(2));
|
|
|
|
act(() => {
|
|
result.current.selectSourceCorpus('nb-2');
|
|
});
|
|
|
|
await act(async () => {
|
|
await result.current.deleteSourceCorpus('nb-1');
|
|
});
|
|
|
|
expect(result.current.activeSourceCorpus).toEqual({ id: 'nb-2', name: 'Personal' });
|
|
});
|
|
|
|
it('deleteSourceCorpus does not remove from list on API error', async () => {
|
|
sourceCorpusApi.deleteSourceCorpus.mockRejectedValue(new Error('Server error'));
|
|
vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
|
|
const { result } = renderHook(() => useSourceCorpus());
|
|
await waitFor(() => expect(result.current.sourceCorpora).toHaveLength(2));
|
|
|
|
await act(async () => {
|
|
await result.current.deleteSourceCorpus('nb-1');
|
|
});
|
|
|
|
expect(result.current.sourceCorpora).toHaveLength(2);
|
|
console.error.mockRestore();
|
|
});
|
|
|
|
it('handles listSourceCorpora API failure gracefully', async () => {
|
|
sourceCorpusApi.listSourceCorpora.mockRejectedValue(new Error('Network error'));
|
|
vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
|
|
const { result } = renderHook(() => useSourceCorpus());
|
|
|
|
await waitFor(() => {
|
|
expect(sourceCorpusApi.listSourceCorpora).toHaveBeenCalled();
|
|
});
|
|
|
|
expect(result.current.sourceCorpora).toEqual([]);
|
|
console.error.mockRestore();
|
|
});
|
|
});
|