@kaeawc/auto-mobile 0.0.44 → 0.0.45
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/README.md.backup +3 -3
- package/dist/ios/screen-capture/Package.swift +35 -0
- package/dist/ios/screen-capture/Sources/ScreenCaptureCore/AudioPcm16Encoder.swift +22 -0
- package/dist/ios/screen-capture/Sources/ScreenCaptureCore/CommandLineOptions.swift +152 -0
- package/dist/ios/screen-capture/Sources/ScreenCaptureCore/DeviceInfo.swift +25 -0
- package/dist/ios/screen-capture/Sources/ScreenCaptureCore/FrameProtocol.swift +113 -0
- package/dist/ios/screen-capture/Sources/ScreenCaptureCore/FrameWriter.swift +62 -0
- package/dist/ios/screen-capture/Sources/ScreenCaptureCore/SimulatorWindowInfo.swift +51 -0
- package/dist/ios/screen-capture/Sources/ScreenCaptureHelper/AudioSampleBuffer.swift +41 -0
- package/dist/ios/screen-capture/Sources/ScreenCaptureHelper/CMIOSystem.swift +26 -0
- package/dist/ios/screen-capture/Sources/ScreenCaptureHelper/DeviceCaptureSession.swift +117 -0
- package/dist/ios/screen-capture/Sources/ScreenCaptureHelper/DeviceDiscovery.swift +29 -0
- package/dist/ios/screen-capture/Sources/ScreenCaptureHelper/FrameWriter+PixelBuffer.swift +25 -0
- package/dist/ios/screen-capture/Sources/ScreenCaptureHelper/SimulatorCaptureSession.swift +159 -0
- package/dist/ios/screen-capture/Sources/ScreenCaptureHelper/SimulatorWindowDiscovery.swift +37 -0
- package/dist/ios/screen-capture/Sources/ScreenCaptureHelper/main.swift +255 -0
- package/dist/ios/screen-capture/Tests/ScreenCaptureCoreTests/AudioPcm16EncoderTests.swift +17 -0
- package/dist/ios/screen-capture/Tests/ScreenCaptureCoreTests/CommandLineOptionsTests.swift +181 -0
- package/dist/ios/screen-capture/Tests/ScreenCaptureCoreTests/DeviceInfoTests.swift +35 -0
- package/dist/ios/screen-capture/Tests/ScreenCaptureCoreTests/FrameProtocolTests.swift +123 -0
- package/dist/ios/screen-capture/Tests/ScreenCaptureCoreTests/FrameWriterTests.swift +62 -0
- package/dist/ios/screen-capture/Tests/ScreenCaptureCoreTests/SimulatorAudioCaptureAvailabilityTests.swift +27 -0
- package/dist/ios/screen-capture/Tests/ScreenCaptureCoreTests/SimulatorWindowInfoTests.swift +40 -0
- package/dist/schemas/tool-definitions.json +1274 -849
- package/dist/src/db/migrations/2026_07_03_000_repair_datetime_now_defaults.ts +11 -2
- package/dist/src/index.js +670 -407
- package/dist/src/index.js.map +1 -1
- package/package.json +25 -6
- package/schemas/tool-definitions.json +1274 -849
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import XCTest
|
|
2
|
+
@testable import ScreenCaptureCore
|
|
3
|
+
|
|
4
|
+
final class CommandLineOptionsTests: XCTestCase {
|
|
5
|
+
func testParsesAudioForSimulatorCapture() throws {
|
|
6
|
+
let options = try CommandLineOptions.parse([
|
|
7
|
+
"screen-capture-helper", "--simulator-window", "42", "--audio"
|
|
8
|
+
])
|
|
9
|
+
|
|
10
|
+
XCTAssertEqual(options.mode, .captureSimulator(windowID: 42, fps: 5, audio: true))
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
func testDefaultsToCaptureWithoutDeviceID() throws {
|
|
14
|
+
let opts = try CommandLineOptions.parse(["screen-capture-helper"])
|
|
15
|
+
XCTAssertEqual(opts.mode, .capture(deviceID: nil))
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
func testParsesDeviceID() throws {
|
|
19
|
+
let opts = try CommandLineOptions.parse([
|
|
20
|
+
"screen-capture-helper", "--device-id", "ABC123"
|
|
21
|
+
])
|
|
22
|
+
XCTAssertEqual(opts.mode, .capture(deviceID: "ABC123"))
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
func testParsesListDevices() throws {
|
|
26
|
+
let opts = try CommandLineOptions.parse([
|
|
27
|
+
"screen-capture-helper", "--list-devices"
|
|
28
|
+
])
|
|
29
|
+
XCTAssertEqual(opts.mode, .listDevices)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
func testParsesHelpFlag() throws {
|
|
33
|
+
let opts = try CommandLineOptions.parse([
|
|
34
|
+
"screen-capture-helper", "--help"
|
|
35
|
+
])
|
|
36
|
+
XCTAssertEqual(opts.mode, .help)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
func testMissingDeviceIDValueThrows() {
|
|
40
|
+
XCTAssertThrowsError(try CommandLineOptions.parse([
|
|
41
|
+
"screen-capture-helper", "--device-id"
|
|
42
|
+
])) { error in
|
|
43
|
+
XCTAssertEqual(
|
|
44
|
+
error as? CommandLineOptions.ParseError,
|
|
45
|
+
.missingValue(flag: "--device-id")
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
func testUnknownArgumentThrows() {
|
|
51
|
+
XCTAssertThrowsError(try CommandLineOptions.parse([
|
|
52
|
+
"screen-capture-helper", "--bogus"
|
|
53
|
+
])) { error in
|
|
54
|
+
XCTAssertEqual(
|
|
55
|
+
error as? CommandLineOptions.ParseError,
|
|
56
|
+
.unknownArgument("--bogus")
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
func testParsesListSimulators() throws {
|
|
62
|
+
let opts = try CommandLineOptions.parse([
|
|
63
|
+
"screen-capture-helper", "--list-simulators"
|
|
64
|
+
])
|
|
65
|
+
XCTAssertEqual(opts.mode, .listSimulators)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
func testParsesSimulatorWindowWithDefaultFPS() throws {
|
|
69
|
+
let opts = try CommandLineOptions.parse([
|
|
70
|
+
"screen-capture-helper", "--simulator-window", "98765"
|
|
71
|
+
])
|
|
72
|
+
XCTAssertEqual(
|
|
73
|
+
opts.mode,
|
|
74
|
+
.captureSimulator(
|
|
75
|
+
windowID: 98765,
|
|
76
|
+
fps: CommandLineOptions.defaultSimulatorFPS,
|
|
77
|
+
audio: false
|
|
78
|
+
)
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
func testParsesSimulatorFPSWithinRange() throws {
|
|
83
|
+
let opts = try CommandLineOptions.parse([
|
|
84
|
+
"screen-capture-helper",
|
|
85
|
+
"--simulator-window", "1",
|
|
86
|
+
"--simulator-fps", "30",
|
|
87
|
+
])
|
|
88
|
+
XCTAssertEqual(opts.mode, .captureSimulator(windowID: 1, fps: 30, audio: false))
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
func testRejectsSimulatorFPSBelowRange() {
|
|
92
|
+
XCTAssertThrowsError(try CommandLineOptions.parse([
|
|
93
|
+
"screen-capture-helper",
|
|
94
|
+
"--simulator-window", "1",
|
|
95
|
+
"--simulator-fps", "4",
|
|
96
|
+
])) { error in
|
|
97
|
+
XCTAssertEqual(
|
|
98
|
+
error as? CommandLineOptions.ParseError,
|
|
99
|
+
.invalidValue(flag: "--simulator-fps", value: "4")
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
func testRejectsSimulatorFPSAboveRange() {
|
|
105
|
+
XCTAssertThrowsError(try CommandLineOptions.parse([
|
|
106
|
+
"screen-capture-helper",
|
|
107
|
+
"--simulator-window", "1",
|
|
108
|
+
"--simulator-fps", "61",
|
|
109
|
+
])) { error in
|
|
110
|
+
XCTAssertEqual(
|
|
111
|
+
error as? CommandLineOptions.ParseError,
|
|
112
|
+
.invalidValue(flag: "--simulator-fps", value: "61")
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
func testRejectsSimulatorFPSWithoutWindow() {
|
|
118
|
+
XCTAssertThrowsError(try CommandLineOptions.parse([
|
|
119
|
+
"screen-capture-helper", "--simulator-fps", "15",
|
|
120
|
+
])) { error in
|
|
121
|
+
guard case CommandLineOptions.ParseError.conflictingFlags = error else {
|
|
122
|
+
XCTFail("expected conflictingFlags, got \(error)")
|
|
123
|
+
return
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
func testDefaultSimulatorFPSConstantIs5() {
|
|
129
|
+
XCTAssertEqual(CommandLineOptions.defaultSimulatorFPS, 5)
|
|
130
|
+
XCTAssertEqual(CommandLineOptions.minSimulatorFPS, 5)
|
|
131
|
+
XCTAssertEqual(CommandLineOptions.maxSimulatorFPS, 60)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
func testInvalidSimulatorWindowValueThrows() {
|
|
135
|
+
XCTAssertThrowsError(try CommandLineOptions.parse([
|
|
136
|
+
"screen-capture-helper", "--simulator-window", "not-a-number"
|
|
137
|
+
])) { error in
|
|
138
|
+
XCTAssertEqual(
|
|
139
|
+
error as? CommandLineOptions.ParseError,
|
|
140
|
+
.invalidValue(flag: "--simulator-window", value: "not-a-number")
|
|
141
|
+
)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
func testMissingSimulatorWindowValueThrows() {
|
|
146
|
+
XCTAssertThrowsError(try CommandLineOptions.parse([
|
|
147
|
+
"screen-capture-helper", "--simulator-window"
|
|
148
|
+
])) { error in
|
|
149
|
+
XCTAssertEqual(
|
|
150
|
+
error as? CommandLineOptions.ParseError,
|
|
151
|
+
.missingValue(flag: "--simulator-window")
|
|
152
|
+
)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
func testConflictingDeviceAndSimulatorFlagsThrow() {
|
|
157
|
+
XCTAssertThrowsError(try CommandLineOptions.parse([
|
|
158
|
+
"screen-capture-helper",
|
|
159
|
+
"--device-id", "abc",
|
|
160
|
+
"--simulator-window", "1",
|
|
161
|
+
])) { error in
|
|
162
|
+
guard case CommandLineOptions.ParseError.conflictingFlags = error else {
|
|
163
|
+
XCTFail("expected conflictingFlags, got \(error)")
|
|
164
|
+
return
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
func testConflictingListFlagsThrow() {
|
|
170
|
+
XCTAssertThrowsError(try CommandLineOptions.parse([
|
|
171
|
+
"screen-capture-helper",
|
|
172
|
+
"--list-devices",
|
|
173
|
+
"--list-simulators",
|
|
174
|
+
])) { error in
|
|
175
|
+
guard case CommandLineOptions.ParseError.conflictingFlags = error else {
|
|
176
|
+
XCTFail("expected conflictingFlags, got \(error)")
|
|
177
|
+
return
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import XCTest
|
|
2
|
+
@testable import ScreenCaptureCore
|
|
3
|
+
|
|
4
|
+
final class DeviceInfoTests: XCTestCase {
|
|
5
|
+
func testRoundTripsThroughJSON() throws {
|
|
6
|
+
let original = DeviceListResponse(devices: [
|
|
7
|
+
DeviceInfo(
|
|
8
|
+
uniqueID: "00008140-001A2B3C0AE2401E",
|
|
9
|
+
localizedName: "iPhone",
|
|
10
|
+
modelID: "iPhone15,2",
|
|
11
|
+
manufacturer: "Apple"
|
|
12
|
+
)
|
|
13
|
+
])
|
|
14
|
+
let data = try JSONEncoder().encode(original)
|
|
15
|
+
let decoded = try JSONDecoder().decode(DeviceListResponse.self, from: data)
|
|
16
|
+
XCTAssertEqual(decoded, original)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
func testJSONUsesExpectedKeys() throws {
|
|
20
|
+
let device = DeviceInfo(
|
|
21
|
+
uniqueID: "id",
|
|
22
|
+
localizedName: "name",
|
|
23
|
+
modelID: "model",
|
|
24
|
+
manufacturer: "vendor"
|
|
25
|
+
)
|
|
26
|
+
let data = try JSONEncoder().encode(device)
|
|
27
|
+
let json = try XCTUnwrap(
|
|
28
|
+
JSONSerialization.jsonObject(with: data) as? [String: String]
|
|
29
|
+
)
|
|
30
|
+
XCTAssertEqual(json["uniqueID"], "id")
|
|
31
|
+
XCTAssertEqual(json["localizedName"], "name")
|
|
32
|
+
XCTAssertEqual(json["modelID"], "model")
|
|
33
|
+
XCTAssertEqual(json["manufacturer"], "vendor")
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// swiftlint:disable force_unwrapping
|
|
2
|
+
// Force-unwrap is idiomatic in test fixtures (fail fast on bad setup); disabled file-wide.
|
|
3
|
+
|
|
4
|
+
import XCTest
|
|
5
|
+
@testable import ScreenCaptureCore
|
|
6
|
+
|
|
7
|
+
final class FrameProtocolTests: XCTestCase {
|
|
8
|
+
func testEncodeAudioHeaderUsesReservedZeroWidthMarker() {
|
|
9
|
+
let data = FrameProtocol.encodeAudioHeader(payloadLength: 320)
|
|
10
|
+
|
|
11
|
+
XCTAssertEqual(data.count, FrameProtocol.headerSize)
|
|
12
|
+
// Marker "AMF1" on the wire.
|
|
13
|
+
XCTAssertEqual(Array(data.prefix(4)), [0x41, 0x4D, 0x46, 0x31])
|
|
14
|
+
// Field bytes (offset 8): width=0, height=8000 (0x1F40), bytesPerRow=1, timestampMs=320 (0x140).
|
|
15
|
+
XCTAssertEqual(Array(data[8..<24]), [
|
|
16
|
+
0, 0, 0, 0,
|
|
17
|
+
0x40, 0x1F, 0, 0,
|
|
18
|
+
1, 0, 0, 0,
|
|
19
|
+
0x40, 1, 0, 0,
|
|
20
|
+
])
|
|
21
|
+
XCTAssertEqual(
|
|
22
|
+
FrameProtocol.decodeHeader(data),
|
|
23
|
+
FrameProtocol.Header(width: 0, height: 8_000, bytesPerRow: 1, timestampMs: 320)
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
func testEncodeHeaderPlacesMarkerChecksumAndLittleEndianFields() {
|
|
28
|
+
let header = FrameProtocol.Header(
|
|
29
|
+
width: 0x01020304,
|
|
30
|
+
height: 0x05060708,
|
|
31
|
+
bytesPerRow: 0x090A0B0C,
|
|
32
|
+
timestampMs: 0x0D0E0F10
|
|
33
|
+
)
|
|
34
|
+
let data = FrameProtocol.encodeHeader(header)
|
|
35
|
+
XCTAssertEqual(data.count, FrameProtocol.headerSize)
|
|
36
|
+
XCTAssertEqual(data.withUnsafeBytes { $0.loadUnaligned(as: UInt32.self).littleEndian }, FrameProtocol.magic)
|
|
37
|
+
XCTAssertEqual(Array(data[8..<24]), [
|
|
38
|
+
0x04, 0x03, 0x02, 0x01,
|
|
39
|
+
0x08, 0x07, 0x06, 0x05,
|
|
40
|
+
0x0C, 0x0B, 0x0A, 0x09,
|
|
41
|
+
0x10, 0x0F, 0x0E, 0x0D,
|
|
42
|
+
])
|
|
43
|
+
// The stored checksum matches the CRC-32 of the field bytes.
|
|
44
|
+
let storedChecksum = data.dropFirst(4).withUnsafeBytes { $0.loadUnaligned(as: UInt32.self).littleEndian }
|
|
45
|
+
XCTAssertEqual(storedChecksum, FrameProtocol.crc32(data.subdata(in: 8..<24)))
|
|
46
|
+
XCTAssertEqual(FrameProtocol.decodeHeader(data), header)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
func testCrc32MatchesStandardCheckVector() {
|
|
50
|
+
// The canonical CRC-32 check value for the ASCII string "123456789",
|
|
51
|
+
// shared with the TypeScript decoder so the two agree byte for byte.
|
|
52
|
+
XCTAssertEqual(FrameProtocol.crc32(Data("123456789".utf8)), 0xCBF4_3926)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
func testDecodeRejectsWrongMarker() {
|
|
56
|
+
var data = FrameProtocol.encodeHeader(
|
|
57
|
+
FrameProtocol.Header(width: 2, height: 2, bytesPerRow: 8, timestampMs: 1)
|
|
58
|
+
)
|
|
59
|
+
data[0] ^= 0xFF // corrupt the marker
|
|
60
|
+
XCTAssertNil(FrameProtocol.decodeHeader(data))
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
func testDecodeRejectsWrongChecksum() {
|
|
64
|
+
var data = FrameProtocol.encodeHeader(
|
|
65
|
+
FrameProtocol.Header(width: 2, height: 2, bytesPerRow: 8, timestampMs: 1)
|
|
66
|
+
)
|
|
67
|
+
data[8] ^= 0xFF // corrupt a field without fixing the checksum
|
|
68
|
+
XCTAssertNil(FrameProtocol.decodeHeader(data))
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
func testDecodeHeaderRoundTrip() {
|
|
72
|
+
let header = FrameProtocol.Header(
|
|
73
|
+
width: 1170,
|
|
74
|
+
height: 2532,
|
|
75
|
+
bytesPerRow: 4680,
|
|
76
|
+
timestampMs: 1_234_567
|
|
77
|
+
)
|
|
78
|
+
let data = FrameProtocol.encodeHeader(header)
|
|
79
|
+
XCTAssertEqual(FrameProtocol.decodeHeader(data), header)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
func testDecodeRejectsShortBuffer() {
|
|
83
|
+
let truncated = Data(repeating: 0, count: FrameProtocol.headerSize - 1)
|
|
84
|
+
XCTAssertNil(FrameProtocol.decodeHeader(truncated))
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/// Decoding a header from an unaligned buffer must not trap. The pre-fix
|
|
88
|
+
/// `load(as:)` requires 4-byte alignment; feeding it a header at an odd
|
|
89
|
+
/// address traps in debug (issue #3627). `loadUnaligned` decodes correctly.
|
|
90
|
+
func testDecodeHeaderHandlesUnalignedBuffer() {
|
|
91
|
+
let header = FrameProtocol.Header(
|
|
92
|
+
width: 0x1122_3344,
|
|
93
|
+
height: 0x5566_7788,
|
|
94
|
+
bytesPerRow: 0x99AA_BBCC,
|
|
95
|
+
timestampMs: 0xDDEE_FF00
|
|
96
|
+
)
|
|
97
|
+
let encoded = FrameProtocol.encodeHeader(header)
|
|
98
|
+
|
|
99
|
+
// Place the 16 header bytes at an odd (unaligned) offset in a raw buffer.
|
|
100
|
+
let raw = UnsafeMutableRawPointer.allocate(
|
|
101
|
+
byteCount: FrameProtocol.headerSize + 1,
|
|
102
|
+
alignment: 1
|
|
103
|
+
)
|
|
104
|
+
defer { raw.deallocate() }
|
|
105
|
+
encoded.withUnsafeBytes { src in
|
|
106
|
+
raw.advanced(by: 1).copyMemory(from: src.baseAddress!, byteCount: FrameProtocol.headerSize)
|
|
107
|
+
}
|
|
108
|
+
let unaligned = Data(
|
|
109
|
+
bytesNoCopy: raw.advanced(by: 1),
|
|
110
|
+
count: FrameProtocol.headerSize,
|
|
111
|
+
deallocator: .none
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
XCTAssertEqual(FrameProtocol.decodeHeader(unaligned), header)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
func testEncodeDecodeRoundTrip() {
|
|
118
|
+
let header = FrameProtocol.Header(
|
|
119
|
+
width: 1290, height: 2796, bytesPerRow: 5160, timestampMs: 123_456
|
|
120
|
+
)
|
|
121
|
+
XCTAssertEqual(FrameProtocol.decodeHeader(FrameProtocol.encodeHeader(header)), header)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// swiftlint:disable force_unwrapping
|
|
2
|
+
// Force-unwrap is idiomatic in test fixtures (fail fast on bad setup); disabled file-wide.
|
|
3
|
+
|
|
4
|
+
import XCTest
|
|
5
|
+
@testable import ScreenCaptureCore
|
|
6
|
+
|
|
7
|
+
final class BufferSink: FrameSink {
|
|
8
|
+
var data = Data()
|
|
9
|
+
func write(_ chunk: Data) { data.append(chunk) }
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
final class FrameWriterTests: XCTestCase {
|
|
13
|
+
func testWritesHeaderFollowedByPayload() {
|
|
14
|
+
let sink = BufferSink()
|
|
15
|
+
let start = Date(timeIntervalSince1970: 0)
|
|
16
|
+
let writer = FrameWriter(sink: sink, startTime: start)
|
|
17
|
+
|
|
18
|
+
let width = 2
|
|
19
|
+
let height = 3
|
|
20
|
+
let bytesPerRow = 8
|
|
21
|
+
// 24 bytes of BGRA: 2px × 3 rows × 4 bytes
|
|
22
|
+
let payload = Array(0..<UInt8(bytesPerRow * height))
|
|
23
|
+
|
|
24
|
+
payload.withUnsafeBufferPointer { ptr in
|
|
25
|
+
writer.write(
|
|
26
|
+
width: width,
|
|
27
|
+
height: height,
|
|
28
|
+
bytesPerRow: bytesPerRow,
|
|
29
|
+
baseAddress: UnsafeRawPointer(ptr.baseAddress!),
|
|
30
|
+
timestamp: Date(timeIntervalSince1970: 0.5)
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
XCTAssertEqual(sink.data.count, FrameProtocol.headerSize + bytesPerRow * height)
|
|
35
|
+
let header = FrameProtocol.decodeHeader(sink.data.prefix(FrameProtocol.headerSize))
|
|
36
|
+
XCTAssertEqual(header?.width, UInt32(width))
|
|
37
|
+
XCTAssertEqual(header?.height, UInt32(height))
|
|
38
|
+
XCTAssertEqual(header?.bytesPerRow, UInt32(bytesPerRow))
|
|
39
|
+
XCTAssertEqual(header?.timestampMs, 500)
|
|
40
|
+
XCTAssertEqual(Array(sink.data.dropFirst(FrameProtocol.headerSize)), payload)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
func testTimestampClampedToZeroForPriorStartTime() {
|
|
44
|
+
let sink = BufferSink()
|
|
45
|
+
let writer = FrameWriter(
|
|
46
|
+
sink: sink,
|
|
47
|
+
startTime: Date(timeIntervalSince1970: 100)
|
|
48
|
+
)
|
|
49
|
+
let payload: [UInt8] = [0, 1, 2, 3]
|
|
50
|
+
payload.withUnsafeBufferPointer { ptr in
|
|
51
|
+
writer.write(
|
|
52
|
+
width: 1,
|
|
53
|
+
height: 1,
|
|
54
|
+
bytesPerRow: 4,
|
|
55
|
+
baseAddress: UnsafeRawPointer(ptr.baseAddress!),
|
|
56
|
+
timestamp: Date(timeIntervalSince1970: 0)
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
let header = FrameProtocol.decodeHeader(sink.data.prefix(FrameProtocol.headerSize))
|
|
60
|
+
XCTAssertEqual(header?.timestampMs, 0)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import XCTest
|
|
2
|
+
@testable import ScreenCaptureCore
|
|
3
|
+
|
|
4
|
+
final class SimulatorAudioCaptureAvailabilityTests: XCTestCase {
|
|
5
|
+
func testAllowsAudioWhenExactlyOneSimulatorWindowIsVisible() {
|
|
6
|
+
XCTAssertNil(SimulatorAudioCaptureAvailability.errorMessage(for: [window(id: 1)]))
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
func testRejectsAudioWhenMultipleSimulatorWindowsAreVisible() {
|
|
10
|
+
XCTAssertEqual(
|
|
11
|
+
SimulatorAudioCaptureAvailability.errorMessage(for: [window(id: 1), window(id: 2)]),
|
|
12
|
+
"iOS Simulator audio capture requires exactly one visible Simulator window because ScreenCaptureKit cannot isolate audio to a selected Simulator window. Close other Simulator windows and try again."
|
|
13
|
+
)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
private func window(id: UInt32) -> SimulatorWindowInfo {
|
|
17
|
+
SimulatorWindowInfo(
|
|
18
|
+
windowID: id,
|
|
19
|
+
title: "iPhone",
|
|
20
|
+
applicationName: "Simulator",
|
|
21
|
+
bundleIdentifier: simulatorBundleIdentifier,
|
|
22
|
+
processID: 1,
|
|
23
|
+
width: 390,
|
|
24
|
+
height: 844
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import XCTest
|
|
2
|
+
@testable import ScreenCaptureCore
|
|
3
|
+
|
|
4
|
+
final class SimulatorWindowInfoTests: XCTestCase {
|
|
5
|
+
func testRoundTripsThroughJSON() throws {
|
|
6
|
+
let original = SimulatorWindowListResponse(windows: [
|
|
7
|
+
SimulatorWindowInfo(
|
|
8
|
+
windowID: 12345,
|
|
9
|
+
title: "iPhone 15 Pro — iOS 17.4",
|
|
10
|
+
applicationName: "Simulator",
|
|
11
|
+
bundleIdentifier: simulatorBundleIdentifier,
|
|
12
|
+
processID: 4242,
|
|
13
|
+
width: 1170,
|
|
14
|
+
height: 2532
|
|
15
|
+
)
|
|
16
|
+
])
|
|
17
|
+
let data = try JSONEncoder().encode(original)
|
|
18
|
+
let decoded = try JSONDecoder().decode(SimulatorWindowListResponse.self, from: data)
|
|
19
|
+
XCTAssertEqual(decoded, original)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
func testTitleMayBeNil() throws {
|
|
23
|
+
let info = SimulatorWindowInfo(
|
|
24
|
+
windowID: 1,
|
|
25
|
+
title: nil,
|
|
26
|
+
applicationName: "Simulator",
|
|
27
|
+
bundleIdentifier: simulatorBundleIdentifier,
|
|
28
|
+
processID: 1,
|
|
29
|
+
width: 0,
|
|
30
|
+
height: 0
|
|
31
|
+
)
|
|
32
|
+
let data = try JSONEncoder().encode(info)
|
|
33
|
+
let decoded = try JSONDecoder().decode(SimulatorWindowInfo.self, from: data)
|
|
34
|
+
XCTAssertNil(decoded.title)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
func testSimulatorBundleIdentifierConstant() {
|
|
38
|
+
XCTAssertEqual(simulatorBundleIdentifier, "com.apple.iphonesimulator")
|
|
39
|
+
}
|
|
40
|
+
}
|