threadStatus.test.tsx2.7 KBView on GitHub
import { DEFAULT_THREAD_ID } from '@/modules/cedar-os/src/store/messages/MessageTypes';
import { useCedarStore } from '@/modules/cedar-os/src/store/CedarStore';

/**
 * Phase 1 (design: chat-thread-store) — per-thread status metadata.
 * Exercises the real store slice headlessly: setThreadStatus / pin / unpin /
 * createdThisSession, with no UI in the loop.
 */
describe('agentThreadsSlice — thread status metadata', () => {
  beforeEach(() => {
    useCedarStore.setState((state) => ({
      ...state,
      threadMap: {
        [DEFAULT_THREAD_ID]: {
          id: DEFAULT_THREAD_ID,
          lastLoaded: new Date().toISOString(),
          messages: [],
        },
      },
      mainThreadId: DEFAULT_THREAD_ID,
      messages: [],
    }));
  });

  it('setThreadStatus writes status and bumps lastActiveAt on streaming', () => {
    const before = useCedarStore.getState().threadMap[DEFAULT_THREAD_ID].lastActiveAt;
    expect(before).toBeUndefined();

    useCedarStore.getState().setThreadStatus(DEFAULT_THREAD_ID, 'streaming');
    const streaming = useCedarStore.getState().threadMap[DEFAULT_THREAD_ID];
    expect(streaming.status).toBe('streaming');
    expect(typeof streaming.lastActiveAt).toBe('string');
    expect(Date.parse(streaming.lastActiveAt!)).not.toBeNaN();
  });

  it('setThreadStatus does not bump lastActiveAt for non-streaming transitions', () => {
    useCedarStore.getState().setThreadStatus(DEFAULT_THREAD_ID, 'streaming');
    const stamp = useCedarStore.getState().threadMap[DEFAULT_THREAD_ID].lastActiveAt;

    useCedarStore.getState().setThreadStatus(DEFAULT_THREAD_ID, 'finished');
    const finished = useCedarStore.getState().threadMap[DEFAULT_THREAD_ID];
    expect(finished.status).toBe('finished');
    // lastActiveAt preserved from the streaming transition, not re-stamped.
    expect(finished.lastActiveAt).toBe(stamp);
  });

  it('setThreadStatus is a no-op for an unknown thread (no throw)', () => {
    expect(() =>
      useCedarStore.getState().setThreadStatus('missing-thread', 'streaming'),
    ).not.toThrow();
  });

  it('pinThread / unpinThread toggle pinned', () => {
    expect(useCedarStore.getState().threadMap[DEFAULT_THREAD_ID].pinned).toBeUndefined();

    useCedarStore.getState().pinThread(DEFAULT_THREAD_ID);
    expect(useCedarStore.getState().threadMap[DEFAULT_THREAD_ID].pinned).toBe(true);

    useCedarStore.getState().unpinThread(DEFAULT_THREAD_ID);
    expect(useCedarStore.getState().threadMap[DEFAULT_THREAD_ID].pinned).toBe(false);
  });

  it('createThread stamps createdThisSession', () => {
    const id = useCedarStore.getState().createThread(undefined, 'Fresh');
    expect(useCedarStore.getState().threadMap[id].createdThisSession).toBe(true);
  });
});