Spaces:
Paused
Paused
| import OpenClawKit | |
| import Foundation | |
| import Testing | |
| import UIKit | |
| @testable import OpenClaw | |
| private func withUserDefaults<T>(_ updates: [String: Any?], _ body: () throws -> T) rethrows -> T { | |
| let defaults = UserDefaults.standard | |
| var snapshot: [String: Any?] = [:] | |
| for key in updates.keys { | |
| snapshot[key] = defaults.object(forKey: key) | |
| } | |
| for (key, value) in updates { | |
| if let value { | |
| defaults.set(value, forKey: key) | |
| } else { | |
| defaults.removeObject(forKey: key) | |
| } | |
| } | |
| defer { | |
| for (key, value) in snapshot { | |
| if let value { | |
| defaults.set(value, forKey: key) | |
| } else { | |
| defaults.removeObject(forKey: key) | |
| } | |
| } | |
| } | |
| return try body() | |
| } | |
| (.serialized) struct NodeAppModelInvokeTests { | |
| func decodeParamsFailsWithoutJSON() { | |
| #expect(throws: Error.self) { | |
| _ = try NodeAppModel._test_decodeParams(OpenClawCanvasNavigateParams.self, from: nil) | |
| } | |
| } | |
| func encodePayloadEmitsJSON() throws { | |
| struct Payload: Codable, Equatable { | |
| var value: String | |
| } | |
| let json = try NodeAppModel._test_encodePayload(Payload(value: "ok")) | |
| #expect(json.contains("\"value\"")) | |
| } | |
| func handleInvokeRejectsBackgroundCommands() async { | |
| let appModel = NodeAppModel() | |
| appModel.setScenePhase(.background) | |
| let req = BridgeInvokeRequest(id: "bg", command: OpenClawCanvasCommand.present.rawValue) | |
| let res = await appModel._test_handleInvoke(req) | |
| #expect(res.ok == false) | |
| #expect(res.error?.code == .backgroundUnavailable) | |
| } | |
| func handleInvokeRejectsCameraWhenDisabled() async { | |
| let appModel = NodeAppModel() | |
| let req = BridgeInvokeRequest(id: "cam", command: OpenClawCameraCommand.snap.rawValue) | |
| let defaults = UserDefaults.standard | |
| let key = "camera.enabled" | |
| let previous = defaults.object(forKey: key) | |
| defaults.set(false, forKey: key) | |
| defer { | |
| if let previous { | |
| defaults.set(previous, forKey: key) | |
| } else { | |
| defaults.removeObject(forKey: key) | |
| } | |
| } | |
| let res = await appModel._test_handleInvoke(req) | |
| #expect(res.ok == false) | |
| #expect(res.error?.code == .unavailable) | |
| #expect(res.error?.message.contains("CAMERA_DISABLED") == true) | |
| } | |
| func handleInvokeRejectsInvalidScreenFormat() async { | |
| let appModel = NodeAppModel() | |
| let params = OpenClawScreenRecordParams(format: "gif") | |
| let data = try? JSONEncoder().encode(params) | |
| let json = data.flatMap { String(data: $0, encoding: .utf8) } | |
| let req = BridgeInvokeRequest( | |
| id: "screen", | |
| command: OpenClawScreenCommand.record.rawValue, | |
| paramsJSON: json) | |
| let res = await appModel._test_handleInvoke(req) | |
| #expect(res.ok == false) | |
| #expect(res.error?.message.contains("screen format must be mp4") == true) | |
| } | |
| func handleInvokeCanvasCommandsUpdateScreen() async throws { | |
| let appModel = NodeAppModel() | |
| appModel.screen.navigate(to: "http://example.com") | |
| let present = BridgeInvokeRequest(id: "present", command: OpenClawCanvasCommand.present.rawValue) | |
| let presentRes = await appModel._test_handleInvoke(present) | |
| #expect(presentRes.ok == true) | |
| #expect(appModel.screen.urlString.isEmpty) | |
| let navigateParams = OpenClawCanvasNavigateParams(url: "http://localhost:18789/") | |
| let navData = try JSONEncoder().encode(navigateParams) | |
| let navJSON = String(decoding: navData, as: UTF8.self) | |
| let navigate = BridgeInvokeRequest( | |
| id: "nav", | |
| command: OpenClawCanvasCommand.navigate.rawValue, | |
| paramsJSON: navJSON) | |
| let navRes = await appModel._test_handleInvoke(navigate) | |
| #expect(navRes.ok == true) | |
| #expect(appModel.screen.urlString == "http://localhost:18789/") | |
| let evalParams = OpenClawCanvasEvalParams(javaScript: "1+1") | |
| let evalData = try JSONEncoder().encode(evalParams) | |
| let evalJSON = String(decoding: evalData, as: UTF8.self) | |
| let eval = BridgeInvokeRequest( | |
| id: "eval", | |
| command: OpenClawCanvasCommand.evalJS.rawValue, | |
| paramsJSON: evalJSON) | |
| let evalRes = await appModel._test_handleInvoke(eval) | |
| #expect(evalRes.ok == true) | |
| let payloadData = try #require(evalRes.payloadJSON?.data(using: .utf8)) | |
| let payload = try JSONSerialization.jsonObject(with: payloadData) as? [String: Any] | |
| #expect(payload?["result"] as? String == "2") | |
| } | |
| func handleInvokeA2UICommandsFailWhenHostMissing() async throws { | |
| let appModel = NodeAppModel() | |
| let reset = BridgeInvokeRequest(id: "reset", command: OpenClawCanvasA2UICommand.reset.rawValue) | |
| let resetRes = await appModel._test_handleInvoke(reset) | |
| #expect(resetRes.ok == false) | |
| #expect(resetRes.error?.message.contains("A2UI_HOST_NOT_CONFIGURED") == true) | |
| let jsonl = "{\"beginRendering\":{}}" | |
| let pushParams = OpenClawCanvasA2UIPushJSONLParams(jsonl: jsonl) | |
| let pushData = try JSONEncoder().encode(pushParams) | |
| let pushJSON = String(decoding: pushData, as: UTF8.self) | |
| let push = BridgeInvokeRequest( | |
| id: "push", | |
| command: OpenClawCanvasA2UICommand.pushJSONL.rawValue, | |
| paramsJSON: pushJSON) | |
| let pushRes = await appModel._test_handleInvoke(push) | |
| #expect(pushRes.ok == false) | |
| #expect(pushRes.error?.message.contains("A2UI_HOST_NOT_CONFIGURED") == true) | |
| } | |
| func handleInvokeUnknownCommandReturnsInvalidRequest() async { | |
| let appModel = NodeAppModel() | |
| let req = BridgeInvokeRequest(id: "unknown", command: "nope") | |
| let res = await appModel._test_handleInvoke(req) | |
| #expect(res.ok == false) | |
| #expect(res.error?.code == .invalidRequest) | |
| } | |
| func handleDeepLinkSetsErrorWhenNotConnected() async { | |
| let appModel = NodeAppModel() | |
| let url = URL(string: "openclaw://agent?message=hello")! | |
| await appModel.handleDeepLink(url: url) | |
| #expect(appModel.screen.errorText?.contains("Gateway not connected") == true) | |
| } | |
| func handleDeepLinkRejectsOversizedMessage() async { | |
| let appModel = NodeAppModel() | |
| let msg = String(repeating: "a", count: 20001) | |
| let url = URL(string: "openclaw://agent?message=\(msg)")! | |
| await appModel.handleDeepLink(url: url) | |
| #expect(appModel.screen.errorText?.contains("Deep link too large") == true) | |
| } | |
| func sendVoiceTranscriptThrowsWhenGatewayOffline() async { | |
| let appModel = NodeAppModel() | |
| await #expect(throws: Error.self) { | |
| try await appModel.sendVoiceTranscript(text: "hello", sessionKey: "main") | |
| } | |
| } | |
| func canvasA2UIActionDispatchesStatus() async { | |
| let appModel = NodeAppModel() | |
| let body: [String: Any] = [ | |
| "userAction": [ | |
| "name": "tap", | |
| "id": "action-1", | |
| "surfaceId": "main", | |
| "sourceComponentId": "button-1", | |
| "context": ["value": "ok"], | |
| ], | |
| ] | |
| await appModel._test_handleCanvasA2UIAction(body: body) | |
| #expect(appModel.screen.urlString.isEmpty) | |
| } | |
| } | |