Files
openclaw/apps/macos/Tests/OpenClawIPCTests/StatusItemMouseRouterTests.swift
Peter Steinberger b8a6ea12cc refactor(macos): remove the custom menu bar hover card (#118116)
* refactor(macos): remove the custom menu bar hover card

The status item showed two hover affordances at once: the native AppKit
tooltip and a custom SwiftUI panel rendering an "Idle / No recent activity"
card. Keep the native tooltip and drop the custom HUD.

The HUD was the only consumer of the status item's hover tracking, so the
NSTrackingArea and onHoverChanged plumbing in StatusItemMouseRouter goes
with it; click routing is unchanged. WorkActivityStore.lastToolLabel had no
other reader either.

* chore(i18n): refresh native i18n inventory after hover card removal

Deleting HoverHUD.swift shifted the MenuBar.swift line numbers that
apps/.i18n/native-source.json records. Regenerated with
`pnpm native:i18n:baseline`; only line numbers change, no strings added
or removed.

* refactor(macos): drop the orphaned anchored chat panel

Removing the hover card left WebChatManager.togglePanel() without a caller,
and it was the only thing that created the menu-bar-anchored chat popover.
Delete that presentation path: togglePanel/panelHidden, the panel controller
state, WebChatPresentation, WebChatPanel, presentAnchored and its dismiss
monitor, and WindowPlacement.anchoredBelowFrame.

Chat is unaffected as a window - WebChatManager.show() still backs the Dock
menu, --chat and deep links. The status item highlight that tracked chat
window visibility is preserved, renamed to onChatWindowVisibilityChanged
now that no panel exists to confuse it with.

Periphery reports no unused code.

* chore: drop CHANGELOG edit from this PR

AGENTS.md: CHANGELOG.md is release-only and release generation derives it
from merged PRs. Release-note context lives in the PR body instead.

* chore: restore CHANGELOG to the branch base

Correct the previous commit, which restored CHANGELOG.md from current
origin/main and so pulled in an unrelated entry added after this branch
forked. Reset to the merge-base content: this PR now touches no changelog.
2026-08-02 13:10:27 -07:00

142 lines
4.9 KiB
Swift

import AppKit
import Testing
@testable import OpenClaw
@Suite(.serialized)
@MainActor
struct StatusItemMouseRouterTests {
@Test func `installed monitor consumes the target event before native dispatch`() throws {
let monitorToken = NSObject()
var installedMask: NSEvent.EventTypeMask = []
var installedHandler: StatusItemMouseRouter.EventMonitorHandler?
var leftClicks = 0
let installedToken = try #require(StatusItemMouseRouter.installMonitor(
using: { mask, handler in
installedMask = mask
installedHandler = handler
return monitorToken
},
handler: { event in
StatusItemMouseRouter.route(
event,
hitsTarget: true,
onLeftClick: { leftClicks += 1 },
onRightClick: {})
}))
#expect(installedMask == [.leftMouseDown, .rightMouseDown])
#expect(installedToken as AnyObject === monitorToken)
let handler = try #require(installedHandler)
let left = try Self.mouseEvent(.leftMouseDown)
#expect(handler(left) == nil)
#expect(leftClicks == 1)
}
@Test func `routes left and right clicks without opening the native menu`() throws {
var leftClicks = 0
var rightClicks = 0
let left = try Self.mouseEvent(.leftMouseDown)
let right = try Self.mouseEvent(.rightMouseDown)
#expect(StatusItemMouseRouter.route(
left,
hitsTarget: true,
onLeftClick: { leftClicks += 1 },
onRightClick: { rightClicks += 1 }) == nil)
#expect(leftClicks == 1)
#expect(rightClicks == 0)
#expect(StatusItemMouseRouter.route(
right,
hitsTarget: true,
onLeftClick: { leftClicks += 1 },
onRightClick: { rightClicks += 1 }) == nil)
#expect(leftClicks == 1)
#expect(rightClicks == 1)
}
@Test func `retargets clicks without reinstalling the monitor`() throws {
let window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 80, height: 24),
styleMask: .borderless,
backing: .buffered,
defer: false)
let firstButton = NSView(frame: NSRect(x: 0, y: 0, width: 24, height: 24))
let secondButton = NSView(frame: NSRect(x: 40, y: 0, width: 24, height: 24))
window.contentView?.addSubview(firstButton)
window.contentView?.addSubview(secondButton)
var monitorInstallCount = 0
var leftClicks = 0
let router = StatusItemMouseRouter(
eventMonitorInstaller: { _, _ in
monitorInstallCount += 1
return NSObject()
},
eventMonitorRemover: { _ in })
router.install(
on: firstButton,
onLeftClick: { leftClicks += 1 },
onRightClick: {})
let firstClick = try Self.mouseEvent(
.leftMouseDown,
location: NSPoint(x: 12, y: 12),
windowNumber: window.windowNumber)
#expect(router.route(firstClick) == nil)
#expect(leftClicks == 1)
router.install(
on: secondButton,
onLeftClick: { leftClicks += 1 },
onRightClick: {})
#expect(monitorInstallCount == 1)
#expect(router.route(firstClick) === firstClick)
let secondClick = try Self.mouseEvent(
.leftMouseDown,
location: NSPoint(x: 52, y: 12),
windowNumber: window.windowNumber)
#expect(router.route(secondClick) == nil)
#expect(leftClicks == 2)
}
@Test func `non-target and unrelated events continue to native dispatch`() throws {
var leftClicks = 0
var rightClicks = 0
let nonTarget = try Self.mouseEvent(.leftMouseDown)
let unrelated = try Self.mouseEvent(.mouseMoved)
let routedNonTarget = try #require(StatusItemMouseRouter.route(
nonTarget,
hitsTarget: false,
onLeftClick: { leftClicks += 1 },
onRightClick: { rightClicks += 1 }))
let routedUnrelated = try #require(StatusItemMouseRouter.route(
unrelated,
hitsTarget: true,
onLeftClick: { leftClicks += 1 },
onRightClick: { rightClicks += 1 }))
#expect(routedNonTarget === nonTarget)
#expect(routedUnrelated === unrelated)
#expect(leftClicks == 0)
#expect(rightClicks == 0)
}
private static func mouseEvent(
_ type: NSEvent.EventType,
location: NSPoint = .zero,
windowNumber: Int = 0) throws -> NSEvent
{
try #require(NSEvent.mouseEvent(
with: type,
location: location,
modifierFlags: [],
timestamp: 0,
windowNumber: windowNumber,
context: nil,
eventNumber: 1,
clickCount: 1,
pressure: 1))
}
}