mirror of
https://github.com/ecomfe/vue-echarts.git
synced 2025-10-27 02:43:58 +08:00
167 lines
4.8 KiB
TypeScript
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();
|
|
});
|
|
});
|
|
});
|