mirror of
https://github.com/samsonjs/Peekaboo.git
synced 2026-04-26 14:57:47 +00:00
- Update to swift-tools-version 6.0 and enable StrictConcurrency - Make all data models and types Sendable for concurrency safety - Migrate commands from ParsableCommand to AsyncParsableCommand - Remove AsyncUtils.swift and synchronous bridging patterns - Update WindowBounds property names to snake_case for consistency - Ensure all error types conform to Sendable protocol - Add comprehensive Swift 6 migration documentation This migration enables full Swift 6 concurrency checking and data race safety while maintaining backward compatibility with the existing API. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
432 lines
15 KiB
Swift
432 lines
15 KiB
Swift
import CoreGraphics
|
|
@testable import peekaboo
|
|
import Testing
|
|
|
|
@Suite("Models Tests", .tags(.models, .unit))
|
|
struct ModelsTests {
|
|
// MARK: - Enum Tests
|
|
|
|
@Test("CaptureMode enum values and parsing", .tags(.fast))
|
|
func captureMode() {
|
|
// Test CaptureMode enum values
|
|
#expect(CaptureMode.screen.rawValue == "screen")
|
|
#expect(CaptureMode.window.rawValue == "window")
|
|
#expect(CaptureMode.multi.rawValue == "multi")
|
|
|
|
// Test CaptureMode from string
|
|
#expect(CaptureMode(rawValue: "screen") == .screen)
|
|
#expect(CaptureMode(rawValue: "window") == .window)
|
|
#expect(CaptureMode(rawValue: "multi") == .multi)
|
|
#expect(CaptureMode(rawValue: "invalid") == nil)
|
|
}
|
|
|
|
@Test("ImageFormat enum values and parsing", .tags(.fast))
|
|
func imageFormat() {
|
|
// Test ImageFormat enum values
|
|
#expect(ImageFormat.png.rawValue == "png")
|
|
#expect(ImageFormat.jpg.rawValue == "jpg")
|
|
|
|
// Test ImageFormat from string
|
|
#expect(ImageFormat(rawValue: "png") == .png)
|
|
#expect(ImageFormat(rawValue: "jpg") == .jpg)
|
|
#expect(ImageFormat(rawValue: "invalid") == nil)
|
|
}
|
|
|
|
@Test("CaptureFocus enum values and parsing", .tags(.fast))
|
|
func captureFocus() {
|
|
// Test CaptureFocus enum values
|
|
#expect(CaptureFocus.background.rawValue == "background")
|
|
#expect(CaptureFocus.auto.rawValue == "auto")
|
|
#expect(CaptureFocus.foreground.rawValue == "foreground")
|
|
|
|
// Test CaptureFocus from string
|
|
#expect(CaptureFocus(rawValue: "background") == .background)
|
|
#expect(CaptureFocus(rawValue: "auto") == .auto)
|
|
#expect(CaptureFocus(rawValue: "foreground") == .foreground)
|
|
#expect(CaptureFocus(rawValue: "invalid") == nil)
|
|
}
|
|
|
|
@Test("WindowDetailOption enum values and parsing", .tags(.fast))
|
|
func windowDetailOption() {
|
|
// Test WindowDetailOption enum values
|
|
#expect(WindowDetailOption.off_screen.rawValue == "off_screen")
|
|
#expect(WindowDetailOption.bounds.rawValue == "bounds")
|
|
#expect(WindowDetailOption.ids.rawValue == "ids")
|
|
|
|
// Test WindowDetailOption from string
|
|
#expect(WindowDetailOption(rawValue: "off_screen") == .off_screen)
|
|
#expect(WindowDetailOption(rawValue: "bounds") == .bounds)
|
|
#expect(WindowDetailOption(rawValue: "ids") == .ids)
|
|
#expect(WindowDetailOption(rawValue: "invalid") == nil)
|
|
}
|
|
|
|
// MARK: - Parameterized Enum Tests
|
|
|
|
@Test("CaptureMode raw values are valid", .tags(.fast))
|
|
func captureModeRawValuesValid() {
|
|
let validValues = ["screen", "window", "multi"]
|
|
for rawValue in validValues {
|
|
#expect(CaptureMode(rawValue: rawValue) != nil)
|
|
}
|
|
}
|
|
|
|
@Test("ImageFormat raw values are valid", .tags(.fast))
|
|
func imageFormatRawValuesValid() {
|
|
let validValues = ["png", "jpg"]
|
|
for rawValue in validValues {
|
|
#expect(ImageFormat(rawValue: rawValue) != nil)
|
|
}
|
|
}
|
|
|
|
@Test("CaptureFocus raw values are valid", .tags(.fast))
|
|
func captureFocusRawValuesValid() {
|
|
let validValues = ["background", "auto", "foreground"]
|
|
for rawValue in validValues {
|
|
#expect(CaptureFocus(rawValue: rawValue) != nil)
|
|
}
|
|
}
|
|
|
|
// MARK: - Model Structure Tests
|
|
|
|
@Test("WindowBounds initialization and properties", .tags(.fast))
|
|
func windowBounds() {
|
|
let bounds = WindowBounds(x_coordinate: 100, y_coordinate: 200, width: 1200, height: 800)
|
|
|
|
#expect(bounds.x_coordinate == 100)
|
|
#expect(bounds.y_coordinate == 200)
|
|
#expect(bounds.width == 1200)
|
|
#expect(bounds.height == 800)
|
|
}
|
|
|
|
@Test("SavedFile with all properties", .tags(.fast))
|
|
func savedFile() {
|
|
let savedFile = SavedFile(
|
|
path: "/tmp/test.png",
|
|
item_label: "Screen 1",
|
|
window_title: "Safari - Main Window",
|
|
window_id: 12345,
|
|
window_index: 0,
|
|
mime_type: "image/png"
|
|
)
|
|
|
|
#expect(savedFile.path == "/tmp/test.png")
|
|
#expect(savedFile.item_label == "Screen 1")
|
|
#expect(savedFile.window_title == "Safari - Main Window")
|
|
#expect(savedFile.window_id == 12345)
|
|
#expect(savedFile.window_index == 0)
|
|
#expect(savedFile.mime_type == "image/png")
|
|
}
|
|
|
|
@Test("SavedFile with nil optional values", .tags(.fast))
|
|
func savedFileWithNilValues() {
|
|
let savedFile = SavedFile(
|
|
path: "/tmp/screen.png",
|
|
item_label: nil,
|
|
window_title: nil,
|
|
window_id: nil,
|
|
window_index: nil,
|
|
mime_type: "image/png"
|
|
)
|
|
|
|
#expect(savedFile.path == "/tmp/screen.png")
|
|
#expect(savedFile.item_label == nil)
|
|
#expect(savedFile.window_title == nil)
|
|
#expect(savedFile.window_id == nil)
|
|
#expect(savedFile.window_index == nil)
|
|
#expect(savedFile.mime_type == "image/png")
|
|
}
|
|
|
|
@Test("ApplicationInfo initialization", .tags(.fast))
|
|
func applicationInfo() {
|
|
let appInfo = ApplicationInfo(
|
|
app_name: "Safari",
|
|
bundle_id: "com.apple.Safari",
|
|
pid: 1234,
|
|
is_active: true,
|
|
window_count: 2
|
|
)
|
|
|
|
#expect(appInfo.app_name == "Safari")
|
|
#expect(appInfo.bundle_id == "com.apple.Safari")
|
|
#expect(appInfo.pid == 1234)
|
|
#expect(appInfo.is_active == true)
|
|
#expect(appInfo.window_count == 2)
|
|
}
|
|
|
|
@Test("WindowInfo with bounds", .tags(.fast))
|
|
func windowInfo() {
|
|
let bounds = WindowBounds(x_coordinate: 100, y_coordinate: 100, width: 1200, height: 800)
|
|
let windowInfo = WindowInfo(
|
|
window_title: "Safari - Main Window",
|
|
window_id: 12345,
|
|
window_index: 0,
|
|
bounds: bounds,
|
|
is_on_screen: true
|
|
)
|
|
|
|
#expect(windowInfo.window_title == "Safari - Main Window")
|
|
#expect(windowInfo.window_id == 12345)
|
|
#expect(windowInfo.window_index == 0)
|
|
#expect(windowInfo.bounds != nil)
|
|
#expect(windowInfo.bounds?.x_coordinate == 100)
|
|
#expect(windowInfo.bounds?.y_coordinate == 100)
|
|
#expect(windowInfo.bounds?.width == 1200)
|
|
#expect(windowInfo.bounds?.height == 800)
|
|
#expect(windowInfo.is_on_screen == true)
|
|
}
|
|
|
|
@Test("TargetApplicationInfo", .tags(.fast))
|
|
func targetApplicationInfo() {
|
|
let targetApp = TargetApplicationInfo(
|
|
app_name: "Safari",
|
|
bundle_id: "com.apple.Safari",
|
|
pid: 1234
|
|
)
|
|
|
|
#expect(targetApp.app_name == "Safari")
|
|
#expect(targetApp.bundle_id == "com.apple.Safari")
|
|
#expect(targetApp.pid == 1234)
|
|
}
|
|
|
|
// MARK: - Collection Data Tests
|
|
|
|
@Test("ApplicationListData contains applications", .tags(.fast))
|
|
func applicationListData() {
|
|
let app1 = ApplicationInfo(
|
|
app_name: "Safari",
|
|
bundle_id: "com.apple.Safari",
|
|
pid: 1234,
|
|
is_active: true,
|
|
window_count: 2
|
|
)
|
|
|
|
let app2 = ApplicationInfo(
|
|
app_name: "Terminal",
|
|
bundle_id: "com.apple.Terminal",
|
|
pid: 5678,
|
|
is_active: false,
|
|
window_count: 1
|
|
)
|
|
|
|
let appListData = ApplicationListData(applications: [app1, app2])
|
|
|
|
#expect(appListData.applications.count == 2)
|
|
#expect(appListData.applications[0].app_name == "Safari")
|
|
#expect(appListData.applications[1].app_name == "Terminal")
|
|
}
|
|
|
|
@Test("WindowListData with target application", .tags(.fast))
|
|
func windowListData() {
|
|
let bounds = WindowBounds(x_coordinate: 100, y_coordinate: 100, width: 1200, height: 800)
|
|
let window = WindowInfo(
|
|
window_title: "Safari - Main Window",
|
|
window_id: 12345,
|
|
window_index: 0,
|
|
bounds: bounds,
|
|
is_on_screen: true
|
|
)
|
|
|
|
let targetApp = TargetApplicationInfo(
|
|
app_name: "Safari",
|
|
bundle_id: "com.apple.Safari",
|
|
pid: 1234
|
|
)
|
|
|
|
let windowListData = WindowListData(
|
|
windows: [window],
|
|
target_application_info: targetApp
|
|
)
|
|
|
|
#expect(windowListData.windows.count == 1)
|
|
#expect(windowListData.windows[0].window_title == "Safari - Main Window")
|
|
#expect(windowListData.target_application_info.app_name == "Safari")
|
|
#expect(windowListData.target_application_info.bundle_id == "com.apple.Safari")
|
|
#expect(windowListData.target_application_info.pid == 1234)
|
|
}
|
|
|
|
@Test("ImageCaptureData with saved files", .tags(.fast))
|
|
func imageCaptureData() {
|
|
let savedFile = SavedFile(
|
|
path: "/tmp/test.png",
|
|
item_label: "Screen 1",
|
|
window_title: nil,
|
|
window_id: nil,
|
|
window_index: nil,
|
|
mime_type: "image/png"
|
|
)
|
|
|
|
let imageData = ImageCaptureData(saved_files: [savedFile])
|
|
|
|
#expect(imageData.saved_files.count == 1)
|
|
#expect(imageData.saved_files[0].path == "/tmp/test.png")
|
|
#expect(imageData.saved_files[0].item_label == "Screen 1")
|
|
#expect(imageData.saved_files[0].mime_type == "image/png")
|
|
}
|
|
|
|
// MARK: - Error Tests
|
|
|
|
@Test("CaptureError descriptions are user-friendly", .tags(.fast))
|
|
func captureErrorDescriptions() {
|
|
#expect(CaptureError.noDisplaysAvailable.errorDescription == "No displays available for capture.")
|
|
#expect(CaptureError.screenRecordingPermissionDenied.errorDescription!
|
|
.contains("Screen recording permission is required")
|
|
)
|
|
#expect(CaptureError.invalidDisplayID.errorDescription == "Invalid display ID provided.")
|
|
#expect(CaptureError.captureCreationFailed(nil).errorDescription == "Failed to create the screen capture.")
|
|
#expect(CaptureError.windowNotFound.errorDescription == "The specified window could not be found.")
|
|
#expect(CaptureError.windowCaptureFailed(nil).errorDescription == "Failed to capture the specified window.")
|
|
let fileError = CaptureError.fileWriteError("/tmp/test.png", nil)
|
|
#expect(fileError.errorDescription?
|
|
.starts(with: "Failed to write capture file to path: /tmp/test.png.") == true
|
|
)
|
|
#expect(CaptureError.appNotFound("Safari")
|
|
.errorDescription == "Application with identifier 'Safari' not found or is not running."
|
|
)
|
|
#expect(CaptureError.invalidWindowIndex(5).errorDescription == "Invalid window index: 5.")
|
|
}
|
|
|
|
@Test("CaptureError exit codes", .tags(.fast))
|
|
func captureErrorExitCodes() {
|
|
let testCases: [(CaptureError, Int32)] = [
|
|
(.noDisplaysAvailable, 10),
|
|
(.screenRecordingPermissionDenied, 11),
|
|
(.accessibilityPermissionDenied, 12),
|
|
(.invalidDisplayID, 13),
|
|
(.captureCreationFailed(nil), 14),
|
|
(.windowNotFound, 15),
|
|
(.windowCaptureFailed(nil), 16),
|
|
(.fileWriteError("test", nil), 17),
|
|
(.appNotFound("test"), 18),
|
|
(.invalidWindowIndex(0), 19),
|
|
(.invalidArgument("test"), 20),
|
|
(.unknownError("test"), 1)
|
|
]
|
|
|
|
for (error, expectedCode) in testCases {
|
|
#expect(error.exitCode == expectedCode)
|
|
}
|
|
}
|
|
|
|
// MARK: - WindowData Tests
|
|
|
|
@Test("WindowData initialization from CGRect", .tags(.fast))
|
|
func windowData() {
|
|
let bounds = CGRect(x: 100, y: 200, width: 1200, height: 800)
|
|
let windowData = WindowData(
|
|
windowId: 12345,
|
|
title: "Safari - Main Window",
|
|
bounds: bounds,
|
|
isOnScreen: true,
|
|
windowIndex: 0
|
|
)
|
|
|
|
#expect(windowData.windowId == 12345)
|
|
#expect(windowData.title == "Safari - Main Window")
|
|
#expect(windowData.bounds.origin.x == 100)
|
|
#expect(windowData.bounds.origin.y == 200)
|
|
#expect(windowData.bounds.size.width == 1200)
|
|
#expect(windowData.bounds.size.height == 800)
|
|
#expect(windowData.isOnScreen == true)
|
|
#expect(windowData.windowIndex == 0)
|
|
}
|
|
|
|
@Test("WindowSpecifier variants", .tags(.fast))
|
|
func windowSpecifier() {
|
|
let titleSpecifier = WindowSpecifier.title("Main Window")
|
|
let indexSpecifier = WindowSpecifier.index(0)
|
|
|
|
switch titleSpecifier {
|
|
case let .title(title):
|
|
#expect(title == "Main Window")
|
|
case .index:
|
|
Issue.record("Expected title specifier")
|
|
}
|
|
|
|
switch indexSpecifier {
|
|
case .title:
|
|
Issue.record("Expected index specifier")
|
|
case let .index(index):
|
|
#expect(index == 0)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Extended Model Tests
|
|
|
|
@Suite("Model Edge Cases", .tags(.models, .unit))
|
|
struct ModelEdgeCaseTests {
|
|
@Test(
|
|
"WindowBounds with edge values",
|
|
arguments: [
|
|
(x: 0, y: 0, width: 0, height: 0),
|
|
(x: -100, y: -100, width: 100, height: 100),
|
|
(x: Int.max, y: Int.max, width: 1, height: 1)
|
|
]
|
|
)
|
|
func windowBoundsEdgeCases(x x_coordinate: Int, y y_coordinate: Int, width: Int, height: Int) {
|
|
let bounds = WindowBounds(x_coordinate: x_coordinate, y_coordinate: y_coordinate, width: width, height: height)
|
|
#expect(bounds.x_coordinate == x_coordinate)
|
|
#expect(bounds.y_coordinate == y_coordinate)
|
|
#expect(bounds.width == width)
|
|
#expect(bounds.height == height)
|
|
}
|
|
|
|
@Test("ApplicationInfo with extreme values", .tags(.fast))
|
|
func applicationInfoExtremeValues() {
|
|
let appInfo = ApplicationInfo(
|
|
app_name: String(repeating: "A", count: 1000),
|
|
bundle_id: String(repeating: "com.test.", count: 100),
|
|
pid: Int32.max,
|
|
is_active: true,
|
|
window_count: Int.max
|
|
)
|
|
|
|
#expect(appInfo.app_name.count == 1000)
|
|
#expect(appInfo.bundle_id.contains("com.test."))
|
|
#expect(appInfo.pid == Int32.max)
|
|
#expect(appInfo.window_count == Int.max)
|
|
}
|
|
|
|
@Test(
|
|
"SavedFile path validation",
|
|
arguments: [
|
|
"/tmp/test.png",
|
|
"/Users/test/Desktop/screenshot.jpg",
|
|
"~/Documents/capture.png",
|
|
"./relative/path/image.png",
|
|
"/path with spaces/image.png",
|
|
"/path/with/特殊文字.png"
|
|
]
|
|
)
|
|
func savedFilePathValidation(path: String) {
|
|
let savedFile = SavedFile(
|
|
path: path,
|
|
item_label: nil,
|
|
window_title: nil,
|
|
window_id: nil,
|
|
window_index: nil,
|
|
mime_type: "image/png"
|
|
)
|
|
|
|
#expect(savedFile.path == path)
|
|
#expect(!savedFile.path.isEmpty)
|
|
}
|
|
|
|
@Test(
|
|
"MIME type validation",
|
|
arguments: ["image/png", "image/jpeg", "image/jpg"]
|
|
)
|
|
func mimeTypeValidation(mimeType: String) {
|
|
let savedFile = SavedFile(
|
|
path: "/tmp/test",
|
|
item_label: nil,
|
|
window_title: nil,
|
|
window_id: nil,
|
|
window_index: nil,
|
|
mime_type: mimeType
|
|
)
|
|
|
|
#expect(savedFile.mime_type == mimeType)
|
|
#expect(savedFile.mime_type.starts(with: "image/"))
|
|
}
|
|
}
|