Files
steipete--codexbar/Tests/CodexBarTests/StatusMenuOverviewClickTests.swift
2026-07-13 12:22:33 +08:00

301 lines
11 KiB
Swift

import AppKit
import CodexBarCore
import SwiftUI
import Testing
@testable import CodexBar
@MainActor
@Suite(.serialized)
struct StatusMenuOverviewClickTests {
@Test
func `routes runtime click without gesture recognizer`() {
var clicked = false
let view = MenuCardItemHostingView(
rootView: Text("Overview row"),
highlightState: MenuCardHighlightState(),
allowsMenuHighlight: true,
onClick: { clicked = true })
view.frame = NSRect(x: 0, y: 0, width: 320, height: 44)
#expect(view._test_simulateRuntimeClick())
#expect(clicked)
}
@Test
func `routes gpu selection runtime click without gesture recognizer`() {
var clicked = false
let wrapped = MenuCardSectionContainerView(
highlightState: MenuCardHighlightState(),
showsSubmenuIndicator: false,
submenuIndicatorAlignment: .trailing,
submenuIndicatorTopPadding: 0,
refreshMonitor: nil)
{
Text("Overview GPU row")
}
let view = GPUSelectionHostingView(
rootView: wrapped,
allowsMenuHighlight: true,
onClick: { clicked = true })
view.frame = NSRect(x: 0, y: 0, width: 320, height: 44)
#expect(view._test_simulateRuntimeClick())
#expect(clicked)
}
@Test
func `gpu tracking activates only for mouseUp inside row`() {
let wrapped = MenuCardSectionContainerView(
highlightState: MenuCardHighlightState(),
showsSubmenuIndicator: false,
submenuIndicatorAlignment: .trailing,
submenuIndicatorTopPadding: 0,
refreshMonitor: nil)
{
Text("Overview GPU row")
}
let view = GPUSelectionHostingView(
rootView: wrapped,
allowsMenuHighlight: true,
onClick: {})
view.frame = NSRect(x: 0, y: 0, width: 320, height: 44)
let events = Self.mouseClick(at: NSPoint(x: 160, y: 22))
#expect(view._test_primaryPressDecision(for: events.down) == nil)
#expect(view._test_primaryPressDecision(for: events.up) == true)
}
@Test
func `gpu tracking cancels when release leaves row`() {
let wrapped = MenuCardSectionContainerView(
highlightState: MenuCardHighlightState(),
showsSubmenuIndicator: true,
submenuIndicatorAlignment: .trailing,
submenuIndicatorTopPadding: 0,
refreshMonitor: nil)
{
Text("Overview GPU row")
}
let view = GPUSelectionHostingView(
rootView: wrapped,
allowsMenuHighlight: true,
onClick: {})
view.frame = NSRect(x: 0, y: 0, width: 320, height: 44)
let outsideUp = Self.mouseClick(at: NSPoint(x: 340, y: 22)).up
#expect(view._test_primaryPressDecision(for: outsideUp) == false)
}
@Test
func `gpu tracking yields an outside drag to native submenu tracking`() {
let wrapped = MenuCardSectionContainerView(
highlightState: MenuCardHighlightState(),
showsSubmenuIndicator: true,
submenuIndicatorAlignment: .trailing,
submenuIndicatorTopPadding: 0,
refreshMonitor: nil)
{
Text("Overview GPU row")
}
let view = GPUSelectionHostingView(
rootView: wrapped,
allowsMenuHighlight: true,
onClick: {})
view.frame = NSRect(x: 0, y: 0, width: 320, height: 44)
let insideDrag = Self.mouseDrag(at: NSPoint(x: 160, y: 22))
let outsideDrag = Self.mouseDrag(at: NSPoint(x: 340, y: 22))
#expect(!view._test_primaryPressShouldYieldToMenu(for: insideDrag))
#expect(view._test_primaryPressShouldYieldToMenu(for: outsideDrag))
}
@Test
func `hitTest preserves button targets in standard hosting view`() {
let view = MenuCardItemHostingView(
rootView: Text("Overview row"),
highlightState: MenuCardHighlightState(),
allowsMenuHighlight: true,
onClick: {})
view.frame = NSRect(x: 0, y: 0, width: 320, height: 44)
let button = NSButton(frame: NSRect(x: 10, y: 10, width: 50, height: 20))
view.addSubview(button)
let hit = view.hitTest(NSPoint(x: 15, y: 15))
#expect(hit !== view)
#expect(hit === button || hit?.isDescendant(of: button) == true)
}
@Test
func `hitTest preserves button targets in gpu selection hosting view`() {
let wrapped = MenuCardSectionContainerView(
highlightState: MenuCardHighlightState(),
showsSubmenuIndicator: false,
submenuIndicatorAlignment: .trailing,
submenuIndicatorTopPadding: 0,
refreshMonitor: nil)
{
Text("Overview GPU row")
}
let view = GPUSelectionHostingView(
rootView: wrapped,
allowsMenuHighlight: true,
onClick: {})
view.frame = NSRect(x: 0, y: 0, width: 320, height: 44)
let button = NSButton(frame: NSRect(x: 10, y: 10, width: 50, height: 20))
view.addSubview(button)
let hit = view.hitTest(NSPoint(x: 15, y: 15))
#expect(hit !== view)
#expect(hit === button || hit?.isDescendant(of: button) == true)
}
@Test
func `gpu hosting preserves nested SwiftUI button target`() {
let interactiveRegionStore = MenuCardInteractiveRegionStore()
let content = Button("Copy") {}
.frame(width: 80, height: 30)
.menuCardInteractiveControl()
.frame(width: 320, height: 44, alignment: .trailing)
let wrapped = MenuCardSectionContainerView(
highlightState: MenuCardHighlightState(),
showsSubmenuIndicator: false,
submenuIndicatorAlignment: .trailing,
submenuIndicatorTopPadding: 0,
refreshMonitor: nil,
interactiveRegionStore: interactiveRegionStore)
{
content
}
let view = GPUSelectionHostingView(
rootView: wrapped,
allowsMenuHighlight: true,
containsInteractiveControls: true,
interactiveRegionStore: interactiveRegionStore,
onClick: {})
view.frame = NSRect(x: 0, y: 0, width: 320, height: 51)
Self.settleWindowlessLayout(view)
let buttonPoint = NSPoint(x: 280, y: 39)
#expect(view._test_hitsHostedInteractiveControl(at: buttonPoint))
#expect(!view._test_hitsHostedInteractiveControl(at: NSPoint(x: 280, y: 8)))
#expect(view.hitTest(buttonPoint) !== view)
#expect(!view._test_simulateRuntimeClick(at: buttonPoint))
}
@Test
func `standard hosting forwards nested SwiftUI control events without invoking row`() {
var rowClicked = false
let interactiveRegionStore = MenuCardInteractiveRegionStore()
let content = Button("Copy") {}
.frame(width: 80, height: 30)
.menuCardInteractiveControl()
.frame(width: 320, height: 44, alignment: .trailing)
let wrapped = MenuCardSectionContainerView(
highlightState: MenuCardHighlightState(),
showsSubmenuIndicator: false,
submenuIndicatorAlignment: .trailing,
submenuIndicatorTopPadding: 0,
refreshMonitor: nil,
interactiveRegionStore: interactiveRegionStore)
{
content
}
let view = MenuCardItemHostingView(
rootView: wrapped,
highlightState: MenuCardHighlightState(),
allowsMenuHighlight: true,
containsInteractiveControls: true,
interactiveRegionStore: interactiveRegionStore,
onClick: { rowClicked = true })
view.frame = NSRect(x: 0, y: 0, width: 320, height: 51)
Self.settleWindowlessLayout(view)
let buttonPoint = NSPoint(x: 280, y: 39)
#expect(view._test_hitsHostedInteractiveControl(at: buttonPoint))
#expect(!view._test_hitsHostedInteractiveControl(at: NSPoint(x: 280, y: 8)))
let events = Self.mouseClick(at: buttonPoint)
view.mouseDown(with: events.down)
view.mouseUp(with: events.up)
let forwarded = view._test_forwardedHostedControlEvents
#expect(forwarded.mouseDown)
#expect(forwarded.mouseUp)
#expect(!rowClicked)
}
@Test
func `hidden SwiftUI button region keeps row clickable`() {
var rowClicked = false
let interactiveRegionStore = MenuCardInteractiveRegionStore()
let content = Button("Hidden copy") {}
.frame(width: 80, height: 30)
.menuCardInteractiveControl(isEnabled: false)
.frame(width: 320, height: 44, alignment: .trailing)
let wrapped = MenuCardSectionContainerView(
highlightState: MenuCardHighlightState(),
showsSubmenuIndicator: false,
submenuIndicatorAlignment: .trailing,
submenuIndicatorTopPadding: 0,
refreshMonitor: nil,
interactiveRegionStore: interactiveRegionStore)
{
content
}
let view = MenuCardItemHostingView(
rootView: wrapped,
highlightState: MenuCardHighlightState(),
allowsMenuHighlight: true,
containsInteractiveControls: true,
interactiveRegionStore: interactiveRegionStore,
onClick: { rowClicked = true })
view.frame = NSRect(x: 0, y: 0, width: 320, height: 44)
Self.settleWindowlessLayout(view)
let buttonPoint = NSPoint(x: 280, y: 22)
#expect(!view._test_hitsHostedInteractiveControl(at: buttonPoint))
#expect(view._test_simulateRuntimeClick(at: buttonPoint))
#expect(rowClicked)
}
private static func settleWindowlessLayout(_ view: NSView) {
view.needsLayout = true
view.layoutSubtreeIfNeeded()
RunLoop.current.run(until: Date().addingTimeInterval(0.02))
view.layoutSubtreeIfNeeded()
}
private static func mouseClick(at point: NSPoint) -> (down: NSEvent, up: NSEvent) {
let down = NSEvent.mouseEvent(
with: .leftMouseDown,
location: point,
modifierFlags: [],
timestamp: 0,
windowNumber: 0,
context: nil,
eventNumber: 1,
clickCount: 1,
pressure: 1)!
let up = NSEvent.mouseEvent(
with: .leftMouseUp,
location: point,
modifierFlags: [],
timestamp: 0,
windowNumber: 0,
context: nil,
eventNumber: 2,
clickCount: 1,
pressure: 0)!
return (down, up)
}
private static func mouseDrag(at point: NSPoint) -> NSEvent {
NSEvent.mouseEvent(
with: .leftMouseDragged,
location: point,
modifierFlags: [],
timestamp: 0,
windowNumber: 0,
context: nil,
eventNumber: 3,
clickCount: 1,
pressure: 1)!
}
}