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([]);
  });
});