import { describe, expect, it } from "vitest"; import { renderVom } from "../render"; import type { VomNode, VomScene } from "../types"; function node(p: Partial & { id: number }): VomNode { const { id, parentId, tag, rect, paintOrder, position, pointerEvents, ...rest } = p; return { id, parentId: parentId ?? null, tag: tag ?? "div", rect: rect ?? null, paintOrder: paintOrder ?? 0, position: position ?? "static", pointerEvents: pointerEvents ?? "auto", ...rest, }; } function scene(nodes: VomNode[]): VomScene { return { viewport: { width: 1280, height: 720 }, nodes, }; } function coreRefs(refs: ReturnType["refs"]) { return refs.map(({ ref, backendNodeId }) => ({ ref, backendNodeId })); } describe("renderVom single-layer page", () => { it("renders visual surfaces as screenshot-only refs", () => { const input = scene([ node({ id: 1, role: "RootWebArea", frameId: "main" }), node({ id: 2, parentId: 1, role: "Iframe", frameId: "main", backendNodeId: 20 }), ]); input.visualSurfaces = [ { parentId: 2, backendNodeId: 99, frameId: "child", renderingKind: "canvas", visibleRect: { x: 10, y: 20, w: 800, h: 500 }, label: "Data grid", memberCount: 2, }, ]; const out = renderVom(input); expect(out.text).toContain( '@e1 surface "Data grid" [rendering=canvas; layers=2; visual-only; requires=image-understanding; use: bsk screenshot --ref @e1]', ); expect(out.refs).toContainEqual( expect.objectContaining({ ref: "e1", backendNodeId: 99, kind: "surface", visibleRect: { x: 10, y: 20, w: 800, h: 500 }, capabilities: ["screenshot"], }), ); }); it("renders an unnamed visual surface with a stable generic label", () => { const input = scene([node({ id: 1, role: "RootWebArea", frameId: "main" })]); input.visualSurfaces = [ { parentId: 1, backendNodeId: 99, frameId: "main", renderingKind: "canvas", visibleRect: { x: 10, y: 20, w: 800, h: 500 }, memberCount: 1, }, ]; const out = renderVom(input); expect(out.text).toContain( '@e1 surface "canvas visual surface" [rendering=canvas; visual-only; requires=image-understanding; use: bsk screenshot --ref @e1]', ); expect(out.refs).toContainEqual( expect.objectContaining({ ref: "e1", name: "canvas visual surface", kind: "surface", capabilities: ["screenshot"], }), ); }); it("does not derive handle context across frame scopes", () => { const out = renderVom( scene([ node({ id: 1, role: "RootWebArea", frameId: "main", contextScopeId: "main", }), node({ id: 2, parentId: 1, role: "StaticText", name: "创建于", frameId: "main", contextScopeId: "main", }), node({ id: 3, parentId: 1, role: "Iframe", frameId: "main", contextScopeId: "main", }), node({ id: 4, parentId: 3, role: "button", name: "表格视图", frameId: "child", contextScopeId: "child", }), node({ id: 5, parentId: 1, role: "button", name: "表格视图", frameId: "main", contextScopeId: "main", }), ]), ); expect(out.text).toContain('@e1 button "表格视图"'); expect(out.text).not.toContain('@e1 button "表格视图" [ctx: 创建于]'); }); it("preserves handle context within a frame scope", () => { const out = renderVom( scene([ node({ id: 1, role: "RootWebArea", contextScopeId: "main" }), node({ id: 2, parentId: 1, role: "Iframe", contextScopeId: "main" }), node({ id: 3, parentId: 2, role: "StaticText", name: "工具栏", contextScopeId: "child", }), node({ id: 4, parentId: 2, role: "button", name: "更多", contextScopeId: "child", }), node({ id: 5, parentId: 1, role: "button", name: "更多", contextScopeId: "main", }), ]), ); expect(out.text).toContain('@e1 button "更多" [ctx: 工具栏]'); }); it("always emits a complete @vom single-layer document when there is no blocker", () => { const out = renderVom( scene([ node({ id: 1, role: "RootWebArea", name: "Shop" }), node({ id: 2, parentId: 1, role: "navigation", name: "主菜单" }), node({ id: 3, parentId: 2, role: "link", name: "首页", tag: "a" }), node({ id: 4, parentId: 1, role: "main" }), node({ id: 5, parentId: 4, role: "heading", name: "今日推荐" }), node({ id: 6, parentId: 4, role: "button", name: "加入购物车", tag: "button" }), ]), ); expect(out.text).toBe( [ "@vom 1", "@view 1280x720", "@layers 1 focus=L1", "L1 page", ' RootWebArea "Shop"', ' navigation "主菜单"', ' @e1 link "首页"', " main", ' heading "今日推荐"', ' @e2 button "加入购物车"', ].join("\n"), ); expect(coreRefs(out.refs)).toEqual([ { ref: "e1", backendNodeId: 3 }, { ref: "e2", backendNodeId: 6 }, ]); expect(out.truncated).toBe(false); }); it("does not assign refs to structural or display-only nodes", () => { const out = renderVom( scene([ node({ id: 1, role: "RootWebArea", name: "Doc" }), node({ id: 2, parentId: 1, role: "heading", name: "Title" }), node({ id: 3, parentId: 1, role: "img", name: "Logo" }), node({ id: 4, parentId: 1, role: "button", name: "Continue", tag: "button" }), ]), ); expect(out.text).toContain(' RootWebArea "Doc"'); expect(out.text).toContain(' heading "Title"'); expect(out.text).toContain(' img "Logo"'); expect(out.text).toContain(' @e1 button "Continue"'); expect(coreRefs(out.refs)).toEqual([{ ref: "e1", backendNodeId: 4 }]); }); it("assigns refs to listbox controls", () => { const out = renderVom( scene([ node({ id: 1, role: "RootWebArea", name: "Doc" }), node({ id: 2, parentId: 1, role: "listbox", name: "Choices" }), ]), ); expect(out.text).toContain(' @e1 listbox "Choices"'); expect(coreRefs(out.refs)).toEqual([{ ref: "e1", backendNodeId: 2 }]); }); it("annotates external links regardless of role casing", () => { const out = renderVom( scene([ node({ id: 1, role: "RootWebArea", name: "Doc" }), node({ id: 2, parentId: 1, role: "Link", name: "Docs", href: "docs.example.org", tag: "a", }), ]), ); expect(out.text).toContain('@e1 Link "Docs" [→ docs.example.org]'); }); it("renders additional structural roles without assigning refs", () => { const out = renderVom( scene([ node({ id: 1, role: "WebArea" }), node({ id: 2, parentId: 1, role: "Iframe" }), node({ id: 3, parentId: 1, role: "alertdialog" }), node({ id: 4, parentId: 3, role: "section" }), node({ id: 5, parentId: 4, role: "button", name: "Close", tag: "button" }), ]), ); expect(out.text).toContain(" WebArea"); expect(out.text).toContain(" Iframe"); expect(out.text).toContain(" alertdialog"); expect(out.text).toContain(" section"); expect(out.text).toContain(' @e1 button "Close"'); expect(coreRefs(out.refs)).toEqual([{ ref: "e1", backendNodeId: 5 }]); }); it("skips generic nodes without adding an extra indentation level", () => { const out = renderVom( scene([ node({ id: 1, role: "RootWebArea", name: "Doc" }), node({ id: 2, parentId: 1, role: "generic" }), node({ id: 3, parentId: 2, role: "button", name: "OK", tag: "button" }), ]), ); expect(out.text).not.toContain("generic"); expect(out.text).toContain(' @e1 button "OK"'); }); it("masks sensitive values and never emits the clear text value", () => { const out = renderVom( scene([ node({ id: 1, role: "RootWebArea", name: "Login" }), node({ id: 2, parentId: 1, role: "textbox", name: "密码", value: "hunter2", tag: "input", sensitive: true, }), ]), ); expect(out.text).toContain('@e1 textbox "密码" [filled] ="•••"'); expect(out.text).not.toContain("hunter2"); }); it("renders a sensitive filled state without retaining its clear text value", () => { const out = renderVom( scene([ node({ id: 1, role: "RootWebArea", name: "Login" }), node({ id: 2, parentId: 1, role: "textbox", name: "密码", tag: "input", sensitive: true, inputState: "filled", }), ]), ); expect(out.text).toContain('@e1 textbox "密码" [filled] ="•••"'); }); it("does not imply an empty sensitive textbox has a value", () => { const out = renderVom( scene([ node({ id: 1, role: "RootWebArea", name: "Login" }), node({ id: 2, parentId: 1, role: "textbox", name: "密码", tag: "input", sensitive: true, inputState: "empty", }), ]), ); expect(out.text).toContain('@e1 textbox "密码" [empty]'); expect(out.text).not.toContain('="•••"'); }); it("continues rendering later siblings after a branch exceeds maxDepth", () => { const out = renderVom( scene([ node({ id: 1, role: "RootWebArea", name: "Doc" }), node({ id: 2, parentId: 1, role: "section" }), node({ id: 3, parentId: 2, role: "heading", name: "Too deep" }), node({ id: 4, parentId: 1, role: "button", name: "Later", tag: "button" }), ]), { maxDepth: 2 }, ); expect(out.truncated).toBe(true); expect(out.text).toContain(' RootWebArea "Doc"'); expect(out.text).toContain(" section"); expect(out.text).not.toContain("Too deep"); expect(out.text).toContain(' @e1 button "Later"'); expect(coreRefs(out.refs)).toEqual([{ ref: "e1", backendNodeId: 4 }]); }); it("truncates when maxTokens is exceeded while keeping refs in sync with rendered text", () => { const out = renderVom( scene([ node({ id: 1, role: "RootWebArea", name: "Doc" }), node({ id: 2, parentId: 1, role: "button", name: "First", tag: "button" }), node({ id: 3, parentId: 1, role: "button", name: "Second", tag: "button" }), ]), { maxTokens: 22 }, ); expect(out.truncated).toBe(true); const renderedRefs = new Set(Array.from(out.text.matchAll(/@(e\d+)/g), (m) => m[1])); expect(out.refs.every((r) => renderedRefs.has(r.ref))).toBe(true); }); it("recovers custom clickable controls from DOM-only signals", () => { const out = renderVom( scene([ node({ id: 1, role: "RootWebArea", name: "Doc" }), node({ id: 2, parentId: 1, role: "generic", text: "Open settings", cursor: "pointer", attrs: { onclick: "handleOpen()" }, rect: { x: 20, y: 20, w: 160, h: 40 }, }), ]), ); expect(out.text).toContain('@e1 button "Open settings"'); expect(coreRefs(out.refs)).toEqual([{ ref: "e1", backendNodeId: 2 }]); }); it("does not treat implementation CSS classes as control names", () => { const out = renderVom( scene([ node({ id: 1, role: "RootWebArea", name: "Doc" }), node({ id: 2, parentId: 1, role: "generic", cursor: "pointer", attrs: { onclick: "handleClick()" }, rect: { x: 20, y: 20, w: 40, h: 40 }, }), node({ id: 3, parentId: 2, role: "img", tag: "svg", attrs: { class: "kocomz" }, }), ]), ); expect(out.text).not.toContain("kocomz"); expect(out.refs).toEqual([]); }); it("recovers icon controls only from explicit semantic icon evidence", () => { const out = renderVom( scene([ node({ id: 1, role: "RootWebArea", name: "Doc" }), node({ id: 2, parentId: 1, role: "generic", cursor: "pointer", attrs: { onclick: "handleFilter()" }, rect: { x: 20, y: 20, w: 40, h: 40 }, }), node({ id: 3, parentId: 2, role: "img", tag: "svg", attrs: { "aria-label": "Filter rows", class: "kocomz" }, }), ]), ); expect(out.text).toContain('@e1 button "Filter rows"'); expect(coreRefs(out.refs)).toEqual([{ ref: "e1", backendNodeId: 2 }]); }); it("keeps a semantic image as evidence for its clickable wrapper", () => { const out = renderVom( scene([ node({ id: 1, role: "RootWebArea", name: "Doc" }), node({ id: 2, parentId: 1, role: "generic", cursor: "pointer", rect: { x: 20, y: 20, w: 40, h: 40 }, }), node({ id: 3, parentId: 2, role: "img", name: "Account", cursor: "pointer", tag: "div", }), ]), ); expect(out.text).toContain('@e1 button "Account"'); expect(coreRefs(out.refs)).toEqual([{ ref: "e1", backendNodeId: 2 }]); }); it("keeps the deepest custom control when wrapper and child both look clickable", () => { const out = renderVom( scene([ node({ id: 1, role: "RootWebArea", name: "Doc" }), node({ id: 2, parentId: 1, role: "generic", text: "Card", cursor: "pointer", attrs: { onclick: "handleCard()" }, rect: { x: 20, y: 20, w: 280, h: 120 }, }), node({ id: 3, parentId: 2, role: "generic", text: "Details", cursor: "pointer", attrs: { onclick: "handleDetails()" }, rect: { x: 40, y: 70, w: 90, h: 32 }, }), ]), ); expect(out.text).not.toContain('@e1 button "Card"'); expect(out.text).toContain('@e1 button "Details"'); expect(coreRefs(out.refs)).toEqual([{ ref: "e1", backendNodeId: 3 }]); }); it("does not recover clickable wrappers around native controls", () => { const out = renderVom( scene([ node({ id: 1, role: "RootWebArea", name: "Doc" }), node({ id: 2, parentId: 1, role: "generic", text: "Checkout", cursor: "pointer", attrs: { onclick: "handleCheckout()" }, hasNativeDescendant: true, rect: { x: 20, y: 20, w: 280, h: 120 }, }), node({ id: 3, parentId: 2, role: "button", name: "Pay", tag: "button" }), ]), ); expect(out.text).not.toContain('@e1 button "Checkout"'); expect(out.text).toContain('@e1 button "Pay"'); expect(coreRefs(out.refs)).toEqual([{ ref: "e1", backendNodeId: 3 }]); }); it("adds context to duplicate weak action labels", () => { const out = renderVom( scene([ node({ id: 1, role: "RootWebArea", name: "Admin" }), node({ id: 2, parentId: 1, role: "section", name: "Project Alpha" }), node({ id: 3, parentId: 2, role: "button", name: "View", tag: "button" }), node({ id: 4, parentId: 1, role: "section", name: "Project Beta" }), node({ id: 5, parentId: 4, role: "button", name: "View", tag: "button" }), ]), ); expect(out.text).toContain('@e1 button "View" [ctx: Project Alpha]'); expect(out.text).toContain('@e2 button "View" [ctx: Project Beta]'); }); it("filters noisy template/layout text from weak label context", () => { const out = renderVom( scene([ node({ id: 1, role: "RootWebArea", name: "Doc" }), node({ id: 2, parentId: 1, role: "section", name: "top 开始 top 结束 bigpic、登录 开始 { const out = renderVom( scene([ node({ id: 1, role: "RootWebArea", name: "Doc" }), node({ id: 2, parentId: 1, role: "button", name: "Background", tag: "button", rect: { x: 120, y: 120, w: 120, h: 40 }, paintOrder: 1, }), node({ id: 3, parentId: 1, role: "generic", tag: "div", rect: { x: 100, y: 100, w: 180, h: 120 }, paintOrder: 10, position: "fixed", }), ]), ); expect(out.text).toContain("@layers 1 focus=L1"); expect(out.text).toContain('@e1 button "Background"'); expect(coreRefs(out.refs)).toEqual([{ ref: "e1", backendNodeId: 2 }]); }); it("can filter refs blocked by active positioned regions without changing layer output", () => { const out = renderVom( scene([ node({ id: 1, role: "RootWebArea", name: "Doc" }), node({ id: 2, parentId: 1, role: "button", name: "Background", tag: "button", rect: { x: 120, y: 120, w: 120, h: 40 }, paintOrder: 1, }), node({ id: 3, parentId: 1, role: "generic", tag: "div", rect: { x: 100, y: 100, w: 180, h: 120 }, paintOrder: 10, position: "fixed", }), node({ id: 4, parentId: 1, role: "button", name: "Foreground", tag: "button", rect: { x: 320, y: 120, w: 120, h: 40 }, paintOrder: 11, }), ]), { activeRegionPolicy: true }, ); expect(out.text).toContain("@layers 1 focus=L1"); expect(out.text).toContain("L1 page"); expect(out.text).not.toContain("active-region"); expect(out.text).not.toContain("Background"); expect(out.text).toContain('@e1 button "Foreground"'); expect(coreRefs(out.refs)).toEqual([{ ref: "e1", backendNodeId: 4 }]); }); it("keeps paint-order comparisons inside their frame document", () => { const out = renderVom( { ...scene([ node({ id: 1, role: "RootWebArea", frameId: "main" }), node({ id: 2, parentId: 1, role: "button", name: "Top-level action", tag: "button", rect: { x: 120, y: 120, w: 120, h: 40 }, paintOrder: 2, frameId: "main", }), node({ id: 3, parentId: 1, role: "Iframe", tag: "iframe", rect: { x: 0, y: 0, w: 1000, h: 700 }, paintOrder: 1, frameId: "main", }), node({ id: 4, parentId: 3, role: "generic", rect: { x: 0, y: 0, w: 1000, h: 700 }, paintOrder: 100, position: "fixed", frameId: "child", }), node({ id: 5, parentId: 4, role: "button", name: "Child action", tag: "button", rect: { x: 120, y: 120, w: 120, h: 40 }, paintOrder: 101, frameId: "child", }), ]), rootFrameId: "main", }, { activeRegionPolicy: true }, ); expect(out.text).toContain("@layers 1 focus=L1"); expect(out.text).toContain('button "Top-level action"'); expect(out.text).toContain('button "Child action"'); }); it("uses the iframe owner when a parent-frame region covers child content", () => { const out = renderVom( { ...scene([ node({ id: 1, role: "RootWebArea", frameId: "main" }), node({ id: 2, parentId: 1, role: "Iframe", tag: "iframe", rect: { x: 100, y: 100, w: 400, h: 300 }, paintOrder: 1, frameId: "main", }), node({ id: 3, parentId: 2, role: "button", name: "Covered child action", tag: "button", rect: { x: 150, y: 150, w: 120, h: 40 }, paintOrder: 100, frameId: "child", }), node({ id: 4, parentId: 1, role: "generic", rect: { x: 120, y: 120, w: 200, h: 120 }, paintOrder: 2, position: "fixed", frameId: "main", }), ]), rootFrameId: "main", }, { activeRegionPolicy: true }, ); expect(out.text).not.toContain("Covered child action"); }); it("renders conditional surface items inline on the trigger", () => { const out = renderVom({ ...scene([ node({ id: 1, role: "RootWebArea", name: "Doc" }), node({ id: 2, parentId: 1, role: "button", name: "Products", tag: "button" }), ]), surfaces: [ { triggerId: 2, triggerAction: "hover", subItems: ["Shoes", "Bags", "Accessories"], }, ], }); expect(out.text).toContain('@e1 button "Products" [hover first: Shoes | Bags | Accessories]'); expect(coreRefs(out.refs)).toEqual([{ ref: "e1", backendNodeId: 2 }]); }); it("injects active scope blocks immediately after their trigger without refs", () => { const out = renderVom({ ...scene([ node({ id: 1, role: "RootWebArea", name: "Doc" }), node({ id: 2, parentId: 1, role: "tab", name: "Reviews (12)", tag: "button" }), ]), activeScopeBlocks: [ { triggerId: 2, label: "Reviews (12)", lines: ["Jane - ear cups are small", "Bob - great sound"], }, ], }); expect(out.text).toContain( [ ' @e1 tab "Reviews (12)"', " [§ active: Reviews (12)]", " Jane - ear cups are small", " Bob - great sound", ].join("\n"), ); expect(coreRefs(out.refs)).toEqual([{ ref: "e1", backendNodeId: 2 }]); }); it("does not render redundant children inside named ref controls", () => { const out = renderVom( scene([ node({ id: 1, role: "RootWebArea", name: "Doc" }), node({ id: 2, parentId: 1, role: "button", name: "Save", tag: "button" }), node({ id: 3, parentId: 2, role: "img", name: "disk icon", tag: "svg" }), ]), ); expect(out.text).toContain('@e1 button "Save"'); expect(out.text).not.toContain("disk icon"); expect(coreRefs(out.refs)).toEqual([{ ref: "e1", backendNodeId: 2 }]); }); it("does not render redundant children inside native text inputs", () => { const out = renderVom( scene([ node({ id: 1, role: "RootWebArea", name: "Doc" }), node({ id: 2, parentId: 1, role: "textbox", name: "手机号", value: "13311030827", tag: "input", }), node({ id: 3, parentId: 2, role: "StaticText", name: "13311030827" }), ]), ); expect(out.text).toContain('@e1 textbox "手机号" [filled] ="13311030827"'); expect(out.text).not.toContain('StaticText "13311030827"'); }); it("renders placeholder separately from input value and state", () => { const out = renderVom( scene([ node({ id: 1, role: "RootWebArea", name: "Doc" }), node({ id: 2, parentId: 1, role: "textbox", name: "手机号", value: "没有手机号的用编码代替", placeholder: "手机号", inputState: "default", tag: "input", }), ]), ); expect(out.text).toContain( '@e1 textbox "手机号" [default] placeholder="手机号" ="没有手机号的用编码代替"', ); }); it("keeps only local context for duplicate labels", () => { const out = renderVom( scene([ node({ id: 1, role: "RootWebArea", name: "北京市小客车指标调控管理信息系统" }), node({ id: 2, parentId: 1, role: "section" }), node({ id: 3, parentId: 2, role: "StaticText", name: "账号" }), node({ id: 4, parentId: 2, role: "link", name: "操作说明", tag: "a" }), node({ id: 5, parentId: 1, role: "section" }), node({ id: 6, parentId: 5, role: "StaticText", name: "配置指标" }), node({ id: 7, parentId: 5, role: "link", name: "操作说明", tag: "a" }), ]), ); expect(out.text).toContain('@e1 link "操作说明" [ctx: 账号]'); expect(out.text).toContain('@e2 link "操作说明" [ctx: 配置指标]'); expect(out.text).not.toContain("北京市小客车指标调控管理信息系统]"); }); it("does not add low-value root context to duplicated brand links", () => { const out = renderVom( scene([ node({ id: 1, role: "RootWebArea", name: "北京市小客车指标调控管理信息系统" }), node({ id: 2, parentId: 1, role: "link", name: "北京市交通委员会", tag: "a" }), node({ id: 3, parentId: 1, role: "link", name: "北京市交通委员会", tag: "a" }), ]), ); expect(out.text).toContain('@e1 link "北京市交通委员会"'); expect(out.text).toContain('@e2 link "北京市交通委员会"'); expect(out.text).not.toContain("[ctx:"); }); it("does not render redundant table cell descendants when the cell name covers them", () => { const out = renderVom( scene([ node({ id: 1, role: "RootWebArea", name: "Doc" }), node({ id: 2, parentId: 1, role: "table" }), node({ id: 3, parentId: 2, role: "row" }), node({ id: 4, parentId: 3, role: "cell", name: "申请人姓名:刘珺瑶" }), node({ id: 5, parentId: 4, role: "StaticText", name: "申请人姓名:" }), node({ id: 6, parentId: 4, role: "StaticText", name: "刘珺瑶" }), ]), ); expect(out.text).toContain('cell "申请人姓名:刘珺瑶"'); expect(out.text).not.toContain('StaticText "申请人姓名:"'); expect(out.text).not.toContain('StaticText "刘珺瑶"'); }); it("keeps interactive descendants inside table cells", () => { const out = renderVom( scene([ node({ id: 1, role: "RootWebArea", name: "Doc" }), node({ id: 2, parentId: 1, role: "table" }), node({ id: 3, parentId: 2, role: "row" }), node({ id: 4, parentId: 3, role: "cell", name: "操作:下载" }), node({ id: 5, parentId: 4, role: "link", name: "下载", tag: "a" }), ]), ); expect(out.text).toContain('cell "操作:下载"'); expect(out.text).toContain('@e1 link "下载"'); }); }); describe("renderVom double-layer page", () => { it("renders a blocking modal first and folds the base page into an occluded summary", () => { const out = renderVom( scene([ node({ id: 1, role: "RootWebArea", name: "Shop", tag: "body", rect: { x: 0, y: 0, w: 1000, h: 3000 }, }), node({ id: 10, parentId: 1, role: "button", name: "底层按钮", tag: "button" }), node({ id: 2, parentId: 1, tag: "div", rect: { x: 0, y: 0, w: 1280, h: 720 }, paintOrder: 50, position: "fixed", pointerEvents: "auto", }), node({ id: 3, parentId: 1, role: "dialog", name: "提示", modal: true, rect: { x: 400, y: 200, w: 480, h: 320 }, paintOrder: 60, position: "fixed", }), node({ id: 4, parentId: 3, role: "textbox", name: "密码", value: "secret", tag: "input", sensitive: true, }), node({ id: 5, parentId: 3, role: "button", name: "确认", tag: "button" }), ]), ); expect(out.text).toBe( [ "@vom 1", "@view 1280x720", "@layers 2 focus=L1", "L1 modal cover=100%", ' dialog "提示"', ' @e1 textbox "密码" [filled] ="•••"', ' @e2 button "确认"', "L2 page … occluded by L1 (~2 nodes, not actionable)", ].join("\n"), ); expect(out.text).not.toContain("底层按钮"); expect(coreRefs(out.refs)).toEqual([ { ref: "e1", backendNodeId: 4 }, { ref: "e2", backendNodeId: 5 }, ]); }); it("renders iframe descendants inside the top layer through parentId without iframe-specific renderer code", () => { const out = renderVom( scene([ node({ id: 1, role: "RootWebArea", name: "JD", tag: "body", rect: { x: 0, y: 0, w: 1000, h: 3000 }, }), node({ id: 2, parentId: 1, tag: "div", rect: { x: 0, y: 0, w: 1280, h: 720 }, paintOrder: 50, position: "fixed", }), node({ id: 3, parentId: 2, role: "Iframe", name: "登录框", tag: "iframe", rect: { x: 100, y: 240, w: 800, h: 400 }, paintOrder: 51, }), node({ id: 101, parentId: 3, role: "textbox", name: "请输入手机号", tag: "input" }), node({ id: 102, parentId: 3, role: "textbox", name: "密码", value: "secret", tag: "input", sensitive: true, }), ]), ); expect(out.text).toContain("L1 modal cover=100%"); expect(out.text).toContain(' Iframe "登录框"'); expect(out.text).toContain(' @e1 textbox "请输入手机号" [empty]'); expect(out.text).toContain(' @e2 textbox "密码" [filled] ="•••"'); expect(out.refs.map((r) => r.backendNodeId)).toEqual([101, 102]); }); it("redacts form values and returns semantic metadata for rendered refs", () => { const out = renderVom( scene([ node({ id: 1, role: "RootWebArea", name: "Form" }), node({ id: 2, backendNodeId: 42, frameId: "child-frame", parentId: 1, role: "textbox", name: "Email", value: "user@example.com", inputState: "filled", tag: "input", }), ]), { redactValues: true }, ); expect(out.text).toContain('[filled] ="•••"'); expect(out.text).not.toContain("user@example.com"); expect(out.refs[0]).toMatchObject({ ref: "e1", backendNodeId: 42, frameId: "child-frame", role: "textbox", name: "Email", line: expect.any(Number), }); expect(out.text.split("\n")[out.refs[0]?.line ?? -1]).toContain('@e1 textbox "Email"'); }); });