searchSlice.test.ts9.9 KBView on GitHub /**
* Tests for SearchSlice — search value, highlight, folder, loading flags, filter management
*/
import { act } from '@testing-library/react';
import { useCedarStore } from '@/modules/cedar-os/src/store/CedarStore';
import { loadFiltersFromStorage } from '@/modules/threads/hooks/searchSlice';
import type { ActiveFilter } from '@/modules/threads/hooks/searchSlice';
const makeFilter = (overrides: Partial<ActiveFilter> = {}): ActiveFilter => ({
id: 'filter-1',
type: 'from',
value: '<email>',
display: 'From: Alice',
...overrides,
});
const resetSearch = () =>
useCedarStore.setState((s) => ({
...s,
value: '',
highlight: '',
folder: '',
isLoading: false,
isAISearching: false,
activeFilters: [],
}));
beforeEach(() => {
resetSearch();
localStorage.clear();
});
afterEach(resetSearch);
// ---------------------------------------------------------------------------
// setValue
// ---------------------------------------------------------------------------
describe('setValue', () => {
it('sets the search query', () => {
act(() => useCedarStore.getState().setValue('quarterly report'));
expect(useCedarStore.getState().value).toBe('quarterly report');
});
it('clears the query when set to empty string', () => {
act(() => useCedarStore.getState().setValue('something'));
act(() => useCedarStore.getState().setValue(''));
expect(useCedarStore.getState().value).toBe('');
});
});
// ---------------------------------------------------------------------------
// setHighlight
// ---------------------------------------------------------------------------
describe('setHighlight', () => {
it('sets the highlight term', () => {
act(() => useCedarStore.getState().setHighlight('report'));
expect(useCedarStore.getState().highlight).toBe('report');
});
});
// ---------------------------------------------------------------------------
// setFolder
// ---------------------------------------------------------------------------
describe('setFolder', () => {
it('sets the active folder', () => {
act(() => useCedarStore.getState().setFolder('inbox'));
expect(useCedarStore.getState().folder).toBe('inbox');
});
it('replaces the previous folder', () => {
act(() => useCedarStore.getState().setFolder('inbox'));
act(() => useCedarStore.getState().setFolder('sent'));
expect(useCedarStore.getState().folder).toBe('sent');
});
});
// ---------------------------------------------------------------------------
// setIsLoading / setIsAISearching
// ---------------------------------------------------------------------------
describe('setIsLoading', () => {
it('sets loading to true', () => {
act(() => useCedarStore.getState().setIsLoading(true));
expect(useCedarStore.getState().isLoading).toBe(true);
});
it('sets loading to false', () => {
act(() => useCedarStore.getState().setIsLoading(true));
act(() => useCedarStore.getState().setIsLoading(false));
expect(useCedarStore.getState().isLoading).toBe(false);
});
});
describe('setIsAISearching', () => {
it('sets AI searching flag', () => {
act(() => useCedarStore.getState().setIsAISearching(true));
expect(useCedarStore.getState().isAISearching).toBe(true);
});
});
// ---------------------------------------------------------------------------
// setSearchState (batch update)
// ---------------------------------------------------------------------------
describe('setSearchState', () => {
it('applies a partial state update', () => {
act(() =>
useCedarStore.getState().setSearchState({
value: 'invoice',
highlight: 'invoice',
folder: 'inbox',
}),
);
const state = useCedarStore.getState();
expect(state.value).toBe('invoice');
expect(state.highlight).toBe('invoice');
expect(state.folder).toBe('inbox');
});
it('does not overwrite unspecified fields', () => {
act(() => useCedarStore.getState().setFolder('sent'));
act(() => useCedarStore.getState().setSearchState({ value: 'test' }));
expect(useCedarStore.getState().folder).toBe('sent');
});
});
// ---------------------------------------------------------------------------
// addFilter
// ---------------------------------------------------------------------------
describe('addFilter', () => {
it('adds a new filter', () => {
const f = makeFilter();
act(() => useCedarStore.getState().addFilter(f));
expect(useCedarStore.getState().activeFilters).toContainEqual(f);
});
it('replaces an existing filter of the same type', () => {
act(() =>
useCedarStore.getState().addFilter(makeFilter({ id: 'f-1', type: 'from', value: '<email>' })),
);
act(() =>
useCedarStore.getState().addFilter(makeFilter({ id: 'f-2', type: 'from', value: '<email>' })),
);
const filters = useCedarStore.getState().activeFilters;
expect(filters).toHaveLength(1);
expect(filters[0].value).toBe('<email>');
});
it('keeps filters of different types', () => {
act(() => useCedarStore.getState().addFilter(makeFilter({ id: 'f-1', type: 'from' })));
act(() => useCedarStore.getState().addFilter(makeFilter({ id: 'f-2', type: 'to', value: '<email>' })));
expect(useCedarStore.getState().activeFilters).toHaveLength(2);
});
it('persists to localStorage', () => {
const f = makeFilter();
act(() => useCedarStore.getState().addFilter(f));
const saved = loadFiltersFromStorage();
expect(saved).toContainEqual(f);
});
});
// ---------------------------------------------------------------------------
// removeFilter
// ---------------------------------------------------------------------------
describe('removeFilter', () => {
it('removes the matching filter by ID', () => {
act(() => useCedarStore.getState().addFilter(makeFilter({ id: 'f-1' })));
act(() => useCedarStore.getState().addFilter(makeFilter({ id: 'f-2', type: 'to' })));
act(() => useCedarStore.getState().removeFilter('f-1'));
const filters = useCedarStore.getState().activeFilters;
expect(filters).toHaveLength(1);
expect(filters[0].id).toBe('f-2');
});
it('is a no-op for an unknown filter ID', () => {
act(() => useCedarStore.getState().addFilter(makeFilter({ id: 'f-1' })));
act(() => useCedarStore.getState().removeFilter('does-not-exist'));
expect(useCedarStore.getState().activeFilters).toHaveLength(1);
});
it('updates localStorage after removal', () => {
act(() => useCedarStore.getState().addFilter(makeFilter({ id: 'f-1' })));
act(() => useCedarStore.getState().removeFilter('f-1'));
expect(loadFiltersFromStorage()).toHaveLength(0);
});
});
// ---------------------------------------------------------------------------
// clearAllFilters
// ---------------------------------------------------------------------------
describe('clearAllFilters', () => {
it('clears all filters, value, and highlight', () => {
act(() => {
useCedarStore.getState().addFilter(makeFilter({ id: 'f-1' }));
useCedarStore.getState().addFilter(makeFilter({ id: 'f-2', type: 'to' }));
useCedarStore.getState().setValue('some query');
useCedarStore.getState().setHighlight('some');
});
act(() => useCedarStore.getState().clearAllFilters());
const state = useCedarStore.getState();
expect(state.activeFilters).toEqual([]);
expect(state.value).toBe('');
expect(state.highlight).toBe('');
});
it('removes the localStorage key', () => {
act(() => useCedarStore.getState().addFilter(makeFilter()));
act(() => useCedarStore.getState().clearAllFilters());
expect(loadFiltersFromStorage()).toEqual([]);
});
});
// ---------------------------------------------------------------------------
// setActiveFilters
// ---------------------------------------------------------------------------
describe('setActiveFilters', () => {
it('replaces all active filters at once', () => {
act(() => useCedarStore.getState().addFilter(makeFilter({ id: 'old' })));
const newFilters = [
makeFilter({ id: 'new-1', type: 'from' }),
makeFilter({ id: 'new-2', type: 'to' }),
];
act(() => useCedarStore.getState().setActiveFilters(newFilters));
expect(useCedarStore.getState().activeFilters).toEqual(newFilters);
});
});
// ---------------------------------------------------------------------------
// resetSearchState
// ---------------------------------------------------------------------------
describe('resetSearchState', () => {
it('resets all search state to initial values', () => {
act(() => {
useCedarStore.getState().setValue('test query');
useCedarStore.getState().setHighlight('test');
useCedarStore.getState().setFolder('inbox');
useCedarStore.getState().setIsLoading(true);
useCedarStore.getState().setIsAISearching(true);
useCedarStore.getState().addFilter(makeFilter());
});
act(() => useCedarStore.getState().resetSearchState());
const state = useCedarStore.getState();
expect(state.value).toBe('');
expect(state.highlight).toBe('');
expect(state.folder).toBe('');
expect(state.isLoading).toBe(false);
expect(state.isAISearching).toBe(false);
expect(state.activeFilters).toEqual([]);
});
});
// ---------------------------------------------------------------------------
// loadFiltersFromStorage (standalone helper)
// ---------------------------------------------------------------------------
describe('loadFiltersFromStorage', () => {
it('returns empty array when nothing is stored', () => {
expect(loadFiltersFromStorage()).toEqual([]);
});
it('returns stored filters', () => {
const filters = [makeFilter({ id: 'f-1' })];
localStorage.setItem('mail-active-filters', JSON.stringify(filters));
expect(loadFiltersFromStorage()).toEqual(filters);
});
it('returns empty array when localStorage contains invalid JSON', () => {
localStorage.setItem('mail-active-filters', 'not json {{{');
expect(loadFiltersFromStorage()).toEqual([]);
});
});