Cursor: Apply local changes for cloud agent
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
@testable import OSGKeyboardShared
|
||||
import XCTest
|
||||
|
||||
final class AIReplyVariantTests: XCTestCase {
|
||||
func testStrictParserReturnsThreeKindsInStableUIOrder() throws {
|
||||
let raw = """
|
||||
{"variants":[
|
||||
{"kind":"playful","emotion":"celebratory","text":"好呀,走起 🎉"},
|
||||
{"kind":"ordinary","emotion":"warm","text":"好呀,到时见。"},
|
||||
{"kind":"formal","emotion":"neutral","text":"好的,届时见。"}
|
||||
]}
|
||||
"""
|
||||
|
||||
let variants = try XCTUnwrap(AIReplyVariantParser.parse(raw))
|
||||
|
||||
XCTAssertEqual(variants.map(\.kind), [.ordinary, .formal, .playful])
|
||||
XCTAssertEqual(variants.map(\.emotion), [.warm, .neutral, .celebratory])
|
||||
XCTAssertEqual(variants.map(\.text), ["好呀,到时见。", "好的,届时见。", "好呀,走起 🎉"])
|
||||
}
|
||||
|
||||
func testUnknownEmotionSafelyDowngradesToNeutral() throws {
|
||||
let raw = """
|
||||
{"variants":[
|
||||
{"kind":"ordinary","emotion":"unsafe-symbol-name","text":"A"},
|
||||
{"kind":"formal","emotion":"neutral","text":"B"},
|
||||
{"kind":"playful","emotion":"playful","text":"C"}
|
||||
]}
|
||||
"""
|
||||
|
||||
let variants = try XCTUnwrap(AIReplyVariantParser.parse(raw))
|
||||
|
||||
XCTAssertEqual(variants[0].emotion, .neutral)
|
||||
}
|
||||
|
||||
func testStrictParserRejectsUnknownFieldsAndMissingKinds() {
|
||||
let extraField = """
|
||||
{"variants":[
|
||||
{"kind":"ordinary","emotion":"neutral","text":"A","icon":"star"},
|
||||
{"kind":"formal","emotion":"neutral","text":"B"},
|
||||
{"kind":"playful","emotion":"playful","text":"C"}
|
||||
]}
|
||||
"""
|
||||
let duplicatedKind = """
|
||||
{"variants":[
|
||||
{"kind":"ordinary","emotion":"neutral","text":"A"},
|
||||
{"kind":"ordinary","emotion":"warm","text":"B"},
|
||||
{"kind":"playful","emotion":"playful","text":"C"}
|
||||
]}
|
||||
"""
|
||||
|
||||
XCTAssertNil(AIReplyVariantParser.parse(extraField))
|
||||
XCTAssertNil(AIReplyVariantParser.parse(duplicatedKind))
|
||||
}
|
||||
|
||||
func testFencedJSONFallsBackToOneCleanOrdinaryReply() throws {
|
||||
let raw = """
|
||||
```json
|
||||
{"variants":[
|
||||
{"kind":"ordinary","emotion":"warm","text":"先确认一下具体时间,可以吗?"},
|
||||
{"kind":"formal","emotion":"neutral","text":"请先确认具体时间。"}
|
||||
]}
|
||||
```
|
||||
"""
|
||||
|
||||
let result = try XCTUnwrap(AIReplyVariantParser.parseOrFallback(raw))
|
||||
guard case .single(let fallback) = result else {
|
||||
return XCTFail("Expected a safe single fallback")
|
||||
}
|
||||
|
||||
XCTAssertEqual(fallback.kind, .ordinary)
|
||||
XCTAssertEqual(fallback.emotion, .neutral)
|
||||
XCTAssertEqual(fallback.text, "先确认一下具体时间,可以吗?")
|
||||
XCTAssertFalse(fallback.text.contains("variants"))
|
||||
XCTAssertFalse(fallback.text.contains("```"))
|
||||
}
|
||||
|
||||
func testMalformedJSONExtractsReplyInsteadOfReturningStructure() throws {
|
||||
let raw = #"{"kind":"ordinary","text":"可以,周五见。","emotion":"warm",}"#
|
||||
|
||||
XCTAssertEqual(
|
||||
AIReplyVariantParser.fallbackText(from: raw),
|
||||
"可以,周五见。"
|
||||
)
|
||||
}
|
||||
|
||||
func testParserRejectsSourceRestatementAndKeepsConversationalReplies() throws {
|
||||
let source = "630语音因为技术改造,能力回退😪"
|
||||
let restatement = """
|
||||
{"variants":[
|
||||
{"kind":"ordinary","emotion":"neutral","text":"630语音因技术改造,能力回退了,有点无奈。"},
|
||||
{"kind":"formal","emotion":"neutral","text":"630语音因技术改造,能力有所回退,特此说明。"},
|
||||
{"kind":"playful","emotion":"playful","text":"630语音被技术改造坑了一把,能力回退了 😅"}
|
||||
]}
|
||||
"""
|
||||
let replies = """
|
||||
{"variants":[
|
||||
{"kind":"ordinary","emotion":"empathetic","text":"那确实有点可惜,希望后面尽快恢复。"},
|
||||
{"kind":"formal","emotion":"calm","text":"了解,希望后续改造完成后能恢复原有能力。"},
|
||||
{"kind":"playful","emotion":"playful","text":"这是先退两步,准备以后起飞吗 😅"}
|
||||
]}
|
||||
"""
|
||||
|
||||
XCTAssertNil(
|
||||
AIReplyVariantParser.parseOrFallback(
|
||||
restatement,
|
||||
sourceText: source
|
||||
)
|
||||
)
|
||||
XCTAssertEqual(
|
||||
try XCTUnwrap(
|
||||
AIReplyVariantParser.parse(replies, sourceText: source)
|
||||
).count,
|
||||
3
|
||||
)
|
||||
}
|
||||
|
||||
func testReplyKindOwnsLocalPresentationMetadata() {
|
||||
XCTAssertEqual(
|
||||
AIReplyVariant.Kind.allCases.map(\.systemImage),
|
||||
["bubble.left.fill", "briefcase.fill", "theatermasks.fill"]
|
||||
)
|
||||
XCTAssertEqual(
|
||||
AIReplyVariant.Kind.allCases.map(\.titleKey),
|
||||
[
|
||||
"keyboard.ai.replyVariant.ordinary",
|
||||
"keyboard.ai.replyVariant.formal",
|
||||
"keyboard.ai.replyVariant.playful"
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
func testEmotionMapsOnlyToLocalSFSymbolAllowlist() {
|
||||
XCTAssertEqual(
|
||||
AIReplyVariant.Emotion.celebratory.systemImage(fallback: .ordinary),
|
||||
"party.popper.fill"
|
||||
)
|
||||
XCTAssertEqual(
|
||||
AIReplyVariant.Emotion.empathetic.systemImage(fallback: .playful),
|
||||
"heart.text.square.fill"
|
||||
)
|
||||
XCTAssertEqual(
|
||||
AIReplyVariant.Emotion.neutral.systemImage(fallback: .formal),
|
||||
"briefcase.fill"
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user