Files
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

290 lines
8.3 KiB
TypeScript

import "@testing-library/jest-dom/vitest";
import {
act,
fireEvent,
render,
screen,
waitFor
} from "@testing-library/react";
import { afterEach, describe, expect, it, vi } from "vitest";
import { CollapsibleReveal } from "./CollapsibleReveal";
describe("CollapsibleReveal", () => {
afterEach(() => {
vi.restoreAllMocks();
vi.unstubAllGlobals();
});
it("mounts collapsed first, then expands on the next frame for push animation", async () => {
let scheduledFrame: FrameRequestCallback | null = null;
Object.defineProperty(HTMLElement.prototype, "scrollHeight", {
configurable: true,
get: () => 42
});
vi.spyOn(window, "requestAnimationFrame").mockImplementation((callback) => {
scheduledFrame = callback;
return 1;
});
vi.spyOn(window, "cancelAnimationFrame").mockImplementation(
() => undefined
);
const { rerender } = render(
<CollapsibleReveal expanded={false}>
<div>detail content</div>
</CollapsibleReveal>
);
expect(screen.queryByText("detail content")).toBeNull();
rerender(
<CollapsibleReveal expanded>
<div>detail content</div>
</CollapsibleReveal>
);
expect(screen.queryByText("detail content")).toBeNull();
act(() => {
scheduledFrame?.(8);
});
const reveal = screen
.getByText("detail content")
.closest(".agent-collapsible-reveal");
expect(reveal).toHaveAttribute("data-expanded", "false");
expect(reveal).toHaveStyle({ height: "0px" });
act(() => {
scheduledFrame?.(16);
});
expect(reveal).toHaveAttribute("data-expanded", "true");
expect(reveal).toHaveStyle({ height: "42px" });
fireEvent.transitionEnd(reveal as HTMLElement, { propertyName: "height" });
await waitFor(() => expect(reveal).toHaveStyle({ height: "auto" }));
rerender(
<CollapsibleReveal expanded={false}>
<div>detail content</div>
</CollapsibleReveal>
);
expect(reveal).toHaveAttribute("data-expanded", "false");
expect(reveal).toHaveStyle({ height: "42px" });
act(() => {
scheduledFrame?.(32);
});
expect(reveal).toHaveStyle({ height: "0px" });
fireEvent.transitionEnd(reveal as HTMLElement, { propertyName: "height" });
expect(screen.queryByText("detail content")).toBeNull();
});
it("pre-mounts collapsed content on idle without starting the reveal animation", () => {
let idleCallback: IdleRequestCallback | null = null;
vi.stubGlobal("requestIdleCallback", (callback: IdleRequestCallback) => {
idleCallback = callback;
return 1;
});
vi.stubGlobal("cancelIdleCallback", () => undefined);
vi.spyOn(window, "requestAnimationFrame");
render(
<CollapsibleReveal expanded={false} preMountOnIdle>
<div>detail content</div>
</CollapsibleReveal>
);
expect(screen.queryByText("detail content")).toBeNull();
act(() => {
idleCallback?.({
didTimeout: false,
timeRemaining: () => 50
});
});
const reveal = screen
.getByText("detail content")
.closest(".agent-collapsible-reveal");
expect(reveal).toHaveAttribute("data-expanded", "false");
expect(reveal).toHaveAttribute("aria-hidden", "true");
expect(reveal).toHaveStyle({ height: "0px" });
expect(window.requestAnimationFrame).not.toHaveBeenCalled();
});
it("collapses pre-mounted content without re-reading a stale layout height", async () => {
let scheduledFrame: FrameRequestCallback | null = null;
let scrollHeightReads = 0;
Object.defineProperty(HTMLElement.prototype, "scrollHeight", {
configurable: true,
get: () => {
scrollHeightReads += 1;
return 42;
}
});
vi.spyOn(window, "requestAnimationFrame").mockImplementation((callback) => {
scheduledFrame = callback;
return 1;
});
vi.spyOn(window, "cancelAnimationFrame").mockImplementation(
() => undefined
);
const { rerender } = render(
<CollapsibleReveal expanded={false} preMountOnIdle>
<div>detail content</div>
</CollapsibleReveal>
);
rerender(
<CollapsibleReveal expanded preMountOnIdle>
<div>detail content</div>
</CollapsibleReveal>
);
act(() => {
scheduledFrame?.(8);
});
const reveal = screen
.getByText("detail content")
.closest(".agent-collapsible-reveal");
act(() => {
scheduledFrame?.(16);
});
expect(reveal).toHaveStyle({ height: "42px" });
const readsAfterExpansion = scrollHeightReads;
expect(readsAfterExpansion).toBeGreaterThan(0);
fireEvent.transitionEnd(reveal as HTMLElement, { propertyName: "height" });
await waitFor(() => expect(reveal).toHaveStyle({ height: "auto" }));
rerender(
<CollapsibleReveal expanded={false} preMountOnIdle>
<div>detail content</div>
</CollapsibleReveal>
);
expect(reveal).toHaveStyle({ height: "42px" });
expect(scrollHeightReads).toBe(readsAfterExpansion);
act(() => {
scheduledFrame?.(32);
});
expect(reveal).toHaveStyle({ height: "0px" });
fireEvent.transitionEnd(reveal as HTMLElement, { propertyName: "height" });
expect(screen.getByText("detail content")).toBeInTheDocument();
});
it("tracks resize updates at auto height without animating and collapses from the latest size", () => {
let resizeCallback: ResizeObserverCallback | null = null;
const observe = vi.fn();
const disconnect = vi.fn();
vi.stubGlobal(
"ResizeObserver",
vi.fn().mockImplementation(function ResizeObserverMock(
callback: ResizeObserverCallback
) {
resizeCallback = callback;
return { observe, disconnect };
})
);
vi.spyOn(window, "requestAnimationFrame");
const { rerender } = render(
<CollapsibleReveal expanded>
<div>streaming detail content</div>
</CollapsibleReveal>
);
const reveal = screen
.getByText("streaming detail content")
.closest(".agent-collapsible-reveal");
const inner = reveal?.querySelector(".agent-collapsible-reveal__inner");
Object.defineProperty(inner as Element, "scrollHeight", {
configurable: true,
value: 84
});
expect(reveal).toHaveStyle({ height: "auto" });
expect(observe).toHaveBeenCalled();
act(() => {
resizeCallback?.(
[
{
contentRect: { height: 84 }
} as ResizeObserverEntry
],
{} as ResizeObserver
);
});
expect(window.requestAnimationFrame).not.toHaveBeenCalled();
expect(reveal).toHaveStyle({ height: "auto" });
rerender(
<CollapsibleReveal expanded={false}>
<div>streaming detail content</div>
</CollapsibleReveal>
);
expect(reveal).toHaveStyle({ height: "84px" });
});
it("reports a collapsing height transition once when content unmounts", () => {
const onHeightTransitionEnd = vi.fn();
const { rerender } = render(
<CollapsibleReveal expanded onHeightTransitionEnd={onHeightTransitionEnd}>
<div>detail content</div>
</CollapsibleReveal>
);
const reveal = screen
.getByText("detail content")
.closest(".agent-collapsible-reveal");
rerender(
<CollapsibleReveal
expanded={false}
onHeightTransitionEnd={onHeightTransitionEnd}
>
<div>detail content</div>
</CollapsibleReveal>
);
fireEvent.transitionEnd(reveal as HTMLElement, { propertyName: "height" });
expect(onHeightTransitionEnd).toHaveBeenCalledTimes(1);
});
it("reports an expansion canceled before its mount frame", () => {
vi.spyOn(window, "requestAnimationFrame").mockImplementation(() => 1);
const onHeightTransitionEnd = vi.fn();
const { rerender } = render(
<CollapsibleReveal
expanded={false}
onHeightTransitionEnd={onHeightTransitionEnd}
>
<div>detail content</div>
</CollapsibleReveal>
);
rerender(
<CollapsibleReveal expanded onHeightTransitionEnd={onHeightTransitionEnd}>
<div>detail content</div>
</CollapsibleReveal>
);
rerender(
<CollapsibleReveal
expanded={false}
onHeightTransitionEnd={onHeightTransitionEnd}
>
<div>detail content</div>
</CollapsibleReveal>
);
expect(onHeightTransitionEnd).toHaveBeenCalledTimes(1);
});
});