mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-15 11:56:01 +00:00
223 lines
8.5 KiB
Swift
223 lines
8.5 KiB
Swift
import Foundation
|
|
import OpenClawKit
|
|
import Testing
|
|
|
|
struct WatchChatStatusLocalizationTests {
|
|
@Test func `snapshot parser keeps semantic status presentation`() throws {
|
|
let snapshot = try #require(WatchAppSnapshotMessage.parsePayload(Self.semanticPayload()))
|
|
|
|
#expect(snapshot.gatewayStatus.code == .gatewayProblem)
|
|
#expect(snapshot.gatewayStatus.localizationKey == "Gateway update required")
|
|
#expect(snapshot.gatewayStatus.verbatim == nil)
|
|
#expect(snapshot.agentAvatarURL == "https://example.com/avatar.png")
|
|
#expect(snapshot.talkStatus.code == .talkOff)
|
|
#expect(snapshot.chatStatus?.code == .chatConnectIPhone)
|
|
}
|
|
|
|
@Test func `legacy snapshot text decodes but is not written again`() throws {
|
|
let legacyJSON = """
|
|
{
|
|
"gatewayStatusText": "Ancien statut",
|
|
"gatewayConnected": false,
|
|
"agentName": "Main",
|
|
"sessionKey": "main",
|
|
"talkStatusText": "Ancien mode vocal",
|
|
"talkEnabled": true,
|
|
"talkListening": false,
|
|
"talkSpeaking": false,
|
|
"pendingApprovalCount": 0,
|
|
"chatStatusText": "Ancien chat"
|
|
}
|
|
"""
|
|
let snapshot = try JSONDecoder().decode(
|
|
WatchAppSnapshotMessage.self,
|
|
from: Data(legacyJSON.utf8))
|
|
|
|
#expect(snapshot.gatewayStatus.verbatim == "Ancien statut")
|
|
#expect(snapshot.talkStatus.verbatim == "Ancien mode vocal")
|
|
#expect(snapshot.chatStatus?.verbatim == "Ancien chat")
|
|
|
|
let encoded = try JSONEncoder().encode(snapshot)
|
|
let object = try #require(
|
|
JSONSerialization.jsonObject(with: encoded) as? [String: Any])
|
|
#expect(object["gatewayStatusText"] == nil)
|
|
#expect(object["talkStatusText"] == nil)
|
|
#expect(object["chatStatusText"] == nil)
|
|
#expect(object["chatStatus"] != nil)
|
|
}
|
|
|
|
@Test func `watch locale resolves semantic chat status independently from phone`() throws {
|
|
let snapshot = try #require(WatchAppSnapshotMessage.parsePayload(Self.semanticPayload()))
|
|
let rendered = WatchAppSnapshotMessage.localizedChatStatusText(
|
|
status: snapshot.chatStatus,
|
|
chatCount: 0,
|
|
hasAppSnapshot: true,
|
|
localize: { key in
|
|
key == .connectIPhoneChat
|
|
? "Connectez le chat de l'iPhone"
|
|
: key.rawValue
|
|
})
|
|
|
|
#expect(rendered == "Connectez le chat de l'iPhone")
|
|
#expect(rendered != "Connect iPhone chat to read messages")
|
|
}
|
|
|
|
@Test func `payload parser preserves legacy text for unknown semantic statuses`() throws {
|
|
var payload = Self.semanticPayload()
|
|
payload["gatewayStatus"] = ["code": "futureGateway"]
|
|
payload["gatewayStatusText"] = "Future gateway state"
|
|
payload["gatewayConnected"] = true
|
|
payload["talkStatus"] = ["code": "futureTalk"]
|
|
payload["talkStatusText"] = "Future talk state"
|
|
payload["talkSpeaking"] = true
|
|
|
|
let snapshot = try #require(WatchAppSnapshotMessage.parsePayload(payload))
|
|
|
|
#expect(snapshot.gatewayStatus.code == .legacy)
|
|
#expect(snapshot.gatewayStatus.verbatim == "Future gateway state")
|
|
#expect(snapshot.talkStatus.code == .legacy)
|
|
#expect(snapshot.talkStatus.verbatim == "Future talk state")
|
|
}
|
|
|
|
@Test func `persisted snapshot preserves legacy text for unknown semantic statuses`() throws {
|
|
let json = """
|
|
{
|
|
"gatewayStatus": {"code": "futureGateway"},
|
|
"gatewayStatusText": "Future gateway state",
|
|
"gatewayConnected": true,
|
|
"agentName": "Main",
|
|
"sessionKey": "main",
|
|
"talkStatus": {"code": "futureTalk"},
|
|
"talkStatusText": "Future talk state",
|
|
"talkEnabled": true,
|
|
"talkListening": false,
|
|
"talkSpeaking": true,
|
|
"pendingApprovalCount": 0
|
|
}
|
|
"""
|
|
|
|
let snapshot = try JSONDecoder().decode(
|
|
WatchAppSnapshotMessage.self,
|
|
from: Data(json.utf8))
|
|
|
|
#expect(snapshot.gatewayStatus.code == .legacy)
|
|
#expect(snapshot.gatewayStatus.verbatim == "Future gateway state")
|
|
#expect(snapshot.talkStatus.code == .legacy)
|
|
#expect(snapshot.talkStatus.verbatim == "Future talk state")
|
|
}
|
|
|
|
@Test func `persisted semantic command status follows current watch locale`() {
|
|
let status = WatchAppCommandStatus(command: .sendChat, code: .sent)
|
|
let english = status.localizedText(localize: Self.english)
|
|
let french = status.localizedText(localize: Self.french)
|
|
|
|
#expect(english == "Chat: sent")
|
|
#expect(french == "Discussion : envoyée")
|
|
#expect(status.command == .sendChat)
|
|
}
|
|
|
|
@Test func `legacy approval outcomes recover semantic localization`() throws {
|
|
let allowed = try #require(
|
|
WatchExecApprovalOutcome.decodeLegacyLocalizedText("Approval allowed once."))
|
|
let custom = try #require(
|
|
WatchExecApprovalOutcome.decodeLegacyLocalizedText("Gateway custom outcome"))
|
|
|
|
#expect(allowed.code == .allowedOnce)
|
|
#expect(allowed.verbatim == nil)
|
|
#expect(custom.code == .verbatim)
|
|
#expect(custom.verbatim == "Gateway custom outcome")
|
|
}
|
|
|
|
@Test func `semantic approval outcomes win before legacy decision fallbacks`() {
|
|
let parsedOutcome = WatchExecApprovalResolvedMessage.parseTransportOutcome("allowedAlways")
|
|
let allowedAlways = WatchExecApprovalOutcome.resolved(
|
|
outcome: parsedOutcome,
|
|
legacyText: "Approval denied.",
|
|
decision: .deny,
|
|
source: "another-reviewer")
|
|
let deniedElsewhere = WatchExecApprovalOutcome.resolved(
|
|
outcome: nil,
|
|
legacyText: nil,
|
|
decision: .deny,
|
|
source: "another-reviewer")
|
|
let unknownElsewhere = WatchExecApprovalOutcome.resolved(
|
|
outcome: nil,
|
|
legacyText: nil,
|
|
decision: nil,
|
|
source: "another-reviewer")
|
|
|
|
#expect(allowedAlways.code == .allowedAlways)
|
|
#expect(deniedElsewhere.code == .denied)
|
|
#expect(unknownElsewhere.code == .resolvedElsewhere)
|
|
#expect(WatchExecApprovalResolvedMessage.parseTransportOutcome("future") == nil)
|
|
}
|
|
|
|
@Test func `gateway presentation localizes key and keeps backend override verbatim`() {
|
|
let localized = OpenClawWatchAppStatus(
|
|
code: .gatewayProblem,
|
|
localizationKey: "Gateway update required")
|
|
let backendOverride = OpenClawWatchAppStatus(
|
|
code: .gatewayProblem,
|
|
verbatim: "Gateway says update channel beta")
|
|
let localizedTalkFailure = OpenClawWatchAppStatus(
|
|
code: .talkFailure,
|
|
localizationKey: "Paused")
|
|
|
|
#expect(localized.localizedText(
|
|
localizePresentation: { key, _ in
|
|
key == "Gateway update required" ? "Mise à jour requise" : key
|
|
}) == "Mise à jour requise")
|
|
#expect(backendOverride.localizedText() == "Gateway says update channel beta")
|
|
#expect(localizedTalkFailure.localizedText(
|
|
localizePresentation: { key, _ in
|
|
key == "Paused" ? "En pause" : key
|
|
}) == "En pause")
|
|
}
|
|
|
|
private static func semanticPayload() -> [String: Any] {
|
|
[
|
|
"type": OpenClawWatchPayloadType.appSnapshot.rawValue,
|
|
"gatewayStatus": [
|
|
"code": OpenClawWatchAppStatusCode.gatewayProblem.rawValue,
|
|
"localizationKey": "Gateway update required",
|
|
],
|
|
"gatewayConnected": false,
|
|
"agentName": "Main",
|
|
"agentAvatarURL": "https://example.com/avatar.png",
|
|
"sessionKey": "main",
|
|
"talkStatus": [
|
|
"code": OpenClawWatchAppStatusCode.talkOff.rawValue,
|
|
],
|
|
"talkEnabled": false,
|
|
"talkListening": false,
|
|
"talkSpeaking": false,
|
|
"pendingApprovalCount": 0,
|
|
"chatStatus": [
|
|
"code": OpenClawWatchAppStatusCode.chatConnectIPhone.rawValue,
|
|
],
|
|
]
|
|
}
|
|
|
|
private static func english(_ key: WatchStatusLocalizationKey) -> String {
|
|
switch key {
|
|
case .chat:
|
|
"Chat"
|
|
case .sentFormat:
|
|
"%@: sent"
|
|
default:
|
|
key.rawValue
|
|
}
|
|
}
|
|
|
|
private static func french(_ key: WatchStatusLocalizationKey) -> String {
|
|
switch key {
|
|
case .chat:
|
|
"Discussion"
|
|
case .sentFormat:
|
|
"%@ : envoyée"
|
|
default:
|
|
key.rawValue
|
|
}
|
|
}
|
|
}
|