refactor(graphic): simplify runtime internals

This commit is contained in:
Justineo
2026-02-08 10:11:00 +08:00
committed by GU Yiling
parent 3eb2f6ef6f
commit 6a09e5e0f3
10 changed files with 185 additions and 245 deletions

View File

@@ -11,43 +11,43 @@ export function mergeProps(
keys: readonly string[],
props: Record<string, unknown>,
): void {
keys.forEach((key) => {
for (const key of keys) {
if (props[key] !== undefined) {
target[key] = props[key];
}
});
}
}
export function buildStyle(
props: Record<string, unknown>,
extraKeys: readonly string[],
): Record<string, unknown> | undefined {
const style = { ...(props.style as Record<string, unknown> | undefined) };
mergeProps(style, BASE_STYLE_KEYS, props);
mergeProps(style, extraKeys, props);
const out = { ...(props.style as Record<string, unknown> | undefined) };
mergeProps(out, BASE_STYLE_KEYS, props);
mergeProps(out, extraKeys, props);
if (props.styleTransition !== undefined) {
style.transition = props.styleTransition;
out.transition = props.styleTransition;
}
return Object.keys(style).length > 0 ? style : undefined;
return Object.keys(out).length ? out : undefined;
}
export function buildShape(
type: string,
props: Record<string, unknown>,
): Record<string, unknown> | undefined {
const shape = { ...(props.shape as Record<string, unknown> | undefined) };
const shapeKeys = SHAPE_KEYS_BY_TYPE[type];
if (shapeKeys) {
mergeProps(shape, shapeKeys, props);
const out = { ...(props.shape as Record<string, unknown> | undefined) };
const keys = SHAPE_KEYS_BY_TYPE[type];
if (keys) {
mergeProps(out, keys, props);
}
if (props.shapeTransition !== undefined) {
shape.transition = props.shapeTransition;
out.transition = props.shapeTransition;
}
return Object.keys(shape).length > 0 ? shape : undefined;
return Object.keys(out).length ? out : undefined;
}
export function buildInfo(node: GraphicNode): unknown {

View File

@@ -16,45 +16,43 @@ type BuildResult = {
snapshot: GraphicSnapshot;
};
function buildElementOption(node: GraphicNode, children: Option[] | undefined): Option {
const element: Record<string, unknown> = {
function toElement(node: GraphicNode, children?: Option[]): Option {
const out: Record<string, unknown> = {
type: node.type,
id: node.id,
};
const common = pruneCommonPropsByType(node.type, pickCommonProps(node.props));
Object.assign(element, common);
Object.assign(out, pruneCommonPropsByType(node.type, pickCommonProps(node.props)));
if (isGroupGraphic(node.type)) {
if (children) {
element.children = children;
if (children?.length) {
out.children = children;
}
const info = buildInfo(node);
if (info !== undefined) {
element.info = info;
out.info = info;
}
return element as Option;
return out as Option;
}
const shapeKeys = SHAPE_KEYS_BY_TYPE[node.type];
if (shapeKeys) {
if (SHAPE_KEYS_BY_TYPE[node.type]) {
const shape = buildShape(node.type, node.props);
if (shape) {
element.shape = shape;
out.shape = shape;
}
}
const style = buildStyle(node.props, styleKeysByType(node.type));
if (style) {
element.style = style;
out.style = style;
}
const info = buildInfo(node);
if (info !== undefined) {
element.info = info;
out.info = info;
}
return element as Option;
return out as Option;
}
export function buildGraphicOption(nodes: Iterable<GraphicNode>, rootId: string): BuildResult {
@@ -65,9 +63,12 @@ export function buildGraphicOption(nodes: Iterable<GraphicNode>, rootId: string)
let hasDuplicateId = false;
for (const node of nodes) {
const list = byParent.get(node.parentId) ?? [];
list.push(node);
byParent.set(node.parentId, list);
const list = byParent.get(node.parentId);
if (list) {
list.push(node);
} else {
byParent.set(node.parentId, [node]);
}
if (ids.has(node.id)) {
hasDuplicateId = true;
@@ -82,24 +83,24 @@ export function buildGraphicOption(nodes: Iterable<GraphicNode>, rootId: string)
const snapshot: GraphicSnapshot = { ids, parentById, hasDuplicateId };
const buildChildren = (parentId: string | null): Option[] => {
const children = byParent.get(parentId) ?? [];
return children.map((child) =>
buildElementOption(child, child.type === "group" ? buildChildren(child.id) : undefined),
const childrenOf = (parentId: string | null): Option[] => {
const list = byParent.get(parentId) ?? [];
return list.map((node) =>
toElement(node, node.type === "group" ? childrenOf(node.id) : undefined),
);
};
const root: Record<string, unknown> = {
type: "group",
id: rootId,
$action: "replace",
children: buildChildren(null),
};
return {
option: {
graphic: {
elements: [root],
elements: [
{
type: "group",
id: rootId,
$action: "replace",
children: childrenOf(null),
},
],
},
} as Option,
snapshot,

View File

@@ -29,49 +29,39 @@ export type GraphicCollector = {
optionRef: Ref<Option | null>;
getNodes: () => Iterable<GraphicNode>;
getSnapshot: () => GraphicSnapshot;
setSnapshot: (snapshot: GraphicSnapshot) => void;
requestFlush: () => void;
getStructureVersion: () => number;
dispose: () => void;
};
export function createStableSerializer() {
type UnknownFn = (...args: unknown[]) => unknown;
const functionIds = new WeakMap<UnknownFn, number>();
const symbolIds = new Map<symbol, number>();
const objectIds = new WeakMap<object, number>();
let functionCursor = 0;
let symbolCursor = 0;
let objectCursor = 0;
let symbolId = 0;
let objectId = 0;
const ensureFunctionId = (fn: UnknownFn): number => {
let id = functionIds.get(fn);
const getSymbolId = (value: symbol): number => {
let id = symbolIds.get(value);
if (id == null) {
functionCursor += 1;
id = functionCursor;
functionIds.set(fn, id);
symbolId += 1;
id = symbolId;
symbolIds.set(value, id);
}
return id;
};
const ensureSymbolId = (symbol: symbol): number => {
let id = symbolIds.get(symbol);
const getObjectId = (value: object): number => {
let id = objectIds.get(value);
if (id == null) {
symbolCursor += 1;
id = symbolCursor;
symbolIds.set(symbol, id);
objectId += 1;
id = objectId;
objectIds.set(value, id);
}
return id;
};
const ensureObjectId = (object: object): number => {
let id = objectIds.get(object);
if (id == null) {
objectCursor += 1;
id = objectCursor;
objectIds.set(object, id);
}
return id;
const isPlainObject = (value: object): boolean => {
const prototype = Object.getPrototypeOf(value);
return prototype === Object.prototype || prototype === null;
};
const stringify = (value: unknown): string => {
@@ -82,37 +72,36 @@ export function createStableSerializer() {
return "n";
}
const valueType = typeof value;
if (valueType === "string") {
const t = typeof value;
if (t === "string") {
return `s:${JSON.stringify(value)}`;
}
if (valueType === "number") {
if (t === "number") {
return `d:${value}`;
}
if (valueType === "boolean") {
if (t === "boolean") {
return value ? "b:1" : "b:0";
}
if (valueType === "bigint") {
if (t === "bigint") {
return `g:${String(value)}`;
}
if (valueType === "symbol") {
return `y:${ensureSymbolId(value as symbol)}`;
if (t === "symbol") {
return `y:${getSymbolId(value as symbol)}`;
}
if (valueType === "function") {
return `f:${ensureFunctionId(value as UnknownFn)}`;
if (t === "function") {
return `o:${getObjectId(value as object)}`;
}
if (Array.isArray(value)) {
return `[${value.map((item) => stringify(item)).join(",")}]`;
}
const objectValue = value as object;
const prototype = Object.getPrototypeOf(objectValue);
if (prototype !== Object.prototype && prototype !== null) {
return `o:${ensureObjectId(objectValue)}`;
const obj = value as object;
if (!isPlainObject(obj)) {
return `o:${getObjectId(obj)}`;
}
const record = objectValue as Record<string, unknown>;
const record = obj as Record<string, unknown>;
const keys = Object.keys(record).sort();
return `{${keys.map((key) => `${key}:${stringify(record[key])}`).join(",")}}`;
};
@@ -120,7 +109,7 @@ export function createStableSerializer() {
return stringify;
}
function buildNodeFingerprint(
function nodeSig(
stringify: (value: unknown) => string,
node: Omit<GraphicNode, "order"> & { order: number },
): string {
@@ -140,25 +129,18 @@ export function createGraphicCollector(options: {
const nodes = new Map<string, GraphicNode>();
const warnedKeys = new Set<string>();
const optionRef = shallowRef<Option | null>(null);
const fingerprintById = new Map<string, string>();
const sigById = new Map<string, string>();
const passById = new Map<string, number>();
const stringify = createStableSerializer();
let order = 0;
let currentPass = 0;
let pass = 0;
let pending = false;
let disposed = false;
let structureVersion = 0;
const snapshot: GraphicSnapshot = {
ids: new Set(),
parentById: new Map(),
hasDuplicateId: false,
};
function beginPass(): void {
order = 0;
currentPass += 1;
pass += 1;
}
function warnOnce(key: string, message: string): void {
@@ -176,8 +158,7 @@ export function createGraphicCollector(options: {
const existing = nodes.get(node.id);
const existingPass = passById.get(node.id);
if (existing && existing.sourceId !== node.sourceId && existingPass === currentPass) {
snapshot.hasDuplicateId = true;
if (existing && existing.sourceId !== node.sourceId && existingPass === pass) {
warnOnce(`duplicate-id:${node.id}`, warnDuplicateId(node.id));
}
@@ -186,14 +167,14 @@ export function createGraphicCollector(options: {
order = node.order + 1;
}
const fingerprint = buildNodeFingerprint(stringify, { ...node, order: nextOrder });
const sig = nodeSig(stringify, { ...node, order: nextOrder });
if (
existing &&
existing.sourceId === node.sourceId &&
existing.order === nextOrder &&
fingerprintById.get(node.id) === fingerprint
sigById.get(node.id) === sig
) {
passById.set(node.id, currentPass);
passById.set(node.id, pass);
return;
}
@@ -202,9 +183,8 @@ export function createGraphicCollector(options: {
...node,
order: nextOrder,
});
fingerprintById.set(node.id, fingerprint);
passById.set(node.id, currentPass);
structureVersion += 1;
sigById.set(node.id, sig);
passById.set(node.id, pass);
requestFlush();
}
@@ -222,8 +202,7 @@ export function createGraphicCollector(options: {
}
nodes.delete(id);
passById.delete(id);
fingerprintById.delete(id);
structureVersion += 1;
sigById.delete(id);
requestFlush();
}
@@ -257,22 +236,12 @@ export function createGraphicCollector(options: {
return nodes.values();
}
function setSnapshot(next: GraphicSnapshot): void {
snapshot.ids = next.ids;
snapshot.parentById = next.parentById;
snapshot.hasDuplicateId = next.hasDuplicateId;
}
function getStructureVersion(): number {
return structureVersion;
}
function dispose(): void {
disposed = true;
pending = false;
nodes.clear();
passById.clear();
fingerprintById.clear();
sigById.clear();
warnedKeys.clear();
optionRef.value = null;
}
@@ -285,9 +254,7 @@ export function createGraphicCollector(options: {
optionRef,
getNodes,
getSnapshot,
setSnapshot,
requestFlush,
getStructureVersion,
dispose,
};
}

View File

@@ -23,35 +23,23 @@ const graphicProps = {
...graphicShapeProps,
} as const;
function resolveId(
function parseIdentity(
props: { id?: string | number },
instance: NonNullable<ReturnType<typeof getCurrentInstance>>,
): string {
instance: { uid: number; vnode: { key: unknown } },
): { id: string; key: string | null; missing: boolean } {
if (props.id != null) {
return String(props.id);
const id = String(props.id);
return { id, key: `id:${id}`, missing: false };
}
const key = instance?.vnode.key;
if (key != null) {
return String(key);
const vnodeKey = instance.vnode.key;
if (vnodeKey != null) {
const id = String(vnodeKey);
return { id, key: `key:${id}`, missing: false };
}
return `__ve_graphic_${instance.uid}`;
return { id: `__ve_graphic_${instance.uid}`, key: null, missing: true };
}
function resolveIdentity(
props: { id?: string | number },
instance: NonNullable<ReturnType<typeof getCurrentInstance>>,
): string | null {
if (props.id != null) {
return `id:${String(props.id)}`;
}
const key = instance.vnode.key;
if (key != null) {
return `key:${String(key)}`;
}
return null;
}
function cloneProps(props: AnyProps): AnyProps {
function copyProps(props: AnyProps): AnyProps {
const raw = toRaw(props) as AnyProps;
const clone: AnyProps = { ...raw };
if (raw.shape && typeof raw.shape === "object") {
@@ -64,13 +52,13 @@ function cloneProps(props: AnyProps): AnyProps {
}
function extractHandlers(attrs: AnyProps): AnyProps {
const handlers: AnyProps = {};
const out: AnyProps = {};
for (const key of Object.keys(attrs)) {
if (key.startsWith("on")) {
handlers[key] = attrs[key];
out[key] = attrs[key];
}
}
return handlers;
return out;
}
export function createGraphicComponent(name: string, type: string) {
@@ -93,23 +81,22 @@ export function createGraphicComponent(name: string, type: string) {
const currentId = shallowRef<string | null>(null);
function register(): void {
const nextId = resolveId(props, instance);
if (!props.id && instance.vnode.key == null) {
const next = parseIdentity(props, instance);
if (next.missing) {
graphicCollector.warnOnce(`missing-id:${instance.uid}`, warnMissingIdentity(name));
}
if (currentId.value && currentId.value !== nextId) {
if (currentId.value && currentId.value !== next.id) {
graphicCollector.unregister(currentId.value, instance.uid);
}
currentId.value = nextId;
const identity = resolveIdentity(props, instance);
const hintedOrder = identity != null ? unref(orderRef)?.get(identity) : undefined;
currentId.value = next.id;
const hintedOrder = next.key ? unref(orderRef)?.get(next.key) : undefined;
graphicCollector.register({
id: nextId,
id: next.id,
type,
parentId: parentIdRef ? unref(parentIdRef) : null,
order: hintedOrder,
props: cloneProps(props as AnyProps),
props: copyProps(props as AnyProps),
handlers: extractHandlers(attrs as AnyProps),
sourceId: instance.uid,
});

View File

@@ -16,13 +16,12 @@ type NormalizedHandlers = Record<string, Array<(...args: unknown[]) => void>>;
export function registerGraphicExtension(): void {
registerVChartExtension(
(ctx: VChartExtensionContext) => {
const handlersById = new Map<string, NormalizedHandlers>();
const boundEvents = new Map<string, (params: unknown) => void>();
let boundChart: EChartsType | null = null;
let warnedOptionGraphicOverride = false;
let lastHandledStructureVersion = -1;
const handlers = new Map<string, NormalizedHandlers>();
const eventFns = new Map<string, (params: unknown) => void>();
let chart: EChartsType | null = null;
let warnedOverride = false;
const normalizeEvent = (key: string): string | null => {
const toEventName = (key: string): string | null => {
if (!key.startsWith("on") || key.length <= 2) {
return null;
}
@@ -30,73 +29,71 @@ export function registerGraphicExtension(): void {
return raw.charAt(0).toLowerCase() + raw.slice(1);
};
const normalizeHandlers = (
rawHandlers: Record<string, unknown>,
): Record<string, Array<(...args: unknown[]) => void>> => {
const result: Record<string, Array<(...args: unknown[]) => void>> = {};
for (const [key, value] of Object.entries(rawHandlers)) {
const event = normalizeEvent(key);
const toHandlers = (input: Record<string, unknown>): NormalizedHandlers => {
const out: NormalizedHandlers = {};
for (const [key, value] of Object.entries(input)) {
const event = toEventName(key);
if (!event) {
continue;
}
const handlers: Array<(...args: unknown[]) => void> = Array.isArray(value)
const list: Array<(...args: unknown[]) => void> = Array.isArray(value)
? (value as unknown[]).filter(
(item): item is (...args: unknown[]) => void => typeof item === "function",
)
: typeof value === "function"
? [value as (...args: unknown[]) => void]
: [];
if (handlers.length > 0) {
result[event] = handlers;
if (list.length > 0) {
out[event] = list;
}
}
return result;
return out;
};
const dispatchEvent = (event: string, params: any) => {
const emit = (event: string, params: any) => {
const id = params?.info?.__veGraphicId;
if (!id) {
return;
}
const handlers = handlersById.get(String(id))?.[event];
if (!handlers) {
const list = handlers.get(String(id))?.[event];
if (!list) {
return;
}
handlers.forEach((handler) => handler(params));
list.forEach((fn) => fn(params));
};
const syncEventBindings = (chart: EChartsType, activeEvents: Set<string>) => {
boundEvents.forEach((handler, event) => {
if (!activeEvents.has(event)) {
chart.off(event, handler as any);
boundEvents.delete(event);
const syncEvents = (chartInst: EChartsType, active: Set<string>) => {
eventFns.forEach((fn, event) => {
if (!active.has(event)) {
chartInst.off(event, fn as any);
eventFns.delete(event);
}
});
activeEvents.forEach((event) => {
if (boundEvents.has(event)) {
active.forEach((event) => {
if (eventFns.has(event)) {
return;
}
const handler = (params: unknown) => dispatchEvent(event, params);
chart.on(event, handler as any);
boundEvents.set(event, handler);
const fn = (params: unknown) => emit(event, params);
chartInst.on(event, fn as any);
eventFns.set(event, fn);
});
};
watch(
() => ctx.chart.value,
(chart, prev) => {
if (prev && boundEvents.size > 0) {
boundEvents.forEach((handler, event) => prev.off(event, handler as any));
boundEvents.clear();
(next, prev) => {
if (prev && eventFns.size > 0) {
eventFns.forEach((fn, event) => prev.off(event, fn as any));
eventFns.clear();
}
boundChart = chart ?? null;
if (boundChart) {
const activeEvents = new Set<string>();
handlersById.forEach((handlers) => {
Object.keys(handlers).forEach((event) => activeEvents.add(event));
chart = next ?? null;
if (chart) {
const active = new Set<string>();
handlers.forEach((entry) => {
Object.keys(entry).forEach((event) => active.add(event));
});
syncEventBindings(boundChart, activeEvents);
syncEvents(chart, active);
}
},
{ immediate: true },
@@ -105,37 +102,30 @@ export function registerGraphicExtension(): void {
const collector = createGraphicCollector({
warn: ctx.warn,
onFlush: () => {
const structureVersion = collector.getStructureVersion();
if (structureVersion === lastHandledStructureVersion) {
return;
}
lastHandledStructureVersion = structureVersion;
const nodes = Array.from(collector.getNodes());
const nextHandlersById = new Map<string, NormalizedHandlers>();
const activeEvents = new Set<string>();
const next = new Map<string, NormalizedHandlers>();
const active = new Set<string>();
for (const node of nodes) {
const handlers = normalizeHandlers(node.handlers);
if (Object.keys(handlers).length > 0) {
nextHandlersById.set(node.id, handlers);
Object.keys(handlers).forEach((event) => activeEvents.add(event));
const nodeHandlers = toHandlers(node.handlers);
if (Object.keys(nodeHandlers).length > 0) {
next.set(node.id, nodeHandlers);
Object.keys(nodeHandlers).forEach((event) => active.add(event));
}
}
handlersById.clear();
nextHandlersById.forEach((handlers, id) => {
handlersById.set(id, handlers);
handlers.clear();
next.forEach((entry, id) => {
handlers.set(id, entry);
});
if (boundChart) {
syncEventBindings(boundChart, activeEvents);
if (chart) {
syncEvents(chart, active);
}
const { option, snapshot } = buildGraphicOption(nodes, ROOT_ID);
const { option } = buildGraphicOption(nodes, ROOT_ID);
collector.optionRef.value = option;
collector.setSnapshot(snapshot);
const updated = ctx.requestUpdate({
updateOptions: {
@@ -151,12 +141,12 @@ export function registerGraphicExtension(): void {
onScopeDispose(() => {
collector.dispose();
if (boundChart && boundEvents.size > 0) {
boundEvents.forEach((handler, event) => boundChart?.off(event, handler as any));
if (chart && eventFns.size > 0) {
eventFns.forEach((fn, event) => chart?.off(event, fn as any));
}
boundEvents.clear();
handlersById.clear();
boundChart = null;
eventFns.clear();
handlers.clear();
chart = null;
});
return {
@@ -164,17 +154,13 @@ export function registerGraphicExtension(): void {
if (!ctx.slots.graphic) {
return option;
}
if (option.graphic && !warnedOptionGraphicOverride) {
if (option.graphic && !warnedOverride) {
ctx.warn(warnOptionGraphicOverride());
warnedOptionGraphicOverride = true;
warnedOverride = true;
}
if (!collector.optionRef.value) {
const { option: initialOption, snapshot } = buildGraphicOption(
collector.getNodes(),
ROOT_ID,
);
const { option: initialOption } = buildGraphicOption(collector.getNodes(), ROOT_ID);
collector.optionRef.value = initialOption;
collector.setSnapshot(snapshot);
}
const graphicOption = collector.optionRef.value!;
return {

View File

@@ -17,17 +17,13 @@ function getGraphicIdentity(vnode: VNode): string | null {
return null;
}
function isGraphicComponent(vnode: VNode): boolean {
function getGraphicType(vnode: VNode): string | null {
const type = vnode.type as Record<string, unknown> | string | symbol;
return Boolean(type && typeof type === "object" && GRAPHIC_COMPONENT_MARKER in type);
}
function isGraphicGroup(vnode: VNode): boolean {
const type = vnode.type as Record<string, unknown> | string | symbol;
return (
Boolean(type && typeof type === "object") &&
(type as Record<string | symbol, unknown>)[GRAPHIC_COMPONENT_MARKER] === "group"
);
if (!type || typeof type !== "object") {
return null;
}
const mark = (type as Record<string | symbol, unknown>)[GRAPHIC_COMPONENT_MARKER];
return typeof mark === "string" ? mark : null;
}
function collectGraphicOrder(
@@ -41,7 +37,8 @@ function collectGraphicOrder(
}
const vnode = value as VNode;
if (isGraphicComponent(vnode)) {
const graphicType = getGraphicType(vnode);
if (graphicType) {
const identity = getGraphicIdentity(vnode);
if (identity) {
orderMap.set(identity, cursor.value);
@@ -50,7 +47,12 @@ function collectGraphicOrder(
}
const children = vnode.children;
if (isGraphicGroup(vnode) && children && typeof children === "object" && "default" in children) {
if (
graphicType === "group" &&
children &&
typeof children === "object" &&
"default" in children
) {
const slot = (children as { default?: () => unknown }).default;
if (slot) {
collectGraphicOrder(slot(), orderMap, cursor);

View File

@@ -16,8 +16,6 @@ type CollectorMock = {
optionRef: { value: unknown };
getNodes: () => Iterable<unknown>;
getSnapshot: () => unknown;
setSnapshot: ReturnType<typeof vi.fn>;
getStructureVersion: () => number;
};
function createCollectorMock(): CollectorMock {
@@ -31,8 +29,6 @@ function createCollectorMock(): CollectorMock {
optionRef: { value: null },
getNodes: () => [],
getSnapshot: () => ({}),
setSnapshot: vi.fn(),
getStructureVersion: () => 0,
};
}

View File

@@ -296,7 +296,7 @@ describe("graphic extension", () => {
scope.stop();
});
it("keeps event bindings stable when handlers are unchanged and skips no-op flush", async () => {
it("keeps event bindings stable when handlers are unchanged", async () => {
registerGraphicExtension();
const requestUpdate = vi.fn(() => true);
@@ -317,7 +317,6 @@ describe("graphic extension", () => {
const vnode = extensions.render()[0] as any;
const collector = vnode.props.collector as {
register: (node: any) => void;
requestFlush: () => void;
};
const onClick = vi.fn();
@@ -348,9 +347,6 @@ describe("graphic extension", () => {
});
await flushMicrotasks();
collector.requestFlush();
await flushMicrotasks();
expect(requestUpdate).toHaveBeenCalledTimes(2);
expect(chart.on).toHaveBeenCalledTimes(1);
expect(chart.off).not.toHaveBeenCalled();

View File

@@ -3,6 +3,7 @@ import { createSSRApp, h } from "vue";
import { renderToString } from "@vue/server-renderer";
import { GraphicMount } from "../src/graphic/mount";
import { GRAPHIC_COMPONENT_MARKER } from "../src/graphic/marker";
describe("GraphicMount (node)", () => {
it("returns null without browser root but still drives collector pass", async () => {
@@ -42,7 +43,14 @@ describe("GraphicMount (node)", () => {
GraphicMount as any,
{ collector },
{
default: () => [42 as any, "text" as any],
default: () => [
42 as any,
"text" as any,
h({
[GRAPHIC_COMPONENT_MARKER]: 1,
render: () => null,
} as any),
],
},
),
});

View File

@@ -383,8 +383,6 @@ describe("graphic", () => {
expect(onFlush).toHaveBeenCalledTimes(0);
const versionAfterDispose = collector.getStructureVersion();
collector.register({
id: "after-dispose",
type: "rect",
@@ -397,7 +395,6 @@ describe("graphic", () => {
collector.requestFlush();
await flushMicrotasks();
expect(collector.getStructureVersion()).toBe(versionAfterDispose);
expect(Array.from(collector.getNodes())).toEqual([]);
expect(collector.optionRef.value).toBeNull();
});