summaryrefslogtreecommitdiffhomepage
path: root/src/features/workspaces/ui/WorkspaceCard.test.ts
blob: 385856d0dd34fac563881182bc5d6a3d3a55a390 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
import type { WorkspaceEntry } from "@dispatch/wire";
import { render, screen } from "@testing-library/svelte";
import userEvent from "@testing-library/user-event";
import { describe, expect, it, vi } from "vitest";
import type { WorkspaceResult } from "../adapter/http";
import type { WorkspaceStore } from "../store.svelte";
import WorkspaceCard from "./WorkspaceCard.svelte";

function fakeEntry(overrides: Partial<WorkspaceEntry> = {}): WorkspaceEntry {
	return {
		id: "my-ws",
		title: "My Workspace",
		defaultCwd: null,
		createdAt: 1,
		lastActivityAt: 2,
		conversationCount: 3,
		...overrides,
	};
}

/** A fake store that records calls + resolves ok. */
function fakeStore() {
	return {
		rename: vi.fn(
			async (id: string, _title: string): Promise<WorkspaceResult<WorkspaceEntry>> => ({
				ok: true,
				value: fakeEntry({ id, title: _title }),
			}),
		),
		setDefaultCwd: vi.fn(
			async (id: string, defaultCwd: string | null): Promise<WorkspaceResult<WorkspaceEntry>> => ({
				ok: true,
				value: fakeEntry({ id, defaultCwd }),
			}),
		),
		remove: vi.fn(
			async (): Promise<WorkspaceResult<{ closedCount: number }>> => ({
				ok: true,
				value: { closedCount: 0 },
			}),
		),
	};
}

describe("WorkspaceCard", () => {
	it("renders the title, slug, and an Open link", () => {
		const store = fakeStore() as unknown as WorkspaceStore;
		const onNavigate = vi.fn();
		render(WorkspaceCard, {
			props: { ws: fakeEntry(), store, onNavigate },
		});
		expect(screen.getByText("My Workspace")).toBeInTheDocument();
		expect(screen.getByText("/my-ws")).toBeInTheDocument();
		expect(screen.getByRole("link", { name: "Open" })).toHaveAttribute("href", "/my-ws");
	});

	it("double-clicking the title reveals an edit input", async () => {
		const user = userEvent.setup();
		const store = fakeStore() as unknown as WorkspaceStore;
		render(WorkspaceCard, { props: { ws: fakeEntry(), store, onNavigate: vi.fn() } });

		await user.dblClick(screen.getByText("My Workspace"));
		expect(screen.getByLabelText("Workspace title")).toHaveValue("My Workspace");
	});

	it("renames via the store on Enter", async () => {
		const user = userEvent.setup();
		const store = fakeStore() as unknown as WorkspaceStore;
		render(WorkspaceCard, { props: { ws: fakeEntry(), store, onNavigate: vi.fn() } });

		await user.dblClick(screen.getByText("My Workspace"));
		const input = screen.getByLabelText("Workspace title");
		await user.clear(input);
		await user.type(input, "Renamed{Enter}");

		expect(store.rename).toHaveBeenCalledWith("my-ws", "Renamed");
	});

	it("enables Set only when the cwd differs, then saves it", async () => {
		const user = userEvent.setup();
		const store = fakeStore() as unknown as WorkspaceStore;
		render(WorkspaceCard, {
			props: { ws: fakeEntry({ defaultCwd: "/old" }), store, onNavigate: vi.fn() },
		});

		const input = screen.getByLabelText("Default working directory");
		expect(input).toHaveValue("/old");
		expect(screen.getByRole("button", { name: "Set" })).toBeDisabled();

		await user.clear(input);
		await user.type(input, "/new/path");
		expect(screen.getByRole("button", { name: "Set" })).toBeEnabled();

		await user.click(screen.getByRole("button", { name: "Set" }));
		expect(store.setDefaultCwd).toHaveBeenCalledWith("my-ws", "/new/path");
	});

	it("clears the cwd to null when saved empty (inherits the server default)", async () => {
		const user = userEvent.setup();
		const store = fakeStore() as unknown as WorkspaceStore;
		render(WorkspaceCard, {
			props: { ws: fakeEntry({ defaultCwd: "/old" }), store, onNavigate: vi.fn() },
		});

		const input = screen.getByLabelText("Default working directory");
		await user.clear(input);
		await user.click(screen.getByRole("button", { name: "Set" }));

		expect(store.setDefaultCwd).toHaveBeenCalledWith("my-ws", null);
	});

	it("the Open link calls onNavigate with the workspace path (no full-card nav)", async () => {
		const user = userEvent.setup();
		const store = fakeStore() as unknown as WorkspaceStore;
		const onNavigate = vi.fn();
		render(WorkspaceCard, { props: { ws: fakeEntry(), store, onNavigate } });

		await user.click(screen.getByRole("link", { name: "Open" }));
		expect(onNavigate).toHaveBeenCalledWith("/my-ws");
	});
});