Files
程序员阿江(Relakkes) a0a8fd4b45 fix(computer-use): load cursor resources from packaged apps
Resolve optional click animation assets from the running helper bundle.
Avoid SwiftPM Bundle.module's fatal fallback to a build-machine path so
visible clicks can return successfully and keep the daemon alive.

Keep procedural feedback when optional frames are absent or unreadable.
Add an input-free resource probe and run it against relocated release and
staged helper apps. Cover standard Resources copying and reject probes
that load from an external build directory.

Validation: 524 XCTest and 15 Swift Testing cases passed, including eight
new resource regressions. Build fixtures, compiled sidecar smoke, Electron
checks, local packaging and final relocated-package probes passed.
2026-09-11 15:09:50 +08:00

171 lines
7.4 KiB
Swift

import AppKit
import ImageIO
import UniformTypeIdentifiers
import XCTest
@testable import cc_haha_computer_use
final class VirtualCursorResourceTests: XCTestCase {
@MainActor
func testPackagedApplicationLoadsFramesFromContentsResourcesInNaturalOrder() throws {
let fixture = try ResourceFixture()
defer { fixture.remove() }
let sequence = try fixture.sequence(at: "Contents/Resources/cu-helper_cc-haha-computer-use.bundle")
try fixture.png(width: 10, to: sequence.appendingPathComponent("frame_10.png"))
try fixture.png(width: 2, to: sequence.appendingPathComponent("frame_2.png"))
let resources = VirtualCursor.loadLensSequence(from: fixture.bundle)
let frames = try XCTUnwrap(resources.frames)
XCTAssertEqual(resources.directory?.standardizedFileURL.path, sequence.standardizedFileURL.path)
XCTAssertEqual(frames.map(\.width), [2, 10])
}
@MainActor
func testMissingOptionalBundleFallsBackWithoutLoadingAnyBuildTreeResources() throws {
let fixture = try ResourceFixture()
defer { fixture.remove() }
let resources = VirtualCursor.loadLensSequence(from: fixture.bundle)
XCTAssertNil(resources.directory)
XCTAssertNil(resources.frames)
}
@MainActor
func testReadmeOnlySequenceFallsBackToProceduralRipple() throws {
let fixture = try ResourceFixture()
defer { fixture.remove() }
let sequence = try fixture.sequence(at: "Contents/Resources/cu-helper_cc-haha-computer-use.bundle")
try Data("Optional animation frames are not installed.".utf8)
.write(to: sequence.appendingPathComponent("README.md"))
let resources = VirtualCursor.loadLensSequence(from: fixture.bundle)
XCTAssertEqual(resources.directory?.standardizedFileURL.path, sequence.standardizedFileURL.path)
XCTAssertNil(resources.frames)
}
@MainActor
func testInvalidFramesDoNotHideLaterValidFrames() throws {
let fixture = try ResourceFixture()
defer { fixture.remove() }
let sequence = try fixture.sequence(at: "Contents/Resources/cu-helper_cc-haha-computer-use.bundle")
try Data("not a PNG".utf8).write(to: sequence.appendingPathComponent("frame_1.png"))
try fixture.png(width: 4, to: sequence.appendingPathComponent("frame_2.PNG"))
let frames = try XCTUnwrap(VirtualCursor.loadLensSequence(from: fixture.bundle).frames)
XCTAssertEqual(frames.map(\.width), [4])
}
@MainActor
func testExecutableSiblingModuleBundleRemainsSupported() throws {
let fixture = try ResourceFixture()
defer { fixture.remove() }
let sequence = try fixture.sequence(at: "Contents/MacOS/cu-helper_cc-haha-computer-use.bundle")
try fixture.png(width: 3, to: sequence.appendingPathComponent("frame_1.png"))
let resources = VirtualCursor.loadLensSequence(from: fixture.bundle)
XCTAssertEqual(resources.directory?.standardizedFileURL.path, sequence.standardizedFileURL.path)
XCTAssertEqual(resources.frames?.map(\.width), [3])
}
@MainActor
func testPackagedSequenceTakesPrecedenceOverLegacyLocationsEvenWithoutFrames() throws {
let fixture = try ResourceFixture()
defer { fixture.remove() }
let packaged = try fixture.sequence(at: "Contents/Resources/cu-helper_cc-haha-computer-use.bundle")
let sibling = try fixture.sequence(at: "Contents/MacOS/cu-helper_cc-haha-computer-use.bundle")
try fixture.png(width: 3, to: sibling.appendingPathComponent("frame_1.png"))
let resources = VirtualCursor.loadLensSequence(from: fixture.bundle)
XCTAssertEqual(resources.directory?.standardizedFileURL.path, packaged.standardizedFileURL.path)
XCTAssertNil(resources.frames, "An intentionally empty deployed sequence must not borrow other frames")
}
@MainActor
func testAllInvalidFramesFallBackToProceduralRipple() throws {
let fixture = try ResourceFixture()
defer { fixture.remove() }
let sequence = try fixture.sequence(at: "Contents/Resources/cu-helper_cc-haha-computer-use.bundle")
try Data("not a PNG".utf8).write(to: sequence.appendingPathComponent("frame_1.png"))
let resources = VirtualCursor.loadLensSequence(from: fixture.bundle)
XCTAssertEqual(resources.directory?.standardizedFileURL.path, sequence.standardizedFileURL.path)
XCTAssertNil(resources.frames)
}
@MainActor
func testResourcePathThatIsAFileDoesNotCrash() throws {
let fixture = try ResourceFixture()
defer { fixture.remove() }
try Data("not a directory".utf8).write(to: fixture.application
.appendingPathComponent("Contents/Resources/cu-helper_cc-haha-computer-use.bundle"))
let resources = VirtualCursor.loadLensSequence(from: fixture.bundle)
XCTAssertNil(resources.directory)
XCTAssertNil(resources.frames)
}
}
private struct ResourceFixture {
let root: URL
let application: URL
let bundle: Bundle
init() throws {
root = FileManager.default.temporaryDirectory
.appendingPathComponent("cu-cursor-resources-\(UUID().uuidString)", isDirectory: true)
application = root.appendingPathComponent("Moved Helper's App.app", isDirectory: true)
let contents = application.appendingPathComponent("Contents", isDirectory: true)
try FileManager.default.createDirectory(
at: contents.appendingPathComponent("MacOS", isDirectory: true),
withIntermediateDirectories: true
)
try FileManager.default.createDirectory(
at: contents.appendingPathComponent("Resources", isDirectory: true),
withIntermediateDirectories: true
)
let info: [String: Any] = [
"CFBundleIdentifier": "dev.cchaha.tests.cursor-resources.\(UUID().uuidString)",
"CFBundleName": "Cursor Resource Fixture",
"CFBundlePackageType": "APPL",
"CFBundleExecutable": "fixture-helper",
]
try PropertyListSerialization.data(fromPropertyList: info, format: .xml, options: 0)
.write(to: contents.appendingPathComponent("Info.plist"))
try Data().write(to: contents.appendingPathComponent("MacOS/fixture-helper"))
bundle = try XCTUnwrap(Bundle(url: application))
}
func sequence(at relativeRoot: String) throws -> URL {
let directory = application.appendingPathComponent(relativeRoot, isDirectory: true)
.appendingPathComponent("LensSequence", isDirectory: true)
try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
return directory
}
func png(width: Int, to url: URL) throws {
let context = try XCTUnwrap(CGContext(
data: nil, width: width, height: 2, bitsPerComponent: 8,
bytesPerRow: width * 4, space: CGColorSpaceCreateDeviceRGB(),
bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue
))
context.setFillColor(CGColor(gray: 1, alpha: 1))
context.fill(CGRect(x: 0, y: 0, width: width, height: 2))
let image = try XCTUnwrap(context.makeImage())
let destination = try XCTUnwrap(CGImageDestinationCreateWithURL(
url as CFURL, UTType.png.identifier as CFString, 1, nil
))
CGImageDestinationAddImage(destination, image, nil)
XCTAssertTrue(CGImageDestinationFinalize(destination))
}
func remove() {
try? FileManager.default.removeItem(at: root)
}
}