Skip to main content
Glama
MenuCommandTests.swift8.58 kB
import CoreGraphics import Foundation import PeekabooFoundation import Testing @testable import PeekabooCLI @testable import PeekabooCore #if !PEEKABOO_SKIP_AUTOMATION // Import the necessary types from the menu command private struct MenuListData: Codable { let app: String let bundle_id: String? let menu_structure: [MenuData] } private struct MenuData: Codable { let title: String let enabled: Bool let items: [MenuItemData]? } private struct MenuItemData: Codable { let title: String let enabled: Bool let key_equivalent: String? let submenu: [MenuItemData]? } @Suite("Menu Command Tests", .serialized, .tags(.automation), .enabled(if: CLITestEnvironment.runAutomationRead)) struct MenuCommandTests { @Test("Menu command exists") func menuCommandExists() { let config = MenuCommand.commandDescription #expect(config.commandName == "menu") #expect(config.abstract.contains("menu bar")) } @Test("Menu command has expected subcommands") func menuSubcommands() { let subcommands = MenuCommand.commandDescription.subcommands #expect(subcommands.count == 3) var names: [String] = [] // Key-path map here trips SILGen; keep loop (docs/silgen-crash-debug.md). names.reserveCapacity(subcommands.count) for descriptor in subcommands { guard let name = descriptor.commandDescription.commandName else { continue } names.append(name) } #expect(names.contains("click")) #expect(names.contains("click-system")) #expect(names.contains("list")) } @Test("Menu click command help") func menuClickHelp() async throws { let result = try await self.runMenuCommand(["menu", "click", "--help"]) #expect(result.exitStatus == 0) let output = self.output(from: result) #expect(output.contains("Click a menu item")) #expect(output.contains("--app")) #expect(output.contains("--path")) #expect(output.contains("--item")) } @Test("Menu click requires app and path/item") func menuClickValidation() async throws { // Test missing app await #expect(throws: (any Error).self) { _ = try await self.runMenuCommand(["menu", "click", "--path", "File > New"]) } // Test missing path/item await #expect(throws: (any Error).self) { _ = try await self.runMenuCommand(["menu", "click", "--app", "Finder"]) } } @Test("Menu path parsing") func menuPathParsing() { // Test simple path let path1 = "File > New" let components1 = path1.split(separator: ">").map { $0.trimmingCharacters(in: .whitespaces) } #expect(components1 == ["File", "New"]) // Test complex path let path2 = "Window > Bring All to Front" let components2 = path2.split(separator: ">").map { $0.trimmingCharacters(in: .whitespaces) } #expect(components2 == ["Window", "Bring All to Front"]) } @Test("Menu click-system command help") func menuSystemHelp() async throws { let result = try await self.runMenuCommand(["menu", "click-system", "--help"]) #expect(result.exitStatus == 0) let output = self.output(from: result) #expect(output.contains("Click system menu items")) #expect(output.contains("--title")) #expect(output.contains("--item")) } @Test("Menu list command help") func menuListHelp() async throws { let result = try await self.runMenuCommand(["menu", "list", "--help"]) #expect(result.exitStatus == 0) let output = self.output(from: result) #expect(output.contains("List all menu items")) #expect(output.contains("--app")) #expect(output.contains("--include-disabled")) } @Test("Menu error codes") func menuErrorCodes() { #expect(ErrorCode.MENU_BAR_NOT_FOUND.rawValue == "MENU_BAR_NOT_FOUND") #expect(ErrorCode.MENU_ITEM_NOT_FOUND.rawValue == "MENU_ITEM_NOT_FOUND") } @Test("Menu click executes menu service") func menuClickExecution() async throws { let args = [ "menu", "click", "--app", "Finder", "--item", "Open", "--no-auto-focus", "--json-output", ] let (result, context) = try await self.runMenuCommandWithContext(args) #expect(result.exitStatus == 0) let calls = await self.menuState(context.menuService) { $0.clickItemCalls } #expect(calls.contains { $0.app == "Finder" && $0.item == "Open" }) } @Test("Menu click path executes menu service") func menuClickPathExecution() async throws { let args = [ "menu", "click", "--app", "Finder", "--path", "File > Save", "--no-auto-focus", "--json-output", ] let (result, context) = try await self.runMenuCommandWithContext(args) #expect(result.exitStatus == 0) let pathCalls = await self.menuState(context.menuService) { $0.clickPathCalls } #expect(pathCalls.contains { $0.app == "Finder" && $0.path == "File > Save" }) } private func runMenuCommand( _ args: [String], configure: (@MainActor (StubMenuService, StubApplicationService) -> Void)? = nil ) async throws -> CommandRunResult { let (result, _) = try await self.runMenuCommandWithContext(args, configure: configure) return result } private func runMenuCommandWithContext( _ args: [String], configure: (@MainActor (StubMenuService, StubApplicationService) -> Void)? = nil ) async throws -> (CommandRunResult, MenuHarnessContext) { let context = await self.makeMenuContext() if let configure { await MainActor.run { configure(context.menuService, context.applicationService) } } let result = try await InProcessCommandRunner.run(args, services: context.services) return (result, context) } private func output(from result: CommandRunResult) -> String { result.stdout.isEmpty ? result.stderr : result.stdout } private func menuState<T: Sendable>( _ service: StubMenuService, _ operation: @MainActor (StubMenuService) -> T ) async -> T { await MainActor.run { operation(service) } } @MainActor private func makeMenuContext() -> MenuHarnessContext { let data = Self.defaultMenuData() let menuService = StubMenuService(menusByApp: data.menusByApp, menuExtras: data.extras) let applicationService = StubApplicationService(applications: [data.appInfo]) let services = TestServicesFactory.makePeekabooServices( applications: applicationService, menu: menuService ) return MenuHarnessContext(services: services, menuService: menuService, applicationService: applicationService) } @MainActor private static func defaultMenuData() -> (appInfo: ServiceApplicationInfo, menusByApp: [String: MenuStructure], extras: [MenuExtraInfo]) { let appInfo = ServiceApplicationInfo( processIdentifier: 101, bundleIdentifier: "com.apple.finder", name: "Finder", bundlePath: "/System/Library/CoreServices/Finder.app", isActive: true, isHidden: false, windowCount: 1 ) let fileMenu = Menu( title: "File", items: [ MenuItem(title: "New", path: "File > New"), MenuItem(title: "Open", path: "File > Open"), MenuItem(title: "Save", path: "File > Save"), ], isEnabled: true ) let viewMenu = Menu( title: "View", items: [ MenuItem(title: "Show Path Bar", path: "View > Show Path Bar"), ], isEnabled: true ) let menuStructure = MenuStructure(application: appInfo, menus: [fileMenu, viewMenu]) let extras = [MenuExtraInfo(title: "WiFi", position: CGPoint(x: 0, y: 0), isVisible: true)] return (appInfo, ["Finder": menuStructure], extras) } private struct MenuHarnessContext { let services: PeekabooServices let menuService: StubMenuService let applicationService: StubApplicationService } } // MARK: - Menu Command Integration Tests (removed real CLI coverage) #endif

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/steipete/Peekaboo'

If you have feedback or need assistance with the MCP directory API, please join our Discord server