Files
openclaw/apps/macos/Tests/OpenClawIPCTests/CanvasManagerVisibilityTests.swift
Peter Steinberger ed2284c31a fix(macos): keep Canvas hidden for agent content operations (#110107)
* fix(macos): keep Canvas hidden for agent content operations

Agent canvas.evalJS, canvas.snapshot, and canvas.a2ui.* commands routed
through CanvasManager.show, which re-presented the panel and called
NSApp.activate(ignoringOtherApps:) on every content update. After a
gateway restart the hosted A2UI page is dead, so the next agent push
always re-opened a panel the user had closed and stole focus.

Content operations now use a non-presenting ensureController/prepare
path; only canvas.present, canvas.navigate, and user actions reveal the
panel. Snapshot is read-only: it requires a visible matching panel and
returns CANVAS_HIDDEN instead of mutating panel state (WebKit cannot
render ordered-out windows).

* chore(i18n): refresh native inventory line offsets
2026-07-17 23:40:35 +01:00

81 lines
2.9 KiB
Swift

import Foundation
import Testing
@testable import OpenClaw
@Suite(.serialized)
@MainActor
struct CanvasManagerVisibilityTests {
@Test func `eval on fresh state creates a hidden surface`() async throws {
let manager = CanvasManager.shared
manager._testResetPanel()
defer { manager._testResetPanel() }
let result = try await manager.eval(sessionKey: "visibility-eval", javaScript: "1 + 1")
#expect(result == "2")
#expect(manager._testHasPanelController)
#expect(manager._testPanelWindowIsVisible == false)
}
@Test func `showDetailed presents the panel`() throws {
let manager = CanvasManager.shared
manager._testResetPanel()
defer { manager._testResetPanel() }
_ = try manager.showDetailed(sessionKey: "visibility-present")
#expect(manager._testPanelWindowIsVisible == true)
}
@Test func `content operations respect a user hide`() async throws {
let manager = CanvasManager.shared
manager._testResetPanel()
defer { manager._testResetPanel() }
_ = try manager.showDetailed(sessionKey: "visibility-hidden")
manager.hideAll()
try manager.prepare(sessionKey: "visibility-hidden", target: "/")
_ = try await manager.eval(sessionKey: "visibility-hidden", javaScript: "1 + 1")
#expect(manager._testPanelWindowIsVisible == false)
}
@Test func `snapshot while hidden throws without presenting`() async throws {
let manager = CanvasManager.shared
manager._testResetPanel()
defer { manager._testResetPanel() }
let output = FileManager.default.temporaryDirectory
.appendingPathComponent("openclaw-canvas-hidden-\(UUID().uuidString).png")
defer { try? FileManager.default.removeItem(at: output) }
try manager.prepare(sessionKey: "visibility-snapshot", target: "/")
do {
_ = try await manager.snapshot(sessionKey: "visibility-snapshot", outPath: output.path)
Issue.record("hidden snapshot should throw CANVAS_HIDDEN")
} catch {
#expect(error.localizedDescription.contains("CANVAS_HIDDEN"))
}
#expect(manager._testHasPanelController)
#expect(manager._testPanelWindowIsVisible == false)
#expect(FileManager.default.fileExists(atPath: output.path) == false)
}
@Test func `snapshot for another session leaves the visible panel alone`() async throws {
let manager = CanvasManager.shared
manager._testResetPanel()
defer { manager._testResetPanel() }
_ = try manager.showDetailed(sessionKey: "visibility-live")
#expect(manager._testPanelWindowIsVisible == true)
await #expect(throws: (any Error).self) {
try await manager.snapshot(sessionKey: "visibility-other", outPath: nil)
}
// Read-only snapshot must not close or switch the live panel.
#expect(manager._testPanelWindowIsVisible == true)
}
}