217 lines
5.9 KiB
TypeScript
217 lines
5.9 KiB
TypeScript
import { PANEL_SETTINGS } from "$lib/constants";
|
|
import { imageState } from "$states/image.svelte";
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
type ImageEventHandler = ((this: HTMLImageElement, ev?: Event) => void) | null;
|
|
type ImageErrorEventHandler =
|
|
| ((
|
|
this: HTMLImageElement,
|
|
ev?: string | Event,
|
|
source?: string,
|
|
lineno?: number,
|
|
colno?: number,
|
|
error?: Error,
|
|
) => void)
|
|
| null;
|
|
|
|
let lastOnload: ImageEventHandler = null;
|
|
let lastOnerror: ImageErrorEventHandler = null;
|
|
|
|
vi.stubGlobal(
|
|
"Image",
|
|
class {
|
|
_onload: ImageEventHandler = null;
|
|
_onerror: ImageErrorEventHandler = null;
|
|
_src: string = "";
|
|
crossOrigin: string = "";
|
|
|
|
set src(val: string) {
|
|
this._src = val;
|
|
if (val.includes("error")) {
|
|
setTimeout(
|
|
() => this._onerror?.call(this as unknown as HTMLImageElement, new Event("error")),
|
|
1,
|
|
);
|
|
} else {
|
|
setTimeout(
|
|
() => this._onload?.call(this as unknown as HTMLImageElement, new Event("load")),
|
|
1,
|
|
);
|
|
}
|
|
}
|
|
get src() {
|
|
return this._src;
|
|
}
|
|
|
|
set onload(val: ImageEventHandler) {
|
|
this._onload = val;
|
|
if (val) lastOnload = val;
|
|
}
|
|
get onload() {
|
|
return this._onload;
|
|
}
|
|
|
|
set onerror(val: ImageErrorEventHandler) {
|
|
this._onerror = val;
|
|
if (val) lastOnerror = val;
|
|
}
|
|
get onerror() {
|
|
return this._onerror;
|
|
}
|
|
},
|
|
);
|
|
|
|
describe("imageState", () => {
|
|
beforeEach(() => {
|
|
lastOnload = null;
|
|
lastOnerror = null;
|
|
imageState.reset();
|
|
});
|
|
|
|
it("should create new instance with default values", () => {
|
|
const newState = new imageState();
|
|
expect(newState.image).toBeUndefined();
|
|
expect(newState.imageLink).toBe("");
|
|
expect(newState.imageReady).toBe(false);
|
|
expect(newState.cropLeft).toBe(0);
|
|
expect(newState.cropTop).toBe(0);
|
|
expect(newState.cropRight).toBe(0);
|
|
expect(newState.cropBottom).toBe(0);
|
|
newState.destroy();
|
|
});
|
|
|
|
it("should initialize with default background image", async () => {
|
|
await imageState.uploadImageByLink(PANEL_SETTINGS.DEFAULT_BACKGROUND_IMAGE);
|
|
|
|
expect(imageState.imageReady).toBe(true);
|
|
expect(imageState.image).toBeDefined();
|
|
expect(imageState.imageLink).toBe(PANEL_SETTINGS.DEFAULT_BACKGROUND_IMAGE);
|
|
});
|
|
|
|
it("should handle manual image upload correctly", async () => {
|
|
const testLink = "https://example.com/test.png";
|
|
const uploadPromise = imageState.uploadImageByLink(testLink);
|
|
|
|
expect(imageState.imageReady).toBe(false);
|
|
|
|
await uploadPromise;
|
|
expect(imageState.imageReady).toBe(true);
|
|
expect(imageState.imageLink).toBe(testLink);
|
|
});
|
|
|
|
it("should reset state to defaults", async () => {
|
|
await imageState.uploadImageByLink("some-image.png");
|
|
imageState.cropLeft = 100;
|
|
|
|
imageState.reset();
|
|
|
|
expect(imageState.imageReady).toBe(false);
|
|
expect(imageState.imageLink).toBe("");
|
|
expect(imageState.cropLeft).toBe(0);
|
|
expect(imageState.image).toBeUndefined();
|
|
});
|
|
|
|
it("should handle image loading error", async () => {
|
|
await expect(imageState.uploadImageByLink("error-link")).rejects.toThrow(
|
|
"Failed to load image",
|
|
);
|
|
|
|
expect(imageState.imageReady).toBe(false);
|
|
});
|
|
|
|
it("should abort previous upload when new upload starts", async () => {
|
|
const upload1 = imageState.uploadImageByLink("test1.jpg");
|
|
const upload2 = imageState.uploadImageByLink("test2.jpg");
|
|
|
|
await expect(upload1).rejects.toThrow("Aborted");
|
|
await expect(upload2).resolves.toBeUndefined();
|
|
|
|
expect(imageState.imageLink).toBe("test2.jpg");
|
|
});
|
|
|
|
it("should cleanup previous image before loading new one", async () => {
|
|
await imageState.uploadImageByLink("test1.jpg");
|
|
const firstImage = imageState.image;
|
|
|
|
await imageState.uploadImageByLink("test2.jpg");
|
|
|
|
expect(firstImage?.onload).toBeNull();
|
|
expect(firstImage?.onerror).toBeNull();
|
|
});
|
|
|
|
it("should set crop values", () => {
|
|
imageState.cropLeft = 10;
|
|
imageState.cropTop = 20;
|
|
imageState.cropRight = 30;
|
|
imageState.cropBottom = 40;
|
|
|
|
expect(imageState.cropLeft).toBe(10);
|
|
expect(imageState.cropTop).toBe(20);
|
|
expect(imageState.cropRight).toBe(30);
|
|
expect(imageState.cropBottom).toBe(40);
|
|
});
|
|
|
|
it("should cleanup image event handlers on reset", async () => {
|
|
await imageState.uploadImageByLink("test.jpg");
|
|
const img = imageState.image;
|
|
|
|
imageState.reset();
|
|
|
|
expect(img?.onload).toBeNull();
|
|
expect(img?.onerror).toBeNull();
|
|
});
|
|
|
|
it("should abort ongoing upload on reset", async () => {
|
|
const upload = imageState.uploadImageByLink("test.jpg");
|
|
|
|
imageState.reset();
|
|
|
|
await expect(upload).rejects.toThrow("Aborted");
|
|
});
|
|
|
|
it("should cleanup resources on destroy", async () => {
|
|
await imageState.uploadImageByLink("test.jpg");
|
|
const img = imageState.image;
|
|
|
|
imageState.destroy();
|
|
|
|
expect(imageState.image).toBeUndefined();
|
|
expect(img?.onload).toBeNull();
|
|
expect(img?.onerror).toBeNull();
|
|
});
|
|
|
|
it("should abort ongoing upload on destroy", async () => {
|
|
const upload = imageState.uploadImageByLink("test.jpg");
|
|
|
|
imageState.destroy();
|
|
|
|
await expect(upload).rejects.toThrow("Aborted");
|
|
});
|
|
|
|
it("should cover aborted onload branch", async () => {
|
|
const promise = imageState.uploadImageByLink("test.png");
|
|
|
|
imageState.destroy();
|
|
|
|
if (lastOnload) {
|
|
lastOnload.call(new Image() as HTMLImageElement, new Event("load"));
|
|
}
|
|
|
|
await expect(promise).rejects.toThrow();
|
|
expect(imageState.imageReady).toBe(false);
|
|
});
|
|
|
|
it("should cover aborted onerror branch", async () => {
|
|
const promise = imageState.uploadImageByLink("test.png");
|
|
|
|
imageState.destroy();
|
|
|
|
if (lastOnerror) {
|
|
lastOnerror.call(new Image() as HTMLImageElement, new Event("load"));
|
|
}
|
|
|
|
await expect(promise).rejects.toThrow();
|
|
expect(imageState.imageReady).toBe(false);
|
|
});
|
|
});
|