feat(desktop): polish desktop shell and shared WebUI surfaces (#4195)
* feat(desktop): add native host scaffold * feat(webui): track turns and usage in gateway * feat(webui): polish desktop chat experience * feat(apps): add ArcGIS and Joplin logos * feat(desktop): polish shell and shared surfaces * fix(webui): avoid preview chips for glob references * test: align CI expectations for token fallback * feat(webui): preview prompt rail entries * feat(webui): add prompt navigator drawer * style(webui): refine prompt navigator placement * style(webui): align prompt navigator with header actions * style(webui): simplify prompt navigator header * refactor(webui): clean thread resource refresh * feat(desktop): add native reply notifications * fix(webui): preserve desktop restart and replay state * fix(desktop): harden gateway proxy startup * fix(web): fall back when readability is unavailable * fix(desktop): hide window instead of closing on macos * fix(webui): unify desktop header actions * fix(webui): simplify prompt history rows * fix(desktop): log notification delivery failures * chore(desktop): clean source package artifacts * fix(cron): support one-time relative reminders * fix(webui): reveal scroll button in place * Revert "fix(cron): support one-time relative reminders" This reverts commit 4c4661da120a3c7283e0768412bae48604e7390b. * refactor(webui): extract token usage heatmap * docs(desktop): clarify contributor guides --------- Co-authored-by: chengyongru <2755839590@qq.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { fireEvent, render, screen } from "@testing-library/react";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import MarkdownTextRenderer from "@/components/MarkdownTextRenderer";
|
||||
|
||||
@@ -12,6 +12,67 @@ describe("MarkdownTextRenderer", () => {
|
||||
expect(link).toHaveClass("text-blue-500", "dark:text-blue-300");
|
||||
});
|
||||
|
||||
it("renders local file links as previewable file references", () => {
|
||||
const onOpenFilePreview = vi.fn();
|
||||
render(
|
||||
<MarkdownTextRenderer onOpenFilePreview={onOpenFilePreview}>
|
||||
{"Edited [hook.py](/Users/test/project/nanobot/agent/hook.py:12)"}
|
||||
</MarkdownTextRenderer>,
|
||||
);
|
||||
|
||||
const reference = screen.getByTestId("inline-file-path");
|
||||
expect(reference).toHaveTextContent("hook.py");
|
||||
expect(reference).toHaveAttribute(
|
||||
"aria-label",
|
||||
"/Users/test/project/nanobot/agent/hook.py",
|
||||
);
|
||||
|
||||
fireEvent.click(reference);
|
||||
|
||||
expect(onOpenFilePreview).toHaveBeenCalledWith(
|
||||
"/Users/test/project/nanobot/agent/hook.py",
|
||||
);
|
||||
});
|
||||
|
||||
it("does not treat non-file hrefs as previews just because the label looks like a file", () => {
|
||||
const onOpenFilePreview = vi.fn();
|
||||
render(
|
||||
<MarkdownTextRenderer onOpenFilePreview={onOpenFilePreview}>
|
||||
{"Download [index.html](/api/media/sig/html)"}
|
||||
</MarkdownTextRenderer>,
|
||||
);
|
||||
|
||||
expect(screen.queryByTestId("inline-file-path")).not.toBeInTheDocument();
|
||||
expect(screen.getByRole("link", { name: "index.html" })).toHaveAttribute(
|
||||
"href",
|
||||
"/api/media/sig/html",
|
||||
);
|
||||
});
|
||||
|
||||
it("renders glob file links as plain text instead of preview targets", () => {
|
||||
const onOpenFilePreview = vi.fn();
|
||||
const { container } = render(
|
||||
<MarkdownTextRenderer onOpenFilePreview={onOpenFilePreview}>
|
||||
{"原始对话通常还在 [*.json](*.json)。"}
|
||||
</MarkdownTextRenderer>,
|
||||
);
|
||||
|
||||
expect(screen.queryByTestId("inline-file-path")).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole("link", { name: "*.json" })).not.toBeInTheDocument();
|
||||
expect(container).toHaveTextContent("*.json");
|
||||
});
|
||||
|
||||
it("keeps glob inline code as code instead of a file preview chip", () => {
|
||||
render(
|
||||
<MarkdownTextRenderer>
|
||||
{"检查 `src/**/*.json`。"}
|
||||
</MarkdownTextRenderer>,
|
||||
);
|
||||
|
||||
expect(screen.queryByTestId("inline-file-path")).not.toBeInTheDocument();
|
||||
expect(screen.getByText("src/**/*.json").tagName).toBe("CODE");
|
||||
});
|
||||
|
||||
it("does not wrap complete fenced code blocks in an extra pre", () => {
|
||||
const { container } = render(
|
||||
<MarkdownTextRenderer highlightCode={false}>
|
||||
@@ -117,6 +178,42 @@ describe("MarkdownTextRenderer", () => {
|
||||
).toHaveAttribute("href", "https://polymarket.com/event/when-will-gpt-5pt6-be-released");
|
||||
});
|
||||
|
||||
it("falls back through favicon sources before showing a globe for compact link rows", () => {
|
||||
const { container } = render(
|
||||
<MarkdownTextRenderer>
|
||||
{
|
||||
"Useful links:\n\n- Savills Hong Kong Corporate Relocation — Corporate relocation services\n https://www.savills.com.hk/services/corporate-relocation.aspx"
|
||||
}
|
||||
</MarkdownTextRenderer>,
|
||||
);
|
||||
const link = screen.getByRole("link", {
|
||||
name: "Open link: Savills Hong Kong Corporate Relocation — Corporate relocation services",
|
||||
});
|
||||
const favicon = () => link.querySelector("img");
|
||||
|
||||
expect(favicon()).toHaveAttribute(
|
||||
"src",
|
||||
"https://www.savills.com.hk/favicon.ico",
|
||||
);
|
||||
|
||||
fireEvent.error(favicon()!);
|
||||
expect(favicon()).toHaveAttribute(
|
||||
"src",
|
||||
"https://icons.duckduckgo.com/ip3/www.savills.com.hk.ico",
|
||||
);
|
||||
|
||||
fireEvent.error(favicon()!);
|
||||
expect(favicon()).toHaveAttribute(
|
||||
"src",
|
||||
"https://www.google.com/s2/favicons?domain=www.savills.com.hk&sz=64",
|
||||
);
|
||||
|
||||
fireEvent.error(favicon()!);
|
||||
expect(favicon()).not.toBeInTheDocument();
|
||||
expect(link.querySelector("svg")).toBeInTheDocument();
|
||||
expect(container).not.toHaveTextContent("SC");
|
||||
});
|
||||
|
||||
it("renders media attachments without an extra preview/code wrapper", () => {
|
||||
render(<MarkdownTextRenderer></MarkdownTextRenderer>);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user