86ddc10e7b
- Neu: TourTargetIDTests – allCases-Anzahl, rawValues, addMomentButton/addTodoButton - Neu: TourStepTargetTests – Onboarding-Steps 3/4 targeten .addMomentButton/.addTodoButton - Neu: GreetingFirstNameTests – Vorname-Extraktion aus Begrüßungslogik - Fix: OnboardingStepQuizTests – an tatsächliche Enum-Cases angepasst (3 statt 5) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
298 lines
10 KiB
Swift
298 lines
10 KiB
Swift
import Testing
|
||
import Foundation
|
||
@testable import nahbar
|
||
|
||
// MARK: - UserProfileStore Tests
|
||
//
|
||
// Testet die reine Logik des UserProfileStore (Initials, isEmpty, Persistenz).
|
||
// Nutzt einen isolierten UserDefaults-Key um Produktionsdaten nicht zu überschreiben.
|
||
|
||
@Suite("UserProfileStore – Initials")
|
||
struct UserProfileStoreInitialsTests {
|
||
|
||
// Wir testen die Initialen-Logik isoliert, ohne den Store zu instanziieren.
|
||
// Die Logik ist: Vorname[0] + Nachname[0] aus split by " "
|
||
// Bei einem Wort: prefix(2).uppercased()
|
||
// Bei leerem String: "?"
|
||
|
||
private func initials(from name: String) -> String {
|
||
let parts = name.split(separator: " ")
|
||
if parts.count >= 2 {
|
||
return (parts[0].prefix(1) + parts[1].prefix(1)).uppercased()
|
||
}
|
||
return name.isEmpty ? "?" : String(name.prefix(2)).uppercased()
|
||
}
|
||
|
||
@Test("Initials aus Vor- und Nachname")
|
||
func initialsFromFullName() {
|
||
#expect(initials(from: "Max Mustermann") == "MM")
|
||
}
|
||
|
||
@Test("Initials aus drei Wörtern")
|
||
func initialsFromThreeWords() {
|
||
#expect(initials(from: "Anna Maria Schmidt") == "AM")
|
||
}
|
||
|
||
@Test("Initials aus einem Wort (2 Buchstaben)")
|
||
func initialsFromOneWord() {
|
||
#expect(initials(from: "Max") == "MA")
|
||
}
|
||
|
||
@Test("Initials aus kurzem Namen (1 Buchstabe)")
|
||
func initialsFromOneLetterName() {
|
||
#expect(initials(from: "A") == "A")
|
||
}
|
||
|
||
@Test("Initials aus leerem String ist ?")
|
||
func initialsFromEmptyStringIsQuestionMark() {
|
||
#expect(initials(from: "") == "?")
|
||
}
|
||
|
||
@Test("Initials sind immer uppercase")
|
||
func initialsAreAlwaysUppercase() {
|
||
let names = ["anna bach", "max mustermann", "tim", ""]
|
||
for name in names {
|
||
let result = initials(from: name)
|
||
#expect(result == result.uppercased(), "Initials für '\(name)' sollten uppercase sein")
|
||
}
|
||
}
|
||
}
|
||
|
||
// MARK: - UserProfileStore isEmpty Tests
|
||
|
||
@Suite("UserProfileStore – isEmpty")
|
||
struct UserProfileStoreIsEmptyTests {
|
||
|
||
// isEmpty = name.isEmpty && displayName.isEmpty && occupation.isEmpty && location.isEmpty
|
||
// && likes.isEmpty && dislikes.isEmpty && socialStyle.isEmpty && gender.isEmpty
|
||
|
||
private func isEmpty(name: String = "", displayName: String = "",
|
||
occupation: String = "", location: String = "",
|
||
likes: String = "", dislikes: String = "",
|
||
socialStyle: String = "", gender: String = "") -> Bool {
|
||
name.isEmpty && displayName.isEmpty && occupation.isEmpty && location.isEmpty
|
||
&& likes.isEmpty && dislikes.isEmpty && socialStyle.isEmpty && gender.isEmpty
|
||
}
|
||
|
||
@Test("Alle Felder leer → isEmpty ist true")
|
||
func allFieldsEmptyIsTrue() {
|
||
#expect(isEmpty())
|
||
}
|
||
|
||
@Test("Nur Name gesetzt → isEmpty ist false")
|
||
func onlyNameSetIsFalse() {
|
||
#expect(!isEmpty(name: "Max"))
|
||
}
|
||
|
||
@Test("Nur displayName gesetzt → isEmpty ist false")
|
||
func onlyDisplayNameSetIsFalse() {
|
||
#expect(!isEmpty(displayName: "Maxi"))
|
||
}
|
||
|
||
@Test("Nur Beruf gesetzt → isEmpty ist false")
|
||
func onlyOccupationSetIsFalse() {
|
||
#expect(!isEmpty(occupation: "Ingenieur"))
|
||
}
|
||
|
||
@Test("Whitespace-only Name gilt als leer")
|
||
func whitespaceNameIsEmpty() {
|
||
// Der Store trimmt beim Speichern, also gilt " " als leer
|
||
#expect(isEmpty(name: ""))
|
||
}
|
||
|
||
@Test("Nur likes gesetzt → isEmpty ist false")
|
||
func onlyLikesSetIsFalse() {
|
||
#expect(!isEmpty(likes: "Kaffee, Sport"))
|
||
}
|
||
|
||
@Test("Nur dislikes gesetzt → isEmpty ist false")
|
||
func onlyDislikesSetIsFalse() {
|
||
#expect(!isEmpty(dislikes: "Lärm"))
|
||
}
|
||
|
||
@Test("Nur socialStyle gesetzt → isEmpty ist false")
|
||
func onlySocialStyleSetIsFalse() {
|
||
#expect(!isEmpty(socialStyle: "Introvertiert"))
|
||
}
|
||
|
||
@Test("Nur Geschlecht gesetzt → isEmpty ist false")
|
||
func onlyGenderSetIsFalse() {
|
||
#expect(!isEmpty(gender: "Weiblich"))
|
||
}
|
||
|
||
@Test("Alle Vorlieben-Felder leer + Rest leer → isEmpty ist true")
|
||
func allVorliebFieldsEmptyStillEmpty() {
|
||
#expect(isEmpty(likes: "", dislikes: "", socialStyle: "", gender: ""))
|
||
}
|
||
}
|
||
|
||
// MARK: - UserProfileStore Neue Felder Tests
|
||
|
||
@Suite("UserProfileStore – Neue Felder")
|
||
struct UserProfileStoreNewFieldsTests {
|
||
|
||
@Test("socialStyleOptions enthält genau 5 Einträge")
|
||
func socialStyleOptionsCount() {
|
||
let options = ["Introvertiert", "Eher introvertiert", "Ausgeglichen", "Eher extrovertiert", "Extrovertiert"]
|
||
#expect(options.count == 5)
|
||
}
|
||
|
||
@Test("socialStyleOptions sind alle einzigartig")
|
||
func socialStyleOptionsUnique() {
|
||
let options = ["Introvertiert", "Eher introvertiert", "Ausgeglichen", "Eher extrovertiert", "Extrovertiert"]
|
||
#expect(Set(options).count == options.count)
|
||
}
|
||
|
||
@Test("Likes-Parsing: Komma-getrennte Einträge werden korrekt aufgeteilt")
|
||
func likesParsing() {
|
||
let likes = "Kaffee, Sport, Natur"
|
||
let items = likes.split(separator: ",").map { $0.trimmingCharacters(in: .whitespaces) }.filter { !$0.isEmpty }
|
||
#expect(items == ["Kaffee", "Sport", "Natur"])
|
||
}
|
||
|
||
@Test("Likes-Parsing: Leerzeichen um Kommas werden getrimmt")
|
||
func likesParsingTrimsWhitespace() {
|
||
let likes = " Kaffee , Sport "
|
||
let items = likes.split(separator: ",").map { $0.trimmingCharacters(in: .whitespaces) }.filter { !$0.isEmpty }
|
||
#expect(items == ["Kaffee", "Sport"])
|
||
}
|
||
|
||
@Test("Likes-Parsing: Leere Einträge werden gefiltert")
|
||
func likesParsingFiltersEmpty() {
|
||
let likes = "Kaffee,,Sport,"
|
||
let items = likes.split(separator: ",").map { $0.trimmingCharacters(in: .whitespaces) }.filter { !$0.isEmpty }
|
||
#expect(items == ["Kaffee", "Sport"])
|
||
}
|
||
|
||
@Test("Leerer Likes-String ergibt keine Einträge")
|
||
func emptyLikesYieldsNoItems() {
|
||
let items = "".split(separator: ",").map { $0.trimmingCharacters(in: .whitespaces) }.filter { !$0.isEmpty }
|
||
#expect(items.isEmpty)
|
||
}
|
||
|
||
@Test("Geschlechtsoptionen enthalten Männlich, Weiblich, Divers")
|
||
func genderOptionsContainExpectedValues() {
|
||
let options = ["Männlich", "Weiblich", "Divers"]
|
||
#expect(options.contains("Männlich"))
|
||
#expect(options.contains("Weiblich"))
|
||
#expect(options.contains("Divers"))
|
||
#expect(options.count == 3)
|
||
}
|
||
|
||
@Test("Geschlechtsoptionen sind eindeutig")
|
||
func genderOptionsUnique() {
|
||
let options = ["Männlich", "Weiblich", "Divers"]
|
||
#expect(Set(options).count == options.count)
|
||
}
|
||
}
|
||
|
||
// MARK: - Vorname-Extraktion (Begrüßungslogik)
|
||
|
||
@Suite("TodayView – Vorname-Extraktion")
|
||
struct GreetingFirstNameTests {
|
||
|
||
// Spiegelt die Logik aus TodayView.greeting wider:
|
||
// profileStore.name.split(separator: " ").first.map(String.init) ?? ""
|
||
private func firstName(from name: String) -> String {
|
||
name.split(separator: " ").first.map(String.init) ?? ""
|
||
}
|
||
|
||
@Test("Vorname aus vollem Namen")
|
||
func firstNameFromFullName() {
|
||
#expect(firstName(from: "Max Mustermann") == "Max")
|
||
}
|
||
|
||
@Test("Vorname aus dreiteiligem Namen")
|
||
func firstNameFromThreeWordName() {
|
||
#expect(firstName(from: "Anna Maria Schmidt") == "Anna")
|
||
}
|
||
|
||
@Test("Einzelner Name → dieser selbst als Vorname")
|
||
func firstNameFromSingleWord() {
|
||
#expect(firstName(from: "Max") == "Max")
|
||
}
|
||
|
||
@Test("Leerer Name → leerer Vorname")
|
||
func firstNameFromEmptyString() {
|
||
#expect(firstName(from: "").isEmpty)
|
||
}
|
||
|
||
@Test("Nur Leerzeichen → leerer Vorname")
|
||
func firstNameFromWhitespaceOnly() {
|
||
#expect(firstName(from: " ").isEmpty)
|
||
}
|
||
|
||
@Test("Begrüßung mit Vorname enthält Komma und Punkt")
|
||
func greetingFormatWithName() {
|
||
let first = firstName(from: "Max Mustermann")
|
||
let greeting = "Guten Tag, \(first)."
|
||
#expect(greeting == "Guten Tag, Max.")
|
||
}
|
||
|
||
@Test("Begrüßung ohne Name endet mit Punkt (kein Komma)")
|
||
func greetingFormatWithoutName() {
|
||
let first = firstName(from: "")
|
||
let greeting = first.isEmpty ? "Guten Tag." : "Guten Tag, \(first)."
|
||
#expect(greeting == "Guten Tag.")
|
||
}
|
||
}
|
||
|
||
// MARK: - Vorlieben-Nudge Anzeigelogik
|
||
|
||
@Suite("IchView – Vorlieben-Nudge Sichtbarkeit")
|
||
struct VorliebenNudgeTests {
|
||
|
||
// Spiegelt die Bedingung in IchView.infoSection wider:
|
||
// showNudge = likes.isEmpty && dislikes.isEmpty
|
||
// showContent = !likes.isEmpty || !dislikes.isEmpty
|
||
// infoSection nur sichtbar wenn !isEmpty (getrennt getestet)
|
||
|
||
private func showNudge(likes: String, dislikes: String) -> Bool {
|
||
likes.isEmpty && dislikes.isEmpty
|
||
}
|
||
|
||
@Test("Beide leer → Nudge wird angezeigt")
|
||
func bothEmptyShowsNudge() {
|
||
#expect(showNudge(likes: "", dislikes: ""))
|
||
}
|
||
|
||
@Test("Nur likes gesetzt → kein Nudge")
|
||
func onlyLikesSetHidesNudge() {
|
||
#expect(!showNudge(likes: "Kaffee", dislikes: ""))
|
||
}
|
||
|
||
@Test("Nur dislikes gesetzt → kein Nudge")
|
||
func onlyDislikesSetHidesNudge() {
|
||
#expect(!showNudge(likes: "", dislikes: "Lärm"))
|
||
}
|
||
|
||
@Test("Beide gesetzt → kein Nudge")
|
||
func bothSetHidesNudge() {
|
||
#expect(!showNudge(likes: "Kaffee", dislikes: "Lärm"))
|
||
}
|
||
|
||
@Test("Nudge und Content schließen sich gegenseitig aus")
|
||
func nudgeAndContentAreMutuallyExclusive() {
|
||
let cases: [(likes: String, dislikes: String)] = [
|
||
("", ""),
|
||
("Kaffee", ""),
|
||
("", "Lärm"),
|
||
("Kaffee", "Lärm"),
|
||
]
|
||
for c in cases {
|
||
let nudge = showNudge(likes: c.likes, dislikes: c.dislikes)
|
||
let content = !c.likes.isEmpty || !c.dislikes.isEmpty
|
||
// Genau einer von beiden ist wahr, nie beide gleichzeitig
|
||
#expect(nudge != content || (nudge == false && content == false),
|
||
"likes='\(c.likes)' dislikes='\(c.dislikes)': nudge=\(nudge) content=\(content)")
|
||
}
|
||
}
|
||
|
||
@Test("Whitespace-only likes gilt als leer → Nudge erscheint (Store trimmt beim Speichern)")
|
||
func whitespaceOnlyLikesCountsAsEmpty() {
|
||
// Der Store trimmt Eingaben beim Speichern, daher landet nie reines Whitespace
|
||
let trimmed = " ".trimmingCharacters(in: .whitespaces)
|
||
#expect(showNudge(likes: trimmed, dislikes: ""))
|
||
}
|
||
}
|