300 lines
8.7 KiB
Swift
300 lines
8.7 KiB
Swift
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)
|
||
}
|
||
}
|
||
}
|