taskKanbanCardExecute.test.tsx2.7 KBView on GitHub
/**
 * `TaskKanbanCard`'s Execute handler takes the task instead of closing over it.
 *
 * Not a cosmetic signature: the card is `memo`'d, and a callback built inside the board's `map` is
 * a fresh prop identity on every render, so the memo never bailed out and every card on the board
 * re-rendered whenever any board state changed. Mid-drag that state changes every frame, which is
 * what put visible distance between the cursor and the card under it.
 *
 * Passing the task through is what lets one shared function serve every card — the per-card
 * decision left is only WHETHER to offer Execute, which is a boolean. Pinned here because the
 * whole point is that callers stop building a closure per card, and nothing else would notice.
 */
import { fireEvent, render, screen } from '@testing-library/react';

jest.mock('@dnd-kit/sortable', () => ({
  useSortable: () => ({
    attributes: {},
    listeners: {},
    setNodeRef: () => {},
    transform: null,
    transition: undefined,
    isDragging: false,
    isSorting: false,
  }),
}));

jest.mock('@/modules/userTasks/hooks/use-prefetch-task-threads', () => ({
  usePrefetchTaskThreads: () => () => {},
  usePreloadTaskThreads: () => {},
}));

jest.mock('@/modules/userTasks/hooks/use-task-is-executing', () => ({
  useTaskIsExecuting: () => false,
}));

jest.mock('@/modules/store', () => ({
  useIsTaskSelected: () => false,
}));

jest.mock('@/modules/conversationsPage/components/ConversationCompanyAvatar', () => ({
  ConversationCompanyAvatar: () => null,
}));

import { TaskKanbanCard, type TaskCardTask } from '@/modules/userTasks/components/TaskKanbanCard';

const TASK: TaskCardTask = {
  id: 'task-1',
  description: 'Send pricing recap',
  conversationId: 'conv-1',
  status: 'todo',
};

const OTHER: TaskCardTask = { ...TASK, id: 'task-2', description: 'Book the follow-up' };

describe('TaskKanbanCard — Execute', () => {
  it('hands the handler its own task, so two cards can share one function', () => {
    const onExecute = jest.fn();
    render(
      <>
        <TaskKanbanCard task={TASK} draggable={false} onExecute={onExecute} />
        <TaskKanbanCard task={OTHER} draggable={false} onExecute={onExecute} />
      </>,
    );

    const buttons = screen.getAllByRole('button', { name: /create draft/i });
    expect(buttons).toHaveLength(2);

    fireEvent.click(buttons[1]!);
    expect(onExecute).toHaveBeenCalledWith(OTHER);

    fireEvent.click(buttons[0]!);
    expect(onExecute).toHaveBeenLastCalledWith(TASK);
  });

  it('renders no Execute affordance when the surface does not offer one', () => {
    render(<TaskKanbanCard task={TASK} draggable={false} />);
    expect(screen.queryByRole('button', { name: /create draft/i })).toBeNull();
  });
});