mentionToItem.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';
import mentionSuggestion from '@/modules/cedar-os/src/components/chatInput/mentionSuggestion';
import type { MentionProvider } from '@/modules/cedar-os/src/store/agentContext/AgentContextTypes';

/**
 * Phase 8 (design: chat-thread-store) — selecting an @-mention is an explicit attach:
 * mentionSuggestion.command commits a ContextItem (addContextItem) to the active thread's
 * context set, instead of writing to the legacy additionalContext channel.
 */

// Minimal TipTap editor chain stub — records the inserted mention node's attrs.
function makeEditorStub() {
  const inserted: unknown[] = [];
  const chain = {
    focus: () => chain,
    insertContentAt: (_range: unknown, content: unknown) => {
      inserted.push(content);
      return chain;
    },
    run: () => true,
  };
  return { editor: { chain: () => chain }, inserted };
}

const conversationProvider: MentionProvider = {
  id: 'conversations',
  trigger: '@',
  contextKind: 'conversation',
  getItems: () => [],
  toContextEntry: (item) => ({ id: item.id ?? 'x', source: 'mention', data: {} }),
};

describe('mentionSuggestion.command — mention → committed ContextItem', () => {
  beforeEach(() => {
    useCedarStore.setState((state) => ({
      ...state,
      threadMap: {
        [DEFAULT_THREAD_ID]: { id: DEFAULT_THREAD_ID, messages: [], chatContext: { items: [] } },
      },
      mainThreadId: DEFAULT_THREAD_ID,
      activeThreadId: DEFAULT_THREAD_ID,
      messages: [],
    }));
    useCedarStore.getState().registerMentionProvider(conversationProvider);
  });

  it('adds a conversation ContextItem to the active thread and stamps { kind, id } on the node', () => {
    const { editor, inserted } = makeEditorStub();

    mentionSuggestion.command({
      editor: editor as never,
      range: { from: 0, to: 0 } as never,
      props: { id: 'conv_1', label: 'Numeral', providerId: 'conversations' } as never,
    });

    // Committed to chatContext.items as an explicit user attach (optimistic upsert).
    const items = useCedarStore.getState().threadMap[DEFAULT_THREAD_ID].chatContext?.items ?? [];
    const attached = items.find((i) => i.kind === 'conversation' && i.id === 'conv_1');
    expect(attached).toBeTruthy();
    expect(attached?.label).toBe('Numeral');
    expect(attached?.addedBy).toBe('user');

    // The mention node carries the structured { kind, id } for the transcript chip (Phase 9).
    const node = (inserted[0] as Array<{ type: string; attrs?: Record<string, unknown> }>)[0];
    expect(node.type).toBe('mention');
    expect(node.attrs?.kind).toBe('conversation');
    expect(node.attrs?.id).toBe('conv_1');
  });
});