mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-08-12 07:49:00 +00:00
* docs: design frontend performance remediation * docs: plan frontend performance remediation * test(frontend): add route asset performance budgets * perf(nginx): compress textual responses safely * perf(frontend): lazy load case study media * perf(frontend): bound static demo file tracing * perf(frontend): restore static locale boundaries * perf(frontend): defer closed workspace panels * perf(frontend): split editors and deduplicate highlighting * perf(frontend): index incremental message derivation * perf(frontend): stabilize paged history cache policy * perf(frontend): bound streaming markdown renders * perf(frontend): virtualize message history * perf(frontend): bound and virtualize chat lists * perf(frontend): suspend inactive decorative animation * perf(browser): stream latest frames as binary * perf(artifacts): stream bounded text previews * docs: finalize performance runtime boundaries * style(backend): apply test formatting * fix(frontend): keep translation functions client-side * perf(frontend): defer decorative animation bundles * test(frontend): lock optimized route budgets * fix: harden frontend performance boundaries * test(frontend): update i18n provider fixture * fix(frontend): preserve sidebar pagination position * style(backend): format artifact range test
138 lines
3.6 KiB
TypeScript
138 lines
3.6 KiB
TypeScript
import { afterEach, describe, expect, it, rs } from "@rstest/core";
|
|
|
|
import { observeRenderActivity } from "@/core/dom/render-activity";
|
|
|
|
class IntersectionObserverMock {
|
|
static instances: IntersectionObserverMock[] = [];
|
|
|
|
callback: IntersectionObserverCallback;
|
|
disconnect = rs.fn();
|
|
observe = rs.fn();
|
|
|
|
constructor(callback: IntersectionObserverCallback) {
|
|
this.callback = callback;
|
|
IntersectionObserverMock.instances.push(this);
|
|
}
|
|
|
|
emit(isIntersecting: boolean) {
|
|
this.callback(
|
|
[{ isIntersecting } as IntersectionObserverEntry],
|
|
this as never,
|
|
);
|
|
}
|
|
}
|
|
|
|
class MediaQueryListMock {
|
|
matches = false;
|
|
addEventListener = rs.fn();
|
|
removeEventListener = rs.fn();
|
|
private listener?: (event: MediaQueryListEvent) => void;
|
|
|
|
constructor() {
|
|
this.addEventListener.mockImplementation(
|
|
(_type: string, listener: (event: MediaQueryListEvent) => void) => {
|
|
this.listener = listener;
|
|
},
|
|
);
|
|
}
|
|
|
|
emit(matches: boolean) {
|
|
this.matches = matches;
|
|
this.listener?.({ matches } as MediaQueryListEvent);
|
|
}
|
|
}
|
|
|
|
describe("observeRenderActivity", () => {
|
|
afterEach(() => {
|
|
IntersectionObserverMock.instances = [];
|
|
rs.restoreAllMocks();
|
|
rs.unstubAllGlobals();
|
|
});
|
|
|
|
it("pauses for hidden documents and offscreen elements, then cleans up", () => {
|
|
rs.stubGlobal("IntersectionObserver", IntersectionObserverMock);
|
|
let hidden = false;
|
|
rs.spyOn(document, "hidden", "get").mockImplementation(() => hidden);
|
|
const removeEventListener = rs.spyOn(document, "removeEventListener");
|
|
const states: boolean[] = [];
|
|
|
|
const cleanup = observeRenderActivity(
|
|
document.createElement("div"),
|
|
(active) => {
|
|
states.push(active);
|
|
},
|
|
);
|
|
const observer = IntersectionObserverMock.instances[0]!;
|
|
|
|
expect(states).toEqual([true]);
|
|
observer.emit(false);
|
|
expect(states).toEqual([true, false]);
|
|
|
|
observer.emit(true);
|
|
hidden = true;
|
|
document.dispatchEvent(new Event("visibilitychange"));
|
|
expect(states).toEqual([true, false, true, false]);
|
|
|
|
hidden = false;
|
|
document.dispatchEvent(new Event("visibilitychange"));
|
|
expect(states.at(-1)).toBe(true);
|
|
|
|
cleanup();
|
|
expect(observer.disconnect).toHaveBeenCalledOnce();
|
|
expect(removeEventListener).toHaveBeenCalledWith(
|
|
"visibilitychange",
|
|
expect.any(Function),
|
|
);
|
|
});
|
|
|
|
it("pauses animations while reduced motion is requested", () => {
|
|
const mediaQuery = new MediaQueryListMock();
|
|
rs.stubGlobal(
|
|
"matchMedia",
|
|
rs.fn(() => mediaQuery),
|
|
);
|
|
const states: boolean[] = [];
|
|
|
|
const cleanup = observeRenderActivity(
|
|
document.createElement("div"),
|
|
(active) => states.push(active),
|
|
);
|
|
|
|
expect(matchMedia).toHaveBeenCalledWith("(prefers-reduced-motion: reduce)");
|
|
expect(states).toEqual([true]);
|
|
|
|
mediaQuery.emit(true);
|
|
expect(states).toEqual([true, false]);
|
|
|
|
mediaQuery.emit(false);
|
|
expect(states).toEqual([true, false, true]);
|
|
|
|
cleanup();
|
|
expect(mediaQuery.removeEventListener).toHaveBeenCalledWith(
|
|
"change",
|
|
expect.any(Function),
|
|
);
|
|
});
|
|
|
|
it("can preserve visible content while reduced motion is requested", () => {
|
|
const mediaQuery = new MediaQueryListMock();
|
|
mediaQuery.matches = true;
|
|
rs.stubGlobal(
|
|
"matchMedia",
|
|
rs.fn(() => mediaQuery),
|
|
);
|
|
const states: boolean[] = [];
|
|
|
|
const cleanup = observeRenderActivity(
|
|
document.createElement("div"),
|
|
(active) => states.push(active),
|
|
true,
|
|
false,
|
|
);
|
|
|
|
expect(states).toEqual([true]);
|
|
expect(matchMedia).not.toHaveBeenCalled();
|
|
cleanup();
|
|
});
|
|
});
|