summaryrefslogtreecommitdiffhomepage
path: root/src/features/tabs/ui.test.ts
blob: 1ae18c83f38fbf511c1fda27c01ff0f621b479d8 (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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
import { render, screen } from "@testing-library/svelte";
import userEvent from "@testing-library/user-event";
import { describe, expect, it, vi } from "vitest";
import type { Tab } from "./tabs";
import TabBar from "./ui/TabBar.svelte";

const sampleTabs: readonly Tab[] = [
	{ conversationId: "c1", model: "openai/gpt-4", title: "First" },
	{ conversationId: "c2", model: "anthropic/claude-3", title: "Second" },
	{ conversationId: "c3", model: "google/gemini", title: "Third" },
];

describe("TabBar", () => {
	it("renders one role=tab element per tab showing each title", () => {
		render(TabBar, {
			props: {
				tabs: sampleTabs,
				activeConversationId: "c1",
				onSelect: vi.fn(),
				onClose: vi.fn(),
				onNewDraft: vi.fn(),
			},
		});

		const tabs = screen.getAllByRole("tab");
		expect(tabs).toHaveLength(sampleTabs.length);
		expect(tabs[0]).toHaveTextContent("First");
		expect(tabs[1]).toHaveTextContent("Second");
		expect(tabs[2]).toHaveTextContent("Third");
	});

	it("applies tab-active to the active tab only", () => {
		render(TabBar, {
			props: {
				tabs: sampleTabs,
				activeConversationId: "c2",
				onSelect: vi.fn(),
				onClose: vi.fn(),
				onNewDraft: vi.fn(),
			},
		});

		const tabs = screen.getAllByRole("tab");
		expect(tabs[0]).not.toHaveClass("tab-active");
		expect(tabs[1]).toHaveClass("tab-active");
		expect(tabs[2]).not.toHaveClass("tab-active");
	});

	it("applies tab-active to New chat button when activeConversationId is null", () => {
		render(TabBar, {
			props: {
				tabs: sampleTabs,
				activeConversationId: null,
				onSelect: vi.fn(),
				onClose: vi.fn(),
				onNewDraft: vi.fn(),
			},
		});

		const newChat = screen.getByRole("button", { name: "New chat" });
		expect(newChat).toHaveClass("tab-active");
	});

	it("calls onSelect with the conversationId when a tab is clicked", async () => {
		const onSelect = vi.fn();
		const onClose = vi.fn();
		const user = userEvent.setup();

		render(TabBar, {
			props: {
				tabs: sampleTabs,
				activeConversationId: "c1",
				onSelect,
				onClose,
				onNewDraft: vi.fn(),
			},
		});

		const tabs = screen.getAllByRole("tab");
		const secondTab = tabs[1];
		if (!secondTab) throw new Error("second tab not found");
		await user.click(secondTab);

		expect(onSelect).toHaveBeenCalledTimes(1);
		expect(onSelect).toHaveBeenCalledWith("c2");
		expect(onClose).not.toHaveBeenCalled();
	});

	it("calls onClose when the close button is clicked and does not call onSelect", async () => {
		const onSelect = vi.fn();
		const onClose = vi.fn();
		const user = userEvent.setup();

		render(TabBar, {
			props: {
				tabs: sampleTabs,
				activeConversationId: "c1",
				onSelect,
				onClose,
				onNewDraft: vi.fn(),
			},
		});

		const closeButtons = screen.getAllByRole("button", { name: "Close tab" });
		const firstClose = closeButtons[0];
		if (!firstClose) throw new Error("first close button not found");
		await user.click(firstClose);

		expect(onClose).toHaveBeenCalledTimes(1);
		expect(onClose).toHaveBeenCalledWith("c1");
		expect(onSelect).not.toHaveBeenCalled();
	});

	it("calls onNewDraft when the New chat button is clicked", async () => {
		const onNewDraft = vi.fn();
		const user = userEvent.setup();

		render(TabBar, {
			props: {
				tabs: sampleTabs,
				activeConversationId: "c1",
				onSelect: vi.fn(),
				onClose: vi.fn(),
				onNewDraft,
			},
		});

		const newChat = screen.getByRole("button", { name: "New chat" });
		await user.click(newChat);

		expect(onNewDraft).toHaveBeenCalledTimes(1);
	});

	it("the New chat button has the sticky class", () => {
		render(TabBar, {
			props: {
				tabs: sampleTabs,
				activeConversationId: "c1",
				onSelect: vi.fn(),
				onClose: vi.fn(),
				onNewDraft: vi.fn(),
			},
		});

		const newChat = screen.getByRole("button", { name: "New chat" });
		expect(newChat).toHaveClass("sticky");
	});

	it("shows visible 'New Chat' text when activeConversationId is null", () => {
		render(TabBar, {
			props: {
				tabs: sampleTabs,
				activeConversationId: null,
				onSelect: vi.fn(),
				onClose: vi.fn(),
				onNewDraft: vi.fn(),
			},
		});

		const newChat = screen.getByRole("button", { name: "New chat" });
		expect(newChat).toHaveTextContent("New Chat");
	});

	it("does not show 'New Chat' text when a real tab is active", () => {
		render(TabBar, {
			props: {
				tabs: sampleTabs,
				activeConversationId: "c1",
				onSelect: vi.fn(),
				onClose: vi.fn(),
				onNewDraft: vi.fn(),
			},
		});

		const newChat = screen.getByRole("button", { name: "New chat" });
		expect(newChat).not.toHaveTextContent("New Chat");
	});
});