feat(packages/excalidraw): export throttleRAF (#10912)
This commit is contained in:
@@ -3,6 +3,12 @@ import {
|
|||||||
mapFind,
|
mapFind,
|
||||||
reduceToCommonValue,
|
reduceToCommonValue,
|
||||||
} from "@excalidraw/common";
|
} from "@excalidraw/common";
|
||||||
|
import { vi } from "vitest";
|
||||||
|
|
||||||
|
// Import directly to avoid the @excalidraw/common throttleRAF mock from setupTests.ts.
|
||||||
|
import { throttleRAF } from "./utils";
|
||||||
|
|
||||||
|
type RafCallback = FrameRequestCallback;
|
||||||
|
|
||||||
describe("@excalidraw/common/utils", () => {
|
describe("@excalidraw/common/utils", () => {
|
||||||
describe("isTransparent()", () => {
|
describe("isTransparent()", () => {
|
||||||
@@ -79,4 +85,87 @@ describe("@excalidraw/common/utils", () => {
|
|||||||
expect(mapFind([1, 2], () => null)).toBe(undefined);
|
expect(mapFind([1, 2], () => null)).toBe(undefined);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("throttleRAF()", () => {
|
||||||
|
let frameCallbacks: Map<number, RafCallback>;
|
||||||
|
let nextFrameId: number;
|
||||||
|
|
||||||
|
const runScheduledFrame = (timestamp = 16) => {
|
||||||
|
const callbacks = [...frameCallbacks.values()];
|
||||||
|
frameCallbacks.clear();
|
||||||
|
callbacks.forEach((callback) => callback(timestamp));
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
frameCallbacks = new Map();
|
||||||
|
nextFrameId = 0;
|
||||||
|
|
||||||
|
vi.spyOn(window, "requestAnimationFrame").mockImplementation(
|
||||||
|
(callback) => {
|
||||||
|
const frameId = ++nextFrameId;
|
||||||
|
frameCallbacks.set(frameId, callback);
|
||||||
|
return frameId;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
vi.spyOn(window, "cancelAnimationFrame").mockImplementation((frameId) => {
|
||||||
|
frameCallbacks.delete(frameId);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should invoke the callback with the last args from the same frame", () => {
|
||||||
|
const fn = vi.fn();
|
||||||
|
const throttled = throttleRAF(fn);
|
||||||
|
|
||||||
|
throttled("first", 1);
|
||||||
|
throttled("second", 2);
|
||||||
|
throttled("last", 3);
|
||||||
|
|
||||||
|
expect(fn).not.toHaveBeenCalled();
|
||||||
|
expect(window.requestAnimationFrame).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
runScheduledFrame();
|
||||||
|
|
||||||
|
expect(fn).toHaveBeenCalledTimes(1);
|
||||||
|
expect(fn).toHaveBeenCalledWith("last", 3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should flush the pending callback immediately", () => {
|
||||||
|
const fn = vi.fn();
|
||||||
|
const throttled = throttleRAF(fn);
|
||||||
|
|
||||||
|
throttled("first");
|
||||||
|
throttled("last");
|
||||||
|
|
||||||
|
throttled.flush();
|
||||||
|
|
||||||
|
expect(window.cancelAnimationFrame).toHaveBeenCalledTimes(1);
|
||||||
|
expect(fn).toHaveBeenCalledTimes(1);
|
||||||
|
expect(fn).toHaveBeenCalledWith("last");
|
||||||
|
|
||||||
|
runScheduledFrame();
|
||||||
|
|
||||||
|
expect(fn).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should cancel the pending callback", () => {
|
||||||
|
const fn = vi.fn();
|
||||||
|
const throttled = throttleRAF(fn);
|
||||||
|
|
||||||
|
throttled("first");
|
||||||
|
throttled("last");
|
||||||
|
|
||||||
|
throttled.cancel();
|
||||||
|
|
||||||
|
expect(window.cancelAnimationFrame).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
runScheduledFrame();
|
||||||
|
|
||||||
|
expect(fn).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -169,10 +169,6 @@ export const throttleRAF = <T extends any[]>(fn: (...args: T) => void) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const ret = (...args: T) => {
|
const ret = (...args: T) => {
|
||||||
if (isTestEnv()) {
|
|
||||||
fn(...args);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
lastArgs = args;
|
lastArgs = args;
|
||||||
if (timerId === null) {
|
if (timerId === null) {
|
||||||
scheduleFunc();
|
scheduleFunc();
|
||||||
|
|||||||
@@ -267,6 +267,7 @@ export {
|
|||||||
sceneCoordsToViewportCoords,
|
sceneCoordsToViewportCoords,
|
||||||
viewportCoordsToSceneCoords,
|
viewportCoordsToSceneCoords,
|
||||||
getFormFactor,
|
getFormFactor,
|
||||||
|
throttleRAF,
|
||||||
} from "@excalidraw/common";
|
} from "@excalidraw/common";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|||||||
@@ -28,7 +28,9 @@ const { h } = window;
|
|||||||
const mouse = new Pointer("mouse");
|
const mouse = new Pointer("mouse");
|
||||||
|
|
||||||
vi.mock("@excalidraw/common", async (importOriginal) => {
|
vi.mock("@excalidraw/common", async (importOriginal) => {
|
||||||
const module: any = await importOriginal();
|
const module = await importOriginal<typeof import("@excalidraw/common")>();
|
||||||
|
const { mockThrottleRAF } = await import("./helpers/mocks");
|
||||||
|
|
||||||
return {
|
return {
|
||||||
__esmodule: true,
|
__esmodule: true,
|
||||||
...module,
|
...module,
|
||||||
@@ -37,6 +39,7 @@ vi.mock("@excalidraw/common", async (importOriginal) => {
|
|||||||
...module.KEYS,
|
...module.KEYS,
|
||||||
CTRL_OR_CMD: "ctrlKey",
|
CTRL_OR_CMD: "ctrlKey",
|
||||||
},
|
},
|
||||||
|
throttleRAF: mockThrottleRAF,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,25 @@ import React from "react";
|
|||||||
import { vi } from "vitest";
|
import { vi } from "vitest";
|
||||||
|
|
||||||
import type { parseMermaidToExcalidraw } from "@excalidraw/mermaid-to-excalidraw";
|
import type { parseMermaidToExcalidraw } from "@excalidraw/mermaid-to-excalidraw";
|
||||||
|
import type { throttleRAF as throttleRAFType } from "@excalidraw/common";
|
||||||
|
|
||||||
|
type ThrottledFn<T extends unknown[]> = ((...args: T) => void) & {
|
||||||
|
flush: () => void;
|
||||||
|
cancel: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mockThrottleRAF: typeof throttleRAFType = <T extends unknown[]>(
|
||||||
|
fn: (...args: T) => void,
|
||||||
|
) => {
|
||||||
|
const ret = ((...args: T) => {
|
||||||
|
fn(...args);
|
||||||
|
}) as ThrottledFn<T>;
|
||||||
|
|
||||||
|
ret.flush = () => {};
|
||||||
|
ret.cancel = () => {};
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
export const mockMermaidToExcalidraw = (opts: {
|
export const mockMermaidToExcalidraw = (opts: {
|
||||||
parseMermaidToExcalidraw: typeof parseMermaidToExcalidraw;
|
parseMermaidToExcalidraw: typeof parseMermaidToExcalidraw;
|
||||||
|
|||||||
@@ -6,9 +6,19 @@ import "@testing-library/jest-dom";
|
|||||||
import { vi } from "vitest";
|
import { vi } from "vitest";
|
||||||
|
|
||||||
import polyfill from "./packages/excalidraw/polyfill";
|
import polyfill from "./packages/excalidraw/polyfill";
|
||||||
|
import { mockThrottleRAF } from "./packages/excalidraw/tests/helpers/mocks";
|
||||||
import { yellow } from "./packages/excalidraw/tests/helpers/colorize";
|
import { yellow } from "./packages/excalidraw/tests/helpers/colorize";
|
||||||
import { testPolyfills } from "./packages/excalidraw/tests/helpers/polyfills";
|
import { testPolyfills } from "./packages/excalidraw/tests/helpers/polyfills";
|
||||||
|
|
||||||
|
vi.mock("@excalidraw/common", async (importOriginal) => {
|
||||||
|
const module = await importOriginal<typeof import("@excalidraw/common")>();
|
||||||
|
|
||||||
|
return {
|
||||||
|
...module,
|
||||||
|
throttleRAF: mockThrottleRAF,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
// mock for pep.js not working with setPointerCapture()
|
// mock for pep.js not working with setPointerCapture()
|
||||||
HTMLElement.prototype.setPointerCapture = vi.fn();
|
HTMLElement.prototype.setPointerCapture = vi.fn();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user