Przeglądaj źródła

test: Add unit tests for package/utils (#3357)

* Update return type to reflect actual signature

* add tests

* Set getDimensions as optional

* add newlines between specs

* remove redundant assertion

* move fixtures to separate files

* Add spacing

* Move tests, add cases

* Add unit tests for package/utils exportToSvg

* extract default object in test

* Move test suite to new file
Hampus Lavin 4 lat temu
rodzic
commit
4ac1841d92

+ 2 - 2
src/packages/utils.ts

@@ -11,7 +11,7 @@ import { restore } from "../data/restore";
 type ExportOpts = {
   elements: readonly ExcalidrawElement[];
   appState?: Partial<Omit<AppState, "offsetTop" | "offsetLeft">>;
-  getDimensions: (
+  getDimensions?: (
     width: number,
     height: number,
   ) => { width: number; height: number; scale: number };
@@ -83,7 +83,7 @@ export const exportToSvg = ({
   appState = getDefaultAppState(),
   exportPadding,
   metadata,
-}: ExportOpts & {
+}: Omit<ExportOpts, "getDimensions"> & {
   exportPadding?: number;
   metadata?: string;
 }): SVGSVGElement => {

+ 31 - 0
src/tests/fixtures/diagramFixture.ts

@@ -0,0 +1,31 @@
+import {
+  diamondFixture,
+  ellipseFixture,
+  rectangleFixture,
+} from "./elementFixture";
+
+export const diagramFixture = {
+  type: "excalidraw",
+  version: 2,
+  source: "https://excalidraw.com",
+  elements: [diamondFixture, ellipseFixture, rectangleFixture],
+  appState: {
+    viewBackgroundColor: "#ffffff",
+    gridSize: null,
+  },
+};
+
+export const diagramFactory = ({
+  overrides = {},
+  elementOverrides = {},
+} = {}) => ({
+  ...diagramFixture,
+  elements: [
+    { ...diamondFixture, ...elementOverrides },
+    { ...ellipseFixture, ...elementOverrides },
+    { ...rectangleFixture, ...elementOverrides },
+  ],
+  ...overrides,
+});
+
+export default diagramFixture;

+ 37 - 0
src/tests/fixtures/elementFixture.ts

@@ -0,0 +1,37 @@
+import { ExcalidrawElement } from "../../element/types";
+
+const elementBase: Omit<ExcalidrawElement, "type"> = {
+  id: "vWrqOAfkind2qcm7LDAGZ",
+  x: 414,
+  y: 237,
+  width: 214,
+  height: 214,
+  angle: 0,
+  strokeColor: "#000000",
+  backgroundColor: "#15aabf",
+  fillStyle: "hachure",
+  strokeWidth: 1,
+  strokeStyle: "solid",
+  roughness: 1,
+  opacity: 100,
+  groupIds: [],
+  strokeSharpness: "sharp",
+  seed: 1041657908,
+  version: 120,
+  versionNonce: 1188004276,
+  isDeleted: false,
+  boundElementIds: null,
+};
+
+export const rectangleFixture: ExcalidrawElement = {
+  ...elementBase,
+  type: "rectangle",
+};
+export const ellipseFixture: ExcalidrawElement = {
+  ...elementBase,
+  type: "ellipse",
+};
+export const diamondFixture: ExcalidrawElement = {
+  ...elementBase,
+  type: "diamond",
+};

+ 80 - 0
src/tests/packages/__snapshots__/utils.test.ts.snap

@@ -0,0 +1,80 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`exportToSvg with default arguments 1`] = `
+Object {
+  "collaborators": Map {},
+  "currentChartType": "bar",
+  "currentItemBackgroundColor": "transparent",
+  "currentItemEndArrowhead": "arrow",
+  "currentItemFillStyle": "hachure",
+  "currentItemFontFamily": 1,
+  "currentItemFontSize": 20,
+  "currentItemLinearStrokeSharpness": "round",
+  "currentItemOpacity": 100,
+  "currentItemRoughness": 1,
+  "currentItemStartArrowhead": null,
+  "currentItemStrokeColor": "#000000",
+  "currentItemStrokeSharpness": "sharp",
+  "currentItemStrokeStyle": "solid",
+  "currentItemStrokeWidth": 1,
+  "currentItemTextAlign": "left",
+  "cursorButton": "up",
+  "draggingElement": null,
+  "editingElement": null,
+  "editingGroupId": null,
+  "editingLinearElement": null,
+  "elementLocked": false,
+  "elementType": "selection",
+  "errorMessage": null,
+  "exportBackground": true,
+  "exportEmbedScene": false,
+  "exportPadding": undefined,
+  "exportWithDarkMode": false,
+  "fileHandle": null,
+  "gridSize": null,
+  "height": 768,
+  "isBindingEnabled": true,
+  "isLibraryOpen": false,
+  "isLoading": false,
+  "isResizing": false,
+  "isRotating": false,
+  "lastPointerDownWith": "mouse",
+  "metadata": undefined,
+  "multiElement": null,
+  "name": "name",
+  "offsetLeft": 0,
+  "offsetTop": 0,
+  "openMenu": null,
+  "pasteDialog": Object {
+    "data": null,
+    "shown": false,
+  },
+  "previousSelectedElementIds": Object {},
+  "resizingElement": null,
+  "scrollX": 0,
+  "scrollY": 0,
+  "scrolledOutside": false,
+  "selectedElementIds": Object {},
+  "selectedGroupIds": Object {},
+  "selectionElement": null,
+  "shouldAddWatermark": false,
+  "shouldCacheIgnoreZoom": false,
+  "showHelpDialog": false,
+  "showStats": false,
+  "startBoundElement": null,
+  "suggestedBindings": Array [],
+  "theme": "light",
+  "toastMessage": null,
+  "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
+  "width": 1024,
+  "zenModeEnabled": false,
+  "zoom": Object {
+    "translation": Object {
+      "x": 0,
+      "y": 0,
+    },
+    "value": 1,
+  },
+}
+`;

+ 121 - 0
src/tests/packages/utils.test.ts

@@ -0,0 +1,121 @@
+import * as utils from "../../packages/utils";
+import { diagramFactory } from "../fixtures/diagramFixture";
+import * as mockedSceneExportUtils from "../../scene/export";
+
+jest.mock("../../scene/export", () => ({
+  __esmodule: true,
+  ...jest.requireActual("../../scene/export"),
+  exportToSvg: jest.fn(),
+}));
+
+describe("exportToCanvas", () => {
+  const EXPORT_PADDING = 10;
+
+  it("with default arguments", () => {
+    const canvas = utils.exportToCanvas({
+      ...diagramFactory({ elementOverrides: { width: 100, height: 100 } }),
+    });
+
+    expect(canvas.width).toBe(100 + 2 * EXPORT_PADDING);
+    expect(canvas.height).toBe(100 + 2 * EXPORT_PADDING);
+  });
+
+  it("when custom width and height", () => {
+    const canvas = utils.exportToCanvas({
+      ...diagramFactory({ elementOverrides: { width: 100, height: 100 } }),
+      getDimensions: () => ({ width: 200, height: 200, scale: 1 }),
+    });
+
+    expect(canvas.width).toBe(200);
+    expect(canvas.height).toBe(200);
+  });
+});
+
+describe("exportToBlob", () => {
+  describe("mime type", () => {
+    afterEach(jest.restoreAllMocks);
+
+    it("should change image/jpg to image/jpeg", async () => {
+      const blob = await utils.exportToBlob({
+        ...diagramFactory(),
+        getDimensions: (width, height) => ({ width, height, scale: 1 }),
+        mimeType: "image/jpg",
+      });
+      expect(blob?.type).toBe("image/jpeg");
+    });
+
+    it("should default to image/png", async () => {
+      const blob = await utils.exportToBlob({
+        ...diagramFactory(),
+      });
+      expect(blob?.type).toBe("image/png");
+    });
+
+    it("should warn when using quality with image/png", async () => {
+      const consoleSpy = jest
+        .spyOn(console, "warn")
+        .mockImplementationOnce(() => void 0);
+
+      await utils.exportToBlob({
+        ...diagramFactory(),
+        mimeType: "image/png",
+        quality: 1,
+      });
+
+      expect(consoleSpy).toHaveBeenCalledWith(
+        '"quality" will be ignored for "image/png" mimeType',
+      );
+    });
+  });
+});
+
+describe("exportToSvg", () => {
+  const mockedExportUtil = mockedSceneExportUtils.exportToSvg as jest.Mock;
+  const passedElements = () => mockedExportUtil.mock.calls[0][0];
+  const passedOptions = () => mockedExportUtil.mock.calls[0][1];
+
+  afterEach(jest.resetAllMocks);
+
+  it("with default arguments", () => {
+    utils.exportToSvg({
+      ...diagramFactory({
+        overrides: { appState: void 0 },
+      }),
+    });
+
+    const passedOptionsWhenDefault = {
+      ...passedOptions(),
+      // To avoid varying snapshots
+      name: "name",
+    };
+
+    expect(passedElements().length).toBe(3);
+    expect(passedOptionsWhenDefault).toMatchSnapshot();
+  });
+
+  it("with deleted elements", () => {
+    utils.exportToSvg({
+      ...diagramFactory({
+        overrides: { appState: void 0 },
+        elementOverrides: { isDeleted: true },
+      }),
+    });
+
+    expect(passedElements().length).toBe(0);
+  });
+
+  it("with exportPadding and metadata", () => {
+    const METADATA = "some metada";
+
+    utils.exportToSvg({
+      ...diagramFactory({ overrides: { appState: { name: "diagram name" } } }),
+      exportPadding: 0,
+      metadata: METADATA,
+    });
+
+    expect(passedElements().length).toBe(3);
+    expect(passedOptions()).toEqual(
+      expect.objectContaining({ exportPadding: 0, metadata: METADATA }),
+    );
+  });
+});

Plik diff jest za duży
+ 40 - 0
src/tests/scene/__snapshots__/export.test.ts.snap


+ 96 - 0
src/tests/scene/export.test.ts

@@ -0,0 +1,96 @@
+import { NonDeletedExcalidrawElement } from "../../element/types";
+import * as exportUtils from "../../scene/export";
+import { diamondFixture, ellipseFixture } from "../fixtures/elementFixture";
+
+describe("exportToSvg", () => {
+  const ELEMENT_HEIGHT = 100;
+  const ELEMENT_WIDTH = 100;
+  const ELEMENTS = [
+    { ...diamondFixture, height: ELEMENT_HEIGHT, width: ELEMENT_WIDTH },
+    { ...ellipseFixture, height: ELEMENT_HEIGHT, width: ELEMENT_WIDTH },
+  ] as NonDeletedExcalidrawElement[];
+
+  const DEFAULT_OPTIONS = {
+    exportBackground: false,
+    viewBackgroundColor: "#ffffff",
+    shouldAddWatermark: false,
+  };
+
+  it("with default arguments", () => {
+    const svgElement = exportUtils.exportToSvg(ELEMENTS, DEFAULT_OPTIONS);
+
+    expect(svgElement).toMatchSnapshot();
+  });
+
+  it("with background color", () => {
+    const BACKGROUND_COLOR = "#abcdef";
+
+    const svgElement = exportUtils.exportToSvg(ELEMENTS, {
+      ...DEFAULT_OPTIONS,
+      exportBackground: true,
+      viewBackgroundColor: BACKGROUND_COLOR,
+    });
+
+    expect(svgElement.querySelector("rect")).toHaveAttribute(
+      "fill",
+      BACKGROUND_COLOR,
+    );
+  });
+
+  it("with watermark", () => {
+    const svgElement = exportUtils.exportToSvg(ELEMENTS, {
+      ...DEFAULT_OPTIONS,
+      shouldAddWatermark: true,
+    });
+
+    expect(svgElement.querySelector("text")?.textContent).toMatchInlineSnapshot(
+      `"Made with Excalidraw"`,
+    );
+  });
+
+  it("with dark mode", () => {
+    const svgElement = exportUtils.exportToSvg(ELEMENTS, {
+      ...DEFAULT_OPTIONS,
+      exportWithDarkMode: true,
+    });
+
+    expect(svgElement.getAttribute("filter")).toMatchInlineSnapshot(
+      `"themeFilter"`,
+    );
+  });
+
+  it("with exportPadding, metadata", () => {
+    const svgElement = exportUtils.exportToSvg(ELEMENTS, {
+      ...DEFAULT_OPTIONS,
+      exportPadding: 0,
+      metadata: "some metadata",
+    });
+
+    expect(svgElement.innerHTML).toMatch(/some metadata/);
+    expect(svgElement).toHaveAttribute("height", ELEMENT_HEIGHT.toString());
+    expect(svgElement).toHaveAttribute("width", ELEMENT_WIDTH.toString());
+    expect(svgElement).toHaveAttribute(
+      "viewBox",
+      `0 0 ${ELEMENT_WIDTH} ${ELEMENT_HEIGHT}`,
+    );
+  });
+
+  it("with scale", () => {
+    const SCALE = 2;
+
+    const svgElement = exportUtils.exportToSvg(ELEMENTS, {
+      ...DEFAULT_OPTIONS,
+      exportPadding: 0,
+      scale: SCALE,
+    });
+
+    expect(svgElement).toHaveAttribute(
+      "height",
+      (ELEMENT_HEIGHT * SCALE).toString(),
+    );
+    expect(svgElement).toHaveAttribute(
+      "width",
+      (ELEMENT_WIDTH * SCALE).toString(),
+    );
+  });
+});

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików