From 43192fa9c335578a04e35217dab0b1959de51a17 Mon Sep 17 00:00:00 2001 From: M4n0x Date: Wed, 4 Nov 2026 10:18:16 +0100 Subject: [PATCH 1/2] fix: keep Settings controls clickable above the bar preview --- Sources/StableBar/ScreenPicker.swift | 5 +- Tests/StableBarTests/SettingsClickTests.swift | 58 +++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 Tests/StableBarTests/SettingsClickTests.swift diff --git a/Sources/StableBar/ScreenPicker.swift b/Sources/StableBar/ScreenPicker.swift index b38e2a2..7e386c4 100644 --- a/Sources/StableBar/ScreenPicker.swift +++ b/Sources/StableBar/ScreenPicker.swift @@ -35,10 +35,11 @@ extension ScreenKind { struct Wallpaper: View { static let image = Bundle.main.url(forResource: "SetupDay", withExtension: "heic").flatMap(NSImage.init(contentsOf:)) + var backgroundImage = Self.image var body: some View { GeometryReader { geometry in - if let image = Self.image { + if let image = backgroundImage { Image(nsImage: image) .resizable() .scaledToFill() @@ -49,6 +50,8 @@ struct Wallpaper: View { SettingsPalette.background } } + // Clipping the scaled image only limits its drawing, not its hit area. + .allowsHitTesting(false) } } diff --git a/Tests/StableBarTests/SettingsClickTests.swift b/Tests/StableBarTests/SettingsClickTests.swift new file mode 100644 index 0000000..6588f6f --- /dev/null +++ b/Tests/StableBarTests/SettingsClickTests.swift @@ -0,0 +1,58 @@ +import AppKit +import SwiftUI +import Testing +@testable import StableBar + +@Test(arguments: [900.0, 2000.0]) @MainActor +func previewWallpaperLeavesTheSettingsHeaderClickable(width: Double) throws { + let fixture = try ThemeGalleryFixture() + defer { fixture.close() } + let store = try fixture.store() + defer { store.stop() } + let editor = ConfigurationEditor(store: store) + var tabClicks = 0 + let image = NSImage(size: NSSize(width: 1536, height: 1024), flipped: false) { rect in + NSColor.gray.setFill() + rect.fill() + return true + } + let content = VStack(spacing: 0) { + Button("Theme") { tabClicks += 1 } + .buttonStyle(.plain) + .frame(height: 52) + ScreenBand(editor: editor, kinds: [.laptop, .monitor], screen: .laptop, useSharedBar: { _ in }) + .frame(height: 100) + Wallpaper(backgroundImage: image) + .frame(height: 82) + .clipShape(RoundedRectangle(cornerRadius: 9)) + } + .frame(width: width, height: 234) + _ = NSApplication.shared + // SwiftUI needs an ordered window for hit testing. Keep it invisible, and never start a bar. + let window = NSWindow(contentRect: CGRect(x: 0, y: 0, width: width, height: 234), styleMask: [.borderless], + backing: .buffered, defer: false) + window.isReleasedWhenClosed = false + window.alphaValue = 0 + window.contentView = NSHostingView(rootView: content) + window.orderFrontRegardless() + defer { window.close() } + RunLoop.main.run(until: Date().addingTimeInterval(0.2)) + + func click(x: Double, y: Double) throws { + for type in [NSEvent.EventType.leftMouseDown, .leftMouseUp] { + let event = try #require(NSEvent.mouseEvent(with: type, location: CGPoint(x: x, y: 234 - y), + modifierFlags: [], timestamp: 0, windowNumber: window.windowNumber, context: nil, + eventNumber: 0, clickCount: 1, pressure: 1)) + window.sendEvent(event) + RunLoop.main.run(until: Date().addingTimeInterval(0.01)) + } + } + try click(x: width / 2, y: 26) + #expect(tabClicks == 1) + try click(x: 140, y: 95) + let selectedScreen = editor.barScreen + #expect(selectedScreen == .monitor) + try click(x: width - 65, y: 102) + let editingBar = store.barEditing.isActive + #expect(editingBar) +} From 070ed9e898d54a9926a06084391973b6cc34a861 Mon Sep 17 00:00:00 2001 From: M4n0x Date: Wed, 4 Nov 2026 10:28:36 +0100 Subject: [PATCH 2/2] test: wait for Settings clicks by polling --- Tests/StableBarTests/SettingsClickTests.swift | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/Tests/StableBarTests/SettingsClickTests.swift b/Tests/StableBarTests/SettingsClickTests.swift index 6588f6f..78f04c2 100644 --- a/Tests/StableBarTests/SettingsClickTests.swift +++ b/Tests/StableBarTests/SettingsClickTests.swift @@ -4,7 +4,7 @@ import Testing @testable import StableBar @Test(arguments: [900.0, 2000.0]) @MainActor -func previewWallpaperLeavesTheSettingsHeaderClickable(width: Double) throws { +func previewWallpaperLeavesTheSettingsHeaderClickable(width: Double) async throws { let fixture = try ThemeGalleryFixture() defer { fixture.close() } let store = try fixture.store() @@ -33,26 +33,38 @@ func previewWallpaperLeavesTheSettingsHeaderClickable(width: Double) throws { backing: .buffered, defer: false) window.isReleasedWhenClosed = false window.alphaValue = 0 - window.contentView = NSHostingView(rootView: content) + let host = NSHostingView(rootView: content) + window.contentView = host window.orderFrontRegardless() defer { window.close() } - RunLoop.main.run(until: Date().addingTimeInterval(0.2)) - func click(x: Double, y: Double) throws { + func poll(until condition: () -> Bool) async -> Bool { + for _ in 0..<100 { + host.layoutSubtreeIfNeeded() + if condition() { return true } + await Task.yield() + } + return false + } + let ready = await poll { + window.isVisible && host.frame.size == NSSize(width: width, height: 234) + && host.hitTest(CGPoint(x: width / 2, y: 26)) != nil + } + try #require(ready) + + func click(x: Double, y: Double, until condition: () -> Bool) async throws -> Bool { for type in [NSEvent.EventType.leftMouseDown, .leftMouseUp] { let event = try #require(NSEvent.mouseEvent(with: type, location: CGPoint(x: x, y: 234 - y), modifierFlags: [], timestamp: 0, windowNumber: window.windowNumber, context: nil, eventNumber: 0, clickCount: 1, pressure: 1)) window.sendEvent(event) - RunLoop.main.run(until: Date().addingTimeInterval(0.01)) } + return await poll(until: condition) } - try click(x: width / 2, y: 26) - #expect(tabClicks == 1) - try click(x: 140, y: 95) - let selectedScreen = editor.barScreen - #expect(selectedScreen == .monitor) - try click(x: width - 65, y: 102) - let editingBar = store.barEditing.isActive + let clickedTab = try await click(x: width / 2, y: 26) { tabClicks == 1 } + #expect(clickedTab) + let selectedScreen = try await click(x: 140, y: 95) { editor.barScreen == .monitor } + #expect(selectedScreen) + let editingBar = try await click(x: width - 65, y: 102) { store.barEditing.isActive } #expect(editingBar) }