import { describe, it, expect, beforeEach, vi } from "vitest"; import { defineComponent, h, nextTick, provide, ref, shallowRef } from "vue"; import { render } from "./helpers/testing"; import { init, enqueueChart, resetECharts, type ChartStub, } from "./helpers/mock"; import type { InitOptions, Option, UpdateOptions } from "../src/types"; import { withConsoleWarn } from "./helpers/dom"; import ECharts, { UPDATE_OPTIONS_KEY } from "../src/ECharts"; import { renderChart } from "./helpers/renderChart"; let chartStub: ChartStub; beforeEach(() => { resetECharts(); chartStub = enqueueChart(); }); describe("ECharts component", () => { it("initializes and reacts to reactive props", async () => { const option = ref({ title: { text: "coffee" } }); const group = ref("group-a"); const exposed = shallowRef(); const screen = renderChart( () => ({ option: option.value, group: group.value }), exposed, ); await nextTick(); expect(init).toHaveBeenCalledTimes(1); const [rootEl, theme, initOptions] = init.mock.calls[0]; expect(rootEl).toBeInstanceOf(HTMLElement); expect(theme).toBeNull(); expect(initOptions).toBeUndefined(); expect(chartStub.setOption).toHaveBeenCalledTimes(1); expect(chartStub.setOption.mock.calls[0][0]).toMatchObject({ title: { text: "coffee" }, }); expect(chartStub.group).toBe("group-a"); option.value = { title: { text: "latte" } }; await nextTick(); expect(chartStub.setOption).toHaveBeenCalledTimes(2); expect(chartStub.setOption.mock.calls[1][0]).toMatchObject({ title: { text: "latte" }, }); group.value = "group-b"; await nextTick(); expect(chartStub.group).toBe("group-b"); screen.unmount(); await nextTick(); expect(chartStub.dispose).toHaveBeenCalledTimes(1); }); it("exposes setOption for manual updates", async () => { const optionRef = ref(); const exposed = shallowRef(); renderChart( () => ({ option: optionRef.value, manualUpdate: true }), exposed, ); await nextTick(); expect(typeof exposed.value?.setOption).toBe("function"); const manualOption = { series: [{ type: "bar", data: [1, 2, 3] }] }; exposed.value.setOption(manualOption); expect(chartStub.setOption).toHaveBeenCalledTimes(1); expect(chartStub.setOption.mock.calls[0][0]).toMatchObject(manualOption); expect(chartStub.setOption.mock.calls[0][1]).toEqual({}); }); it("ignores setOption when manual-update is false", async () => { const option = ref({ title: { text: "initial" } }); const exposed = shallowRef(); renderChart(() => ({ option: option.value }), exposed); await nextTick(); const initialCalls = chartStub.setOption.mock.calls.length; withConsoleWarn((warnSpy) => { exposed.value.setOption({ title: { text: "ignored" } }, true); expect(chartStub.setOption).toHaveBeenCalledTimes(initialCalls); expect(warnSpy).toHaveBeenCalledWith( expect.stringContaining( "[vue-echarts] `setOption` is only available when `manual-update` is `true`.", ), ); }); }); it("warns when option prop changes in manual-update mode", async () => { const option = ref({ title: { text: "initial" } }); const exposed = shallowRef(); renderChart(() => ({ option: option.value, manualUpdate: true }), exposed); await nextTick(); const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => { // noop }); option.value = { title: { text: "next" } }; await nextTick(); expect(warnSpy).toHaveBeenCalled(); expect(warnSpy.mock.calls[0][0]).toContain( "[vue-echarts] `option` prop changes are ignored when `manual-update` is `true`.", ); warnSpy.mockRestore(); }); it("does not replay manual option after initOptions-triggered reinit", async () => { const initOptions = ref({ renderer: "canvas" }); const exposed = shallowRef(); renderChart( () => ({ manualUpdate: true, initOptions: initOptions.value }), exposed, ); await nextTick(); const manualOption: Option = { title: { text: "manual" }, series: [{ type: "bar", data: [1, 2, 3] }], }; exposed.value.setOption(manualOption); expect(chartStub.setOption).toHaveBeenCalledTimes(1); expect(chartStub.setOption.mock.calls[0][0]).toMatchObject(manualOption); const firstStub = chartStub; const replacementStub = enqueueChart(); chartStub = replacementStub; initOptions.value = { renderer: "svg" as const }; await nextTick(); expect(firstStub.dispose).toHaveBeenCalledTimes(1); expect(replacementStub.setOption).not.toHaveBeenCalled(); }); it("re-initializes manual chart from option prop after reinit", async () => { const option = ref