Files
vueecharts/tests/wc.test.ts

167 lines
4.8 KiB
TypeScript

import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
declare global {
interface HTMLElement {
__dispose?: (() => void) | null;
}
}
type LoadOptions = { suffix?: string };
const loadModule = (() => {
let counter = 0;
return async (mode: "stub" | "native", options?: LoadOptions) => {
const suffix = options?.suffix ?? `${mode}-${++counter}`;
return import(/* @vite-ignore */ `../src/wc?${suffix}`);
};
})();
describe("register", () => {
describe("with stubbed customElements", () => {
class CustomElementRegistryStub {
private readonly registry = new Map<string, CustomElementConstructor>();
define(name: string, ctor: CustomElementConstructor): void {
if (this.registry.has(name)) {
throw new DOMException("already defined", "NotSupportedError");
}
this.registry.set(name, ctor);
}
get(name: string): CustomElementConstructor | undefined {
return this.registry.get(name);
}
}
let registry: CustomElementRegistryStub;
beforeEach(() => {
vi.resetModules();
vi.unstubAllGlobals();
registry = new CustomElementRegistryStub();
vi.stubGlobal(
"customElements",
registry as unknown as CustomElementRegistry,
);
});
afterEach(() => {
vi.unstubAllGlobals();
vi.restoreAllMocks();
});
it("returns false when custom elements are unavailable", async () => {
vi.unstubAllGlobals();
vi.stubGlobal(
"customElements",
undefined as unknown as CustomElementRegistry,
);
const { register } = await loadModule("stub");
expect(register()).toBe(false);
expect(register()).toBe(false);
});
it("returns false when browser APIs are disabled", async () => {
vi.resetModules();
// Simulate missing browser API by providing a registry without `get`
vi.stubGlobal("customElements", {
define() {},
} as unknown as CustomElementRegistry);
const { register } = await loadModule("stub", { suffix: "no-get" });
expect(register()).toBe(false);
expect(register()).toBe(false);
});
it("registers the custom element once", async () => {
const defineSpy = vi.spyOn(registry, "define");
const { register, TAG_NAME } = await loadModule("stub");
expect(register()).toBe(true);
expect(defineSpy).toHaveBeenCalledTimes(1);
expect(registry.get(TAG_NAME)).toBeTypeOf("function");
defineSpy.mockClear();
expect(register()).toBe(true);
expect(defineSpy).not.toHaveBeenCalled();
});
it("handles definition failures gracefully", async () => {
const defineSpy = vi.spyOn(registry, "define").mockImplementation(() => {
throw new Error("boom");
});
const { register, TAG_NAME } = await loadModule("stub");
expect(register()).toBe(false);
expect(register()).toBe(false);
expect(defineSpy).toHaveBeenCalledTimes(1);
expect(registry.get(TAG_NAME)).toBeUndefined();
});
it("skips redefinition when element already registered", async () => {
const existing = class extends HTMLElement {};
const { register, TAG_NAME } = await loadModule("stub");
registry.define(TAG_NAME, existing);
const defineSpy = vi.spyOn(registry, "define");
expect(register()).toBe(true);
expect(defineSpy).not.toHaveBeenCalled();
expect(registry.get(TAG_NAME)).toBe(existing);
});
it("exposes a constructor with disconnect hook", async () => {
const { register, TAG_NAME } = await loadModule("stub");
expect(register()).toBe(true);
const ctor = registry.get(TAG_NAME);
expect(typeof ctor).toBe("function");
expect("disconnectedCallback" in (ctor?.prototype ?? {})).toBe(true);
});
});
describe("with native customElements", () => {
let original: CustomElementConstructor | undefined;
beforeEach(() => {
vi.restoreAllMocks();
vi.unstubAllGlobals();
original = customElements.get("x-vue-echarts");
document.body.innerHTML = "";
});
afterEach(() => {
document.body.innerHTML = "";
if (original) {
customElements.define("x-vue-echarts", original);
}
});
it("disposes chart when element is removed from DOM", async () => {
const { register, TAG_NAME } = await loadModule("native");
expect(register()).toBe(true);
const element = document.createElement(TAG_NAME) as HTMLElement & {
__dispose: (() => void) | null;
};
const dispose = vi.fn();
element.__dispose = dispose;
document.body.appendChild(element);
document.body.removeChild(element);
await Promise.resolve();
expect(dispose).toHaveBeenCalledTimes(1);
expect(element.__dispose).toBeNull();
});
});
});