mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
test: expand coverage across jobs and tools (#380)
This commit is contained in:
@@ -0,0 +1,244 @@
|
||||
// @vitest-environment jsdom
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
vi.mock("zustand/middleware", async (importOriginal) => {
|
||||
const actual: Record<string, unknown> = await importOriginal();
|
||||
return { ...actual, persist: (config: unknown) => config };
|
||||
});
|
||||
|
||||
vi.stubGlobal("URL", {
|
||||
...globalThis.URL,
|
||||
revokeObjectURL: vi.fn(),
|
||||
});
|
||||
|
||||
import { dashStyleToArray, hexToRgba, useEditorStore } from "@/stores/editor-store";
|
||||
import type { CanvasObject, SelectionState } from "@/types/editor";
|
||||
|
||||
const INITIAL_STATE = useEditorStore.getState();
|
||||
|
||||
function state() {
|
||||
return useEditorStore.getState();
|
||||
}
|
||||
|
||||
function makeRect(id: string, attrs: Record<string, number | string> = {}): CanvasObject {
|
||||
return {
|
||||
id,
|
||||
type: "rect",
|
||||
layerId: state().activeLayerId,
|
||||
attrs: {
|
||||
x: 10,
|
||||
y: 20,
|
||||
width: 30,
|
||||
height: 40,
|
||||
strokeWidth: 2,
|
||||
rotation: 0,
|
||||
...attrs,
|
||||
},
|
||||
} as CanvasObject;
|
||||
}
|
||||
|
||||
function makeLine(id: string, points = [0, 0, 10, 10]): CanvasObject {
|
||||
return {
|
||||
id,
|
||||
type: "line",
|
||||
layerId: state().activeLayerId,
|
||||
attrs: {
|
||||
points,
|
||||
strokeWidth: 2,
|
||||
rotation: 0,
|
||||
},
|
||||
} as CanvasObject;
|
||||
}
|
||||
|
||||
function makeEllipse(id: string): CanvasObject {
|
||||
return {
|
||||
id,
|
||||
type: "ellipse",
|
||||
layerId: state().activeLayerId,
|
||||
attrs: {
|
||||
x: 30,
|
||||
y: 40,
|
||||
radiusX: 10,
|
||||
radiusY: 20,
|
||||
rotation: 0,
|
||||
},
|
||||
} as CanvasObject;
|
||||
}
|
||||
|
||||
describe("editor store branch helpers", () => {
|
||||
beforeEach(() => {
|
||||
useEditorStore.setState({ ...INITIAL_STATE }, true);
|
||||
});
|
||||
|
||||
it("converts hex colors to rgba strings", () => {
|
||||
expect(hexToRgba("#336699", 0.5)).toBe("rgba(51, 102, 153, 0.5)");
|
||||
});
|
||||
|
||||
it("converts dash styles to canvas dash arrays", () => {
|
||||
expect(dashStyleToArray("dashed", 3)).toEqual([12, 6]);
|
||||
expect(dashStyleToArray("dotted", 3)).toEqual([3, 6]);
|
||||
expect(dashStyleToArray("solid", 3)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("initializes crop bounds when entering crop mode and clears them when leaving", () => {
|
||||
state().setTool("crop");
|
||||
|
||||
expect(state().cropState).toEqual({
|
||||
x: 192,
|
||||
y: 108,
|
||||
width: 1536,
|
||||
height: 864,
|
||||
aspectRatio: null,
|
||||
});
|
||||
expect(state().isCropping).toBe(true);
|
||||
|
||||
state().setTool("move");
|
||||
|
||||
expect(state().cropState).toBeNull();
|
||||
expect(state().isCropping).toBe(false);
|
||||
});
|
||||
|
||||
it("resizes canvas from a bottom-right anchor by offsetting objects", () => {
|
||||
state().addObject(makeRect("rect"));
|
||||
state().addObject(makeLine("line"));
|
||||
|
||||
state().resizeCanvas(2000, 1100, "bottom-right", "#abcdef");
|
||||
|
||||
expect(state().canvasBackground).toBe("#abcdef");
|
||||
expect(state().objects[0].attrs).toMatchObject({ x: 90, y: 40 });
|
||||
expect((state().objects[1].attrs as { points: number[] }).points).toEqual([80, 20, 90, 30]);
|
||||
});
|
||||
|
||||
it("rotates point and center-based objects for 90 and 270 degrees", () => {
|
||||
useEditorStore.setState({
|
||||
canvasSize: { width: 100, height: 50 },
|
||||
objects: [makeLine("line"), makeEllipse("ellipse")],
|
||||
});
|
||||
|
||||
state().rotateCanvas(90);
|
||||
expect((state().objects[0].attrs as { points: number[] }).points).toEqual([50, 0, 40, 10]);
|
||||
expect(state().objects[1].attrs).toMatchObject({
|
||||
x: 10,
|
||||
y: 30,
|
||||
radiusX: 20,
|
||||
radiusY: 10,
|
||||
rotation: 90,
|
||||
});
|
||||
|
||||
state().rotateCanvas(270);
|
||||
expect((state().objects[0].attrs as { points: number[] }).points).toEqual([0, 0, 10, 10]);
|
||||
expect(state().objects[1].attrs).toMatchObject({
|
||||
x: 30,
|
||||
y: 40,
|
||||
radiusX: 10,
|
||||
radiusY: 20,
|
||||
rotation: 0,
|
||||
});
|
||||
});
|
||||
|
||||
it("flips point and center-based objects horizontally and vertically", () => {
|
||||
useEditorStore.setState({
|
||||
canvasSize: { width: 100, height: 80 },
|
||||
objects: [makeLine("line"), makeEllipse("ellipse")],
|
||||
});
|
||||
|
||||
state().flipCanvasHorizontal();
|
||||
expect((state().objects[0].attrs as { points: number[] }).points).toEqual([100, 0, 90, 10]);
|
||||
expect(state().objects[1].attrs).toMatchObject({ x: 70, rotation: 0 });
|
||||
|
||||
state().flipCanvasVertical();
|
||||
expect((state().objects[0].attrs as { points: number[] }).points).toEqual([100, 80, 90, 70]);
|
||||
expect(state().objects[1].attrs).toMatchObject({ y: 40, rotation: 0 });
|
||||
});
|
||||
|
||||
it("inverts geometric selections and masked selections", () => {
|
||||
useEditorStore.setState({ canvasSize: { width: 4, height: 3 } });
|
||||
const geometricSelection: SelectionState = {
|
||||
type: "rect",
|
||||
bounds: { x: 1, y: 1, width: 2, height: 1 },
|
||||
};
|
||||
|
||||
state().setSelection(geometricSelection);
|
||||
state().invertSelection();
|
||||
|
||||
expect(state().selection?.bounds).toEqual({ x: 0, y: 0, width: 4, height: 3 });
|
||||
expect(Array.from(state().selection?.mask ?? [])).toEqual([1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1]);
|
||||
|
||||
const maskedSelection: SelectionState = {
|
||||
type: "wand",
|
||||
bounds: { x: 1, y: 0, width: 2, height: 2 },
|
||||
mask: new Uint8Array([1, 0, 0, 1]),
|
||||
};
|
||||
state().setSelection(maskedSelection);
|
||||
state().invertSelection();
|
||||
|
||||
expect(Array.from(state().selection?.mask ?? [])).toEqual([1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1]);
|
||||
});
|
||||
|
||||
it("ignores crop and clipboard operations when there is no state to apply", () => {
|
||||
state().applyCrop();
|
||||
state().cutObjects();
|
||||
state().pasteObjects();
|
||||
state().pasteInPlace();
|
||||
|
||||
expect(state().canvasSize).toEqual({ width: 1920, height: 1080 });
|
||||
expect(state().objects).toEqual([]);
|
||||
expect(state().clipboard).toBeNull();
|
||||
});
|
||||
|
||||
it("pastes copied objects offset or in place onto the active layer", () => {
|
||||
state().addObject(makeRect("rect"));
|
||||
state().setSelectedObjects(["rect"]);
|
||||
state().copyObjects();
|
||||
|
||||
state().pasteObjects();
|
||||
const offsetPaste = state().objects[1];
|
||||
expect(offsetPaste.id).not.toBe("rect");
|
||||
expect(offsetPaste.layerId).toBe(state().activeLayerId);
|
||||
expect(offsetPaste.attrs).toMatchObject({ x: 20, y: 30 });
|
||||
expect(state().selectedObjectIds).toEqual([offsetPaste.id]);
|
||||
|
||||
state().pasteInPlace();
|
||||
const inPlacePaste = state().objects[2];
|
||||
expect(inPlacePaste.id).not.toBe("rect");
|
||||
expect(inPlacePaste.attrs).toMatchObject({ x: 10, y: 20 });
|
||||
expect(state().selectedObjectIds).toEqual([inPlacePaste.id]);
|
||||
});
|
||||
|
||||
it("batch nudges positioned and point-based objects in one history entry", () => {
|
||||
state().addObject(makeRect("rect"));
|
||||
state().addObject(makeLine("line"));
|
||||
const version = state()._historyVersion;
|
||||
|
||||
state().batchNudge(["rect", "line"], 5, -3);
|
||||
|
||||
expect(state().objects[0].attrs).toMatchObject({ x: 15, y: 17 });
|
||||
expect((state().objects[1].attrs as { points: number[] }).points).toEqual([5, -3, 15, 7]);
|
||||
expect(state().lastAction).toBe("Nudge");
|
||||
expect(state()._historyVersion).toBe(version + 1);
|
||||
});
|
||||
|
||||
it("clamps editor control ranges at their documented limits", () => {
|
||||
state().setBrushSize(0);
|
||||
state().setBrushOpacity(2);
|
||||
state().setBrushHardness(-1);
|
||||
state().setBrushFlow(2);
|
||||
state().setShapeFillOpacity(-1);
|
||||
state().setShapeStrokeOpacity(2);
|
||||
state().setFillTolerance(999);
|
||||
state().setGradientOpacity(-1);
|
||||
state().setPixelBrushStrength(0);
|
||||
|
||||
expect(state()).toMatchObject({
|
||||
brushSize: 1,
|
||||
brushOpacity: 1,
|
||||
brushHardness: 0,
|
||||
brushFlow: 1,
|
||||
shapeFillOpacity: 0,
|
||||
shapeStrokeOpacity: 1,
|
||||
fillTolerance: 255,
|
||||
gradientOpacity: 0,
|
||||
pixelBrushStrength: 1,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,140 @@
|
||||
// @vitest-environment jsdom
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const revokeObjectURL = vi.fn();
|
||||
const createObjectURL = vi.fn((_obj: Blob | MediaSource) => "blob:fake-url");
|
||||
|
||||
vi.stubGlobal("URL", {
|
||||
...globalThis.URL,
|
||||
createObjectURL,
|
||||
revokeObjectURL,
|
||||
});
|
||||
|
||||
const imagePreviewMock = vi.hoisted(() => ({
|
||||
needsServerPreview: vi.fn(() => false),
|
||||
fetchDecodedPreview: vi.fn(() => Promise.resolve(null)),
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/image-preview", () => imagePreviewMock);
|
||||
|
||||
vi.mock("@/lib/analytics", () => ({
|
||||
track: vi.fn(),
|
||||
}));
|
||||
|
||||
import { previewKindFor, useFileStore } from "@/stores/file-store";
|
||||
|
||||
function makeFile(name: string, size = 1024, type = "image/png"): File {
|
||||
const buf = new ArrayBuffer(size);
|
||||
return new File([buf], name, { type });
|
||||
}
|
||||
|
||||
describe("useFileStore branch coverage", () => {
|
||||
beforeEach(() => {
|
||||
useFileStore.getState().reset();
|
||||
vi.clearAllMocks();
|
||||
imagePreviewMock.needsServerPreview.mockReturnValue(false);
|
||||
imagePreviewMock.fetchDecodedPreview.mockResolvedValue(null);
|
||||
let urlCounter = 0;
|
||||
createObjectURL.mockImplementation((_obj: Blob | MediaSource) => `blob:url-${++urlCounter}`);
|
||||
});
|
||||
|
||||
it("maps unknown modalities to no preview", () => {
|
||||
expect(previewKindFor("unknown" as never)).toBe("none");
|
||||
});
|
||||
|
||||
it("removeFile is a no-op for missing indexes", () => {
|
||||
useFileStore.getState().setFiles([makeFile("a.png")]);
|
||||
const before = useFileStore.getState().entries;
|
||||
revokeObjectURL.mockClear();
|
||||
|
||||
useFileStore.getState().removeFile(3);
|
||||
|
||||
expect(useFileStore.getState().entries).toBe(before);
|
||||
expect(revokeObjectURL).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("removeFile revokes processed preview URLs", () => {
|
||||
useFileStore.getState().setFiles([makeFile("a.png")]);
|
||||
useFileStore.getState().updateEntry(0, {
|
||||
processedUrl: "blob:processed",
|
||||
processedPreviewUrl: "blob:processed-preview",
|
||||
});
|
||||
revokeObjectURL.mockClear();
|
||||
|
||||
useFileStore.getState().removeFile(0);
|
||||
|
||||
expect(revokeObjectURL).toHaveBeenCalledWith("blob:processed");
|
||||
expect(revokeObjectURL).toHaveBeenCalledWith("blob:processed-preview");
|
||||
});
|
||||
|
||||
it("setError stops processing only when an error is present", () => {
|
||||
useFileStore.getState().setProcessing(true);
|
||||
useFileStore.getState().setError(null);
|
||||
expect(useFileStore.getState()).toMatchObject({ error: null, processing: true });
|
||||
|
||||
useFileStore.getState().setError("failed");
|
||||
expect(useFileStore.getState()).toMatchObject({ error: "failed", processing: false });
|
||||
});
|
||||
|
||||
it("setProcessedUrl and setSizes are no-ops without a selected entry", () => {
|
||||
expect(() => useFileStore.getState().setProcessedUrl("blob:result")).not.toThrow();
|
||||
expect(() => useFileStore.getState().setSizes(1, 2)).not.toThrow();
|
||||
expect(useFileStore.getState().entries).toEqual([]);
|
||||
expect(useFileStore.getState().processedUrl).toBeNull();
|
||||
expect(useFileStore.getState().processedSize).toBeNull();
|
||||
});
|
||||
|
||||
it("stores processed preview URLs on the selected entry", () => {
|
||||
useFileStore.getState().setFiles([makeFile("a.png")]);
|
||||
|
||||
useFileStore.getState().setProcessedUrl("blob:result", "blob:preview");
|
||||
|
||||
expect(useFileStore.getState().entries[0]).toMatchObject({
|
||||
processedUrl: "blob:result",
|
||||
processedPreviewUrl: "blob:preview",
|
||||
processedFilename: null,
|
||||
status: "completed",
|
||||
});
|
||||
expect(useFileStore.getState().processedPreviewUrl).toBe("blob:preview");
|
||||
});
|
||||
|
||||
it("applies decoded previews only when the entry still contains the same file", async () => {
|
||||
imagePreviewMock.needsServerPreview.mockReturnValue(true);
|
||||
imagePreviewMock.fetchDecodedPreview.mockImplementation((file: File) =>
|
||||
Promise.resolve(
|
||||
file.name === "a.heic"
|
||||
? { url: "blob:decoded-a", originalWidth: 640, originalHeight: 480 }
|
||||
: { url: "blob:decoded-b", originalWidth: 320, originalHeight: 240 },
|
||||
),
|
||||
);
|
||||
|
||||
const firstFile = makeFile("a.heic", 100, "image/heic");
|
||||
const replacementFile = makeFile("b.heic", 100, "image/heic");
|
||||
useFileStore.getState().setFiles([firstFile]);
|
||||
useFileStore.getState().setFiles([replacementFile]);
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(useFileStore.getState().entries[0].blobUrl).toBe("blob:decoded-b");
|
||||
});
|
||||
|
||||
expect(useFileStore.getState().entries[0]).toMatchObject({
|
||||
file: replacementFile,
|
||||
originalWidth: 320,
|
||||
originalHeight: 240,
|
||||
previewLoading: false,
|
||||
});
|
||||
expect(useFileStore.getState().entries[0].blobUrl).not.toBe("blob:decoded-a");
|
||||
});
|
||||
|
||||
it("clears previewLoading when decoded preview returns null", async () => {
|
||||
imagePreviewMock.needsServerPreview.mockReturnValue(true);
|
||||
imagePreviewMock.fetchDecodedPreview.mockResolvedValue(null);
|
||||
|
||||
useFileStore.getState().setFiles([makeFile("a.heic", 100, "image/heic")]);
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(useFileStore.getState().entries[0].previewLoading).toBe(false);
|
||||
});
|
||||
expect(useFileStore.getState().entries[0].blobUrl).toBe("blob:url-1");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,199 @@
|
||||
// @vitest-environment jsdom
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
vi.mock("@/lib/api", () => ({
|
||||
formatHeaders: vi.fn((headers: HeadersInit) => new Headers(headers)),
|
||||
}));
|
||||
|
||||
const fetchMock = vi.fn();
|
||||
vi.stubGlobal("fetch", fetchMock);
|
||||
|
||||
import { formatHeaders } from "@/lib/api";
|
||||
import { useHtmlToImageStore } from "@/stores/html-to-image-store";
|
||||
|
||||
const DEFAULT_STATE = useHtmlToImageStore.getState();
|
||||
|
||||
function state() {
|
||||
return useHtmlToImageStore.getState();
|
||||
}
|
||||
|
||||
function okJson(data: unknown) {
|
||||
return Promise.resolve({
|
||||
ok: true,
|
||||
json: () => Promise.resolve(data),
|
||||
} as Response);
|
||||
}
|
||||
|
||||
function failJson(data: unknown) {
|
||||
return Promise.resolve({
|
||||
ok: false,
|
||||
json: () => Promise.resolve(data),
|
||||
} as Response);
|
||||
}
|
||||
|
||||
describe("useHtmlToImageStore", () => {
|
||||
beforeEach(() => {
|
||||
useHtmlToImageStore.setState({ ...DEFAULT_STATE }, true);
|
||||
fetchMock.mockReset();
|
||||
vi.mocked(formatHeaders).mockClear();
|
||||
});
|
||||
|
||||
it("clears stale errors when switching input mode and editing input", () => {
|
||||
useHtmlToImageStore.setState({ error: "old error" });
|
||||
|
||||
state().setMode("html");
|
||||
expect(state().mode).toBe("html");
|
||||
expect(state().error).toBeNull();
|
||||
|
||||
useHtmlToImageStore.setState({ error: "old error" });
|
||||
state().setHtmlContent("<main>Test</main>");
|
||||
expect(state().htmlContent).toBe("<main>Test</main>");
|
||||
expect(state().error).toBeNull();
|
||||
|
||||
useHtmlToImageStore.setState({ error: "old error" });
|
||||
state().setUrl("https://example.com");
|
||||
expect(state().url).toBe("https://example.com");
|
||||
expect(state().error).toBeNull();
|
||||
});
|
||||
|
||||
it("updates capture settings without clearing unrelated state", () => {
|
||||
state().setFormat("webp");
|
||||
state().setQuality(82);
|
||||
state().setFullPage(true);
|
||||
state().setDevicePreset("custom");
|
||||
state().setViewportWidth(390);
|
||||
state().setViewportHeight(844);
|
||||
|
||||
expect(state()).toMatchObject({
|
||||
format: "webp",
|
||||
quality: 82,
|
||||
fullPage: true,
|
||||
devicePreset: "custom",
|
||||
viewportWidth: 390,
|
||||
viewportHeight: 844,
|
||||
});
|
||||
});
|
||||
|
||||
it("does not capture when the current mode has no input", async () => {
|
||||
await state().capture();
|
||||
expect(fetchMock).not.toHaveBeenCalled();
|
||||
|
||||
state().setMode("html");
|
||||
state().setUrl("https://example.com");
|
||||
await state().capture();
|
||||
|
||||
expect(fetchMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not start a second capture while already capturing", async () => {
|
||||
state().setUrl("https://example.com");
|
||||
useHtmlToImageStore.setState({ capturing: true });
|
||||
|
||||
await state().capture();
|
||||
|
||||
expect(fetchMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("posts URL capture options and stores successful result metadata", async () => {
|
||||
fetchMock.mockResolvedValueOnce(
|
||||
await okJson({ downloadUrl: "/downloads/result.png", processedSize: 1234 }),
|
||||
);
|
||||
state().setUrl("https://example.com");
|
||||
state().setFormat("jpg");
|
||||
state().setQuality(75);
|
||||
state().setFullPage(true);
|
||||
state().setDevicePreset("mobile");
|
||||
state().setViewportWidth(414);
|
||||
state().setViewportHeight(896);
|
||||
|
||||
await state().capture();
|
||||
|
||||
expect(fetchMock).toHaveBeenCalledTimes(1);
|
||||
const [url, options] = fetchMock.mock.calls[0] as [string, RequestInit];
|
||||
expect(url).toBe("/api/v1/tools/image/html-to-image");
|
||||
expect(options.method).toBe("POST");
|
||||
expect(JSON.parse(options.body as string)).toEqual({
|
||||
url: "https://example.com",
|
||||
format: "jpg",
|
||||
quality: 75,
|
||||
fullPage: true,
|
||||
devicePreset: "mobile",
|
||||
viewportWidth: 414,
|
||||
viewportHeight: 896,
|
||||
});
|
||||
expect(state().resultUrl).toBe("/downloads/result.png");
|
||||
expect(state().resultSize).toBe(1234);
|
||||
expect(state().capturing).toBe(false);
|
||||
expect(state().error).toBeNull();
|
||||
});
|
||||
|
||||
it("posts HTML content instead of URL in html mode", async () => {
|
||||
fetchMock.mockResolvedValueOnce(await okJson({ downloadUrl: "/out.png", processedSize: 10 }));
|
||||
state().setMode("html");
|
||||
state().setHtmlContent("<h1>Hello</h1>");
|
||||
|
||||
await state().capture();
|
||||
|
||||
const options = fetchMock.mock.calls[0][1] as RequestInit;
|
||||
expect(JSON.parse(options.body as string)).toMatchObject({
|
||||
html: "<h1>Hello</h1>",
|
||||
format: "png",
|
||||
});
|
||||
expect(JSON.parse(options.body as string)).not.toHaveProperty("url");
|
||||
});
|
||||
|
||||
it("prefers details then error then fallback text for failed captures", async () => {
|
||||
state().setUrl("https://example.com");
|
||||
fetchMock.mockResolvedValueOnce(await failJson({ details: "Invalid URL" }));
|
||||
await state().capture();
|
||||
expect(state().error).toBe("Invalid URL");
|
||||
|
||||
fetchMock.mockResolvedValueOnce(await failJson({ error: "Timed out" }));
|
||||
await state().capture();
|
||||
expect(state().error).toBe("Timed out");
|
||||
|
||||
fetchMock.mockResolvedValueOnce(await failJson({}));
|
||||
await state().capture();
|
||||
expect(state().error).toBe("Capture failed");
|
||||
});
|
||||
|
||||
it("stores network error messages and non-Error fallback text", async () => {
|
||||
state().setUrl("https://example.com");
|
||||
fetchMock.mockRejectedValueOnce(new Error("Network down"));
|
||||
|
||||
await state().capture();
|
||||
expect(state().error).toBe("Network down");
|
||||
expect(state().capturing).toBe(false);
|
||||
|
||||
fetchMock.mockRejectedValueOnce("offline");
|
||||
await state().capture();
|
||||
expect(state().error).toBe("Network error");
|
||||
expect(state().capturing).toBe(false);
|
||||
});
|
||||
|
||||
it("reset restores defaults after a completed capture", async () => {
|
||||
fetchMock.mockResolvedValueOnce(await okJson({ downloadUrl: "/out.png", processedSize: 10 }));
|
||||
state().setMode("html");
|
||||
state().setHtmlContent("<p>Done</p>");
|
||||
state().setQuality(40);
|
||||
await state().capture();
|
||||
|
||||
state().reset();
|
||||
|
||||
expect(state()).toMatchObject({
|
||||
mode: "url",
|
||||
url: "",
|
||||
htmlContent: "",
|
||||
format: "png",
|
||||
quality: 90,
|
||||
fullPage: false,
|
||||
devicePreset: "desktop",
|
||||
viewportWidth: 1280,
|
||||
viewportHeight: 720,
|
||||
capturing: false,
|
||||
resultUrl: null,
|
||||
resultSize: null,
|
||||
error: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user