mirror of
https://github.com/ecomfe/vue-echarts.git
synced 2025-12-11 19:22:43 +08:00
test: clean up code and improve test coverage
This commit is contained in:
@@ -185,10 +185,6 @@ export default defineComponent({
|
||||
});
|
||||
|
||||
function init() {
|
||||
if (!root.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
const instance = (chart.value = initChart(
|
||||
root.value,
|
||||
realTheme.value,
|
||||
@@ -355,6 +351,7 @@ export default defineComponent({
|
||||
// transition.
|
||||
root.value.__dispose = cleanup;
|
||||
} else {
|
||||
/* c8 ignore next */
|
||||
cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -109,14 +109,7 @@ export function useSlotOption(slots: Slots, onSlotsChange: () => void) {
|
||||
.forEach((key) => {
|
||||
const [prefix, ...rest] = key.split("-") as [SlotPrefix, ...string[]];
|
||||
const tail = SLOT_OPTION_PATHS[prefix];
|
||||
if (!tail) {
|
||||
return;
|
||||
}
|
||||
|
||||
const path = [...rest, ...tail];
|
||||
if (path.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Traverse to the parent of the leaf, cloning or creating along the way
|
||||
let cur: Record<string, unknown> | undefined = root;
|
||||
|
||||
@@ -43,3 +43,8 @@ export function register(): boolean {
|
||||
registered = true;
|
||||
return registered;
|
||||
}
|
||||
|
||||
// Test helper to reset cached registration state.
|
||||
export function __resetRegisterState(): void {
|
||||
registered = null;
|
||||
}
|
||||
|
||||
@@ -181,4 +181,47 @@ describe("useAutoresize", () => {
|
||||
|
||||
scope.stop();
|
||||
});
|
||||
|
||||
it("skips the initial resize callback when dimensions are unchanged", async () => {
|
||||
const resize = vi.fn();
|
||||
const chart = ref<EChartsType | undefined>();
|
||||
const autoresize = ref<AutoResize | undefined>(true);
|
||||
const root = ref<HTMLElement | undefined>();
|
||||
const container = createSizedContainer(160, 90);
|
||||
|
||||
const originalRO = globalThis.ResizeObserver;
|
||||
const callbacks: Array<() => void> = [];
|
||||
|
||||
class StubResizeObserver {
|
||||
callback: ResizeObserverCallback;
|
||||
observe = vi.fn();
|
||||
disconnect = vi.fn();
|
||||
|
||||
constructor(cb: ResizeObserverCallback) {
|
||||
this.callback = cb;
|
||||
callbacks.push(() => cb([], this as unknown as ResizeObserver));
|
||||
}
|
||||
}
|
||||
|
||||
(globalThis as any).ResizeObserver = StubResizeObserver as any;
|
||||
|
||||
const scope = effectScope();
|
||||
scope.run(() => {
|
||||
useAutoresize(
|
||||
chart as Ref<EChartsType | undefined>,
|
||||
autoresize as Ref<AutoResize | undefined>,
|
||||
root as Ref<HTMLElement | undefined>,
|
||||
);
|
||||
});
|
||||
|
||||
chart.value = { resize } as unknown as EChartsType;
|
||||
root.value = container;
|
||||
await nextTick();
|
||||
|
||||
callbacks[0]?.();
|
||||
expect(resize).not.toHaveBeenCalled();
|
||||
|
||||
scope.stop();
|
||||
(globalThis as any).ResizeObserver = originalRO;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -413,7 +413,13 @@ describe("ECharts component", () => {
|
||||
await nextTick();
|
||||
|
||||
chartStub.dispose.mockClear();
|
||||
exposed.value.root.value = undefined;
|
||||
Object.defineProperty(exposed.value.root, "value", {
|
||||
configurable: true,
|
||||
get: () => undefined,
|
||||
set: () => {
|
||||
/* ignore */
|
||||
},
|
||||
});
|
||||
|
||||
screen.unmount();
|
||||
await nextTick();
|
||||
@@ -842,4 +848,34 @@ describe("ECharts component", () => {
|
||||
|
||||
warnSpy.mockRestore();
|
||||
});
|
||||
|
||||
it("ignores falsy listeners during event binding", async () => {
|
||||
const option = ref({});
|
||||
const exposed = shallowRef<any>();
|
||||
|
||||
renderChart(
|
||||
() => ({ option: option.value, onClick: undefined as unknown as () => {} }),
|
||||
exposed,
|
||||
);
|
||||
await nextTick();
|
||||
|
||||
expect(chartStub.on).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("skips option watcher when chart instance is missing", async () => {
|
||||
const option = ref<any>(null);
|
||||
const exposed = shallowRef<any>();
|
||||
|
||||
init.mockImplementation(() => undefined as any);
|
||||
|
||||
renderChart(() => ({ option: option.value }), exposed);
|
||||
await nextTick();
|
||||
|
||||
chartStub.setOption.mockClear();
|
||||
|
||||
option.value = { title: { text: "later" } };
|
||||
await nextTick();
|
||||
|
||||
expect(chartStub.setOption).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -43,6 +43,20 @@ describe("smart-update", () => {
|
||||
expect(summary?.idsSorted).toEqual(["1", "2"]);
|
||||
expect(summary?.noIdCount).toBe(3);
|
||||
});
|
||||
|
||||
it("counts primitive array items and sorts scalar keys", () => {
|
||||
const option: EChartsOption = {
|
||||
dataset: ["raw", { id: "has-id" }],
|
||||
backgroundColor: "#000",
|
||||
color: "#fff",
|
||||
} as unknown as EChartsOption;
|
||||
|
||||
const signature = buildSignature(option);
|
||||
|
||||
expect(signature.arrays.dataset?.noIdCount).toBe(1);
|
||||
expect(signature.arrays.dataset?.idsSorted).toEqual(["has-id"]);
|
||||
expect(signature.scalars).toEqual(["backgroundColor", "color"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("planUpdate", () => {
|
||||
@@ -364,6 +378,38 @@ describe("smart-update", () => {
|
||||
expect(result.plan.notMerge).toBe(false);
|
||||
expect(result.option.series).not.toEqual(base.series);
|
||||
});
|
||||
|
||||
it("adds replaceMerge when ids disappear entirely", () => {
|
||||
const base: EChartsOption = {
|
||||
series: [
|
||||
{ id: "espresso", type: "bar" },
|
||||
{ id: "mocha", type: "line" },
|
||||
],
|
||||
};
|
||||
|
||||
const update: EChartsOption = {
|
||||
series: [{ type: "bar" }, { type: "line" }],
|
||||
};
|
||||
|
||||
const result = planUpdate(buildSignature(base), update);
|
||||
|
||||
expect(result.plan.replaceMerge).toEqual(["series"]);
|
||||
expect(result.option.series).toEqual(update.series);
|
||||
});
|
||||
|
||||
it("ignores undefined array summaries carried over in previous signatures", () => {
|
||||
const base: EChartsOption = {
|
||||
series: [{ id: "flat-white", type: "bar" }],
|
||||
};
|
||||
|
||||
const prev = buildSignature(base);
|
||||
(prev.arrays as Record<string, unknown>).phantom = undefined;
|
||||
|
||||
const result = planUpdate(prev, base);
|
||||
|
||||
expect(result.plan.notMerge).toBe(false);
|
||||
expect(result.plan.replaceMerge).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,15 +6,11 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
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}`);
|
||||
};
|
||||
})();
|
||||
const loadModule = async () => {
|
||||
const mod = await import("../src/wc");
|
||||
mod.__resetRegisterState();
|
||||
return mod;
|
||||
};
|
||||
|
||||
describe("register", () => {
|
||||
describe("with stubbed customElements", () => {
|
||||
@@ -58,7 +54,7 @@ describe("register", () => {
|
||||
undefined as unknown as CustomElementRegistry,
|
||||
);
|
||||
|
||||
const { register } = await loadModule("stub");
|
||||
const { register } = await loadModule();
|
||||
|
||||
expect(register()).toBe(false);
|
||||
expect(register()).toBe(false);
|
||||
@@ -71,7 +67,7 @@ describe("register", () => {
|
||||
define() {},
|
||||
} as unknown as CustomElementRegistry);
|
||||
|
||||
const { register } = await loadModule("stub", { suffix: "no-get" });
|
||||
const { register } = await loadModule();
|
||||
expect(register()).toBe(false);
|
||||
expect(register()).toBe(false);
|
||||
});
|
||||
@@ -79,7 +75,7 @@ describe("register", () => {
|
||||
it("registers the custom element once", async () => {
|
||||
const defineSpy = vi.spyOn(registry, "define");
|
||||
|
||||
const { register, TAG_NAME } = await loadModule("stub");
|
||||
const { register, TAG_NAME } = await loadModule();
|
||||
|
||||
expect(register()).toBe(true);
|
||||
expect(defineSpy).toHaveBeenCalledTimes(1);
|
||||
@@ -95,17 +91,17 @@ describe("register", () => {
|
||||
throw new Error("boom");
|
||||
});
|
||||
|
||||
const { register, TAG_NAME } = await loadModule("stub");
|
||||
const { register, TAG_NAME } = await loadModule();
|
||||
|
||||
expect(register()).toBe(false);
|
||||
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");
|
||||
const { register, TAG_NAME } = await loadModule();
|
||||
registry.define(TAG_NAME, existing);
|
||||
|
||||
const defineSpy = vi.spyOn(registry, "define");
|
||||
@@ -116,7 +112,7 @@ describe("register", () => {
|
||||
});
|
||||
|
||||
it("exposes a constructor with disconnect hook", async () => {
|
||||
const { register, TAG_NAME } = await loadModule("stub");
|
||||
const { register, TAG_NAME } = await loadModule();
|
||||
|
||||
expect(register()).toBe(true);
|
||||
|
||||
@@ -130,6 +126,7 @@ describe("register", () => {
|
||||
let original: CustomElementConstructor | undefined;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetModules();
|
||||
vi.restoreAllMocks();
|
||||
vi.unstubAllGlobals();
|
||||
original = customElements.get("x-vue-echarts");
|
||||
@@ -144,7 +141,7 @@ describe("register", () => {
|
||||
});
|
||||
|
||||
it("disposes chart when element is removed from DOM", async () => {
|
||||
const { register, TAG_NAME } = await loadModule("native");
|
||||
const { register, TAG_NAME } = await loadModule();
|
||||
|
||||
expect(register()).toBe(true);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user