startNewChat.test.ts2.4 KBView on GitHub
import { useCedarStore } from '@/modules/store';
import { startNewChat } from '@/modules/cedar-os/src/cedar-os-components/chatComponents/startNewChat';

/**
 * "New chat" is ONE action, reached from the rail's button, the composer's button and ⌘N.
 *
 * It used to be two: the rail started a context-free DRAFT chat, while ⌘N and the header button
 * ran `newChatWithSameContext`, which minted a thread and copied the current one's context into
 * it. A shortcut that produces a different chat than the button advertising it teaches the wrong
 * thing — and the minting half is what used to litter the rail with empty "New chat" rows.
 */
describe('startNewChat', () => {
  beforeEach(() => {
    useCedarStore.setState({
      threadMap: {},
      mainThreadId: '',
      activeThreadId: '',
      messages: [],
      showChat: false,
      composerFocusNonce: 0,
    });
  });

  it('drops to the draft chat instead of minting a thread', () => {
    const store = useCedarStore.getState();
    // Stand in a real chat with a transcript, the way you would when you reach for ⌘N.
    const existing = store.createThread(undefined, 'Acme renewal');
    store.switchThread(existing);
    expect(useCedarStore.getState().mainThreadId).toBe(existing);

    startNewChat();

    const after = useCedarStore.getState();
    expect(after.mainThreadId).toBe('');
    expect(after.activeThreadId).toBe('');
    expect(after.messages).toEqual([]);
    // The chat you left is still there — a new chat replaces what you are LOOKING at, not what
    // you have. And no new row was written for the one you have not typed into yet.
    expect(after.threadMap[existing]).toBeDefined();
    expect(Object.keys(after.threadMap)).toEqual([existing]);
  });

  it('starts context-free, so the fresh chat cannot inherit the open conversation', () => {
    useCedarStore.getState().setActiveConversationId('conv_1');

    startNewChat();

    expect(useCedarStore.getState().activeConversationId).toBeNull();
  });

  it('reveals the chat and asks for the caret, so ⌘N is followed by typing', () => {
    const before = useCedarStore.getState().composerFocusNonce;

    startNewChat();

    const after = useCedarStore.getState();
    expect(after.showChat).toBe(true);
    // A nonce, not a flag: two ⌘Ns in a row must both reach the composer.
    expect(after.composerFocusNonce).toBe(before + 1);
    startNewChat();
    expect(useCedarStore.getState().composerFocusNonce).toBe(before + 2);
  });
});