Files
nahbar/nahbar/nahbarTests/ModelTests.swift
T

300 lines
8.7 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Testing
import Foundation
@testable import nahbar
// MARK: - NudgeFrequency Tests
@Suite("NudgeFrequency")
struct NudgeFrequencyTests {
@Test("never gibt nil zurück")
func neverReturnsNil() {
#expect(NudgeFrequency.never.days == nil)
}
@Test("weekly gibt 7 Tage zurück")
func weeklyReturnsSeven() {
#expect(NudgeFrequency.weekly.days == 7)
}
@Test("biweekly gibt 14 Tage zurück")
func biweeklyReturnsFourteen() {
#expect(NudgeFrequency.biweekly.days == 14)
}
@Test("monthly gibt 30 Tage zurück")
func monthlyReturnsThirty() {
#expect(NudgeFrequency.monthly.days == 30)
}
@Test("quarterly gibt 90 Tage zurück")
func quarterlyReturnsNinety() {
#expect(NudgeFrequency.quarterly.days == 90)
}
@Test("alle CaseIterable-Fälle haben korrekte Tage")
func allCasesHaveValidDays() {
for freq in NudgeFrequency.allCases {
if freq == .never {
#expect(freq.days == nil)
} else {
#expect(freq.days != nil)
#expect(freq.days! > 0)
}
}
}
}
// MARK: - PersonTag Tests
@Suite("PersonTag")
struct PersonTagTests {
@Test("alle Tags haben ein nicht-leeres Icon")
func allTagsHaveIcons() {
for tag in PersonTag.allCases {
#expect(!tag.icon.isEmpty)
}
}
@Test("family hat house-Icon")
func familyIconIsHouse() {
#expect(PersonTag.family.icon == "house")
}
@Test("rawValue round-trip")
func rawValueRoundTrip() {
for tag in PersonTag.allCases {
let parsed = PersonTag(rawValue: tag.rawValue)
#expect(parsed == tag)
}
}
}
// MARK: - MomentType Tests
@Suite("MomentType")
struct MomentTypeTests {
@Test("alle MomentTypes haben ein nicht-leeres Icon")
func allTypesHaveIcons() {
for type_ in MomentType.allCases {
#expect(!type_.icon.isEmpty)
}
}
@Test("rawValue round-trip")
func rawValueRoundTrip() {
for type_ in MomentType.allCases {
let parsed = MomentType(rawValue: type_.rawValue)
#expect(parsed == type_)
}
}
}
// MARK: - MomentSource Tests
@Suite("MomentSource")
struct MomentSourceTests {
@Test("alle Sources haben ein nicht-leeres Icon")
func allSourcesHaveIcons() {
for source in MomentSource.allCases {
#expect(!source.icon.isEmpty)
}
}
}
// MARK: - Person Computed Properties Tests
// Person ist ein @Model kann ohne Context instanziiert werden,
// solange nur non-Relationship-Properties getestet werden.
@Suite("Person Computed Properties")
struct PersonComputedPropertyTests {
@Test("initials aus Vor- und Nachname")
func initialsFromFullName() {
let p = Person(name: "Max Mustermann")
#expect(p.initials == "MM")
}
@Test("initials aus einem Wort (2 Zeichen)")
func initialsFromSingleWord() {
let p = Person(name: "Max")
#expect(p.initials == "MA")
}
@Test("initials aus drei Wörtern nimmt erste zwei")
func initialsFromThreeWords() {
let p = Person(name: "Max Karl Mustermann")
#expect(p.initials == "MK")
}
@Test("initials sind uppercase")
func initialsAreUppercase() {
let p = Person(name: "anna bach")
#expect(p.initials == p.initials.uppercased())
}
@Test("firstName aus vollem Namen")
func firstNameFromFullName() {
let p = Person(name: "Max Mustermann")
#expect(p.firstName == "Max")
}
@Test("firstName bei einem Wort")
func firstNameFromSingleWord() {
let p = Person(name: "Max")
#expect(p.firstName == "Max")
}
@Test("tag computed property round-trip")
func tagRoundTrip() {
let p = Person(name: "Test", tag: .family)
#expect(p.tag == .family)
p.tag = .work
#expect(p.tag == .work)
#expect(p.tagRaw == PersonTag.work.rawValue)
}
@Test("nudgeFrequency computed property round-trip")
func nudgeFrequencyRoundTrip() {
let p = Person(name: "Test", nudgeFrequency: .weekly)
#expect(p.nudgeFrequency == .weekly)
p.nudgeFrequency = .quarterly
#expect(p.nudgeFrequency == .quarterly)
}
@Test("needsAttention mit .never ist immer false")
func needsAttentionNeverIsAlwaysFalse() {
let p = Person(name: "Test", nudgeFrequency: .never)
#expect(!p.needsAttention)
}
@Test("needsAttention kurz nach Erstellung ist false")
func needsAttentionJustCreatedIsFalse() {
let p = Person(name: "Test", nudgeFrequency: .weekly)
p.createdAt = Date() // gerade erstellt
#expect(!p.needsAttention)
}
@Test("needsAttention nach abgelaufener Nudge-Periode ist true")
func needsAttentionAfterPeriodIsTrue() {
let p = Person(name: "Test", nudgeFrequency: .weekly)
// createdAt auf 10 Tage in der Vergangenheit setzen
p.createdAt = Calendar.current.date(byAdding: .day, value: -10, to: Date())!
#expect(p.needsAttention)
}
@Test("touch() aktualisiert updatedAt")
func touchUpdatesTimestamp() throws {
let p = Person(name: "Test")
let before = p.updatedAt
// Kurze Pause um unterschiedliche Timestamps zu garantieren
Thread.sleep(forTimeInterval: 0.01)
p.touch()
#expect(p.updatedAt > before)
}
@Test("currentPhotoData bevorzugt photo gegenüber photoData")
func currentPhotoDataFallback() {
let p = Person(name: "Test")
#expect(p.currentPhotoData == nil) // Beide nil
let legacyData = Data([0x01, 0x02])
p.photoData = legacyData
#expect(p.currentPhotoData == legacyData) // Fallback auf legacy
}
@Test("hasBirthdayWithin(0) gibt immer false")
func birthdayWithinZeroDays() {
let p = Person(name: "Test", birthday: Date())
#expect(!p.hasBirthdayWithin(days: 0))
}
@Test("hasBirthdayWithin erkennt heutigen Geburtstag")
func birthdayWithinDetectsTodaysBirthday() {
// Geburtstag auf heute setzen (Jahr egal, nur Monat+Tag zählt)
let today = Date()
let cal = Calendar.current
var bdc = cal.dateComponents([.month, .day], from: today)
bdc.year = 1990 // beliebiges vergangenes Jahr
let birthday = cal.date(from: bdc)!
let p = Person(name: "Test", birthday: birthday)
#expect(p.hasBirthdayWithin(days: 1))
}
@Test("hasBirthdayWithin gibt false wenn Geburtstag außerhalb des Fensters")
func birthdayOutsideWindowReturnsFalse() {
let cal = Calendar.current
// Geburtstag auf gestern setzen
let yesterday = cal.date(byAdding: .day, value: -1, to: Date())!
var bdc = cal.dateComponents([.month, .day], from: yesterday)
bdc.year = 1990
let birthday = cal.date(from: bdc)!
let p = Person(name: "Test", birthday: birthday)
// 1-Tage-Fenster ab heute: gestern liegt nicht darin
#expect(!p.hasBirthdayWithin(days: 1))
}
}
// MARK: - Moment Tests
@Suite("Moment Computed Properties")
struct MomentComputedPropertyTests {
@Test("type computed property round-trip")
func typeRoundTrip() {
let m = Moment(text: "Test", type: .meeting)
#expect(m.type == .meeting)
m.type = .thought
#expect(m.type == .thought)
#expect(m.typeRaw == MomentType.thought.rawValue)
}
@Test("source computed property round-trip")
func sourceRoundTrip() {
let m = Moment(text: "Test", source: .whatsapp)
#expect(m.source == .whatsapp)
m.source = .signal
#expect(m.source == .signal)
}
@Test("source nil round-trip")
func sourceNilRoundTrip() {
let m = Moment(text: "Test", source: nil)
#expect(m.source == nil)
#expect(m.sourceRaw == nil)
}
@Test("isImportant startet als false")
func isImportantDefaultsFalse() {
let m = Moment(text: "Test")
#expect(!m.isImportant)
}
}
// MARK: - LogEntry Tests
@Suite("LogEntry Computed Properties")
struct LogEntryComputedPropertyTests {
@Test("type computed property round-trip")
func typeRoundTrip() {
let entry = LogEntry(type: .call, title: "Test")
#expect(entry.type == .call)
entry.type = .nextStep
#expect(entry.type == .nextStep)
#expect(entry.typeRaw == LogEntryType.nextStep.rawValue)
}
@Test("alle LogEntryTypes haben ein nicht-leeres Icon und color")
func allTypesHaveIconAndColor() {
let types: [LogEntryType] = [.nextStep, .calendarEvent, .call]
for type_ in types {
#expect(!type_.icon.isEmpty)
#expect(!type_.color.isEmpty)
}
}
}