agenticTable.test.ts5.0 KBView on GitHub
/**
 * "Agentic Table" — the /agent home pill that creates something.
 *
 * The thing under test is that NOTHING the user sees waits on the network. The click used to
 * block on two sequential writes (~3.5s measured) before a pixel moved; now the id is minted
 * client-side and the whole visible result lands synchronously while one write goes out behind
 * it. So these assertions run BEFORE the create promise is allowed to settle — that is the
 * point, not an accident of the harness.
 */

import { useCedarStore } from '@/modules/store';
import {
  NEW_CHAT_ARTIFACT_INTROS,
  type CreatedChatNode,
} from '@/modules/files/chat/newChatArtifact';
import {
  AGENTIC_TABLE_NAME,
  startAgenticTable,
} from '@/modules/home/components/agentic-table';

/** The server's row, as the create route returns it. */
function serverNode(id: string, over: Partial<CreatedChatNode> = {}): CreatedChatNode {
  return {
    id,
    orgId: 'org_1',
    userId: 'user_1',
    parentId: 'folder_1',
    documentType: 'table',
    path: 'user/chats/t/untitled-table',
    title: AGENTIC_TABLE_NAME,
    description: null,
    content: '',
    lastEditedBy: 'human',
    version: 1,
    wordCount: null,
    lastOpened: null,
    createdAt: new Date(),
    updatedAt: new Date(),
    ...over,
  };
}

/** A create that never settles, so every assertion below is about the synchronous tick. */
function pendingCreate() {
  const calls: Array<Record<string, unknown>> = [];
  const createTable = jest.fn(async (input: Record<string, unknown>) => {
    calls.push(input);
    return new Promise<CreatedChatNode>(() => {});
  });
  return { createTable, calls };
}

beforeEach(() => {
  const store = useCedarStore.getState();
  store.createThread('thread-before', 'Before');
  store.switchThread('thread-before');
});

describe('startAgenticTable', () => {
  it('opens the chat, the table and the intro without awaiting anything', () => {
    const { createTable } = pendingCreate();
    const before = useCedarStore.getState().mainThreadId;

    const { documentId, threadId } = startAgenticTable({ createTable, onFailed: jest.fn() });

    const state = useCedarStore.getState();
    // A NEW thread, looking at the new table — and the chat the user came from untouched.
    expect(state.mainThreadId).toBe(threadId);
    expect(state.mainThreadId).not.toBe(before);
    expect(state.threadMap[threadId]?.selectedArtifact).toEqual({ kind: 'file', id: documentId });
    expect(state.threadMap[before]?.selectedArtifact).toBeFalsy();
    // The intro is the thread's first message.
    const messages = state.threadMap[threadId]?.messages ?? [];
    expect(messages).toHaveLength(1);
    expect(messages[0]).toMatchObject({ role: 'assistant', type: 'text' });
    expect(messages[0]!.content).toBe(NEW_CHAT_ARTIFACT_INTROS.table);
    // Still in flight — nothing above waited for it.
    expect(createTable).toHaveBeenCalledTimes(1);
  });

  it('registers the document as a TABLE up front, so the panel never mounts the prose editor', () => {
    const { createTable } = pendingCreate();

    const { documentId, threadId } = startAgenticTable({ createTable, onFailed: jest.fn() });

    const meta = useCedarStore.getState().getDocumentMeta(documentId);
    expect(meta?.documentType).toBe('table');
    // `isOptimistic` is what holds the grid back until the row is real — without it the grid's
    // Y provider seeds from a 404 and leaves a blank, unsavable table.
    expect(meta?.isOptimistic).toBe(true);
    expect(meta?.path).toBe(`user/chats/${threadId}/${AGENTIC_TABLE_NAME}`);
  });

  it('sends the client-minted id and the thread it belongs to, in one call', () => {
    const { createTable, calls } = pendingCreate();

    const { documentId, threadId } = startAgenticTable({ createTable, onFailed: jest.fn() });

    expect(calls).toHaveLength(1);
    expect(calls[0]).toMatchObject({ id: documentId, threadId, name: AGENTIC_TABLE_NAME });
  });

  it('drops the optimistic document and reports when the write fails', async () => {
    const onFailed = jest.fn();
    const createTable = jest.fn(async () => {
      throw new Error('nope');
    });

    const { documentId, created } = startAgenticTable({ createTable, onFailed });
    // Present the moment the click happens...
    expect(useCedarStore.getState().getDocumentMeta(documentId)).toBeDefined();

    await created;

    // ...and gone once the write is known to have failed, rather than left as a grid whose
    // every edit would retry forever against a row that does not exist.
    expect(useCedarStore.getState().getDocumentMeta(documentId)).toBeUndefined();
    expect(onFailed).toHaveBeenCalledWith(expect.any(Error));
  });

  it('replaces the placeholder with the server row on success', async () => {
    const createTable = jest.fn(async (input: { id: string }) => serverNode(input.id));

    const { documentId, created } = startAgenticTable({ createTable, onFailed: jest.fn() });
    await created;

    expect(useCedarStore.getState().getDocumentMeta(documentId)?.isOptimistic).toBe(false);
  });
});