Files
千羽and千羽 57b1eff1d4 feat(ui): v8 — theme color, unified controls, motion, virtual lists (#7816)
Unify the visual and interaction rules on top of the v7 redesign.

- Theme color #F97315 for --action-* and --ring; dark mode uses #EA580C
  with white text, toned-down mode colors and translucent soft fills
- Buttons mapped to three sizes (28 / 32 / 36); inputs, selects and
  search fields share fieldClass; self-drawn checkbox and radio
- HoverTip replaces title on about 100 icon buttons; it stays closed
  when a closing menu returns focus to its trigger
- Install tailwindcss-animate so menu, popover, dialog and tooltip
  enter/exit animations take effect; sliding segment thumb and tab
  underline
- Remove the command palette and the header prompt entry; provider card
  primary actions become icon buttons
- Virtualize the session list and the Skills discovery list; optimistic
  toggles for MCP and Skills with a 300ms delayed disabled state
- Toasts drawn unstyled with status-colored icons and de-duplicated
- Windows: hide the system title bar and use in-app window controls;
  macOS: brand row under the traffic lights
- Focus rings only for keyboard users
- Skills discovery and the prompt list show the last result from a
  local cache and refresh in the background
- Drop the trailing ellipsis from 36 dialog-opening labels
- Provider list fills the main area

Co-authored-by: 千羽 <qyinter@users.noreply.github.com>
2026-10-03 23:28:59 +08:00

48 lines
1.5 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
const sonner = vi.hoisted(() => {
let next = 0;
const make = () =>
vi.fn(
(_message: unknown, options?: { id?: number }) => options?.id ?? ++next,
);
return { success: make(), error: make(), info: make(), warning: make() };
});
vi.mock("sonner", () => ({ toast: Object.assign(vi.fn(), sonner) }));
import { toast } from "@/lib/toast";
describe("toast", () => {
beforeEach(() => {
vi.useFakeTimers();
for (const fn of Object.values(sonner)) fn.mockClear();
});
it("refreshes the visible toast instead of stacking the same message", () => {
const first = toast.success("所有技能已是最新版本");
toast.success("所有技能已是最新版本");
expect(sonner.success).toHaveBeenCalledTimes(2);
expect(sonner.success.mock.calls[0]).toEqual(["所有技能已是最新版本"]);
expect(sonner.success.mock.calls[1][1]).toEqual({ id: first });
});
it("shows a new toast once the previous one has gone", () => {
toast.info("已复制");
vi.advanceTimersByTime(2500);
toast.info("已复制");
expect(sonner.info.mock.calls[1]).toEqual(["已复制"]);
});
it("keeps different messages and kinds separate", () => {
toast.error("保存失败");
toast.success("保存失败");
toast.error("另一个错误");
expect(sonner.error.mock.calls[1]).toEqual(["另一个错误"]);
expect(sonner.success.mock.calls[0]).toEqual(["保存失败"]);
});
});