feat(keyboard): add custom skills plus events and navigate Shortcuts
Ship user-defined Shortcut skills, companion Events/Navigate recipes, shared skill/style card chrome, and bump the build to 69.
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
// AIEventExtractionTests.swift
|
||||
// OSGKeyboardTests
|
||||
|
||||
import XCTest
|
||||
@testable import OSGKeyboardShared
|
||||
|
||||
final class AIEventExtractionTests: XCTestCase {
|
||||
private var calendar: Calendar {
|
||||
var calendar = Calendar(identifier: .gregorian)
|
||||
calendar.timeZone = TimeZone(identifier: "Asia/Shanghai")!
|
||||
return calendar
|
||||
}
|
||||
|
||||
private var now: Date {
|
||||
date(2026, 8, 13, 20, 24)
|
||||
}
|
||||
|
||||
func testNONEAndEmptyProduceNoItems() {
|
||||
XCTAssertEqual(lines("NONE"), [])
|
||||
XCTAssertEqual(lines("没有日期或时间"), [])
|
||||
XCTAssertEqual(lines("no events"), [])
|
||||
XCTAssertEqual(lines(" \n "), [])
|
||||
}
|
||||
|
||||
func testAllDayDateOnly() {
|
||||
XCTAssertEqual(
|
||||
lines("2026-08-15||提交周报|"),
|
||||
["2026-08-15|ALLDAY|提交周报|"]
|
||||
)
|
||||
}
|
||||
|
||||
func testTimedFillsDefaultOneHour() {
|
||||
XCTAssertEqual(
|
||||
lines("2026-08-15 14:00||项目评审|"),
|
||||
["2026-08-15 14:00|2026-08-15 15:00|项目评审|"]
|
||||
)
|
||||
}
|
||||
|
||||
func testTimedKeepsExplicitEndAndLocation() {
|
||||
XCTAssertEqual(
|
||||
lines("2026-08-15 14:00|2026-08-15 16:00|项目评审|3楼会议室"),
|
||||
["2026-08-15 14:00|2026-08-15 16:00|项目评审|3楼会议室"]
|
||||
)
|
||||
}
|
||||
|
||||
func testTimeOnlyUsesToday() {
|
||||
XCTAssertEqual(
|
||||
lines("15:00||打电话给客户|"),
|
||||
["2026-08-13 15:00|2026-08-13 16:00|打电话给客户|"]
|
||||
)
|
||||
}
|
||||
|
||||
func testTimeOnlyEndOnSameDayOvernightRollsForward() {
|
||||
XCTAssertEqual(
|
||||
lines("2026-08-15 23:00|01:00|跨夜值班|"),
|
||||
["2026-08-15 23:00|2026-08-16 01:00|跨夜值班|"]
|
||||
)
|
||||
}
|
||||
|
||||
func testTwoFieldStartAndTitle() {
|
||||
XCTAssertEqual(
|
||||
lines("2026-08-15 14:00|开会"),
|
||||
["2026-08-15 14:00|2026-08-15 15:00|开会|"]
|
||||
)
|
||||
}
|
||||
|
||||
func testThreeFieldStartTitleLocationWhenMiddleIsNotATime() {
|
||||
XCTAssertEqual(
|
||||
lines("2026-08-15 14:00|开会|会议室A"),
|
||||
["2026-08-15 14:00|2026-08-15 15:00|开会|会议室A"]
|
||||
)
|
||||
}
|
||||
|
||||
func testDropsLinesWithoutStartOrTitle() {
|
||||
let raw = """
|
||||
买牛奶
|
||||
||无开始|
|
||||
2026-08-15 14:00||
|
||||
2026-08-16||有效全天|
|
||||
"""
|
||||
XCTAssertEqual(lines(raw), ["2026-08-16|ALLDAY|有效全天|"])
|
||||
}
|
||||
|
||||
func testMultipleEventsCapAtTwenty() {
|
||||
let raw = (1...25).map { "2026-08-15 10:00||任务\($0)|" }.joined(separator: "\n")
|
||||
let items = lines(raw)
|
||||
XCTAssertEqual(items.count, 20)
|
||||
XCTAssertEqual(items.first, "2026-08-15 10:00|2026-08-15 11:00|任务1|")
|
||||
XCTAssertEqual(items.last, "2026-08-15 10:00|2026-08-15 11:00|任务20|")
|
||||
}
|
||||
|
||||
func testWholeClipboardEchoIsRejected() {
|
||||
let source = String(repeating: "这是一段很长的会议纪要内容,包含许多句子。", count: 4)
|
||||
XCTAssertEqual(
|
||||
AIEventExtraction.lines(
|
||||
from: "2026-08-15||\(source)|",
|
||||
sourceClipboard: source,
|
||||
now: now,
|
||||
calendar: calendar
|
||||
),
|
||||
[]
|
||||
)
|
||||
}
|
||||
|
||||
func testPromptIncludesClockPipeContractAndNONE() {
|
||||
let prompt = AIClipboardSkillCatalog.instruction(
|
||||
skillID: AIClipboardSkillCatalog.extractEventsID,
|
||||
locale: "zh",
|
||||
translationTargetLocaleId: TranslationLanguageCatalog.offLocaleId,
|
||||
now: now
|
||||
)
|
||||
XCTAssertTrue(prompt.contains("本地时区"))
|
||||
XCTAssertTrue(prompt.contains("开始|结束|标题|地点"))
|
||||
XCTAssertTrue(prompt.contains("NONE"))
|
||||
XCTAssertTrue(prompt.contains("不要把整段原文当成一条日程"))
|
||||
}
|
||||
|
||||
func testEnglishPromptIncludesClockAndNONE() {
|
||||
let prompt = AIClipboardSkillCatalog.instruction(
|
||||
skillID: AIClipboardSkillCatalog.extractEventsID,
|
||||
locale: "en",
|
||||
translationTargetLocaleId: TranslationLanguageCatalog.offLocaleId,
|
||||
now: now
|
||||
)
|
||||
XCTAssertTrue(prompt.contains("local timezone"))
|
||||
XCTAssertTrue(prompt.contains("NONE"))
|
||||
XCTAssertTrue(prompt.contains("all-day"))
|
||||
}
|
||||
|
||||
private func lines(_ raw: String) -> [String] {
|
||||
AIEventExtraction.lines(from: raw, now: now, calendar: calendar)
|
||||
}
|
||||
|
||||
private func date(_ y: Int, _ m: Int, _ d: Int, _ h: Int, _ min: Int) -> Date {
|
||||
var components = DateComponents()
|
||||
components.year = y
|
||||
components.month = m
|
||||
components.day = d
|
||||
components.hour = h
|
||||
components.minute = min
|
||||
return calendar.date(from: components)!
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user