fix(mac,asr): harden menu-bar delivery, chunk retry, and polish validator
Retain the external target app for menu-bar paste and polish context, retry failed middle ASR chunks once, and stop false-positive path/number violations.
This commit is contained in:
@@ -38,6 +38,85 @@ final class PolishOutputValidatorTests: XCTestCase {
|
||||
})
|
||||
}
|
||||
|
||||
func testDatesFractionsAndSlashWordsAreNotProtectedPaths() {
|
||||
let cases = [
|
||||
("在 2025/03/01 之前完成", "在2025年3月1日之前完成"),
|
||||
("价格是 3/4 杯面粉", "价格是四分之三杯面粉"),
|
||||
("我给 3/5 分", "我给五分之三"),
|
||||
("读一下 and/or 的用法", "读一下 and or 的用法"),
|
||||
]
|
||||
|
||||
for (input, output) in cases {
|
||||
let violations = PolishOutputValidator.validate(
|
||||
input: input,
|
||||
output: output,
|
||||
dictionary: .empty,
|
||||
lengthRatio: 0.2...3
|
||||
)
|
||||
XCTAssertFalse(
|
||||
violations.contains {
|
||||
if case .missingIdentifiers = $0 { return true }
|
||||
return false
|
||||
},
|
||||
"Must not classify slash value as a protected path: \(input)"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func testStrongPathSignalsRemainHardProtectedIdentifiers() {
|
||||
let inputs = [
|
||||
"/usr/local/bin",
|
||||
"../Sources/App.swift",
|
||||
"Sources/Features/Auth",
|
||||
"src/user_id",
|
||||
]
|
||||
for input in inputs {
|
||||
let violations = PolishOutputValidator.validate(
|
||||
input: "打开 \(input)",
|
||||
output: "打开对应文件",
|
||||
dictionary: .empty,
|
||||
lengthRatio: 0.2...3
|
||||
)
|
||||
XCTAssertTrue(
|
||||
violations.contains {
|
||||
if case .missingIdentifiers(let values) = $0 {
|
||||
return values.contains(input)
|
||||
}
|
||||
return false
|
||||
},
|
||||
"Expected hard path protection for \(input)"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func testOrdinalASRRepairDoesNotReportMissingZeroes() {
|
||||
let violations = PolishOutputValidator.validate(
|
||||
input: "第一点是A第2:00是B",
|
||||
output: "第一点是 A\n2. B",
|
||||
dictionary: .empty,
|
||||
lengthRatio: 0.2...3
|
||||
)
|
||||
XCTAssertFalse(violations.contains {
|
||||
if case .missingNumbers = $0 { return true }
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
func testRealTimeStillReportsMissingZeroes() {
|
||||
let violations = PolishOutputValidator.validate(
|
||||
input: "第一点是坐第2:00班车",
|
||||
output: "第一点是坐第二班车",
|
||||
dictionary: .empty,
|
||||
lengthRatio: 0.2...3
|
||||
)
|
||||
XCTAssertTrue(violations.contains {
|
||||
if case .missingNumbers(let values) = $0 {
|
||||
return values.contains("00")
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
func testNumbersLengthAndLanguageAreObservationOnly() {
|
||||
let violations = PolishOutputValidator.validate(
|
||||
input: "项目 123 明天下午交付并通知全部相关成员",
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
// SpeechHistoryDayDeletionTests.swift
|
||||
// OSGKeyboardTests
|
||||
//
|
||||
// Day-boundary and tombstone coverage for History's delete-day action.
|
||||
|
||||
import XCTest
|
||||
@testable import OSGKeyboardShared
|
||||
|
||||
@MainActor
|
||||
final class SpeechHistoryDayDeletionTests: XCTestCase {
|
||||
|
||||
func testDeleteEntriesRemovesOnlySelectedLocalDayAndRecordsTombstones() {
|
||||
let suiteName = "group.com.osgkeyboard.shared.tests.delete-day.\(UUID().uuidString)"
|
||||
let defaults = UserDefaults(suiteName: suiteName)!
|
||||
defaults.removePersistentDomain(forName: suiteName)
|
||||
defer { defaults.removePersistentDomain(forName: suiteName) }
|
||||
|
||||
let calendar = Calendar.current
|
||||
let selectedDay = Date(timeIntervalSince1970: 1_752_163_200)
|
||||
let start = calendar.startOfDay(for: selectedDay)
|
||||
let end = calendar.date(byAdding: .day, value: 1, to: start)!
|
||||
|
||||
let previous = SpeechHistoryEntry(
|
||||
text: "previous",
|
||||
createdAt: start.addingTimeInterval(-1)
|
||||
)
|
||||
let firstSelected = SpeechHistoryEntry(
|
||||
text: "first selected",
|
||||
createdAt: start.addingTimeInterval(1)
|
||||
)
|
||||
let lastSelected = SpeechHistoryEntry(
|
||||
text: "last selected",
|
||||
createdAt: end.addingTimeInterval(-1)
|
||||
)
|
||||
let next = SpeechHistoryEntry(
|
||||
text: "next",
|
||||
createdAt: end
|
||||
)
|
||||
SpeechHistoryStorage.save(
|
||||
SyncedSpeechHistory(
|
||||
entries: [next, lastSelected, firstSelected, previous]
|
||||
),
|
||||
to: defaults
|
||||
)
|
||||
let store = SpeechHistoryStore(defaults: defaults)
|
||||
|
||||
store.deleteEntries(on: selectedDay)
|
||||
|
||||
let persisted = SpeechHistoryStorage.load(from: defaults)
|
||||
XCTAssertEqual(
|
||||
Set(persisted.entries.map(\.id)),
|
||||
Set([previous.id, next.id])
|
||||
)
|
||||
XCTAssertNotNil(persisted.deletedEntryIDs[firstSelected.id])
|
||||
XCTAssertNotNil(persisted.deletedEntryIDs[lastSelected.id])
|
||||
XCTAssertNil(persisted.deletedEntryIDs[previous.id])
|
||||
XCTAssertNil(persisted.deletedEntryIDs[next.id])
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user