brainHero.test.tsx5.8 KBView on GitHub
/**
 * The /brain hero — the Brain's landing screen, built on the same machinery as the /agent home
 * hero (see tests/modules/home/agentHomeHero.test.tsx).
 *
 * Two things are pinned here:
 *
 *  1. THE TWO DESTINATIONS — Playbook and Knowledge base, each pointing at the route that
 *     actually serves it, with the AGENTS rendered in place rather than behind a third card. A card whose
 *     href drifts is a dead end that still looks like a working button; a card that only reveals
 *     a grid is a click charged for nothing.
 *  2. THE RAIL IS RECENTS — where /agent puts configurable widgets, /brain puts the files you
 *     last touched, and clicking one opens it in the Knowledge explorer rather than nowhere.
 */

import { fireEvent, render, screen } from '@testing-library/react';
import type { ReactNode } from 'react';

const mockNavigate = jest.fn();

jest.mock('motion/react', () => {
  const MotionDiv = ({ children, className }: { children?: ReactNode; className?: string }) => (
    <div className={className}>{children}</div>
  );
  return { motion: new Proxy({}, { get: () => MotionDiv }) };
});
jest.mock('react-router', () => ({ useNavigate: () => mockNavigate }));
jest.mock('@/modules/cedar-os/src/cedar-os-components/chatComponents/welcomeShortcuts', () => ({
  useAllActionsModal: () => [false, jest.fn()],
}));
// The agent-template modal is only mounted once its pill is clicked; the pill row here is about
// which pills exist, not about the catalogue behind one of them.
jest.mock('@/modules/cedar-os/src/cedar-os-components/chatComponents/AgentLibrary', () => ({
  AgentLibraryDialog: () => null,
}));
// The agent grid pulls the whole agent stack (avatars, the create mutation, tRPC). It has its
// own test; here it stands in as a sentinel, so the hero's ORDER can be asserted without it.
jest.mock('@/modules/agents/components/AgentsGrid', () => ({
  AgentsGrid: () => <div data-testid="agents-grid">AGENTS</div>,
}));

// Named `mock*` so jest's hoisting of the factory above the imports still resolves it.
const mockUseQuery = jest.fn();
jest.mock('@tanstack/react-query', () => ({ useQuery: () => mockUseQuery() }));
jest.mock('@/providers/query-provider', () => ({
  useTRPC: () => ({
    files: { searchForLink: { queryOptions: () => ({}) } },
    aop: { listAopsForUser: { queryOptions: () => ({}) } },
  }),
}));
// The administered-user picker and the Deals-default AOP fetch are a self-contained sentinel
// here (both have their own tests) — this suite is about the hero's own composition, not
// about who the picker lets you become or which conversation type it defaults to.
jest.mock('@/modules/administeredUser', () => ({
  AdministeredUserBar: () => null,
  useTargetUserId: () => undefined,
}));

import {
  BrainFilesRail,
  BrainHeroBelowChat,
  BrainHeroGreeting,
} from '@/modules/brain/components/BrainHero';

beforeEach(() => {
  mockNavigate.mockClear();
  mockUseQuery.mockReturnValue({ data: undefined, isLoading: false });
});

describe('the Brain hero', () => {
  it('greets you by the surface, not by the time of day', () => {
    render(<BrainHeroGreeting />);
    expect(screen.getByText('Welcome to your Brain')).toBeInTheDocument();
  });

  it.each([
    ['Playbook', '/brain/playbooks'],
    ['Knowledge base', '/brain/knowledge'],
  ])('the %s card goes to %s', (title, href) => {
    render(<BrainHeroBelowChat onPrompt={jest.fn()} />);

    fireEvent.click(screen.getByText(title).closest('button')!);
    expect(mockNavigate).toHaveBeenCalledWith(href);
  });

  it('offers exactly two destinations, and renders the agents themselves in place', () => {
    render(<BrainHeroBelowChat onPrompt={jest.fn()} />);

    expect(screen.getAllByTestId('brain-destination-card')).toHaveLength(2);
    // Agents are the body of the screen, not a third card pointing at one.
    expect(screen.getByTestId('agents-grid')).toBeInTheDocument();
    expect(screen.queryByText('Agents')).not.toBeInTheDocument();
  });

  it('puts the agents LAST — the one block with no ceiling on its height', () => {
    const { container } = render(<BrainHeroBelowChat onPrompt={jest.fn()} />);

    // The agents section now also carries the administered-user bar and the AOP filter
    // ABOVE the grid, so the direct child is that section (which contains the grid), not
    // the grid itself.
    const stack = Array.from(container.firstElementChild?.children ?? []);
    const agentsSection = screen.getByTestId('agents-section');
    expect(stack.at(-1)).toBe(agentsSection);
    expect(agentsSection).toContainElement(screen.getByTestId('agents-grid'));
  });

  it('loads a pill prompt into the composer instead of sending it', () => {
    const onPrompt = jest.fn();
    render(<BrainHeroBelowChat onPrompt={onPrompt} />);

    fireEvent.click(screen.getByText('Improve playbook'));
    expect(onPrompt).toHaveBeenCalledWith(expect.stringContaining('playbook'));
  });
});

describe('the Brain rail', () => {
  it('lists recent files and opens one in the Knowledge explorer', () => {
    mockUseQuery.mockReturnValue({
      data: [
        {
          id: 'doc_1',
          label: 'ICP notes',
          path: 'user/brain/icp',
          documentType: 'custom',
          lastOpened: '2026-08-30T10:00:00.000Z',
          updatedAt: '2026-08-29T10:00:00.000Z',
        },
      ],
      isLoading: false,
    });

    render(<BrainFilesRail />);
    fireEvent.click(screen.getByText('ICP notes'));

    expect(mockNavigate).toHaveBeenCalledWith('/brain/knowledge?documentId=doc_1');
  });

  it('says so when there is nothing recent, rather than showing an empty box', () => {
    mockUseQuery.mockReturnValue({ data: [], isLoading: false });

    render(<BrainFilesRail />);
    expect(screen.getByText(/Nothing opened yet/)).toBeInTheDocument();
    expect(screen.queryAllByTestId('brain-recent-file')).toHaveLength(0);
  });
});