refactor(webui): improve visual consistency (#5249)

This commit is contained in:
chengyongru
2026-08-05 13:24:45 +08:00
committed by GitHub
parent a54d5d14cb
commit 9098ffd38f
33 changed files with 578 additions and 646 deletions
+82 -11
View File
@@ -509,6 +509,9 @@ describe("App layout", () => {
expect(screen.getByText("cron")).toBeInTheDocument();
expect(screen.getByText("github")).toBeInTheDocument();
expect(screen.getByText("Needs setup")).toBeInTheDocument();
expect(
screen.queryByText("Review the instruction skills this agent can load during a conversation."),
).not.toBeInTheDocument();
expect(screen.getByRole("navigation", { name: "Sidebar navigation" })).toBeInTheDocument();
expect(screen.queryByRole("navigation", { name: "Settings sections" })).not.toBeInTheDocument();
expect(within(sidebar).getByRole("button", { name: "Skills" })).toHaveAttribute(
@@ -534,6 +537,11 @@ describe("App layout", () => {
"true",
);
expect(screen.getByText("Setup required")).toBeInTheDocument();
expect(
screen.queryByText(
"Allow the agent to load this skill when its requirements are ready.",
),
).not.toBeInTheDocument();
expect(screen.getByText("brew install gh")).toBeInTheDocument();
expect(screen.queryByText("Unavailable reason")).not.toBeInTheDocument();
expect(screen.queryByText("Missing CLI")).not.toBeInTheDocument();
@@ -737,6 +745,9 @@ describe("App layout", () => {
expect(
await screen.findByRole("heading", { name: "Trending by marketplace" }),
).toBeInTheDocument();
expect(
screen.queryByText("Each marketplace keeps its own ranking and install metrics."),
).not.toBeInTheDocument();
expect(screen.getByText("find-skills")).toBeInTheDocument();
expect(screen.getByText("ima-skills")).toBeInTheDocument();
expect(screen.getAllByText("SkillHub")).toHaveLength(2);
@@ -1994,6 +2005,11 @@ describe("App layout", () => {
fireEvent.click(await screen.findByRole("menuitem", { name: "Appearance" }));
expect(screen.getByText("Brand logos")).toBeInTheDocument();
expect(screen.getByRole("switch", { name: "Brand logos" })).toBeInTheDocument();
expect(
screen.queryByText("Switch between light and dark appearance."),
).not.toBeInTheDocument();
expect(screen.queryByText("Choose the language used by the WebUI.")).not.toBeInTheDocument();
expect(screen.queryByText("Stored only in this browser.")).not.toBeInTheDocument();
expect(within(settingsNav).getByRole("button", { name: "Settings: Appearance" })).toBeInTheDocument();
fireEvent.pointerDown(within(settingsNav).getByRole("button", { name: "Settings: Appearance" }));
fireEvent.click(await screen.findByRole("menuitem", { name: "Models" }));
@@ -2079,6 +2095,14 @@ describe("App layout", () => {
expect(screen.getByRole("button", { name: "openai/gpt-5.4-image-2" })).toBeInTheDocument();
expect(screen.getByText("Save directory")).toBeInTheDocument();
expect(screen.getByRole("button", { name: "Save" })).toBeDisabled();
expect(
screen.queryByText(
"Expose generate_image in chats when a configured image provider is available.",
),
).not.toBeInTheDocument();
expect(
screen.queryByText("Choose a model supported by the selected image provider."),
).not.toBeInTheDocument();
fireEvent.click(within(settingsNav).getByRole("button", { name: "Web" }));
expect(screen.getByText("Search provider")).toBeInTheDocument();
@@ -2086,6 +2110,12 @@ describe("App layout", () => {
expect(screen.getByRole("button", { name: /Brave Search/ })).toBeInTheDocument();
expect(screen.getByTestId("provider-picker-logo-brave")).toBeInTheDocument();
expect(screen.getByText("BSAo••••ew20")).toBeInTheDocument();
expect(
screen.queryByText("Choose the backend used by the web search tool."),
).not.toBeInTheDocument();
expect(
screen.queryByText("Results returned by each web_search call."),
).not.toBeInTheDocument();
fireEvent.click(screen.getByRole("button", { name: "Edit" }));
fireEvent.change(screen.getByPlaceholderText("Leave blank to keep the current key"), {
target: { value: "unsaved-brave-key" },
@@ -2098,8 +2128,14 @@ describe("App layout", () => {
expect(screen.queryByDisplayValue("unsaved-brave-key")).not.toBeInTheDocument();
fireEvent.click(within(settingsNav).getByRole("button", { name: "System" }));
expect(screen.getByText("Regional")).toBeInTheDocument();
expect(screen.queryByText("Regional")).not.toBeInTheDocument();
expect(screen.getByText("Timezone")).toBeInTheDocument();
expect(
screen.queryByText("Used for schedules and time-aware replies."),
).not.toBeInTheDocument();
expect(
screen.queryByText("Restart nanobot to apply runtime changes."),
).not.toBeInTheDocument();
expect(screen.queryByText("Bot name")).not.toBeInTheDocument();
expect(screen.queryByText("Bot icon")).not.toBeInTheDocument();
expect(screen.queryByText("Tool hint length")).not.toBeInTheDocument();
@@ -2107,16 +2143,15 @@ describe("App layout", () => {
expect(screen.queryByText("Dream")).not.toBeInTheDocument();
expect(screen.queryByText("Unified session")).not.toBeInTheDocument();
expect(screen.getByText("Default workspace")).toBeInTheDocument();
expect(screen.getByRole("button", { name: "Save" })).toBeDisabled();
fireEvent.click(screen.getByRole("button", { name: "UTC" }));
const timezoneSearch = await screen.findByPlaceholderText("Search timezone");
expect(timezoneSearch).toBeInTheDocument();
fireEvent.change(timezoneSearch, {
target: { value: "Shanghai" },
});
await user.click(screen.getByRole("option", { name: /Asia\/Shanghai/ }));
expect(screen.getByRole("button", { name: "Asia/Shanghai" })).toBeInTheDocument();
expect(screen.getByRole("button", { name: "Save" })).toBeEnabled();
expect(screen.getByText("UTC")).toBeInTheDocument();
expect(screen.queryByPlaceholderText("Search timezone")).not.toBeInTheDocument();
expect(screen.queryByRole("listbox", { name: "Select timezone" })).not.toBeInTheDocument();
expect(screen.queryByRole("button", { name: "UTC" })).not.toBeInTheDocument();
expect(screen.queryByRole("button", { name: "Save" })).not.toBeInTheDocument();
expect(
screen.queryByText("Connect SDKs and agents through a local /v1 endpoint."),
).not.toBeInTheDocument();
expect(screen.queryByText("The API uses this local port.")).not.toBeInTheDocument();
});
it("restores the settings section from the URL hash after a page reload", async () => {
@@ -2130,6 +2165,41 @@ describe("App layout", () => {
expect(window.location.hash).toBe("#/settings?section=voice");
});
it("keeps the backend timezone without writing settings on mount", async () => {
const initialSettings = baseSettingsPayload();
mockFetchRoutes({
"/api/settings": initialSettings,
});
const fetchMock = vi.mocked(fetch);
window.history.replaceState(null, "", "/#/settings?section=runtime");
render(<App />);
expect(await screen.findByText("UTC")).toBeInTheDocument();
expect(
fetchMock.mock.calls.filter(([input]) =>
String(input).startsWith("/api/settings/update?timezone="),
),
).toHaveLength(0);
expect(screen.queryByRole("heading", { name: "Regional" })).not.toBeInTheDocument();
expect(
screen.queryByText("Used for schedules and time-aware replies."),
).not.toBeInTheDocument();
expect(screen.queryByRole("button", { name: "Save" })).not.toBeInTheDocument();
expect(screen.queryByPlaceholderText("Search timezone")).not.toBeInTheDocument();
const systemSection = screen.getByRole("heading", { name: "System" }).closest("section");
expect(systemSection).not.toBeNull();
const system = within(systemSection as HTMLElement);
const timezoneLabel = system.getByText("Timezone");
const restartButton = system.getByRole("button", { name: "Restart nanobot" });
expect(
timezoneLabel.compareDocumentPosition(restartButton) & Node.DOCUMENT_POSITION_FOLLOWING,
).toBeTruthy();
expect(
system.queryByText("Restart nanobot to apply runtime changes."),
).not.toBeInTheDocument();
});
it("falls back to Overview for the retired Files settings URL", async () => {
mockFetchRoutes({ "/api/settings": baseSettingsPayload() });
window.history.replaceState(null, "", "/#/settings?section=files");
@@ -2211,6 +2281,7 @@ describe("App layout", () => {
fireEvent.click(appsButton);
expect(await screen.findByRole("heading", { name: "Apps" })).toBeInTheDocument();
expect(screen.queryByText("Add tools to nanobot, then @ them in chat.")).not.toBeInTheDocument();
expect(screen.getByRole("navigation", { name: "Sidebar navigation" })).toBeInTheDocument();
expect(screen.queryByRole("navigation", { name: "Settings sections" })).not.toBeInTheDocument();
expect(within(sidebar).getByRole("button", { name: "Apps" })).toHaveAttribute(
+32
View File
@@ -113,6 +113,38 @@ describe("MessageBubble", () => {
expect(screen.queryByRole("button", { name: "Fork" })).not.toBeInTheDocument();
});
it("does not replay an entrance animation when persisted messages mount", () => {
const messages: UIMessage[] = [
{
id: "u-persisted",
role: "user",
content: "Earlier question",
createdAt: Date.now(),
},
{
id: "a-persisted",
role: "assistant",
content: "Earlier answer",
createdAt: Date.now(),
},
{
id: "t-persisted",
role: "tool",
kind: "trace",
content: "Earlier tool call",
createdAt: Date.now(),
},
];
for (const message of messages) {
const { container, unmount } = render(<MessageBubble message={message} />);
for (const className of ["animate-in", "fade-in-0", "slide-in-from-bottom-1"]) {
expect(container.firstElementChild).not.toHaveClass(className);
}
unmount();
}
});
it("renders failed delivery details on focus without persistent accepted chrome", async () => {
const message: UIMessage = {
id: "u-delivery",
+8 -6
View File
@@ -610,14 +610,16 @@ describe("SettingsView Apps catalog", () => {
renderSettingsView({ initialSection: "apps" });
expect(await screen.findByText("Add tools to nanobot, then @ them in chat.")).toBeInTheDocument();
expect(await screen.findByText("AnyGen")).toBeInTheDocument();
expect(
screen.queryByText("Add tools to nanobot, then @ them in chat."),
).not.toBeInTheDocument();
expect(screen.getByRole("button", { name: "Ready" })).toHaveAttribute("aria-pressed", "false");
expect(screen.getByRole("button", { name: "Apps" })).toHaveAttribute("aria-pressed", "true");
expect(screen.getByRole("button", { name: "Integrations" })).toBeInTheDocument();
expect(screen.queryByRole("button", { name: "Plugins" })).not.toBeInTheDocument();
expect(screen.queryByText("Api")).not.toBeInTheDocument();
expect(screen.getByText("AnyGen")).toBeInTheDocument();
expect(screen.getByText("0 ready")).toBeInTheDocument();
expect(screen.queryByText("0 ready")).not.toBeInTheDocument();
});
it("shows nanobot optional features and enables one", async () => {
@@ -787,7 +789,7 @@ describe("SettingsView Apps catalog", () => {
renderSettingsView({ initialSection: "channels" });
expect(await screen.findByRole("button", { name: "View Matrix settings" })).toBeInTheDocument();
expect(screen.getByText("0 running · 1 channels")).toBeInTheDocument();
expect(screen.queryByText("0 running · 1 channels")).not.toBeInTheDocument();
expect(screen.getAllByText("Failed").length).toBeGreaterThan(0);
expect(screen.queryByText("Enabled, support needs install")).not.toBeInTheDocument();
@@ -841,7 +843,7 @@ describe("SettingsView Apps catalog", () => {
renderSettingsView({ initialSection: "channels" });
expect(await screen.findByRole("button", { name: "View Matrix settings" })).toBeInTheDocument();
expect(screen.getByText("0 running · 1 channels")).toBeInTheDocument();
expect(screen.queryByText("0 running · 1 channels")).not.toBeInTheDocument();
expect(screen.getAllByText("Failed").length).toBeGreaterThan(0);
expect(screen.getByText(runtimeError)).toBeInTheDocument();
expect(screen.getByRole("switch", { name: "Matrix channel" })).toHaveAttribute(
@@ -1353,7 +1355,7 @@ describe("SettingsView Apps catalog", () => {
renderSettingsView({ initialSection: "channels" });
await screen.findByText("No assistant connected");
expect(screen.getByText("0 running · 1 channels")).toBeInTheDocument();
expect(screen.queryByText("0 running · 1 channels")).not.toBeInTheDocument();
expect(screen.getAllByText("Failed").length).toBeGreaterThan(0);
expect(screen.getByText(runtimeError)).toBeInTheDocument();
expect(screen.getByRole("switch", { name: "test assistant" })).toHaveAttribute(