Files
nahbar/nahbar/nahbarTests/UserProfileStoreTests.swift
T
sven 86ddc10e7b Tests: TourTargetID, Step-Targets, Vorname-Extraktion, OnboardingStep-Fix
- 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>
2026-04-22 20:56:48 +02:00

298 lines
10 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: - 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: ""))
}
}