import XCTest import SwiftData @testable import MealMood @MainActor final class OnboardingViewModelTests: XCTestCase { func testAddDishAllowsNameOnlyAndUsesDefaultTag() { let vm = OnboardingViewModel() vm.newDishName = "Test dish" let fallbackId = UUID() vm.addDish(defaultTagId: fallbackId) XCTAssertEqual(vm.addedDishes.count, 1) XCTAssertEqual(vm.addedDishes.first?.name, "Test dish") XCTAssertEqual(vm.addedDishes.first?.tagIds, [fallbackId]) } func testAddDishPreventsDuplicateNames() { let vm = OnboardingViewModel() vm.newDishName = "Lentils" vm.addDish(defaultTagId: UUID()) vm.newDishName = " lentils " vm.addDish(defaultTagId: UUID()) XCTAssertEqual(vm.addedDishes.count, 1) } func testSuggestedDishFallsBackToFirstTagWhenNoNameMatch() { let vm = OnboardingViewModel() let fallback = Tag(name: "Carne", nameEN: "Meat", color: "#E74C3C", sortOrder: 0) let other = Tag(name: "Pescado", nameEN: "Fish", color: "#3498DB", sortOrder: 1) vm.addSuggestedDish( name: "Roast chicken", preferredTagNames: ["Unknown tag"], availableTags: [other, fallback] ) XCTAssertEqual(vm.addedDishes.count, 1) XCTAssertEqual(vm.addedDishes.first?.tagIds, [fallback.id]) } // MARK: Onboarding completion flag /// Returns the container, not just its context: releasing the container /// leaves `mainContext` dangling and SwiftData traps on the next access. private func makeContainer() throws -> ModelContainer { let config = ModelConfiguration(isStoredInMemoryOnly: true) return try ModelContainer( for: AppSettings.self, Tag.self, Dish.self, WeekPlan.self, MealSlot.self, ShoppingItem.self, configurations: config ) } /// Regression: the flag used to be written at the dishes step, which tore the /// flow down before the Week Ready and paywall steps could render — neither /// was ever seen (confirmed in GA4: no step 5/6 events despite completions). func testCompleteOnboardingKeepsFlowOnScreenWhenNotMarkedFinished() throws { let container = try makeContainer() let context = container.mainContext let vm = OnboardingViewModel() vm.newDishName = "Lentils" vm.addDish(defaultTagId: UUID()) let saved = vm.completeOnboarding(context: context, markFinished: false) XCTAssertTrue(saved) let settings = try context.fetch(FetchDescriptor()).first XCTAssertNotNil(settings) XCTAssertFalse(settings?.onboardingCompleted ?? true, "The flag must stay false so the last two steps still render") // The data itself is already committed at this point. XCTAssertEqual(try context.fetch(FetchDescriptor()).count, 1) XCTAssertEqual(try context.fetch(FetchDescriptor()).count, 1) } func testFinishOnboardingSetsTheFlag() throws { let container = try makeContainer() let context = container.mainContext let vm = OnboardingViewModel() vm.completeOnboarding(context: context, markFinished: false) vm.finishOnboarding(context: context) let stored = try context.fetch(FetchDescriptor()).first XCTAssertTrue(stored?.onboardingCompleted ?? false) } func testCompleteOnboardingStillMarksFinishedByDefault() throws { let container = try makeContainer() let context = container.mainContext OnboardingViewModel().completeOnboarding(context: context) let stored = try context.fetch(FetchDescriptor()).first XCTAssertTrue(stored?.onboardingCompleted ?? false) } }