feat: add history store, search and day grouping
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
import Foundation
|
||||
import Testing
|
||||
import SstCore
|
||||
@testable import SstData
|
||||
|
||||
@MainActor
|
||||
struct HistoryStoreTests {
|
||||
func tempFile() -> URL {
|
||||
FileManager.default.temporaryDirectory.appending(path: "sst-tests-\(UUID().uuidString)/history.json")
|
||||
}
|
||||
|
||||
func entry(_ text: String, at seconds: TimeInterval) -> HistoryEntry {
|
||||
HistoryEntry(date: Date(timeIntervalSince1970: 1_700_000_000 + seconds), duration: 2, rawText: text, finalText: text)
|
||||
}
|
||||
|
||||
@Test func newestEntryComesFirstAndSurvivesARoundTrip() {
|
||||
let url = tempFile()
|
||||
let store = HistoryStore(fileURL: url)
|
||||
store.add(entry("eerste", at: 0))
|
||||
store.add(HistoryEntry(
|
||||
date: Date(timeIntervalSince1970: 1_700_000_100), duration: 3, rawText: "cloud code", finalText: "Claude Code",
|
||||
corrections: [AppliedCorrection(heard: "cloud code", wrote: "Claude Code", rule: "cloud code", start: 0, length: 11)]))
|
||||
store.flush()
|
||||
|
||||
let reloaded = HistoryStore(fileURL: url)
|
||||
#expect(reloaded.load() == .loaded)
|
||||
#expect(reloaded.entries.map(\.finalText) == ["Claude Code", "eerste"])
|
||||
#expect(reloaded.entries == store.entries)
|
||||
}
|
||||
|
||||
@Test func missingFileIsEmpty() {
|
||||
let store = HistoryStore(fileURL: tempFile())
|
||||
#expect(store.load() == .empty)
|
||||
#expect(store.entries.isEmpty)
|
||||
}
|
||||
|
||||
@Test func removeAndRemoveAll() {
|
||||
let store = HistoryStore(fileURL: tempFile())
|
||||
let a = entry("a", at: 0), b = entry("b", at: 1)
|
||||
store.add(a); store.add(b)
|
||||
store.remove(id: a.id)
|
||||
#expect(store.entries.map(\.finalText) == ["b"])
|
||||
store.removeAll()
|
||||
#expect(store.entries.isEmpty)
|
||||
}
|
||||
|
||||
@Test func corruptFileIsMovedAsideAndStoreStartsFresh() throws {
|
||||
let url = tempFile()
|
||||
try FileManager.default.createDirectory(at: url.deletingLastPathComponent(), withIntermediateDirectories: true)
|
||||
try "dit is geen json".write(to: url, atomically: true, encoding: .utf8)
|
||||
let store = HistoryStore(fileURL: url)
|
||||
guard case .recoveredCorrupt(let backup) = store.load() else {
|
||||
Issue.record("expected recoveredCorrupt"); return
|
||||
}
|
||||
#expect(FileManager.default.fileExists(atPath: backup.path))
|
||||
#expect(!FileManager.default.fileExists(atPath: url.path))
|
||||
#expect(store.entries.isEmpty)
|
||||
}
|
||||
|
||||
@Test func onChangeFiresForEdits() {
|
||||
let store = HistoryStore(fileURL: tempFile())
|
||||
var calls = 0
|
||||
store.onChange = { calls += 1 }
|
||||
store.add(entry("a", at: 0))
|
||||
store.removeAll()
|
||||
#expect(calls == 2)
|
||||
}
|
||||
|
||||
@Test func entryFromDictationResultKeepsEverything() {
|
||||
let result = DictationResult(
|
||||
date: Date(timeIntervalSince1970: 1_700_000_000), duration: 4.5, rawText: "raw", finalText: "final",
|
||||
corrections: [AppliedCorrection(heard: "a", wrote: "b", rule: "a", start: 0, length: 1)])
|
||||
let e = HistoryEntry(result: result)
|
||||
#expect(e.rawText == "raw" && e.finalText == "final" && e.duration == 4.5 && e.corrections.count == 1)
|
||||
}
|
||||
}
|
||||
|
||||
struct HistorySearchTests {
|
||||
let entries = [
|
||||
HistoryEntry(date: .now, duration: 1, rawText: "ik ga naar het café", finalText: "Ik ga naar het Café"),
|
||||
HistoryEntry(date: .now, duration: 1, rawText: "cloud code is handig", finalText: "Claude Code is handig"),
|
||||
]
|
||||
|
||||
@Test func emptyQueryReturnsEverything() {
|
||||
#expect(HistorySearch.filter(entries, query: " ").count == 2)
|
||||
}
|
||||
|
||||
@Test func ignoresCaseAndDiacritics() {
|
||||
#expect(HistorySearch.filter(entries, query: "CAFE").count == 1)
|
||||
#expect(HistorySearch.filter(entries, query: "café").count == 1)
|
||||
}
|
||||
|
||||
@Test func allWordsMustMatchInAnyOrder() {
|
||||
#expect(HistorySearch.filter(entries, query: "handig claude").count == 1)
|
||||
#expect(HistorySearch.filter(entries, query: "handig café").isEmpty)
|
||||
}
|
||||
|
||||
@Test func searchesTheOriginalTextToo() {
|
||||
#expect(HistorySearch.filter(entries, query: "cloud").count == 1)
|
||||
}
|
||||
}
|
||||
|
||||
struct HistoryGroupingTests {
|
||||
@Test func groupsByDayKeepingOrder() {
|
||||
var calendar = Calendar(identifier: .gregorian)
|
||||
calendar.timeZone = TimeZone(identifier: "UTC")!
|
||||
func at(_ day: Int, _ hour: Int) -> Date {
|
||||
calendar.date(from: DateComponents(year: 2026, month: 10, day: day, hour: hour))!
|
||||
}
|
||||
let entries = [at(2, 15), at(2, 9), at(1, 20)].map {
|
||||
HistoryEntry(date: $0, duration: 1, rawText: "x", finalText: "x")
|
||||
}
|
||||
let days = HistoryGrouping.byDay(entries, calendar: calendar)
|
||||
#expect(days.count == 2)
|
||||
#expect(days[0].entries.count == 2)
|
||||
#expect(days[1].entries.count == 1)
|
||||
#expect(days[0].day > days[1].day)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user