b214bb6c50
Neue wiederverwendbare Tour-Komponente (11 Swift-Dateien): Model: - TourID, TourTargetID, TourStep, Tour (max 6 Steps per precondition) - TourCatalog: statische Registry; initiale Onboarding-Tour mit 6 Steps State: - TourSeenStore: UserDefaults-backed, injizierbar für Tests - TourCoordinator: @Observable, Pending-Queue, Auto-Start-Logik · checkForPendingTours() startet .autoOnUpdate-Touren bei App-Update · .manualOrFirstLaunch-Touren werden explizit per start(_:) gestartet UI: - SpotlightShape: Even-Odd-Fill-Shape mit animatableData - TourCardView: Progress-Dots, Navigation, Haptic-Feedback - TourOverlayView: Spotlight-Cutout via thinMaterial + SpotlightShape eoFill, Coral-Glow-Ring, automatische Card-Positionierung (above/below/center) - TourViewModifiers: .tourTarget() + .tourPresenter() Integration: - NahbarApp: TourCoordinator via @State + .environment() - OnboardingContainerView: FeatureTourView ersetzt durch TourCoordinator.start(.onboarding); .tourPresenter() im fullScreenCover-Kontext; OnboardingPrivacyView bleibt erhalten - ContentView: .tourPresenter() für main-app Update-Touren + checkForPendingTours() - SettingsView: neue "App-Touren"-Sektion zum manuellen Neu-Starten Tests (40 Tests in 4 Dateien): - TourCatalogTests, TourSeenStoreTests, TourCoordinatorTests, AutoStartLogicTests Lokalisierung: DE + EN für alle Tour-Strings in Localizable.xcstrings Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
110 lines
3.3 KiB
Swift
110 lines
3.3 KiB
Swift
import Testing
|
||
import Foundation
|
||
@testable import nahbar
|
||
|
||
// MARK: - TourSeenStore Tests
|
||
|
||
@Suite("TourSeenStore – Grundfunktionen")
|
||
struct TourSeenStoreTests {
|
||
|
||
/// Creates an isolated UserDefaults for each test to avoid state leakage.
|
||
private func makeStore() -> TourSeenStore {
|
||
let suiteName = "test.tour.\(UUID().uuidString)"
|
||
let defaults = UserDefaults(suiteName: suiteName)!
|
||
return TourSeenStore(defaults: defaults)
|
||
}
|
||
|
||
@Test("hasSeen gibt false zurück bevor markSeen aufgerufen wurde")
|
||
func hasSeenFalseInitially() {
|
||
let store = makeStore()
|
||
#expect(!store.hasSeen(.onboarding))
|
||
}
|
||
|
||
@Test("markSeen setzt hasSeen auf true")
|
||
func markSeenSetsHasSeen() {
|
||
let store = makeStore()
|
||
store.markSeen(.onboarding)
|
||
#expect(store.hasSeen(.onboarding))
|
||
}
|
||
|
||
@Test("markSeen für eine ID beeinflusst andere IDs nicht")
|
||
func markSeenDoesNotAffectOtherIDs() {
|
||
let store = makeStore()
|
||
store.markSeen(.onboarding)
|
||
#expect(!store.hasSeen(.v1_2_besuchsfragebogen))
|
||
#expect(!store.hasSeen(.v1_3_personality))
|
||
}
|
||
|
||
@Test("Mehrfaches markSeen ist idempotent")
|
||
func markSeenIsIdempotent() {
|
||
let store = makeStore()
|
||
store.markSeen(.onboarding)
|
||
store.markSeen(.onboarding)
|
||
#expect(store.hasSeen(.onboarding))
|
||
}
|
||
|
||
@Test("reset setzt hasSeen zurück auf false")
|
||
func resetClearsSeenIDs() {
|
||
let store = makeStore()
|
||
store.markSeen(.onboarding)
|
||
store.reset()
|
||
#expect(!store.hasSeen(.onboarding))
|
||
}
|
||
|
||
@Test("reset löscht auch lastSeenAppVersion")
|
||
func resetClearsLastSeenVersion() {
|
||
let store = makeStore()
|
||
store.lastSeenAppVersion = "1.2"
|
||
store.reset()
|
||
#expect(store.lastSeenAppVersion == nil)
|
||
}
|
||
|
||
@Test("lastSeenAppVersion ist initial nil")
|
||
func lastSeenVersionIsInitiallyNil() {
|
||
let store = makeStore()
|
||
#expect(store.lastSeenAppVersion == nil)
|
||
}
|
||
|
||
@Test("lastSeenAppVersion wird korrekt geschrieben und gelesen")
|
||
func lastSeenVersionPersists() {
|
||
let store = makeStore()
|
||
store.lastSeenAppVersion = "1.3"
|
||
#expect(store.lastSeenAppVersion == "1.3")
|
||
}
|
||
}
|
||
|
||
@Suite("TourSeenStore – Persistenz")
|
||
struct TourSeenStorePersistenceTests {
|
||
|
||
@Test("markSeen persistiert über Store-Neuinstanzierung")
|
||
func persistenceAcrossInstances() {
|
||
let suiteName = "test.tour.persist.\(UUID().uuidString)"
|
||
let defaults = UserDefaults(suiteName: suiteName)!
|
||
|
||
let store1 = TourSeenStore(defaults: defaults)
|
||
store1.markSeen(.onboarding)
|
||
|
||
// New instance, same defaults
|
||
let store2 = TourSeenStore(defaults: defaults)
|
||
#expect(store2.hasSeen(.onboarding))
|
||
|
||
// Cleanup
|
||
defaults.removePersistentDomain(forName: suiteName)
|
||
}
|
||
|
||
@Test("lastSeenAppVersion persistiert über Store-Neuinstanzierung")
|
||
func versionPersistenceAcrossInstances() {
|
||
let suiteName = "test.tour.version.\(UUID().uuidString)"
|
||
let defaults = UserDefaults(suiteName: suiteName)!
|
||
|
||
let store1 = TourSeenStore(defaults: defaults)
|
||
store1.lastSeenAppVersion = "2.0"
|
||
|
||
let store2 = TourSeenStore(defaults: defaults)
|
||
#expect(store2.lastSeenAppVersion == "2.0")
|
||
|
||
// Cleanup
|
||
defaults.removePersistentDomain(forName: suiteName)
|
||
}
|
||
}
|