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
|
import { render, screen } from "@testing-library/svelte";
import userEvent from "@testing-library/user-event";
import { describe, expect, it, vi } from "vitest";
import HomeHeader from "./HomeHeader.svelte";
function props() {
return {
onCreate: vi.fn(async (_slug: string): Promise<{ ok: true }> => ({ ok: true })),
onOpenPlugins: vi.fn(),
onSort: vi.fn(),
onOpenArchived: vi.fn(),
};
}
describe("HomeHeader", () => {
it("renders the display title and the primary New workspace button", () => {
const p = props();
render(HomeHeader, { props: p });
expect(screen.getByRole("heading", { name: "Dispatch" })).toBeInTheDocument();
expect(screen.getByRole("button", { name: "New workspace" })).toBeInTheDocument();
});
it("the Plugins button renders only with the plugins port", () => {
const p = props();
const without = render(HomeHeader, { props: p });
expect(without.queryByRole("button", { name: "Plugins" })).toBeNull();
without.unmount();
render(HomeHeader, { props: { ...p, withPlugins: true } });
screen.getByRole("button", { name: "Plugins" }).click();
expect(p.onOpenPlugins).toHaveBeenCalledTimes(1);
});
it("the Sort overflow renders only when asked, and dispatches", async () => {
const user = userEvent.setup();
const p = props();
render(HomeHeader, { props: { ...p, withSort: true } });
await user.click(screen.getByRole("button", { name: "More actions" }));
await user.click(screen.getByRole("button", { name: "Reset arrangement by recent activity" }));
expect(p.onSort).toHaveBeenCalledTimes(1);
});
it("the Create button disables for an invalid slug (validation before submit)", async () => {
const user = userEvent.setup();
const p = props();
render(HomeHeader, { props: p });
await user.click(screen.getByRole("button", { name: "New workspace" }));
const input = screen.getByRole("textbox", { name: "New workspace" });
await user.type(input, "Invalid Slug!");
expect(screen.getByRole("button", { name: "Create" })).toBeDisabled();
expect(p.onCreate).not.toHaveBeenCalled();
// A valid slug enables it.
await user.clear(input);
await user.type(input, "my-workspace");
expect(screen.getByRole("button", { name: "Create" })).toBeEnabled();
});
it("submitting the form with Enter creates via the callback and closes the popover", async () => {
const user = userEvent.setup();
const p = props();
render(HomeHeader, { props: p });
await user.click(screen.getByRole("button", { name: "New workspace" }));
await user.type(screen.getByRole("textbox", { name: "New workspace" }), "my-workspace{Enter}");
expect(p.onCreate).toHaveBeenCalledWith("my-workspace");
});
it("a backend error keeps the form open with the message", async () => {
const user = userEvent.setup();
const onCreate = vi.fn(
async (): Promise<{ ok: false; error: string }> => ({ ok: false, error: "taken" }),
);
render(HomeHeader, { props: { ...props(), onCreate } });
await user.click(screen.getByRole("button", { name: "New workspace" }));
await user.type(screen.getByRole("textbox", { name: "New workspace" }), "my-workspace");
await user.click(screen.getByRole("button", { name: "Create" }));
expect(await screen.findByText("taken")).toBeInTheDocument();
});
it("compact mode collapses the primary button to an icon", () => {
const p = props();
render(HomeHeader, { props: { ...p, compact: true } });
const button = screen.getByRole("button", { name: "New workspace" });
expect(button).toHaveClass("navbar-btn-icon");
expect(button).toHaveTextContent(""); // icon only — the label collapses
expect(button.querySelector("svg")).not.toBeNull();
});
});
|