Ctrl K
ComunidadGuías…

lib/command-palette/registry/providers/MinddyEntityActionsProvider.test.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import { describe, expect, it, vi } from "vitest";
import type { PaletteItem } from "../../types";
import { createMinddyEntityActionsProvider } from "./MinddyEntityActionsProvider";

const labels = {
  copied: "Copied",
  linkCopied: "Link copied",
  openInNewTab: "Open in a new tab",
  copyLink: "Copy link",
  newIssue: "New issue",
  newObjective: "New objective",
  copyProjectKey: "Copy project key",
  openObjectives: "Open objectives",
  openPages: "Open pages",
  openTriage: "Open triage",
  openFeedback: "Open feedback",
  openProjectSettings: "Open project settings",
  viewObjectiveIssues: "View objective issues",
  newIssueForObjective: "New issue for this objective",
  copyObjectiveName: "Copy objective name",
  openProjectBoard: "Open project board",
  newIssueInProject: "New issue in this project",
  copyPageTitle: "Copy page title",
  copyIssueTitle: "Copy issue title",
  openLinkedObjective: "Open linked objective",
};

function setup() {
  const navigate = vi.fn();
  const openInNewTab = vi.fn();
  const copyText = vi.fn(async () => undefined);
  const openCreateIssue = vi.fn();
  const openCreateObjective = vi.fn();
  const provider = createMinddyEntityActionsProvider({
    labels,
    navigate,
    openInNewTab,
    copyText,
    openCreateIssue,
    openCreateObjective,
  });
  return {
    provider,
    navigate,
    openInNewTab,
    copyText,
    openCreateIssue,
    openCreateObjective,
  };
}

function item(entityType: string, data?: unknown): PaletteItem {
  return {
    id: `${entityType}-1`,
    title: entityType,
    filterCategory: entityType,
    entityType,
    href: `/projects/project-1/${entityType}-1`,
    data,
  };
}

function actionIds(entity: PaletteItem): string[] {
  return setup().provider.getActions(entity, {} as never).map((action) => action.id);
}

describe("Minddy entity palette actions", () => {
  it("keeps generic navigation targets linkable", () => {
    expect(actionIds(item("navigation"))).toEqual([
      "entity.open-new-tab",
      "entity.copy-link",
    ]);
  });

  it("covers project creation and every primary project destination", () => {
    const ids = actionIds(
      item("project", { id: "project-1", key: "MIN", name: "Minddy" })
    );
    expect(ids).toEqual(
      expect.arrayContaining([
        "project.new-issue",
        "project.new-objective",
        "project.copy-key",
        "project.objectives",
        "project.pages",
        "project.triage",
        "project.feedback",
        "project.settings",
        "entity.open-new-tab",
        "entity.copy-link",
      ])
    );
    expect(ids).toHaveLength(10);
  });

  it("makes an objective's issues and linked issue creation first-class actions", async () => {
    const context = setup();
    const objective = item("objective", {
      id: "objective-1",
      project_id: "project-1",
      name: "Ship",
      status: "in_progress",
      color: null,
    });
    const actions = context.provider.getActions(objective, {} as never);
    expect(actions.map((action) => action.id)).toEqual(
      expect.arrayContaining([
        "objective.issues",
        "objective.new-issue",
        "objective.new-objective",
        "objective.copy-name",
        "objective.list",
        "objective.project",
        "entity.open-new-tab",
        "entity.copy-link",
      ])
    );
    await actions.find((action) => action.id === "objective.issues")?.execute?.(
      objective,
      {} as never
    );
    expect(context.navigate).toHaveBeenCalledWith(
      "/projects/project-1?objective=objective-1"
    );
    await actions.find((action) => action.id === "objective.new-issue")?.execute?.(
      objective,
      {} as never
    );
    expect(context.openCreateIssue).toHaveBeenCalledWith(
      "project-1",
      "objective-1"
    );
  });

  it("covers page context without exposing unsafe document mutations", () => {
    const ids = actionIds(
      item("page", {
        id: "page-1",
        project_id: "project-1",
        title: "Runbook",
        icon: null,
        updated_at: "2026-08-29T00:00:00Z",
      })
    );
    expect(ids).toEqual(
      expect.arrayContaining([
        "page.new-issue",
        "page.copy-title",
        "page.index",
        "page.project",
        "entity.open-new-tab",
        "entity.copy-link",
      ])
    );
    expect(ids.some((id) => id.includes("delete") || id.includes("trash"))).toBe(false);
  });

  it("adds objective navigation only to issues that are linked to one", () => {
    const baseIssue = {
      id: "issue-1",
      project_id: "project-1",
      number: 42,
      title: "Audit palette",
      status: "in_progress",
      priority: "high",
      effort: "m",
      assignee_id: null,
      updated_at: "2026-08-29T00:00:00Z",
    };
    const linked = actionIds(
      item("issue", { ...baseIssue, objective_id: "objective-1" })
    );
    const unlinked = actionIds(item("issue", { ...baseIssue, objective_id: null }));
    expect(linked).toEqual(
      expect.arrayContaining(["issue.open-objective", "issue.objective-issues"])
    );
    expect(unlinked).not.toContain("issue.open-objective");
    expect(unlinked).not.toContain("issue.objective-issues");
  });

  it("passes stable hrefs through the clipboard and new-tab actions", async () => {
    const context = setup();
    const navigation = item("navigation");
    const actions = context.provider.getActions(navigation, {} as never);
    await actions.find((action) => action.id === "entity.copy-link")?.execute?.(
      navigation,
      {} as never
    );
    await actions.find((action) => action.id === "entity.open-new-tab")?.execute?.(
      navigation,
      {} as never
    );
    expect(context.copyText).toHaveBeenCalledWith(
      navigation.href,
      labels.linkCopied,
      true
    );
    expect(context.openInNewTab).toHaveBeenCalledWith(navigation.href);
  });

  it("does not treat a slash-prefixed title as an application link", async () => {
    const context = setup();
    const page = {
      ...item("page", {
        id: "page-1",
        project_id: "project-1",
        title: "/Runbook",
        icon: null,
        updated_at: "2026-08-29T00:00:00Z",
      }),
      title: "/Runbook",
    };
    const actions = context.provider.getActions(page, {} as never);
    await actions.find((action) => action.id === "page.copy-title")?.execute?.(
      page,
      {} as never
    );
    expect(context.copyText).toHaveBeenCalledWith("/Runbook", labels.copied);
  });
});