cflx 0.6.11

Conflux – a spec-driven parallel coding orchestrator that runs AI agents on git worktrees
/**
 * @vitest-environment jsdom
 */

import React, { act } from 'react';
import { createRoot, Root } from 'react-dom/client';
import { afterEach, beforeAll, describe, expect, it, vi } from 'vitest';
import { ProjectCard } from './ProjectCard';
import { RemoteProject } from '../api/types';

const project: RemoteProject = {
  id: 'project-1',
  name: 'repo@main',
  repo: 'repo',
  branch: 'main',
  status: 'idle',
  is_busy: false,
  error: null,
  sync_state: 'up_to_date',
  ahead_count: 0,
  behind_count: 0,
  sync_required: false,
  local_sha: null,
  remote_sha: null,
  last_remote_check_at: null,
  remote_check_error: null,
  changes: [],
};

let container: HTMLDivElement | null = null;
let root: Root | null = null;

beforeAll(() => {
  globalThis.IS_REACT_ACT_ENVIRONMENT = true;
});

function renderCard(isSelected: boolean, onSelect = vi.fn()) {
  container = document.createElement('div');
  document.body.appendChild(container);
  root = createRoot(container);

  act(() => {
    root!.render(
      <ProjectCard
        project={project}
        isSelected={isSelected}
        onSelect={onSelect}
        onGitSync={vi.fn()}
        onDelete={vi.fn()}
        isLoading={false}
        syncAvailable
        activeCommands={[]}
      />,
    );
  });

  const card = container.querySelector('[role="button"]');
  if (!(card instanceof HTMLDivElement)) {
    throw new Error('Project card button was not rendered');
  }

  return { card, onSelect };
}

afterEach(() => {
  if (root) {
    act(() => {
      root!.unmount();
    });
  }

  if (container) {
    container.remove();
  }

  root = null;
  container = null;
});

describe('ProjectCard', () => {
  it('selects an unselected project on click', () => {
    const onSelect = vi.fn();
    const { card } = renderCard(false, onSelect);

    act(() => {
      card.click();
    });

    expect(onSelect).toHaveBeenCalledWith('project-1');
  });

  it('clears selection when the selected project is clicked again', () => {
    const onSelect = vi.fn();
    const { card } = renderCard(true, onSelect);

    act(() => {
      card.click();
    });

    expect(onSelect).toHaveBeenCalledWith(null);
  });

  it('toggles selection with Enter and Space keyboard activation', () => {
    const onSelect = vi.fn();
    const { card } = renderCard(true, onSelect);

    act(() => {
      card.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
      card.dispatchEvent(new KeyboardEvent('keydown', { key: ' ', bubbles: true }));
    });

    expect(onSelect).toHaveBeenNthCalledWith(1, null);
    expect(onSelect).toHaveBeenNthCalledWith(2, null);
  });
});