moadim 3.2.6

Loop engine for AI agents — routines over REST, MCP, and a built-in web UI
import { fireEvent, render, screen, within } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import type { RoutineResponse } from "../../api/hooks";
import { RoutineFilesystemTree } from "./RoutineFilesystemTree";

function routine(id: string, title: string, overrides: Partial<RoutineResponse> = {}): RoutineResponse {
  return {
    id,
    title,
    agent: "hermes",
    model: null,
    schedule: "0 * * * *",
    prompt: "",
    repositories: [],
    machines: ["m1"],
    enabled: true,
    source: "",
    created_at: 0,
    updated_at: 0,
    last_manual_trigger_at: null,
    last_scheduled_trigger_at: null,
    snoozed_until: null,
    skip_runs: null,
    power_saving: false,
    ttl_secs: null,
    tags: [],
    agent_registered: true,
    agent_command_available: true,
    agent_setup_available: true,
    is_running: false,
    file_path: "",
    folder: null,
    slug: id,
    rel_path: id,
    schedule_description: "hourly",
    goal: null,
    flag_count: 0,
    env_keys: [],
    ...overrides,
  };
}

const callbacks = () => ({
  onEdit: vi.fn(),
  onClone: vi.fn(),
  onDelete: vi.fn(),
  onMove: vi.fn(),
  onToggle: vi.fn(),
  onTrigger: vi.fn(),
  onLogs: vi.fn(),
  onHistory: vi.fn(),
  onFlags: vi.fn(),
  onClearFilters: vi.fn(),
});

describe("RoutineFilesystemTree", () => {
  it("renders routines as nested filesystem folders using folder and slug metadata", () => {
    const routines = [
      routine("root", "Root sync", { slug: "root-sync", rel_path: "root-sync" }),
      routine("db", "DB backup", { folder: "maintenance/db", slug: "backup", rel_path: "maintenance/db/backup" }),
      routine("idx", "Reindex", { folder: "maintenance/search", slug: "reindex", rel_path: "maintenance/search/reindex" }),
    ];

    render(<RoutineFilesystemTree routines={routines} loading={false} filterActive={false} now={new Date(2026, 0, 1)} {...callbacks()} />);

    expect(screen.getByRole("tree", { name: "Routine filesystem" })).toBeInTheDocument();
    expect(screen.getByRole("treeitem", { name: /maintenance folder/i })).toBeInTheDocument();
    expect(screen.getByRole("treeitem", { name: /db folder/i })).toBeInTheDocument();
    expect(screen.getByRole("treeitem", { name: /search folder/i })).toBeInTheDocument();
    expect(screen.getByRole("treeitem", { name: /root-sync routine/i })).toBeInTheDocument();
    expect(screen.getByRole("treeitem", { name: /maintenance\/db\/backup routine/i })).toBeInTheDocument();
    expect(screen.getByRole("treeitem", { name: /maintenance\/search\/reindex routine/i })).toBeInTheDocument();
  });

  it("collapses folders while keeping sibling routines visible", () => {
    const routines = [
      routine("root", "Root sync", { slug: "root-sync", rel_path: "root-sync" }),
      routine("db", "DB backup", { folder: "maintenance/db", slug: "backup", rel_path: "maintenance/db/backup" }),
    ];

    render(<RoutineFilesystemTree routines={routines} loading={false} filterActive={false} now={new Date(2026, 0, 1)} {...callbacks()} />);

    fireEvent.click(screen.getByRole("button", { name: /collapse maintenance folder/i }));

    expect(screen.getByRole("treeitem", { name: /root-sync routine/i })).toBeInTheDocument();
    expect(screen.queryByRole("treeitem", { name: /maintenance\/db\/backup routine/i })).not.toBeInTheDocument();
  });

  it("surfaces the same row actions from each routine node", () => {
    const onMove = vi.fn();
    const onTrigger = vi.fn();
    const onLogs = vi.fn();
    const routineNode = routine("db", "DB backup", { folder: "maintenance/db", slug: "backup", rel_path: "maintenance/db/backup" });

    render(
      <RoutineFilesystemTree
        routines={[routineNode]}
        loading={false}
        filterActive={false}
        now={new Date(2026, 0, 1)}
        {...callbacks()}
        onMove={onMove}
        onTrigger={onTrigger}
        onLogs={onLogs}
      />,
    );

    const node = screen.getByRole("treeitem", { name: /maintenance\/db\/backup routine/i });
    fireEvent.click(within(node).getByRole("button", { name: "Run now" }));
    fireEvent.click(within(node).getByRole("button", { name: "Logs" }));
    fireEvent.click(within(node).getByRole("button", { name: "Move folder" }));

    expect(onTrigger).toHaveBeenCalledWith("db");
    expect(onLogs).toHaveBeenCalledWith("db");
    expect(onMove).toHaveBeenCalledWith("db");
  });
});