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>
This commit is contained in:
co-authored by
Claude Opus 5
parent
f289d02c37
commit
3da673662f
@@ -0,0 +1,83 @@
|
||||
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" })
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user