mirror of
https://github.com/samsonjs/Peekaboo.git
synced 2026-04-27 15:07:41 +00:00
Apply SwiftFormat and fix all SwiftLint violations
- Run SwiftFormat on all Swift files for consistent formatting - Fix all critical SwiftLint violations: * Replace count > 0 with \!isEmpty * Use descriptive variable names instead of i, x, y * Replace % operator with isMultiple(of:) * Fix force try violations * Use trailing closure syntax * Replace for-if patterns with for-where * Fix line length violations * Use Data(_:) instead of .data(using:)\! - Ensure zero SwiftLint errors for clean code quality 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
45f087496a
commit
e894210dbd
14 changed files with 865 additions and 832 deletions
|
|
@ -41,22 +41,20 @@ struct AppsSubcommand: ParsableCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handleError(_ error: Error) {
|
private func handleError(_ error: Error) {
|
||||||
let captureError: CaptureError
|
let captureError: CaptureError = if let err = error as? CaptureError {
|
||||||
if let err = error as? CaptureError {
|
err
|
||||||
captureError = err
|
|
||||||
} else {
|
} else {
|
||||||
captureError = .unknownError(error.localizedDescription)
|
.unknownError(error.localizedDescription)
|
||||||
}
|
}
|
||||||
|
|
||||||
if jsonOutput {
|
if jsonOutput {
|
||||||
let code: ErrorCode
|
let code: ErrorCode = switch captureError {
|
||||||
switch captureError {
|
|
||||||
case .screenRecordingPermissionDenied:
|
case .screenRecordingPermissionDenied:
|
||||||
code = .PERMISSION_ERROR_SCREEN_RECORDING
|
.PERMISSION_ERROR_SCREEN_RECORDING
|
||||||
case .accessibilityPermissionDenied:
|
case .accessibilityPermissionDenied:
|
||||||
code = .PERMISSION_ERROR_ACCESSIBILITY
|
.PERMISSION_ERROR_ACCESSIBILITY
|
||||||
default:
|
default:
|
||||||
code = .INTERNAL_SWIFT_ERROR
|
.INTERNAL_SWIFT_ERROR
|
||||||
}
|
}
|
||||||
outputError(
|
outputError(
|
||||||
message: captureError.localizedDescription,
|
message: captureError.localizedDescription,
|
||||||
|
|
@ -142,24 +140,22 @@ struct WindowsSubcommand: ParsableCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handleError(_ error: Error) {
|
private func handleError(_ error: Error) {
|
||||||
let captureError: CaptureError
|
let captureError: CaptureError = if let err = error as? CaptureError {
|
||||||
if let err = error as? CaptureError {
|
err
|
||||||
captureError = err
|
|
||||||
} else {
|
} else {
|
||||||
captureError = .unknownError(error.localizedDescription)
|
.unknownError(error.localizedDescription)
|
||||||
}
|
}
|
||||||
|
|
||||||
if jsonOutput {
|
if jsonOutput {
|
||||||
let code: ErrorCode
|
let code: ErrorCode = switch captureError {
|
||||||
switch captureError {
|
|
||||||
case .screenRecordingPermissionDenied:
|
case .screenRecordingPermissionDenied:
|
||||||
code = .PERMISSION_ERROR_SCREEN_RECORDING
|
.PERMISSION_ERROR_SCREEN_RECORDING
|
||||||
case .accessibilityPermissionDenied:
|
case .accessibilityPermissionDenied:
|
||||||
code = .PERMISSION_ERROR_ACCESSIBILITY
|
.PERMISSION_ERROR_ACCESSIBILITY
|
||||||
case .appNotFound:
|
case .appNotFound:
|
||||||
code = .APP_NOT_FOUND
|
.APP_NOT_FOUND
|
||||||
default:
|
default:
|
||||||
code = .INTERNAL_SWIFT_ERROR
|
.INTERNAL_SWIFT_ERROR
|
||||||
}
|
}
|
||||||
outputError(
|
outputError(
|
||||||
message: captureError.localizedDescription,
|
message: captureError.localizedDescription,
|
||||||
|
|
|
||||||
|
|
@ -56,8 +56,8 @@ class Logger {
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDebugLogs() -> [String] {
|
func getDebugLogs() -> [String] {
|
||||||
return queue.sync {
|
queue.sync {
|
||||||
return self.debugLogs
|
self.debugLogs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -118,9 +118,11 @@ enum CaptureError: Error, LocalizedError {
|
||||||
case .noDisplaysAvailable:
|
case .noDisplaysAvailable:
|
||||||
"No displays available for capture."
|
"No displays available for capture."
|
||||||
case .screenRecordingPermissionDenied:
|
case .screenRecordingPermissionDenied:
|
||||||
"Screen recording permission is required. Please grant it in System Settings > Privacy & Security > Screen Recording."
|
"Screen recording permission is required. " +
|
||||||
|
"Please grant it in System Settings > Privacy & Security > Screen Recording."
|
||||||
case .accessibilityPermissionDenied:
|
case .accessibilityPermissionDenied:
|
||||||
"Accessibility permission is required for some operations. Please grant it in System Settings > Privacy & Security > Accessibility."
|
"Accessibility permission is required for some operations. " +
|
||||||
|
"Please grant it in System Settings > Privacy & Security > Accessibility."
|
||||||
case .invalidDisplayID:
|
case .invalidDisplayID:
|
||||||
"Invalid display ID provided."
|
"Invalid display ID provided."
|
||||||
case .captureCreationFailed:
|
case .captureCreationFailed:
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,6 @@
|
||||||
// To use this file for development, copy it to Version.swift:
|
// To use this file for development, copy it to Version.swift:
|
||||||
// cp Version.swift.development Version.swift
|
// cp Version.swift.development Version.swift
|
||||||
|
|
||||||
struct Version {
|
enum Version {
|
||||||
static let current = "dev"
|
static let current = "dev"
|
||||||
}
|
}
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
|
import AppKit
|
||||||
@testable import peekaboo
|
@testable import peekaboo
|
||||||
import Testing
|
import Testing
|
||||||
import AppKit
|
|
||||||
|
|
||||||
@Suite("ApplicationFinder Tests", .tags(.applicationFinder, .unit))
|
@Suite("ApplicationFinder Tests", .tags(.applicationFinder, .unit))
|
||||||
struct ApplicationFinderTests {
|
struct ApplicationFinderTests {
|
||||||
|
|
||||||
// MARK: - Test Data
|
// MARK: - Test Data
|
||||||
|
|
||||||
private static let testIdentifiers = [
|
private static let testIdentifiers = [
|
||||||
|
|
@ -15,6 +14,7 @@ struct ApplicationFinderTests {
|
||||||
"", " ", "NonExistentApp12345", "invalid.bundle.id",
|
"", " ", "NonExistentApp12345", "invalid.bundle.id",
|
||||||
String(repeating: "a", count: 1000)
|
String(repeating: "a", count: 1000)
|
||||||
]
|
]
|
||||||
|
|
||||||
// MARK: - Find Application Tests
|
// MARK: - Find Application Tests
|
||||||
|
|
||||||
@Test("Finding an app by exact name match", .tags(.fast))
|
@Test("Finding an app by exact name match", .tags(.fast))
|
||||||
|
|
@ -61,13 +61,15 @@ struct ApplicationFinderTests {
|
||||||
|
|
||||||
// MARK: - Parameterized Tests
|
// MARK: - Parameterized Tests
|
||||||
|
|
||||||
@Test("Finding apps with various identifiers",
|
@Test(
|
||||||
arguments: [
|
"Finding apps with various identifiers",
|
||||||
("Finder", "com.apple.finder"),
|
arguments: [
|
||||||
("finder", "com.apple.finder"),
|
("Finder", "com.apple.finder"),
|
||||||
("FINDER", "com.apple.finder"),
|
("finder", "com.apple.finder"),
|
||||||
("com.apple.finder", "com.apple.finder")
|
("FINDER", "com.apple.finder"),
|
||||||
])
|
("com.apple.finder", "com.apple.finder")
|
||||||
|
]
|
||||||
|
)
|
||||||
func findApplicationVariousIdentifiers(identifier: String, expectedBundleId: String) throws {
|
func findApplicationVariousIdentifiers(identifier: String, expectedBundleId: String) throws {
|
||||||
let result = try ApplicationFinder.findApplication(identifier: identifier)
|
let result = try ApplicationFinder.findApplication(identifier: identifier)
|
||||||
#expect(result.bundleIdentifier == expectedBundleId)
|
#expect(result.bundleIdentifier == expectedBundleId)
|
||||||
|
|
@ -81,7 +83,7 @@ struct ApplicationFinderTests {
|
||||||
let apps = ApplicationFinder.getAllRunningApplications()
|
let apps = ApplicationFinder.getAllRunningApplications()
|
||||||
|
|
||||||
// Should have at least some apps running
|
// Should have at least some apps running
|
||||||
#expect(apps.count > 0)
|
#expect(!apps.isEmpty)
|
||||||
|
|
||||||
// Should include Finder
|
// Should include Finder
|
||||||
let hasFinder = apps.contains { $0.app_name == "Finder" }
|
let hasFinder = apps.contains { $0.app_name == "Finder" }
|
||||||
|
|
@ -130,13 +132,15 @@ struct ApplicationFinderTests {
|
||||||
#expect(findResult.localizedName == "Finder")
|
#expect(findResult.localizedName == "Finder")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test("Bundle identifier parsing edge cases",
|
@Test(
|
||||||
arguments: [
|
"Bundle identifier parsing edge cases",
|
||||||
"com.apple",
|
arguments: [
|
||||||
"apple.finder",
|
"com.apple",
|
||||||
"finder",
|
"apple.finder",
|
||||||
"com.apple.finder.extra"
|
"finder",
|
||||||
])
|
"com.apple.finder.extra"
|
||||||
|
]
|
||||||
|
)
|
||||||
func bundleIdentifierEdgeCases(partialBundleId: String) throws {
|
func bundleIdentifierEdgeCases(partialBundleId: String) throws {
|
||||||
// Should either find Finder or throw appropriate error
|
// Should either find Finder or throw appropriate error
|
||||||
do {
|
do {
|
||||||
|
|
@ -156,8 +160,10 @@ struct ApplicationFinderTests {
|
||||||
#expect(result.bundleIdentifier == "com.apple.finder")
|
#expect(result.bundleIdentifier == "com.apple.finder")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test("Performance: Finding apps multiple times",
|
@Test(
|
||||||
arguments: 1...10)
|
"Performance: Finding apps multiple times",
|
||||||
|
arguments: 1...10
|
||||||
|
)
|
||||||
func findApplicationPerformance(iteration: Int) throws {
|
func findApplicationPerformance(iteration: Int) throws {
|
||||||
// Test that finding an app completes quickly even when called multiple times
|
// Test that finding an app completes quickly even when called multiple times
|
||||||
let result = try ApplicationFinder.findApplication(identifier: "Finder")
|
let result = try ApplicationFinder.findApplication(identifier: "Finder")
|
||||||
|
|
@ -168,7 +174,7 @@ struct ApplicationFinderTests {
|
||||||
func stressTestManyApps() {
|
func stressTestManyApps() {
|
||||||
// Get current app count for baseline
|
// Get current app count for baseline
|
||||||
let apps = ApplicationFinder.getAllRunningApplications()
|
let apps = ApplicationFinder.getAllRunningApplications()
|
||||||
#expect(apps.count > 0)
|
#expect(!apps.isEmpty)
|
||||||
|
|
||||||
// Test search performance doesn't degrade with app list size
|
// Test search performance doesn't degrade with app list size
|
||||||
let startTime = CFAbsoluteTimeGetCurrent()
|
let startTime = CFAbsoluteTimeGetCurrent()
|
||||||
|
|
@ -183,12 +189,14 @@ struct ApplicationFinderTests {
|
||||||
|
|
||||||
// MARK: - Integration Tests
|
// MARK: - Integration Tests
|
||||||
|
|
||||||
@Test("Find and verify running state of system apps",
|
@Test(
|
||||||
arguments: [
|
"Find and verify running state of system apps",
|
||||||
("Finder", true),
|
arguments: [
|
||||||
("Dock", true),
|
("Finder", true),
|
||||||
("SystemUIServer", true)
|
("Dock", true),
|
||||||
])
|
("SystemUIServer", true)
|
||||||
|
]
|
||||||
|
)
|
||||||
func verifySystemAppsRunning(appName: String, shouldBeRunning: Bool) throws {
|
func verifySystemAppsRunning(appName: String, shouldBeRunning: Bool) throws {
|
||||||
do {
|
do {
|
||||||
let result = try ApplicationFinder.findApplication(identifier: appName)
|
let result = try ApplicationFinder.findApplication(identifier: appName)
|
||||||
|
|
@ -227,7 +235,6 @@ struct ApplicationFinderTests {
|
||||||
|
|
||||||
@Suite("ApplicationFinder Edge Cases", .tags(.applicationFinder, .unit))
|
@Suite("ApplicationFinder Edge Cases", .tags(.applicationFinder, .unit))
|
||||||
struct ApplicationFinderEdgeCaseTests {
|
struct ApplicationFinderEdgeCaseTests {
|
||||||
|
|
||||||
@Test("Empty identifier throws appropriate error", .tags(.fast))
|
@Test("Empty identifier throws appropriate error", .tags(.fast))
|
||||||
func emptyIdentifierError() {
|
func emptyIdentifierError() {
|
||||||
#expect(throws: (any Error).self) {
|
#expect(throws: (any Error).self) {
|
||||||
|
|
@ -250,8 +257,10 @@ struct ApplicationFinderEdgeCaseTests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test("Unicode identifiers are handled correctly",
|
@Test(
|
||||||
arguments: ["😀App", "App™", "Приложение", "アプリ"])
|
"Unicode identifiers are handled correctly",
|
||||||
|
arguments: ["😀App", "App™", "Приложение", "アプリ"]
|
||||||
|
)
|
||||||
func unicodeIdentifiers(identifier: String) {
|
func unicodeIdentifiers(identifier: String) {
|
||||||
// Should not crash, either finds or throws appropriate error
|
// Should not crash, either finds or throws appropriate error
|
||||||
do {
|
do {
|
||||||
|
|
@ -291,10 +300,8 @@ struct ApplicationFinderEdgeCaseTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
var successCount = 0
|
var successCount = 0
|
||||||
for await success in group {
|
for await success in group where success {
|
||||||
if success {
|
successCount += 1
|
||||||
successCount += 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// All searches should succeed for Finder
|
// All searches should succeed for Finder
|
||||||
|
|
@ -307,7 +314,7 @@ struct ApplicationFinderEdgeCaseTests {
|
||||||
// Test memory doesn't grow excessively with repeated calls
|
// Test memory doesn't grow excessively with repeated calls
|
||||||
for _ in 1...5 {
|
for _ in 1...5 {
|
||||||
let apps = ApplicationFinder.getAllRunningApplications()
|
let apps = ApplicationFinder.getAllRunningApplications()
|
||||||
#expect(apps.count > 0)
|
#expect(!apps.isEmpty)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we get here without crashing, memory management is working
|
// If we get here without crashing, memory management is working
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
|
import AppKit
|
||||||
|
import Foundation
|
||||||
@testable import peekaboo
|
@testable import peekaboo
|
||||||
import Testing
|
import Testing
|
||||||
import Foundation
|
|
||||||
import AppKit
|
|
||||||
|
|
||||||
@Suite("Image Capture Logic Tests", .tags(.imageCapture, .unit))
|
@Suite("Image Capture Logic Tests", .tags(.imageCapture, .unit))
|
||||||
struct ImageCaptureLogicTests {
|
struct ImageCaptureLogicTests {
|
||||||
|
|
||||||
// MARK: - File Name Generation Tests
|
// MARK: - File Name Generation Tests
|
||||||
|
|
||||||
@Test("File name generation for displays", .tags(.fast))
|
@Test("File name generation for displays", .tags(.fast))
|
||||||
|
|
@ -136,8 +135,10 @@ struct ImageCaptureLogicTests {
|
||||||
#expect(command.screenIndex == 1)
|
#expect(command.screenIndex == 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test("Screen index edge cases",
|
@Test(
|
||||||
arguments: [-1, 0, 1, 5, 99, Int.max])
|
"Screen index edge cases",
|
||||||
|
arguments: [-1, 0, 1, 5, 99, Int.max]
|
||||||
|
)
|
||||||
func screenIndexEdgeCases(index: Int) throws {
|
func screenIndexEdgeCases(index: Int) throws {
|
||||||
let command = try ImageCommand.parse([
|
let command = try ImageCommand.parse([
|
||||||
"--mode", "screen",
|
"--mode", "screen",
|
||||||
|
|
@ -212,7 +213,7 @@ struct ImageCaptureLogicTests {
|
||||||
// We can't directly test the private method, but verify the errors exist
|
// We can't directly test the private method, but verify the errors exist
|
||||||
// Verify the error exists (non-nil check not needed for value types)
|
// Verify the error exists (non-nil check not needed for value types)
|
||||||
#expect(Bool(true))
|
#expect(Bool(true))
|
||||||
#expect(expectedCode.rawValue.count > 0)
|
#expect(!expectedCode.rawValue.isEmpty)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -378,7 +379,6 @@ struct ImageCaptureLogicTests {
|
||||||
|
|
||||||
@Suite("Advanced Image Capture Logic", .tags(.imageCapture, .integration))
|
@Suite("Advanced Image Capture Logic", .tags(.imageCapture, .integration))
|
||||||
struct AdvancedImageCaptureLogicTests {
|
struct AdvancedImageCaptureLogicTests {
|
||||||
|
|
||||||
@Test("Multi-mode capture scenarios", .tags(.fast))
|
@Test("Multi-mode capture scenarios", .tags(.fast))
|
||||||
func multiModeCaptureScenarios() throws {
|
func multiModeCaptureScenarios() throws {
|
||||||
// Multi mode with app (should capture all windows)
|
// Multi mode with app (should capture all windows)
|
||||||
|
|
@ -498,12 +498,12 @@ struct AdvancedImageCaptureLogicTests {
|
||||||
["--mode", "multi", "--app", String(repeating: "LongAppName", count: 100)],
|
["--mode", "multi", "--app", String(repeating: "LongAppName", count: 100)],
|
||||||
["--window-title", String(repeating: "VeryLongTitle", count: 200)],
|
["--window-title", String(repeating: "VeryLongTitle", count: 200)],
|
||||||
["--path", String(repeating: "/very/long/path", count: 50)],
|
["--path", String(repeating: "/very/long/path", count: 50)],
|
||||||
Array(repeating: ["--mode", "screen"], count: 100).flatMap { $0 }
|
Array(repeating: ["--mode", "screen"], count: 100).flatMap(\.self)
|
||||||
]
|
]
|
||||||
|
|
||||||
for config in complexConfigs {
|
for config in complexConfigs {
|
||||||
do {
|
do {
|
||||||
let _ = try ImageCommand.parse(config)
|
_ = try ImageCommand.parse(config)
|
||||||
#expect(Bool(true)) // Command parsed successfully
|
#expect(Bool(true)) // Command parsed successfully
|
||||||
} catch {
|
} catch {
|
||||||
// Some may fail due to argument parsing limits, which is expected
|
// Some may fail due to argument parsing limits, which is expected
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
import ArgumentParser
|
import ArgumentParser
|
||||||
|
import Foundation
|
||||||
@testable import peekaboo
|
@testable import peekaboo
|
||||||
import Testing
|
import Testing
|
||||||
import Foundation
|
|
||||||
|
|
||||||
@Suite("ImageCommand Tests", .tags(.imageCapture, .unit))
|
@Suite("ImageCommand Tests", .tags(.imageCapture, .unit))
|
||||||
struct ImageCommandTests {
|
struct ImageCommandTests {
|
||||||
|
|
||||||
// MARK: - Test Data & Helpers
|
// MARK: - Test Data & Helpers
|
||||||
|
|
||||||
private static let validFormats: [ImageFormat] = [.png, .jpg]
|
private static let validFormats: [ImageFormat] = [.png, .jpg]
|
||||||
|
|
@ -13,7 +12,7 @@ struct ImageCommandTests {
|
||||||
private static let validCaptureFocus: [CaptureFocus] = [.background, .foreground]
|
private static let validCaptureFocus: [CaptureFocus] = [.background, .foreground]
|
||||||
|
|
||||||
private static func createTestCommand(_ args: [String] = []) throws -> ImageCommand {
|
private static func createTestCommand(_ args: [String] = []) throws -> ImageCommand {
|
||||||
return try ImageCommand.parse(args)
|
try ImageCommand.parse(args)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Command Parsing Tests
|
// MARK: - Command Parsing Tests
|
||||||
|
|
@ -124,25 +123,29 @@ struct ImageCommandTests {
|
||||||
|
|
||||||
// MARK: - Parameterized Command Tests
|
// MARK: - Parameterized Command Tests
|
||||||
|
|
||||||
@Test("Various command combinations",
|
@Test(
|
||||||
arguments: [
|
"Various command combinations",
|
||||||
(args: ["--mode", "screen", "--format", "png"], mode: CaptureMode.screen, format: ImageFormat.png),
|
arguments: [
|
||||||
(args: ["--mode", "window", "--format", "jpg"], mode: CaptureMode.window, format: ImageFormat.jpg),
|
(args: ["--mode", "screen", "--format", "png"], mode: CaptureMode.screen, format: ImageFormat.png),
|
||||||
(args: ["--mode", "multi", "--json-output"], mode: CaptureMode.multi, format: ImageFormat.png)
|
(args: ["--mode", "window", "--format", "jpg"], mode: CaptureMode.window, format: ImageFormat.jpg),
|
||||||
])
|
(args: ["--mode", "multi", "--json-output"], mode: CaptureMode.multi, format: ImageFormat.png)
|
||||||
|
]
|
||||||
|
)
|
||||||
func commandCombinations(args: [String], mode: CaptureMode, format: ImageFormat) throws {
|
func commandCombinations(args: [String], mode: CaptureMode, format: ImageFormat) throws {
|
||||||
let command = try ImageCommand.parse(args)
|
let command = try ImageCommand.parse(args)
|
||||||
#expect(command.mode == mode)
|
#expect(command.mode == mode)
|
||||||
#expect(command.format == format)
|
#expect(command.format == format)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test("Invalid arguments throw errors",
|
@Test(
|
||||||
arguments: [
|
"Invalid arguments throw errors",
|
||||||
["--mode", "invalid"],
|
arguments: [
|
||||||
["--format", "bmp"],
|
["--mode", "invalid"],
|
||||||
["--capture-focus", "neither"],
|
["--format", "bmp"],
|
||||||
["--screen-index", "abc"]
|
["--capture-focus", "neither"],
|
||||||
])
|
["--screen-index", "abc"]
|
||||||
|
]
|
||||||
|
)
|
||||||
func invalidArguments(args: [String]) {
|
func invalidArguments(args: [String]) {
|
||||||
#expect(throws: (any Error).self) {
|
#expect(throws: (any Error).self) {
|
||||||
_ = try ImageCommand.parse(args)
|
_ = try ImageCommand.parse(args)
|
||||||
|
|
@ -185,7 +188,7 @@ struct ImageCommandTests {
|
||||||
encoder.keyEncodingStrategy = .convertToSnakeCase
|
encoder.keyEncodingStrategy = .convertToSnakeCase
|
||||||
let data = try encoder.encode(captureData)
|
let data = try encoder.encode(captureData)
|
||||||
|
|
||||||
#expect(data.count > 0)
|
#expect(!data.isEmpty)
|
||||||
|
|
||||||
// Test decoding
|
// Test decoding
|
||||||
let decoder = JSONDecoder()
|
let decoder = JSONDecoder()
|
||||||
|
|
@ -251,15 +254,19 @@ struct ImageCommandTests {
|
||||||
#expect(command.jsonOutput == false)
|
#expect(command.jsonOutput == false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test("Screen index boundary values",
|
@Test(
|
||||||
arguments: [-1, 0, 1, 99, Int.max])
|
"Screen index boundary values",
|
||||||
|
arguments: [-1, 0, 1, 99, Int.max]
|
||||||
|
)
|
||||||
func screenIndexBoundaries(index: Int) throws {
|
func screenIndexBoundaries(index: Int) throws {
|
||||||
let command = try ImageCommand.parse(["--screen-index", String(index)])
|
let command = try ImageCommand.parse(["--screen-index", String(index)])
|
||||||
#expect(command.screenIndex == index)
|
#expect(command.screenIndex == index)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test("Window index boundary values",
|
@Test(
|
||||||
arguments: [-1, 0, 1, 10, Int.max])
|
"Window index boundary values",
|
||||||
|
arguments: [-1, 0, 1, 10, Int.max]
|
||||||
|
)
|
||||||
func windowIndexBoundaries(index: Int) throws {
|
func windowIndexBoundaries(index: Int) throws {
|
||||||
let command = try ImageCommand.parse(["--window-index", String(index)])
|
let command = try ImageCommand.parse(["--window-index", String(index)])
|
||||||
#expect(command.windowIndex == index)
|
#expect(command.windowIndex == index)
|
||||||
|
|
@ -283,7 +290,6 @@ struct ImageCommandTests {
|
||||||
|
|
||||||
@Suite("ImageCommand Advanced Tests", .tags(.imageCapture, .integration))
|
@Suite("ImageCommand Advanced Tests", .tags(.imageCapture, .integration))
|
||||||
struct ImageCommandAdvancedTests {
|
struct ImageCommandAdvancedTests {
|
||||||
|
|
||||||
// MARK: - Complex Scenario Tests
|
// MARK: - Complex Scenario Tests
|
||||||
|
|
||||||
@Test("Complex command with multiple options", .tags(.fast))
|
@Test("Complex command with multiple options", .tags(.fast))
|
||||||
|
|
@ -331,20 +337,22 @@ struct ImageCommandAdvancedTests {
|
||||||
#expect(config.abstract.contains("Capture"))
|
#expect(config.abstract.contains("Capture"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test("Window specifier combinations",
|
@Test(
|
||||||
arguments: [
|
"Window specifier combinations",
|
||||||
(app: "Safari", title: "Home", index: nil),
|
arguments: [
|
||||||
(app: "Finder", title: nil, index: 0),
|
(app: "Safari", title: "Home", index: nil),
|
||||||
(app: "Terminal", title: nil, index: nil)
|
(app: "Finder", title: nil, index: 0),
|
||||||
])
|
(app: "Terminal", title: nil, index: nil)
|
||||||
|
]
|
||||||
|
)
|
||||||
func windowSpecifierCombinations(app: String, title: String?, index: Int?) throws {
|
func windowSpecifierCombinations(app: String, title: String?, index: Int?) throws {
|
||||||
var args = ["--app", app]
|
var args = ["--app", app]
|
||||||
|
|
||||||
if let title = title {
|
if let title {
|
||||||
args.append(contentsOf: ["--window-title", title])
|
args.append(contentsOf: ["--window-title", title])
|
||||||
}
|
}
|
||||||
|
|
||||||
if let index = index {
|
if let index {
|
||||||
args.append(contentsOf: ["--window-index", String(index)])
|
args.append(contentsOf: ["--window-index", String(index)])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -355,13 +363,15 @@ struct ImageCommandAdvancedTests {
|
||||||
#expect(command.windowIndex == index)
|
#expect(command.windowIndex == index)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test("Path expansion handling",
|
@Test(
|
||||||
arguments: [
|
"Path expansion handling",
|
||||||
"~/Desktop/screenshot.png",
|
arguments: [
|
||||||
"/tmp/test.png",
|
"~/Desktop/screenshot.png",
|
||||||
"./relative/path.png",
|
"/tmp/test.png",
|
||||||
"/path with spaces/image.png"
|
"./relative/path.png",
|
||||||
])
|
"/path with spaces/image.png"
|
||||||
|
]
|
||||||
|
)
|
||||||
func pathExpansion(path: String) throws {
|
func pathExpansion(path: String) throws {
|
||||||
let command = try ImageCommand.parse(["--path", path])
|
let command = try ImageCommand.parse(["--path", path])
|
||||||
#expect(command.path == path)
|
#expect(command.path == path)
|
||||||
|
|
@ -405,12 +415,12 @@ struct ImageCommandAdvancedTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test("MIME type assignment logic", .tags(.fast))
|
@Test("MIME type assignment logic", .tags(.fast))
|
||||||
func mimeTypeAssignment() {
|
func mimeTypeAssignment() throws {
|
||||||
// Test MIME type logic for different formats
|
// Test MIME type logic for different formats
|
||||||
let pngCommand = try! ImageCommand.parse(["--format", "png"])
|
let pngCommand = try ImageCommand.parse(["--format", "png"])
|
||||||
#expect(pngCommand.format == .png)
|
#expect(pngCommand.format == .png)
|
||||||
|
|
||||||
let jpgCommand = try! ImageCommand.parse(["--format", "jpg"])
|
let jpgCommand = try ImageCommand.parse(["--format", "jpg"])
|
||||||
#expect(jpgCommand.format == .jpg)
|
#expect(jpgCommand.format == .jpg)
|
||||||
|
|
||||||
// Verify MIME types would be assigned correctly
|
// Verify MIME types would be assigned correctly
|
||||||
|
|
@ -439,16 +449,18 @@ struct ImageCommandAdvancedTests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test("Command option combinations validation",
|
@Test(
|
||||||
arguments: [
|
"Command option combinations validation",
|
||||||
(["--mode", "screen"], true),
|
arguments: [
|
||||||
(["--mode", "window", "--app", "Finder"], true),
|
(["--mode", "screen"], true),
|
||||||
(["--mode", "multi"], true),
|
(["--mode", "window", "--app", "Finder"], true),
|
||||||
(["--app", "Safari"], true),
|
(["--mode", "multi"], true),
|
||||||
(["--window-title", "Test"], true),
|
(["--app", "Safari"], true),
|
||||||
(["--screen-index", "0"], true),
|
(["--window-title", "Test"], true),
|
||||||
(["--window-index", "0"], true)
|
(["--screen-index", "0"], true),
|
||||||
])
|
(["--window-index", "0"], true)
|
||||||
|
]
|
||||||
|
)
|
||||||
func commandOptionCombinations(args: [String], shouldParse: Bool) {
|
func commandOptionCombinations(args: [String], shouldParse: Bool) {
|
||||||
do {
|
do {
|
||||||
let command = try ImageCommand.parse(args)
|
let command = try ImageCommand.parse(args)
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
|
import Foundation
|
||||||
@testable import peekaboo
|
@testable import peekaboo
|
||||||
import Testing
|
import Testing
|
||||||
import Foundation
|
|
||||||
|
|
||||||
@Suite("JSONOutput Tests", .tags(.jsonOutput, .unit))
|
@Suite("JSONOutput Tests", .tags(.jsonOutput, .unit))
|
||||||
struct JSONOutputTests {
|
struct JSONOutputTests {
|
||||||
|
|
||||||
// MARK: - AnyCodable Tests
|
// MARK: - AnyCodable Tests
|
||||||
|
|
||||||
@Test("AnyCodable encoding with various types", .tags(.fast))
|
@Test("AnyCodable encoding with various types", .tags(.fast))
|
||||||
|
|
@ -54,7 +53,8 @@ struct JSONOutputTests {
|
||||||
@Test("AnyCodable decoding", .tags(.fast))
|
@Test("AnyCodable decoding", .tags(.fast))
|
||||||
func anyCodableDecoding() throws {
|
func anyCodableDecoding() throws {
|
||||||
// Test decoding from JSON
|
// Test decoding from JSON
|
||||||
let jsonData = #"{"string": "test", "number": 42, "bool": true, "null": null}"#.data(using: .utf8)!
|
let jsonString = #"{"string": "test", "number": 42, "bool": true, "null": null}"#
|
||||||
|
let jsonData = Data(jsonString.utf8)
|
||||||
let decoded = try JSONDecoder().decode([String: AnyCodable].self, from: jsonData)
|
let decoded = try JSONDecoder().decode([String: AnyCodable].self, from: jsonData)
|
||||||
|
|
||||||
#expect(decoded["string"]?.value as? String == "test")
|
#expect(decoded["string"]?.value as? String == "test")
|
||||||
|
|
@ -195,7 +195,7 @@ struct JSONOutputTests {
|
||||||
app_name: "App \(index)",
|
app_name: "App \(index)",
|
||||||
bundle_id: "com.test.app\(index)",
|
bundle_id: "com.test.app\(index)",
|
||||||
pid: Int32(1000 + index),
|
pid: Int32(1000 + index),
|
||||||
is_active: index % 2 == 0,
|
is_active: index.isMultiple(of: 2),
|
||||||
window_count: index % 10
|
window_count: index % 10
|
||||||
)
|
)
|
||||||
largeAppList.append(appInfo)
|
largeAppList.append(appInfo)
|
||||||
|
|
@ -208,27 +208,27 @@ struct JSONOutputTests {
|
||||||
let encoded = try JSONEncoder().encode(data)
|
let encoded = try JSONEncoder().encode(data)
|
||||||
let encodingTime = CFAbsoluteTimeGetCurrent() - startTime
|
let encodingTime = CFAbsoluteTimeGetCurrent() - startTime
|
||||||
|
|
||||||
#expect(encoded.count > 0)
|
#expect(!encoded.isEmpty)
|
||||||
#expect(encodingTime < 1.0) // Should encode within 1 second
|
#expect(encodingTime < 1.0) // Should encode within 1 second
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test("Thread safety of JSON operations", .tags(.concurrency))
|
@Test("Thread safety of JSON operations", .tags(.concurrency))
|
||||||
func threadSafetyJSONOperations() async {
|
func threadSafetyJSONOperations() async {
|
||||||
await withTaskGroup(of: Bool.self) { group in
|
await withTaskGroup(of: Bool.self) { group in
|
||||||
for i in 0..<10 {
|
for index in 0..<10 {
|
||||||
group.addTask {
|
group.addTask {
|
||||||
do {
|
do {
|
||||||
let appInfo = ApplicationInfo(
|
let appInfo = ApplicationInfo(
|
||||||
app_name: "App \(i)",
|
app_name: "App \(index)",
|
||||||
bundle_id: "com.test.app\(i)",
|
bundle_id: "com.test.app\(index)",
|
||||||
pid: Int32(1000 + i),
|
pid: Int32(1000 + index),
|
||||||
is_active: true,
|
is_active: true,
|
||||||
window_count: 1
|
window_count: 1
|
||||||
)
|
)
|
||||||
|
|
||||||
// Test encoding through AnyCodable instead
|
// Test encoding through AnyCodable instead
|
||||||
let anyCodable = AnyCodable(appInfo)
|
let anyCodable = AnyCodable(appInfo)
|
||||||
let _ = try JSONEncoder().encode(anyCodable)
|
_ = try JSONEncoder().encode(anyCodable)
|
||||||
return true
|
return true
|
||||||
} catch {
|
} catch {
|
||||||
return false
|
return false
|
||||||
|
|
@ -237,10 +237,8 @@ struct JSONOutputTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
var successCount = 0
|
var successCount = 0
|
||||||
for await success in group {
|
for await success in group where success {
|
||||||
if success {
|
successCount += 1
|
||||||
successCount += 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#expect(successCount == 10)
|
#expect(successCount == 10)
|
||||||
|
|
@ -261,7 +259,7 @@ struct JSONOutputTests {
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let encoded = try JSONEncoder().encode(data)
|
let encoded = try JSONEncoder().encode(data)
|
||||||
#expect(encoded.count > 0)
|
#expect(!encoded.isEmpty)
|
||||||
} catch {
|
} catch {
|
||||||
Issue.record("JSON encoding should not fail: \(error)")
|
Issue.record("JSON encoding should not fail: \(error)")
|
||||||
}
|
}
|
||||||
|
|
@ -285,7 +283,7 @@ struct JSONOutputTests {
|
||||||
|
|
||||||
for errorCode in errorCodes {
|
for errorCode in errorCodes {
|
||||||
#expect(!errorCode.rawValue.isEmpty)
|
#expect(!errorCode.rawValue.isEmpty)
|
||||||
#expect(errorCode.rawValue.allSatisfy { $0.isASCII })
|
#expect(errorCode.rawValue.allSatisfy(\.isASCII))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -294,7 +292,6 @@ struct JSONOutputTests {
|
||||||
|
|
||||||
@Suite("JSON Output Format Validation", .tags(.jsonOutput, .integration))
|
@Suite("JSON Output Format Validation", .tags(.jsonOutput, .integration))
|
||||||
struct JSONOutputFormatValidationTests {
|
struct JSONOutputFormatValidationTests {
|
||||||
|
|
||||||
@Test("MCP protocol compliance", .tags(.integration))
|
@Test("MCP protocol compliance", .tags(.integration))
|
||||||
func mcpProtocolCompliance() throws {
|
func mcpProtocolCompliance() throws {
|
||||||
// Test that JSON output follows MCP protocol format
|
// Test that JSON output follows MCP protocol format
|
||||||
|
|
@ -357,7 +354,7 @@ struct JSONOutputFormatValidationTests {
|
||||||
window_id: UInt32(1000 + index),
|
window_id: UInt32(1000 + index),
|
||||||
window_index: index,
|
window_index: index,
|
||||||
bounds: WindowBounds(xCoordinate: index * 10, yCoordinate: index * 10, width: 800, height: 600),
|
bounds: WindowBounds(xCoordinate: index * 10, yCoordinate: index * 10, width: 800, height: 600),
|
||||||
is_on_screen: index % 2 == 0
|
is_on_screen: index.isMultiple(of: 2)
|
||||||
)
|
)
|
||||||
windows.append(window)
|
windows.append(window)
|
||||||
}
|
}
|
||||||
|
|
@ -375,11 +372,11 @@ struct JSONOutputFormatValidationTests {
|
||||||
let encoded = try JSONEncoder().encode(windowData)
|
let encoded = try JSONEncoder().encode(windowData)
|
||||||
let duration = CFAbsoluteTimeGetCurrent() - startTime
|
let duration = CFAbsoluteTimeGetCurrent() - startTime
|
||||||
|
|
||||||
#expect(encoded.count > 0)
|
#expect(!encoded.isEmpty)
|
||||||
#expect(duration < 0.5) // Should complete within 500ms
|
#expect(duration < 0.5) // Should complete within 500ms
|
||||||
|
|
||||||
// Verify the JSON is valid
|
// Verify the JSON is valid
|
||||||
let _ = try JSONSerialization.jsonObject(with: encoded)
|
_ = try JSONSerialization.jsonObject(with: encoded)
|
||||||
#expect(Bool(true)) // JSON was successfully created
|
#expect(Bool(true)) // JSON was successfully created
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import ArgumentParser
|
import ArgumentParser
|
||||||
|
import Foundation
|
||||||
@testable import peekaboo
|
@testable import peekaboo
|
||||||
import Testing
|
import Testing
|
||||||
import Foundation
|
|
||||||
|
|
||||||
@Suite("ListCommand Tests", .tags(.unit))
|
@Suite("ListCommand Tests", .tags(.unit))
|
||||||
struct ListCommandTests {
|
struct ListCommandTests {
|
||||||
|
|
@ -62,15 +62,17 @@ struct ListCommandTests {
|
||||||
|
|
||||||
// MARK: - Parameterized Command Tests
|
// MARK: - Parameterized Command Tests
|
||||||
|
|
||||||
@Test("WindowsSubcommand detail parsing",
|
@Test(
|
||||||
arguments: [
|
"WindowsSubcommand detail parsing",
|
||||||
"off_screen",
|
arguments: [
|
||||||
"bounds",
|
"off_screen",
|
||||||
"ids",
|
"bounds",
|
||||||
"off_screen,bounds",
|
"ids",
|
||||||
"bounds,ids",
|
"off_screen,bounds",
|
||||||
"off_screen,bounds,ids"
|
"bounds,ids",
|
||||||
])
|
"off_screen,bounds,ids"
|
||||||
|
]
|
||||||
|
)
|
||||||
func windowsDetailParsing(details: String) throws {
|
func windowsDetailParsing(details: String) throws {
|
||||||
let command = try WindowsSubcommand.parse([
|
let command = try WindowsSubcommand.parse([
|
||||||
"--app", "Safari",
|
"--app", "Safari",
|
||||||
|
|
@ -245,8 +247,10 @@ struct ListCommandTests {
|
||||||
|
|
||||||
// MARK: - Performance Tests
|
// MARK: - Performance Tests
|
||||||
|
|
||||||
@Test("ApplicationListData encoding performance",
|
@Test(
|
||||||
arguments: [10, 50, 100, 200])
|
"ApplicationListData encoding performance",
|
||||||
|
arguments: [10, 50, 100, 200]
|
||||||
|
)
|
||||||
func applicationListEncodingPerformance(appCount: Int) throws {
|
func applicationListEncodingPerformance(appCount: Int) throws {
|
||||||
// Test performance of encoding many applications
|
// Test performance of encoding many applications
|
||||||
let apps = (0..<appCount).map { index in
|
let apps = (0..<appCount).map { index in
|
||||||
|
|
@ -265,7 +269,7 @@ struct ListCommandTests {
|
||||||
|
|
||||||
// Ensure encoding works correctly
|
// Ensure encoding works correctly
|
||||||
let data = try encoder.encode(appData)
|
let data = try encoder.encode(appData)
|
||||||
#expect(data.count > 0)
|
#expect(!data.isEmpty)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -273,7 +277,6 @@ struct ListCommandTests {
|
||||||
|
|
||||||
@Suite("ListCommand Advanced Tests", .tags(.integration))
|
@Suite("ListCommand Advanced Tests", .tags(.integration))
|
||||||
struct ListCommandAdvancedTests {
|
struct ListCommandAdvancedTests {
|
||||||
|
|
||||||
@Test("ServerStatusSubcommand parsing", .tags(.fast))
|
@Test("ServerStatusSubcommand parsing", .tags(.fast))
|
||||||
func serverStatusSubcommandParsing() throws {
|
func serverStatusSubcommandParsing() throws {
|
||||||
let command = try ServerStatusSubcommand.parse([])
|
let command = try ServerStatusSubcommand.parse([])
|
||||||
|
|
@ -298,12 +301,14 @@ struct ListCommandAdvancedTests {
|
||||||
#expect(statusHelp.contains("status"))
|
#expect(statusHelp.contains("status"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test("Complex window info structures",
|
@Test(
|
||||||
arguments: [
|
"Complex window info structures",
|
||||||
(title: "Main Window", id: 1001, onScreen: true),
|
arguments: [
|
||||||
(title: "Hidden Window", id: 2001, onScreen: false),
|
(title: "Main Window", id: 1001, onScreen: true),
|
||||||
(title: "Minimized", id: 3001, onScreen: false)
|
(title: "Hidden Window", id: 2001, onScreen: false),
|
||||||
])
|
(title: "Minimized", id: 3001, onScreen: false)
|
||||||
|
]
|
||||||
|
)
|
||||||
func complexWindowInfo(title: String, id: UInt32, onScreen: Bool) throws {
|
func complexWindowInfo(title: String, id: UInt32, onScreen: Bool) throws {
|
||||||
let windowInfo = WindowInfo(
|
let windowInfo = WindowInfo(
|
||||||
window_title: title,
|
window_title: title,
|
||||||
|
|
@ -326,13 +331,15 @@ struct ListCommandAdvancedTests {
|
||||||
#expect(decoded.is_on_screen == onScreen)
|
#expect(decoded.is_on_screen == onScreen)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test("Application state combinations",
|
@Test(
|
||||||
arguments: [
|
"Application state combinations",
|
||||||
(active: true, windowCount: 5),
|
arguments: [
|
||||||
(active: false, windowCount: 0),
|
(active: true, windowCount: 5),
|
||||||
(active: true, windowCount: 0),
|
(active: false, windowCount: 0),
|
||||||
(active: false, windowCount: 10)
|
(active: true, windowCount: 0),
|
||||||
])
|
(active: false, windowCount: 10)
|
||||||
|
]
|
||||||
|
)
|
||||||
func applicationStates(active: Bool, windowCount: Int) {
|
func applicationStates(active: Bool, windowCount: Int) {
|
||||||
let appInfo = ApplicationInfo(
|
let appInfo = ApplicationInfo(
|
||||||
app_name: "TestApp",
|
app_name: "TestApp",
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
|
import Foundation
|
||||||
@testable import peekaboo
|
@testable import peekaboo
|
||||||
import Testing
|
import Testing
|
||||||
import Foundation
|
|
||||||
|
|
||||||
@Suite("Logger Tests", .tags(.logger, .unit), .serialized)
|
@Suite("Logger Tests", .tags(.logger, .unit), .serialized)
|
||||||
struct LoggerTests {
|
struct LoggerTests {
|
||||||
|
|
||||||
// MARK: - Basic Functionality Tests
|
// MARK: - Basic Functionality Tests
|
||||||
|
|
||||||
@Test("Logger singleton instance", .tags(.fast))
|
@Test("Logger singleton instance", .tags(.fast))
|
||||||
|
|
@ -121,11 +120,11 @@ struct LoggerTests {
|
||||||
|
|
||||||
await withTaskGroup(of: Void.self) { group in
|
await withTaskGroup(of: Void.self) { group in
|
||||||
// Create multiple concurrent logging tasks
|
// Create multiple concurrent logging tasks
|
||||||
for i in 0..<10 {
|
for index in 0..<10 {
|
||||||
group.addTask {
|
group.addTask {
|
||||||
logger.debug("Concurrent message \(i)")
|
logger.debug("Concurrent message \(index)")
|
||||||
logger.info("Concurrent info \(i)")
|
logger.info("Concurrent info \(index)")
|
||||||
logger.error("Concurrent error \(i)")
|
logger.error("Concurrent error \(index)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -141,10 +140,8 @@ struct LoggerTests {
|
||||||
// Verify no corruption by checking for our messages
|
// Verify no corruption by checking for our messages
|
||||||
let recentLogs = finalLogs.suffix(30)
|
let recentLogs = finalLogs.suffix(30)
|
||||||
var foundMessages = 0
|
var foundMessages = 0
|
||||||
for i in 0..<10 {
|
for index in 0..<10 where recentLogs.contains(where: { $0.contains("Concurrent message \(index)") }) {
|
||||||
if recentLogs.contains(where: { $0.contains("Concurrent message \(i)") }) {
|
foundMessages += 1
|
||||||
foundMessages += 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Should find most or all messages (allowing for some timing issues)
|
// Should find most or all messages (allowing for some timing issues)
|
||||||
|
|
@ -161,15 +158,15 @@ struct LoggerTests {
|
||||||
await withTaskGroup(of: Void.self) { group in
|
await withTaskGroup(of: Void.self) { group in
|
||||||
// Task 1: Rapid mode switching
|
// Task 1: Rapid mode switching
|
||||||
group.addTask {
|
group.addTask {
|
||||||
for i in 0..<50 {
|
for index in 0..<50 {
|
||||||
logger.setJsonOutputMode(i % 2 == 0)
|
logger.setJsonOutputMode(index.isMultiple(of: 2))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Task 2: Continuous logging during mode switches
|
// Task 2: Continuous logging during mode switches
|
||||||
group.addTask {
|
group.addTask {
|
||||||
for i in 0..<100 {
|
for index in 0..<100 {
|
||||||
logger.debug("Mode switch test \(i)")
|
logger.debug("Mode switch test \(index)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -177,7 +174,7 @@ struct LoggerTests {
|
||||||
group.addTask {
|
group.addTask {
|
||||||
for _ in 0..<10 {
|
for _ in 0..<10 {
|
||||||
let logs = logger.getDebugLogs()
|
let logs = logger.getDebugLogs()
|
||||||
#expect(logs.count >= 0) // Should not crash
|
// Logs count is always non-negative
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -202,10 +199,10 @@ struct LoggerTests {
|
||||||
let initialCount = logger.getDebugLogs().count
|
let initialCount = logger.getDebugLogs().count
|
||||||
|
|
||||||
// Generate many log messages
|
// Generate many log messages
|
||||||
for i in 1...100 {
|
for index in 1...100 {
|
||||||
logger.debug("Memory test message \(i)")
|
logger.debug("Memory test message \(index)")
|
||||||
logger.info("Memory test info \(i)")
|
logger.info("Memory test info \(index)")
|
||||||
logger.error("Memory test error \(i)")
|
logger.error("Memory test error \(index)")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for logging
|
// Wait for logging
|
||||||
|
|
@ -264,8 +261,8 @@ struct LoggerTests {
|
||||||
let messageCount = 1000
|
let messageCount = 1000
|
||||||
let startTime = CFAbsoluteTimeGetCurrent()
|
let startTime = CFAbsoluteTimeGetCurrent()
|
||||||
|
|
||||||
for i in 1...messageCount {
|
for index in 1...messageCount {
|
||||||
logger.debug("Performance test message \(i)")
|
logger.debug("Performance test message \(index)")
|
||||||
}
|
}
|
||||||
|
|
||||||
let duration = CFAbsoluteTimeGetCurrent() - startTime
|
let duration = CFAbsoluteTimeGetCurrent() - startTime
|
||||||
|
|
@ -284,8 +281,8 @@ struct LoggerTests {
|
||||||
let logger = Logger.shared
|
let logger = Logger.shared
|
||||||
|
|
||||||
// Add many messages first
|
// Add many messages first
|
||||||
for i in 1...100 {
|
for index in 1...100 {
|
||||||
logger.debug("Retrieval test \(i)")
|
logger.debug("Retrieval test \(index)")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Measure retrieval performance
|
// Measure retrieval performance
|
||||||
|
|
@ -293,7 +290,7 @@ struct LoggerTests {
|
||||||
|
|
||||||
for _ in 1...10 {
|
for _ in 1...10 {
|
||||||
let logs = logger.getDebugLogs()
|
let logs = logger.getDebugLogs()
|
||||||
#expect(logs.count > 0)
|
#expect(!logs.isEmpty)
|
||||||
}
|
}
|
||||||
|
|
||||||
let duration = CFAbsoluteTimeGetCurrent() - startTime
|
let duration = CFAbsoluteTimeGetCurrent() - startTime
|
||||||
|
|
@ -467,8 +464,8 @@ struct LoggerTests {
|
||||||
try? await Task.sleep(nanoseconds: 10_000_000) // 10ms
|
try? await Task.sleep(nanoseconds: 10_000_000) // 10ms
|
||||||
|
|
||||||
// Test consistent JSON mode logging
|
// Test consistent JSON mode logging
|
||||||
for i in 1...10 {
|
for index in 1...10 {
|
||||||
logger.debug("State test \(i)")
|
logger.debug("State test \(index)")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for logging
|
// Wait for logging
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
|
import CoreGraphics
|
||||||
@testable import peekaboo
|
@testable import peekaboo
|
||||||
import Testing
|
import Testing
|
||||||
import CoreGraphics
|
|
||||||
|
|
||||||
@Suite("Models Tests", .tags(.models, .unit))
|
@Suite("Models Tests", .tags(.models, .unit))
|
||||||
struct ModelsTests {
|
struct ModelsTests {
|
||||||
|
|
@ -266,13 +266,19 @@ struct ModelsTests {
|
||||||
@Test("CaptureError descriptions are user-friendly", .tags(.fast))
|
@Test("CaptureError descriptions are user-friendly", .tags(.fast))
|
||||||
func captureErrorDescriptions() {
|
func captureErrorDescriptions() {
|
||||||
#expect(CaptureError.noDisplaysAvailable.errorDescription == "No displays available for capture.")
|
#expect(CaptureError.noDisplaysAvailable.errorDescription == "No displays available for capture.")
|
||||||
#expect(CaptureError.screenRecordingPermissionDenied.errorDescription!.contains("Screen recording permission is required"))
|
#expect(CaptureError.screenRecordingPermissionDenied.errorDescription!
|
||||||
|
.contains("Screen recording permission is required")
|
||||||
|
)
|
||||||
#expect(CaptureError.invalidDisplayID.errorDescription == "Invalid display ID provided.")
|
#expect(CaptureError.invalidDisplayID.errorDescription == "Invalid display ID provided.")
|
||||||
#expect(CaptureError.captureCreationFailed.errorDescription == "Failed to create the screen capture.")
|
#expect(CaptureError.captureCreationFailed.errorDescription == "Failed to create the screen capture.")
|
||||||
#expect(CaptureError.windowNotFound.errorDescription == "The specified window could not be found.")
|
#expect(CaptureError.windowNotFound.errorDescription == "The specified window could not be found.")
|
||||||
#expect(CaptureError.windowCaptureFailed.errorDescription == "Failed to capture the specified window.")
|
#expect(CaptureError.windowCaptureFailed.errorDescription == "Failed to capture the specified window.")
|
||||||
#expect(CaptureError.fileWriteError("/tmp/test.png").errorDescription == "Failed to write capture file to path: /tmp/test.png.")
|
#expect(CaptureError.fileWriteError("/tmp/test.png")
|
||||||
#expect(CaptureError.appNotFound("Safari").errorDescription == "Application with identifier 'Safari' not found or is not running.")
|
.errorDescription == "Failed to write capture file to path: /tmp/test.png."
|
||||||
|
)
|
||||||
|
#expect(CaptureError.appNotFound("Safari")
|
||||||
|
.errorDescription == "Application with identifier 'Safari' not found or is not running."
|
||||||
|
)
|
||||||
#expect(CaptureError.invalidWindowIndex(5).errorDescription == "Invalid window index: 5.")
|
#expect(CaptureError.invalidWindowIndex(5).errorDescription == "Invalid window index: 5.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -346,17 +352,18 @@ struct ModelsTests {
|
||||||
|
|
||||||
@Suite("Model Edge Cases", .tags(.models, .unit))
|
@Suite("Model Edge Cases", .tags(.models, .unit))
|
||||||
struct ModelEdgeCaseTests {
|
struct ModelEdgeCaseTests {
|
||||||
|
@Test(
|
||||||
@Test("WindowBounds with edge values",
|
"WindowBounds with edge values",
|
||||||
arguments: [
|
arguments: [
|
||||||
(x: 0, y: 0, width: 0, height: 0),
|
(x: 0, y: 0, width: 0, height: 0),
|
||||||
(x: -100, y: -100, width: 100, height: 100),
|
(x: -100, y: -100, width: 100, height: 100),
|
||||||
(x: Int.max, y: Int.max, width: 1, height: 1)
|
(x: Int.max, y: Int.max, width: 1, height: 1)
|
||||||
])
|
]
|
||||||
func windowBoundsEdgeCases(x: Int, y: Int, width: Int, height: Int) {
|
)
|
||||||
let bounds = WindowBounds(xCoordinate: x, yCoordinate: y, width: width, height: height)
|
func windowBoundsEdgeCases(x xCoordinate: Int, y yCoordinate: Int, width: Int, height: Int) {
|
||||||
#expect(bounds.xCoordinate == x)
|
let bounds = WindowBounds(xCoordinate: xCoordinate, yCoordinate: yCoordinate, width: width, height: height)
|
||||||
#expect(bounds.yCoordinate == y)
|
#expect(bounds.xCoordinate == xCoordinate)
|
||||||
|
#expect(bounds.yCoordinate == yCoordinate)
|
||||||
#expect(bounds.width == width)
|
#expect(bounds.width == width)
|
||||||
#expect(bounds.height == height)
|
#expect(bounds.height == height)
|
||||||
}
|
}
|
||||||
|
|
@ -377,15 +384,17 @@ struct ModelEdgeCaseTests {
|
||||||
#expect(appInfo.window_count == Int.max)
|
#expect(appInfo.window_count == Int.max)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test("SavedFile path validation",
|
@Test(
|
||||||
arguments: [
|
"SavedFile path validation",
|
||||||
"/tmp/test.png",
|
arguments: [
|
||||||
"/Users/test/Desktop/screenshot.jpg",
|
"/tmp/test.png",
|
||||||
"~/Documents/capture.png",
|
"/Users/test/Desktop/screenshot.jpg",
|
||||||
"./relative/path/image.png",
|
"~/Documents/capture.png",
|
||||||
"/path with spaces/image.png",
|
"./relative/path/image.png",
|
||||||
"/path/with/特殊文字.png"
|
"/path with spaces/image.png",
|
||||||
])
|
"/path/with/特殊文字.png"
|
||||||
|
]
|
||||||
|
)
|
||||||
func savedFilePathValidation(path: String) {
|
func savedFilePathValidation(path: String) {
|
||||||
let savedFile = SavedFile(
|
let savedFile = SavedFile(
|
||||||
path: path,
|
path: path,
|
||||||
|
|
@ -400,8 +409,10 @@ struct ModelEdgeCaseTests {
|
||||||
#expect(!savedFile.path.isEmpty)
|
#expect(!savedFile.path.isEmpty)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test("MIME type validation",
|
@Test(
|
||||||
arguments: ["image/png", "image/jpeg", "image/jpg"])
|
"MIME type validation",
|
||||||
|
arguments: ["image/png", "image/jpeg", "image/jpg"]
|
||||||
|
)
|
||||||
func mimeTypeValidation(mimeType: String) {
|
func mimeTypeValidation(mimeType: String) {
|
||||||
let savedFile = SavedFile(
|
let savedFile = SavedFile(
|
||||||
path: "/tmp/test",
|
path: "/tmp/test",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
|
import AppKit
|
||||||
@testable import peekaboo
|
@testable import peekaboo
|
||||||
import Testing
|
import Testing
|
||||||
import AppKit
|
|
||||||
|
|
||||||
@Suite("PermissionsChecker Tests", .tags(.permissions, .unit))
|
@Suite("PermissionsChecker Tests", .tags(.permissions, .unit))
|
||||||
struct PermissionsCheckerTests {
|
struct PermissionsCheckerTests {
|
||||||
|
|
@ -131,7 +131,6 @@ struct PermissionsCheckerTests {
|
||||||
|
|
||||||
@Suite("Permission Edge Cases", .tags(.permissions, .unit))
|
@Suite("Permission Edge Cases", .tags(.permissions, .unit))
|
||||||
struct PermissionEdgeCaseTests {
|
struct PermissionEdgeCaseTests {
|
||||||
|
|
||||||
@Test("Permission checks are thread-safe", .tags(.integration))
|
@Test("Permission checks are thread-safe", .tags(.integration))
|
||||||
func threadSafePermissionChecks() async {
|
func threadSafePermissionChecks() async {
|
||||||
// Test concurrent permission checks
|
// Test concurrent permission checks
|
||||||
|
|
|
||||||
|
|
@ -10,13 +10,13 @@ struct WindowManagerTests {
|
||||||
func getWindowsForFinderApp() throws {
|
func getWindowsForFinderApp() throws {
|
||||||
// Get Finder's PID
|
// Get Finder's PID
|
||||||
let apps = NSWorkspace.shared.runningApplications
|
let apps = NSWorkspace.shared.runningApplications
|
||||||
let finder = try #require(apps.first(where: { $0.bundleIdentifier == "com.apple.finder" }))
|
let finder = try #require(apps.first { $0.bundleIdentifier == "com.apple.finder" })
|
||||||
|
|
||||||
// Test getting windows for Finder
|
// Test getting windows for Finder
|
||||||
let windows = try WindowManager.getWindowsForApp(pid: finder.processIdentifier)
|
let windows = try WindowManager.getWindowsForApp(pid: finder.processIdentifier)
|
||||||
|
|
||||||
// Finder usually has at least one window
|
// Finder usually has at least one window
|
||||||
#expect(windows.count >= 0)
|
// Windows count is always non-negative
|
||||||
|
|
||||||
// If there are windows, verify they're sorted by index
|
// If there are windows, verify they're sorted by index
|
||||||
if windows.count > 1 {
|
if windows.count > 1 {
|
||||||
|
|
@ -32,14 +32,14 @@ struct WindowManagerTests {
|
||||||
let windows = try WindowManager.getWindowsForApp(pid: 99999)
|
let windows = try WindowManager.getWindowsForApp(pid: 99999)
|
||||||
|
|
||||||
// Should return empty array, not throw
|
// Should return empty array, not throw
|
||||||
#expect(windows.count == 0)
|
#expect(windows.isEmpty)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test("Off-screen window filtering works correctly", .tags(.integration))
|
@Test("Off-screen window filtering works correctly", .tags(.integration))
|
||||||
func getWindowsWithOffScreenOption() throws {
|
func getWindowsWithOffScreenOption() throws {
|
||||||
// Get Finder's PID for testing
|
// Get Finder's PID for testing
|
||||||
let apps = NSWorkspace.shared.runningApplications
|
let apps = NSWorkspace.shared.runningApplications
|
||||||
let finder = try #require(apps.first(where: { $0.bundleIdentifier == "com.apple.finder" }))
|
let finder = try #require(apps.first { $0.bundleIdentifier == "com.apple.finder" })
|
||||||
|
|
||||||
// Test with includeOffScreen = true
|
// Test with includeOffScreen = true
|
||||||
let allWindows = try WindowManager.getWindowsForApp(pid: finder.processIdentifier, includeOffScreen: true)
|
let allWindows = try WindowManager.getWindowsForApp(pid: finder.processIdentifier, includeOffScreen: true)
|
||||||
|
|
@ -103,13 +103,15 @@ struct WindowManagerTests {
|
||||||
|
|
||||||
// MARK: - Parameterized Tests
|
// MARK: - Parameterized Tests
|
||||||
|
|
||||||
@Test("Window retrieval with various options",
|
@Test(
|
||||||
arguments: [
|
"Window retrieval with various options",
|
||||||
(includeOffScreen: true, includeBounds: true, includeIDs: true),
|
arguments: [
|
||||||
(includeOffScreen: false, includeBounds: true, includeIDs: true),
|
(includeOffScreen: true, includeBounds: true, includeIDs: true),
|
||||||
(includeOffScreen: true, includeBounds: false, includeIDs: true),
|
(includeOffScreen: false, includeBounds: true, includeIDs: true),
|
||||||
(includeOffScreen: true, includeBounds: true, includeIDs: false)
|
(includeOffScreen: true, includeBounds: false, includeIDs: true),
|
||||||
])
|
(includeOffScreen: true, includeBounds: true, includeIDs: false)
|
||||||
|
]
|
||||||
|
)
|
||||||
func windowRetrievalOptions(includeOffScreen: Bool, includeBounds: Bool, includeIDs: Bool) throws {
|
func windowRetrievalOptions(includeOffScreen: Bool, includeBounds: Bool, includeIDs: Bool) throws {
|
||||||
let apps = NSWorkspace.shared.runningApplications.filter { $0.activationPolicy == .regular }
|
let apps = NSWorkspace.shared.runningApplications.filter { $0.activationPolicy == .regular }
|
||||||
|
|
||||||
|
|
@ -144,15 +146,17 @@ struct WindowManagerTests {
|
||||||
|
|
||||||
// MARK: - Performance Tests
|
// MARK: - Performance Tests
|
||||||
|
|
||||||
@Test("Window retrieval performance",
|
@Test(
|
||||||
arguments: 1...5)
|
"Window retrieval performance",
|
||||||
|
arguments: 1...5
|
||||||
|
)
|
||||||
func getWindowsPerformance(iteration: Int) throws {
|
func getWindowsPerformance(iteration: Int) throws {
|
||||||
// Test performance of getting windows
|
// Test performance of getting windows
|
||||||
let apps = NSWorkspace.shared.runningApplications
|
let apps = NSWorkspace.shared.runningApplications
|
||||||
let finder = try #require(apps.first(where: { $0.bundleIdentifier == "com.apple.finder" }))
|
let finder = try #require(apps.first { $0.bundleIdentifier == "com.apple.finder" })
|
||||||
|
|
||||||
let windows = try WindowManager.getWindowsForApp(pid: finder.processIdentifier)
|
let windows = try WindowManager.getWindowsForApp(pid: finder.processIdentifier)
|
||||||
#expect(windows.count >= 0)
|
// Windows count is always non-negative
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Error Handling Tests
|
// MARK: - Error Handling Tests
|
||||||
|
|
@ -176,7 +180,6 @@ struct WindowManagerTests {
|
||||||
|
|
||||||
@Suite("WindowManager Advanced Tests", .tags(.windowManager, .integration))
|
@Suite("WindowManager Advanced Tests", .tags(.windowManager, .integration))
|
||||||
struct WindowManagerAdvancedTests {
|
struct WindowManagerAdvancedTests {
|
||||||
|
|
||||||
@Test("Multiple apps window retrieval", .tags(.integration))
|
@Test("Multiple apps window retrieval", .tags(.integration))
|
||||||
func multipleAppsWindows() throws {
|
func multipleAppsWindows() throws {
|
||||||
let apps = NSWorkspace.shared.runningApplications.filter { $0.activationPolicy == .regular }
|
let apps = NSWorkspace.shared.runningApplications.filter { $0.activationPolicy == .regular }
|
||||||
|
|
@ -186,7 +189,7 @@ struct WindowManagerAdvancedTests {
|
||||||
let windows = try WindowManager.getWindowsForApp(pid: app.processIdentifier)
|
let windows = try WindowManager.getWindowsForApp(pid: app.processIdentifier)
|
||||||
|
|
||||||
// Each app should successfully return a window list (even if empty)
|
// Each app should successfully return a window list (even if empty)
|
||||||
#expect(windows.count >= 0)
|
// Windows count is always non-negative
|
||||||
|
|
||||||
// Verify window indices are sequential
|
// Verify window indices are sequential
|
||||||
for (index, window) in windows.enumerated() {
|
for (index, window) in windows.enumerated() {
|
||||||
|
|
@ -214,8 +217,10 @@ struct WindowManagerAdvancedTests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test("System apps window detection",
|
@Test(
|
||||||
arguments: ["com.apple.finder", "com.apple.dock", "com.apple.systemuiserver"])
|
"System apps window detection",
|
||||||
|
arguments: ["com.apple.finder", "com.apple.dock", "com.apple.systemuiserver"]
|
||||||
|
)
|
||||||
func systemAppsWindows(bundleId: String) throws {
|
func systemAppsWindows(bundleId: String) throws {
|
||||||
let apps = NSWorkspace.shared.runningApplications
|
let apps = NSWorkspace.shared.runningApplications
|
||||||
|
|
||||||
|
|
@ -226,7 +231,7 @@ struct WindowManagerAdvancedTests {
|
||||||
let windows = try WindowManager.getWindowsForApp(pid: app.processIdentifier)
|
let windows = try WindowManager.getWindowsForApp(pid: app.processIdentifier)
|
||||||
|
|
||||||
// System apps might have 0 or more windows
|
// System apps might have 0 or more windows
|
||||||
#expect(windows.count >= 0)
|
// Windows count is always non-negative
|
||||||
|
|
||||||
// If windows exist, they should have valid properties
|
// If windows exist, they should have valid properties
|
||||||
for window in windows {
|
for window in windows {
|
||||||
|
|
@ -245,7 +250,7 @@ struct WindowManagerAdvancedTests {
|
||||||
|
|
||||||
for window in windows {
|
for window in windows {
|
||||||
// Title should be valid UTF-8
|
// Title should be valid UTF-8
|
||||||
#expect(window.title.utf8.count > 0)
|
#expect(!window.title.utf8.isEmpty)
|
||||||
|
|
||||||
// Should handle common special characters
|
// Should handle common special characters
|
||||||
let specialChars = ["—", "™", "©", "•", "…"]
|
let specialChars = ["—", "™", "©", "•", "…"]
|
||||||
|
|
@ -287,9 +292,9 @@ struct WindowManagerAdvancedTests {
|
||||||
#expect(results.count == 5)
|
#expect(results.count == 5)
|
||||||
for result in results {
|
for result in results {
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let windows):
|
case let .success(windows):
|
||||||
#expect(windows.count >= 0)
|
// Windows count is always non-negative
|
||||||
case .failure(let error):
|
case let .failure(error):
|
||||||
Issue.record("Concurrent query failed: \(error)")
|
Issue.record("Concurrent query failed: \(error)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue