Files
onyx/Packages/OnyxKit/Tests/NotesProviderTests/NoteRulesTests.swift
T
ScarriffleandClaude Opus 5 3da673662f Zwischenablage, Notizen und der Gesichtsvergleich
Drei neue Module nach dem Bauplan von `ShelfProvider`: reine Regeln getrennt vom
Zustand, versionierte Indexdatei, Inhalte in Dateien statt in `UserDefaults` —
was dort steht, liest jedes Programm mit `defaults read` mit.

Die Zwischenablage achtet `ConcealedType`, `TransientType` und
`AutoGeneratedType`. Diese Marken sind eine Verabredung zwischen Programmen und
kein Systemdienst; wer sie missachtet, sammelt fremde Passwörter ein.

Der Gesichtsvergleich stand im Plan als der eine echte Unsicherheitsfaktor. Ein
Spike hat ihn erledigt: `VNGenerateFaceprintRequest` gibt es nicht, aber
`VNGenerateImageFeaturePrintRequest` ist öffentlich, liefert 768 Elemente und
ein Abstandsmaß — Faktor 3,5 Trennschärfe. Kein eigenes CoreML-Modell nötig.

`FaceMatcher` entscheidet über mehrere Posen, mehrere Einzelbilder und eine
Sperre nach Fehlversuchen. Die Schwelle liegt bewusst nicht mittig: ein falsches
Ja ist ein Fremder am entsperrten Rechner, ein falsches Nein nur ein Passwort,
das man ohnehin kennt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 21:06:06 +02:00

84 lines
3.0 KiB
Swift

import Testing
import Foundation
@testable import NotesProvider
@Suite("Notizen: die Regeln")
struct NoteRulesTests {
@Test("Die Überschrift ist die erste nichtleere Zeile")
func titleIsFirstLine() {
let note = Note(text: "\n\n Einkaufen \nMilch\nBrot")
#expect(NoteRules.title(of: note) == "Einkaufen")
}
@Test("Eine zu lange erste Zeile wird gekürzt")
func longTitleIsTruncated() {
let note = Note(text: String(repeating: "a", count: 200))
let title = NoteRules.title(of: note)
#expect(title.count == NoteRules.titleLength + 1)
#expect(title.hasSuffix("…"))
}
@Test("Eine leere Notiz hat keine Überschrift")
func emptyHasNoTitle() {
#expect(NoteRules.title(of: Note(text: " \n\n ")) == "")
}
@Test("Eine Notiz aus lauter Leerzeichen zählt als leer")
func whitespaceIsEmpty() {
// Sie entsteht, wenn man auf „Neu" drückt und es sich anders überlegt.
#expect(NoteRules.isEmpty(Note(text: " \n\t ")))
#expect(!NoteRules.isEmpty(Note(text: "x")))
}
@Test("Eine leer gewordene Notiz verschwindet beim Speichern")
func emptyNoteIsDropped() {
let note = Note(text: "war mal etwas")
let kept = NoteRules.merge([], updating: note)
#expect(kept.count == 1)
var emptied = note
emptied.text = " "
#expect(NoteRules.merge(kept, updating: emptied).isEmpty)
}
@Test("Zuletzt geändert steht oben")
func newestFirst() {
let old = Note(text: "alt", updatedAt: Date(timeIntervalSince1970: 1))
let new = Note(text: "neu", updatedAt: Date(timeIntervalSince1970: 2))
#expect(NoteRules.sorted([old, new]).first?.text == "neu")
}
@Test("Gleich alte Notizen behalten ihre Reihenfolge")
func sortingIsStable() {
let when = Date(timeIntervalSince1970: 5)
let a = Note(text: "a", updatedAt: when)
let b = Note(text: "b", updatedAt: when)
#expect(NoteRules.sorted([a, b]).map(\.id) == NoteRules.sorted([b, a]).map(\.id))
}
@Test("Eine bestehende Notiz wird ersetzt, nicht verdoppelt")
func updateReplaces() {
let note = Note(text: "erst")
var changed = note
changed.text = "dann"
let result = NoteRules.merge([note], updating: changed)
#expect(result.count == 1)
#expect(result.first?.text == "dann")
}
@Test("Über der Grenze fällt die älteste heraus")
func limitDropsOldest() {
let existing = (0..<3).map {
Note(text: "n\($0)", updatedAt: Date(timeIntervalSince1970: Double($0)))
}
let result = NoteRules.merge(existing,
updating: Note(text: "neu",
updatedAt: Date(timeIntervalSince1970: 99)),
limit: 3)
#expect(result.count == 3)
#expect(result.first?.text == "neu")
#expect(!result.contains { $0.text == "n0" })
}
}