import { describe, expect, it, vi } from "vitest"; import { createAppStore } from "@/lib/state/store"; import { buildHostApi } from "./host-api"; const WORKSPACE_ID = "workspace-1"; const OTHER_WORKSPACE_ID = "workspace-2"; const WORKFLOW_ID = "workflow-1"; const PROVIDER_ID = "bitbucket"; const PROVIDER_SCOPE = "https://bitbucket.org"; function createContextStore() { const store = createAppStore(); store.setState((state) => ({ ...state, workspaces: { ...state.workspaces, activeId: WORKSPACE_ID }, workflows: { activeId: WORKFLOW_ID, items: [ { id: WORKFLOW_ID, workspaceId: WORKSPACE_ID, name: "Default" }, { id: "workflow-2", workspaceId: OTHER_WORKSPACE_ID, name: "Other" }, ], }, kanban: { ...state.kanban, workflowId: WORKFLOW_ID, steps: [ { id: "step-2", title: "Doing", color: "blue", position: 2 }, { id: "step-1", title: "Todo", color: "gray", position: 1 }, ], }, repositories: { ...state.repositories, itemsByWorkspaceId: { [WORKSPACE_ID]: [ { id: "repository-1", workspace_id: WORKSPACE_ID, name: "app", source_type: "provider", local_path: "", provider: PROVIDER_ID, provider_repo_id: "repo-uuid", provider_scope: PROVIDER_SCOPE, provider_owner: "team", provider_name: "app", remote_url: "https://bitbucket.org/team/app.git", default_branch: "main", worktree_branch_prefix: "kandev/", pull_before_worktree: true, setup_script: "", cleanup_script: "", dev_script: "", copy_files: "", created_at: "2026-08-12T00:00:00Z", updated_at: "2026-08-12T00:00:00Z", }, ], }, }, })); return store; } describe("plugin host context", () => { it("exposes provider-neutral workspace and task-creation context", () => { const host = buildHostApi(PROVIDER_ID, createContextStore()); expect(host.context.getActiveWorkspaceId()).toBe(WORKSPACE_ID); expect(host.context.getTaskCreationContext(WORKSPACE_ID)).toEqual({ workspaceId: WORKSPACE_ID, workflowId: WORKFLOW_ID, defaultStepId: "step-1", steps: [ { id: "step-1", title: "Todo" }, { id: "step-2", title: "Doing" }, ], repositories: [ expect.objectContaining({ id: "repository-1", provider: PROVIDER_ID, provider_repo_id: "repo-uuid", provider_scope: PROVIDER_SCOPE, }), ], }); expect( host.context.resolveRepositoryId({ workspaceId: WORKSPACE_ID, providerId: PROVIDER_ID, providerScope: PROVIDER_SCOPE, providerRepositoryId: "repo-uuid", }), ).toBe("repository-1"); }); it("subscribes to derived context changes without exposing the app store", () => { const store = createAppStore(); const host = buildHostApi(PROVIDER_ID, store); const listener = vi.fn(); const unsubscribe = host.context.subscribeActiveWorkspace(listener); store.setState((state) => ({ ...state, workspaces: { ...state.workspaces, activeId: WORKSPACE_ID }, })); store.setState((state) => ({ ...state, tasks: { ...state.tasks } })); unsubscribe(); store.setState((state) => ({ ...state, workspaces: { ...state.workspaces, activeId: OTHER_WORKSPACE_ID }, })); expect(listener).toHaveBeenCalledTimes(1); expect(listener).toHaveBeenCalledWith(WORKSPACE_ID); }); }); describe("plugin host context edge cases", () => { it("reads task-creation steps from a non-active workflow snapshot", () => { const store = createContextStore(); store.setState((state) => ({ ...state, kanbanMulti: { ...state.kanbanMulti, snapshots: { ...state.kanbanMulti.snapshots, "workflow-2": { workflowId: "workflow-2", workflowName: "Other", steps: [{ id: "other-step", title: "Backlog", color: "gray", position: 0 }], tasks: [], }, }, }, })); expect( buildHostApi(PROVIDER_ID, store).context.getTaskCreationContext(OTHER_WORKSPACE_ID), ).toEqual({ workspaceId: OTHER_WORKSPACE_ID, workflowId: "workflow-2", defaultStepId: "other-step", steps: [{ id: "other-step", title: "Backlog" }], repositories: [], }); }); it("fails closed when persisted provider identity is ambiguous", () => { const store = createContextStore(); store.setState((state) => { const [repository] = state.repositories.itemsByWorkspaceId[WORKSPACE_ID] ?? []; if (!repository) return state; return { ...state, repositories: { ...state.repositories, itemsByWorkspaceId: { ...state.repositories.itemsByWorkspaceId, [WORKSPACE_ID]: [repository, { ...repository, id: "repository-duplicate" }], }, }, }; }); expect( buildHostApi(PROVIDER_ID, store).context.resolveRepositoryId({ workspaceId: WORKSPACE_ID, providerId: PROVIDER_ID, providerScope: PROVIDER_SCOPE, providerRepositoryId: "repo-uuid", }), ).toBeUndefined(); }); });