fix(frontend): remove stale spawn modal references from shell and sidebar (#224)
This commit is contained in:
parent
f7df36bb8b
commit
680a85fceb
|
|
@ -1,6 +1,5 @@
|
|||
import { SidebarProvider } from "@/components/ui/sidebar";
|
||||
import { render, screen, waitFor } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { Sidebar } from "./Sidebar";
|
||||
import type { WorkspaceSummary } from "../types/workspace";
|
||||
|
|
@ -25,24 +24,20 @@ const workspace: WorkspaceSummary = {
|
|||
sessions: [],
|
||||
};
|
||||
|
||||
function renderSidebar(onRemoveProject = vi.fn().mockResolvedValue(undefined)) {
|
||||
function renderSidebar() {
|
||||
render(
|
||||
<SidebarProvider>
|
||||
<Sidebar
|
||||
daemonStatus={{ state: "running" }}
|
||||
onCreateProject={vi.fn()}
|
||||
onRemoveProject={onRemoveProject}
|
||||
workspaces={[workspace]}
|
||||
/>
|
||||
</SidebarProvider>,
|
||||
);
|
||||
return onRemoveProject;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
navigateMock.mockReset();
|
||||
vi.spyOn(window, "confirm").mockReturnValue(true);
|
||||
vi.spyOn(window, "alert").mockImplementation(() => undefined);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
|
@ -50,30 +45,6 @@ afterEach(() => {
|
|||
});
|
||||
|
||||
describe("Sidebar", () => {
|
||||
it("confirms project removal before calling the remove handler", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onRemoveProject = renderSidebar();
|
||||
|
||||
await user.click(screen.getByLabelText("Project actions for Project One"));
|
||||
await user.click(await screen.findByRole("menuitem", { name: "Remove project" }));
|
||||
|
||||
expect(window.confirm).toHaveBeenCalledWith(
|
||||
"Remove project Project One? This stops its live sessions and removes it from the sidebar, but keeps the repository folder and stored history on disk.",
|
||||
);
|
||||
await waitFor(() => expect(onRemoveProject).toHaveBeenCalledTimes(1));
|
||||
});
|
||||
|
||||
it("does not remove the project when confirmation is cancelled", async () => {
|
||||
vi.mocked(window.confirm).mockReturnValue(false);
|
||||
const user = userEvent.setup();
|
||||
const onRemoveProject = renderSidebar();
|
||||
|
||||
await user.click(screen.getByLabelText("Project actions for Project One"));
|
||||
await user.click(await screen.findByRole("menuitem", { name: "Remove project" }));
|
||||
|
||||
expect(onRemoveProject).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("hides the worker count in every state that reveals project actions", () => {
|
||||
renderSidebar();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +1,5 @@
|
|||
import { useNavigate, useParams, useRouterState } from "@tanstack/react-router";
|
||||
import {
|
||||
ChevronRight,
|
||||
GitPullRequest,
|
||||
Moon,
|
||||
MoreHorizontal,
|
||||
Plus,
|
||||
Search,
|
||||
Settings,
|
||||
Sun,
|
||||
Trash2,
|
||||
Waypoints,
|
||||
} from "lucide-react";
|
||||
import { ChevronRight, GitPullRequest, Moon, Plus, Search, Settings, Sun, Waypoints } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import {
|
||||
attentionZone,
|
||||
|
|
@ -377,25 +366,6 @@ function ProjectItem({
|
|||
}
|
||||
};
|
||||
|
||||
const removeProject = async () => {
|
||||
setRemoveError(null);
|
||||
const confirmed = window.confirm(
|
||||
`Remove project ${workspace.name}? This stops its live sessions and removes it from the sidebar, but keeps the repository folder and stored history on disk.`,
|
||||
);
|
||||
if (!confirmed) return;
|
||||
|
||||
setIsRemoving(true);
|
||||
try {
|
||||
await onRemoveProject();
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : "Could not remove project";
|
||||
setRemoveError(message);
|
||||
window.alert(message);
|
||||
} finally {
|
||||
setIsRemoving(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<SidebarMenuItem className="mb-px group-data-[collapsible=icon]:mb-0">
|
||||
{/* project-sidebar__proj-row */}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { createFileRoute, Outlet, useNavigate, useParams } from "@tanstack/react-router";
|
||||
import { createFileRoute, Outlet, useNavigate } from "@tanstack/react-router";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { type CSSProperties, useCallback, useEffect } from "react";
|
||||
import { ShellTopbar } from "../components/ShellTopbar";
|
||||
import { Sidebar } from "../components/Sidebar";
|
||||
import { SidebarProvider } from "../components/ui/sidebar";
|
||||
|
|
@ -34,7 +34,6 @@ function errorMessage(error: unknown) {
|
|||
// instead of Zustand. The daemon-status effect runs here exactly once.
|
||||
function ShellLayout() {
|
||||
const navigate = useNavigate();
|
||||
const params = useParams({ strict: false }) as { projectId?: string };
|
||||
const queryClient = useQueryClient();
|
||||
const workspaceQuery = useWorkspaceQuery();
|
||||
const workspaces = workspaceQuery.data ?? [];
|
||||
|
|
@ -67,20 +66,6 @@ function ShellLayout() {
|
|||
[navigate, updateWorkspaces],
|
||||
);
|
||||
|
||||
const removeProject = useCallback(
|
||||
async (projectId: string) => {
|
||||
const { error } = await apiClient.DELETE("/api/v1/projects/{id}", { params: { path: { id: projectId } } });
|
||||
if (error) throw new Error(apiErrorMessage(error));
|
||||
|
||||
updateWorkspaces((current) => current.filter((item) => item.id !== projectId));
|
||||
await queryClient.invalidateQueries({ queryKey: workspaceQueryKey });
|
||||
if (params.projectId === projectId) {
|
||||
void navigate({ to: "/" });
|
||||
}
|
||||
},
|
||||
[navigate, params.projectId, queryClient, updateWorkspaces],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
document.documentElement.dataset.theme = theme;
|
||||
document.documentElement.style.colorScheme = theme;
|
||||
|
|
@ -113,7 +98,7 @@ function ShellLayout() {
|
|||
}, [navigate, workspaces]);
|
||||
|
||||
return (
|
||||
<ShellProvider value={{ daemonStatus, openSpawn, createProject, createTask }}>
|
||||
<ShellProvider value={{ daemonStatus, createProject }}>
|
||||
{/* The topbar spans the full window width above the sidebar row (the
|
||||
macOS traffic lights + TitlebarNav cluster sit in its left inset),
|
||||
and the sidebar hangs below it — so the sidebar border stops at the
|
||||
|
|
@ -130,7 +115,7 @@ function ShellLayout() {
|
|||
onOpenChange={(open) => open !== isSidebarOpen && toggleSidebar()}
|
||||
open={isSidebarOpen}
|
||||
style={
|
||||
{ "--sidebar-width": "var(--ao-sidebar-w, 240px)", "--sidebar-width-icon": "48px" } as React.CSSProperties
|
||||
{ "--sidebar-width": "var(--ao-sidebar-w, 240px)", "--sidebar-width-icon": "48px" } as CSSProperties
|
||||
}
|
||||
>
|
||||
<Sidebar
|
||||
|
|
@ -155,13 +140,6 @@ function ShellLayout() {
|
|||
<TitlebarNav />
|
||||
</SidebarProvider>
|
||||
</div>
|
||||
<SpawnWorkerModal
|
||||
defaultProjectId={spawnProjectId}
|
||||
onCreateTask={createTask}
|
||||
onOpenChange={setSpawnOpen}
|
||||
open={spawnOpen}
|
||||
workspaces={workspaces}
|
||||
/>
|
||||
</ShellProvider>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue