feat(webui): present chats as topics
This commit is contained in:
@@ -858,13 +858,13 @@ describe("App layout", () => {
|
||||
).toBeInTheDocument(),
|
||||
);
|
||||
|
||||
fireEvent.pointerDown(screen.getByLabelText("Chat actions for First chat"), {
|
||||
fireEvent.pointerDown(screen.getByLabelText("Topic actions for First chat"), {
|
||||
button: 0,
|
||||
});
|
||||
fireEvent.click(await screen.findByRole("menuitem", { name: "Delete" }));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(screen.getByText("Delete this chat?")).toBeInTheDocument(),
|
||||
expect(screen.getByText("Delete this topic?")).toBeInTheDocument(),
|
||||
);
|
||||
fireEvent.click(screen.getByRole("button", { name: "Delete" }));
|
||||
|
||||
@@ -876,7 +876,7 @@ describe("App layout", () => {
|
||||
within(sidebar).getByRole("button", { name: /^Second chat$/ }),
|
||||
).toBeInTheDocument(),
|
||||
);
|
||||
expect(screen.queryByText("Delete this chat?")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("Delete this topic?")).not.toBeInTheDocument();
|
||||
expect(document.body.style.pointerEvents).not.toBe("none");
|
||||
}, 15_000);
|
||||
|
||||
@@ -921,7 +921,7 @@ describe("App layout", () => {
|
||||
).toBeInTheDocument(),
|
||||
);
|
||||
|
||||
fireEvent.pointerDown(screen.getByLabelText(/First chat.*会话操作/), {
|
||||
fireEvent.pointerDown(screen.getByLabelText(/First chat.*话题操作/), {
|
||||
button: 0,
|
||||
});
|
||||
fireEvent.click(await screen.findByRole("menuitem", { name: "删除" }));
|
||||
@@ -931,7 +931,7 @@ describe("App layout", () => {
|
||||
);
|
||||
expect(getSessionAutomationsSpy).toHaveBeenCalledWith("websocket:chat-a");
|
||||
expect(
|
||||
screen.getByText("这个对话有关联的自动任务。删除对话也会删除这些自动任务。"),
|
||||
screen.getByText("这个话题有关联的自动任务。删除话题也会删除这些自动任务。"),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByText("This chat has scheduled automations. Deleting it will also delete them."),
|
||||
@@ -989,7 +989,7 @@ describe("App layout", () => {
|
||||
);
|
||||
|
||||
fireEvent.pointerDown(
|
||||
within(mobileSidebar).getByLabelText("Chat actions for Existing chat"),
|
||||
within(mobileSidebar).getByLabelText("Topic actions for Existing chat"),
|
||||
{ button: 0 },
|
||||
);
|
||||
|
||||
@@ -1000,7 +1000,7 @@ describe("App layout", () => {
|
||||
|
||||
fireEvent.click(deleteItem);
|
||||
await waitFor(() =>
|
||||
expect(screen.getByText("Delete this chat?")).toBeInTheDocument(),
|
||||
expect(screen.getByText("Delete this topic?")).toBeInTheDocument(),
|
||||
);
|
||||
}, 15_000);
|
||||
|
||||
@@ -1143,16 +1143,16 @@ describe("App layout", () => {
|
||||
await waitFor(() => expect(connectSpy).toHaveBeenCalled());
|
||||
const sidebar = screen.getByRole("navigation", { name: "Sidebar navigation" });
|
||||
await waitFor(() =>
|
||||
expect(within(sidebar).getByText("Chats")).toBeInTheDocument(),
|
||||
expect(within(sidebar).getByText("Topics")).toBeInTheDocument(),
|
||||
);
|
||||
const group = within(sidebar).getByText("Chats").closest("section");
|
||||
const group = within(sidebar).getByText("Topics").closest("section");
|
||||
expect(group).toBeTruthy();
|
||||
const labels = within(group as HTMLElement)
|
||||
.getAllByRole("button")
|
||||
.map((button) => button.textContent?.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
expect(labels).toEqual(["Alpha plan", "New chat", "Zulu work"]);
|
||||
expect(labels).toEqual(["Alpha plan", "New topic", "Zulu work"]);
|
||||
});
|
||||
|
||||
it("shows running and completed session indicators in the sidebar", async () => {
|
||||
@@ -1969,7 +1969,7 @@ describe("App layout", () => {
|
||||
|
||||
await waitFor(() => expect(connectSpy).toHaveBeenCalled());
|
||||
const sidebar = screen.getByRole("navigation", { name: "Sidebar navigation" });
|
||||
fireEvent.click(within(sidebar).getByRole("button", { name: "New chat" }));
|
||||
fireEvent.click(within(sidebar).getByRole("button", { name: "New topic" }));
|
||||
await waitFor(() => expect(document.title).toBe("nanobot"));
|
||||
|
||||
fireEvent.click(within(sidebar).getByRole("button", { name: "Settings" }));
|
||||
@@ -2009,7 +2009,7 @@ describe("App layout", () => {
|
||||
const sidebar = screen.getByRole("navigation", { name: "Sidebar navigation" });
|
||||
expect(within(sidebar).getByText("Q2 roadmap")).toBeInTheDocument();
|
||||
expect(within(sidebar).getByText("Travel ideas")).toBeInTheDocument();
|
||||
const newChatButton = within(sidebar).getByRole("button", { name: "New chat" });
|
||||
const newChatButton = within(sidebar).getByRole("button", { name: "New topic" });
|
||||
const searchButton = within(sidebar).getByRole("button", { name: "Search" });
|
||||
expect(
|
||||
newChatButton.compareDocumentPosition(searchButton) &
|
||||
@@ -2139,10 +2139,10 @@ describe("App layout", () => {
|
||||
await waitFor(() => expect(connectSpy).toHaveBeenCalled());
|
||||
const sidebar = screen.getByRole("navigation", { name: "Sidebar navigation" });
|
||||
|
||||
const newChatButton = within(sidebar).getByRole("button", { name: "New chat" });
|
||||
const newChatButton = within(sidebar).getByRole("button", { name: "New topic" });
|
||||
expect(newChatButton).toHaveAttribute(
|
||||
"title",
|
||||
"New chat (Ctrl+Shift+O)",
|
||||
"New topic (Ctrl+Shift+O)",
|
||||
);
|
||||
expect(newChatButton).toHaveAttribute(
|
||||
"aria-keyshortcuts",
|
||||
@@ -2157,9 +2157,9 @@ describe("App layout", () => {
|
||||
await waitFor(() => expect(connectSpy).toHaveBeenCalled());
|
||||
const sidebar = screen.getByRole("navigation", { name: "Sidebar navigation" });
|
||||
|
||||
expect(within(sidebar).getByRole("button", { name: "New chat" })).toHaveAttribute(
|
||||
expect(within(sidebar).getByRole("button", { name: "New topic" })).toHaveAttribute(
|
||||
"title",
|
||||
"New chat (⌘⇧O)",
|
||||
"New topic (⌘⇧O)",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -2230,9 +2230,9 @@ describe("App layout", () => {
|
||||
const sidebarAside = container.querySelector("aside.lg\\:block") as HTMLElement;
|
||||
await waitFor(() => expect(sidebarAside.style.width).toBe("56px"));
|
||||
|
||||
expect(screen.queryByRole("button", { name: "Start a new chat" })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole("button", { name: "Start a new topic" })).not.toBeInTheDocument();
|
||||
const rail = screen.getByRole("navigation", { name: "Sidebar navigation" });
|
||||
expect(within(rail).getByRole("button", { name: "New chat" })).toBeInTheDocument();
|
||||
expect(within(rail).getByRole("button", { name: "New topic" })).toBeInTheDocument();
|
||||
expect(within(rail).getByRole("button", { name: "Search" })).toBeInTheDocument();
|
||||
expect(within(rail).queryByRole("button", { name: "View" })).not.toBeInTheDocument();
|
||||
expect(within(rail).queryByText("Existing chat")).not.toBeInTheDocument();
|
||||
@@ -2241,10 +2241,10 @@ describe("App layout", () => {
|
||||
await waitFor(() => expect(sidebarAside.style.width).toBe("272px"));
|
||||
|
||||
const sidebar = screen.getByRole("navigation", { name: "Sidebar navigation" });
|
||||
fireEvent.click(within(sidebar).getByRole("button", { name: "New chat" }));
|
||||
fireEvent.click(within(sidebar).getByRole("button", { name: "New topic" }));
|
||||
expect(createChatSpy).not.toHaveBeenCalled();
|
||||
expect(screen.getByText(HERO_GREETING_PATTERN)).toBeInTheDocument();
|
||||
expect(screen.queryByRole("button", { name: "Start a new chat" })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole("button", { name: "Start a new topic" })).not.toBeInTheDocument();
|
||||
expect(screen.getByRole("button", { name: "Toggle theme from header" })).toBeInTheDocument();
|
||||
expect(within(sidebar).getByRole("button", { name: "Settings" })).toBeInTheDocument();
|
||||
|
||||
|
||||
@@ -142,7 +142,7 @@ describe("ChatList", () => {
|
||||
expect(screen.queryByText("Today")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("keeps default workspace chats in the Chats section instead of a project folder", () => {
|
||||
it("keeps default workspace topics in the Topics section instead of a project folder", () => {
|
||||
const sessions = [
|
||||
session({
|
||||
chatId: "default",
|
||||
@@ -184,7 +184,7 @@ describe("ChatList", () => {
|
||||
expect(screen.getByRole("region", { name: "nanobot" })).toBeInTheDocument();
|
||||
expect(screen.queryByRole("region", { name: "workspace" })).not.toBeInTheDocument();
|
||||
|
||||
const chatsSection = screen.getByRole("region", { name: "Chats" });
|
||||
const chatsSection = screen.getByRole("region", { name: "Topics" });
|
||||
expect(within(chatsSection).getByText("Default workspace chat")).toBeInTheDocument();
|
||||
expect(within(chatsSection).queryByText("Project chat")).not.toBeInTheDocument();
|
||||
});
|
||||
@@ -229,13 +229,13 @@ describe("ChatList", () => {
|
||||
expect(within(projectSection).queryByText("Alpha task")).not.toBeInTheDocument();
|
||||
|
||||
fireEvent.click(
|
||||
within(projectSection).getByRole("button", { name: "Start a new chat in Photos" }),
|
||||
within(projectSection).getByRole("button", { name: "Start a new topic in Photos" }),
|
||||
);
|
||||
expect(onNewChatInProject).toHaveBeenCalledWith("/Users/me/nanobot", "Photos");
|
||||
expect(onToggleGroup).toHaveBeenCalledTimes(1);
|
||||
|
||||
fireEvent.pointerDown(
|
||||
within(projectSection).getByLabelText("Chat actions for Photos"),
|
||||
within(projectSection).getByLabelText("Topic actions for Photos"),
|
||||
{ button: 0 },
|
||||
);
|
||||
fireEvent.click(await screen.findByRole("menuitem", { name: "Rename" }));
|
||||
@@ -300,13 +300,13 @@ describe("ChatList", () => {
|
||||
};
|
||||
|
||||
const { rerender } = render(<ChatList {...baseProps} />);
|
||||
const chatsSection = screen.getByRole("region", { name: "Chats" });
|
||||
const chatsSection = screen.getByRole("region", { name: "Topics" });
|
||||
|
||||
expect(within(chatsSection).getByText("Chat 9")).toBeInTheDocument();
|
||||
expect(within(chatsSection).getByText("Chat 2")).toBeInTheDocument();
|
||||
expect(within(chatsSection).queryByText("Chat 1")).not.toBeInTheDocument();
|
||||
expect(within(chatsSection).queryByRole("button", { name: "Show all" })).not.toBeInTheDocument();
|
||||
fireEvent.click(within(chatsSection).getByRole("button", { name: "2 hidden chats" }));
|
||||
fireEvent.click(within(chatsSection).getByRole("button", { name: "2 hidden topics" }));
|
||||
|
||||
expect(onToggleGroup).toHaveBeenCalledWith("workspace:chats");
|
||||
|
||||
@@ -321,7 +321,7 @@ describe("ChatList", () => {
|
||||
expect(within(chatsSection).getByRole("button", { name: "Show less" })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("sorts Chats section among project groups by recency, not always last", () => {
|
||||
it("sorts Topics section among project groups by recency, not always last", () => {
|
||||
const sessions = [
|
||||
session({
|
||||
chatId: "recent-chat",
|
||||
@@ -367,8 +367,8 @@ describe("ChatList", () => {
|
||||
const regionNames = allRegions.map((r) => r.getAttribute("aria-label") ?? r.textContent);
|
||||
|
||||
// The most recently updated conversation ("Recent chat" at 12:00) must be
|
||||
// in the first group — Chats should come before both projects.
|
||||
const chatsIdx = regionNames.findIndex((n) => n?.includes("Chats"));
|
||||
// in the first group — Topics should come before both projects.
|
||||
const chatsIdx = regionNames.findIndex((n) => n?.includes("Topics"));
|
||||
const projAIdx = regionNames.findIndex((n) => n?.includes("project-a"));
|
||||
const projBIdx = regionNames.findIndex((n) => n?.includes("project-b"));
|
||||
|
||||
@@ -377,7 +377,7 @@ describe("ChatList", () => {
|
||||
expect(within(allRegions[chatsIdx]).getByText("Recent chat")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("keeps one Projects heading when Chats sorts between project groups", () => {
|
||||
it("keeps one Projects heading when Topics sorts between project groups", () => {
|
||||
const sessions = [
|
||||
session({
|
||||
chatId: "project-a",
|
||||
@@ -423,11 +423,11 @@ describe("ChatList", () => {
|
||||
.getAllByRole("region")
|
||||
.map((r) => r.getAttribute("aria-label") ?? "");
|
||||
|
||||
expect(regionNames).toEqual(["project-a", "Chats", "project-b"]);
|
||||
expect(regionNames).toEqual(["project-a", "Topics", "project-b"]);
|
||||
expect(screen.getAllByText("Projects")).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("keeps Chats last when its latest conversation is older than all projects", () => {
|
||||
it("keeps Topics last when its latest conversation is older than all projects", () => {
|
||||
const sessions = [
|
||||
session({
|
||||
chatId: "project-a",
|
||||
@@ -473,7 +473,7 @@ describe("ChatList", () => {
|
||||
.getAllByRole("region")
|
||||
.map((r) => r.getAttribute("aria-label") ?? "");
|
||||
|
||||
expect(regionNames).toEqual(["project-a", "project-b", "Chats"]);
|
||||
expect(regionNames).toEqual(["project-a", "project-b", "Topics"]);
|
||||
expect(screen.getAllByText("Projects")).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -445,7 +445,7 @@ describe("webui i18n", () => {
|
||||
const chat = resources["pt-BR"].common.chat;
|
||||
|
||||
expect(sidebar.settings).toBe("Configurações");
|
||||
expect(chat.newChat).toBe("Nova conversa");
|
||||
expect(chat.newChat).toBe("Novo tópico");
|
||||
expect(settings.nav.browser).toBe("Web");
|
||||
expect(settings.sections.webSearch).toBe("Busca na web");
|
||||
expect(settings.byok.tabs.webSearch).toBe("Busca na web");
|
||||
|
||||
@@ -71,7 +71,7 @@ describe("useSessions", () => {
|
||||
updatedAt: "2026-04-16T10:00:00Z",
|
||||
title: "",
|
||||
preview: "hi",
|
||||
})).toBe("New chat");
|
||||
})).toBe("New topic");
|
||||
|
||||
expect(sessionTitle({
|
||||
key: "websocket:chat-work",
|
||||
|
||||
Reference in New Issue
Block a user