import XCTest import Combine @testable import PortfolioJournal // Note: These tests require Core Data model access. Disabled by default. @MainActor final class SnapshotFormViewModelTests: XCTestCase { override class var defaultTestSuite: XCTestSuite { // Skip all tests in this class - Core Data is not available in test bundle return XCTestSuite(name: "SnapshotFormViewModelTests (Skipped - Core Data not available)") } var testStack: TestCoreDataStack? var cancellables: Set! override func setUp() async throws { try await super.setUp() do { testStack = try TestCoreDataStack() } catch { testStack = nil } cancellables = [] } override func tearDown() async throws { testStack?.reset() testStack = nil cancellables = nil try await super.tearDown() } /// Helper to skip test if Core Data is not available private func requireCoreData() throws -> TestCoreDataStack { guard let stack = testStack else { throw XCTSkip("Core Data is not available in this test environment") } return stack } // MARK: - Test Helpers private func createTestSource() throws -> InvestmentSource { let stack = try requireCoreData() let account = stack.createTestAccount(name: "Test Account", currency: "USD") let source = stack.createTestSource(name: "Test Source", account: account) try? stack.save() return source } // MARK: - Initialization Tests func testInit_setsCorrectCurrencySymbol() async throws { // Given let source = try createTestSource() // When let viewModel = SnapshotFormViewModel(source: source) // Then XCTAssertEqual(viewModel.currencySymbol, "$") } func testInit_inAddMode_hasEmptyValueString() async throws { // Given let source = try createTestSource() // When let viewModel = SnapshotFormViewModel(source: source, mode: .add) // Then XCTAssertEqual(viewModel.valueString, "") } // MARK: - Validation Tests func testValidation_withEmptyValue_isNotValid() async throws { // Given let source = try createTestSource() let viewModel = SnapshotFormViewModel(source: source) // When viewModel.valueString = "" // Then - wait for validation to propagate try? await Task.sleep(nanoseconds: 100_000_000) // 100ms XCTAssertFalse(viewModel.isValid) } func testValidation_withValidValue_isValid() async throws { // Given let source = try createTestSource() let viewModel = SnapshotFormViewModel(source: source) // When viewModel.valueString = "1000.00" // Then - wait for validation to propagate try? await Task.sleep(nanoseconds: 100_000_000) XCTAssertTrue(viewModel.isValid) } func testValidation_withNegativeValue_isNotValid() async throws { // Given let source = try createTestSource() let viewModel = SnapshotFormViewModel(source: source) // When viewModel.valueString = "-100" // Then try? await Task.sleep(nanoseconds: 100_000_000) XCTAssertFalse(viewModel.isValid) } func testValidation_withContributionEnabled_requiresValidContribution() async throws { // Given let source = try createTestSource() let viewModel = SnapshotFormViewModel(source: source) // When viewModel.valueString = "1000" viewModel.includeContribution = true viewModel.contributionString = "" // Then try? await Task.sleep(nanoseconds: 100_000_000) XCTAssertFalse(viewModel.isValid) } func testValidation_withValidContribution_isValid() async throws { // Given let source = try createTestSource() let viewModel = SnapshotFormViewModel(source: source) // When viewModel.valueString = "1000" viewModel.includeContribution = true viewModel.contributionString = "100" // Then try? await Task.sleep(nanoseconds: 100_000_000) XCTAssertTrue(viewModel.isValid) } // MARK: - Parsing Tests func testParsing_withUSFormat_parsesCorrectly() async throws { // Given let source = try createTestSource() let viewModel = SnapshotFormViewModel(source: source) // When viewModel.valueString = "1000.50" // Then XCTAssertEqual(viewModel.value, 1000.50) } func testParsing_withCommaDecimalSeparator_parsesCorrectly() async throws { // Given let source = try createTestSource() let viewModel = SnapshotFormViewModel(source: source) // When - comma as decimal separator (European format) viewModel.valueString = "1000,50" // Then - should parse with fallback XCTAssertNotNil(viewModel.value) } func testParsing_withCurrencySymbol_stripsSymbol() async throws { // Given let source = try createTestSource() let viewModel = SnapshotFormViewModel(source: source) // When viewModel.valueString = "$1000.50" // Then XCTAssertNotNil(viewModel.value) } // MARK: - Title Tests func testTitle_inAddMode_returnsAddSnapshot() async throws { // Given let source = try createTestSource() // When let viewModel = SnapshotFormViewModel(source: source, mode: .add) // Then XCTAssertEqual(viewModel.title, "Add Snapshot") } // MARK: - Button Title Tests func testButtonTitle_inAddMode_returnsAddSnapshot() async throws { // Given let source = try createTestSource() // When let viewModel = SnapshotFormViewModel(source: source, mode: .add) // Then XCTAssertEqual(viewModel.buttonTitle, "Add Snapshot") } // MARK: - Previous Value Tests func testPreviousValue_withNoSnapshots_returnsNil() async throws { // Given let source = try createTestSource() let viewModel = SnapshotFormViewModel(source: source) // When/Then XCTAssertNil(viewModel.previousValue) } func testPreviousValueString_withNoSnapshots_returnsNoPreviousValue() async throws { // Given let source = try createTestSource() let viewModel = SnapshotFormViewModel(source: source) // When/Then XCTAssertEqual(viewModel.previousValueString, "No previous value") } // MARK: - Date Validation Tests func testIsDateInFuture_withCurrentDate_returnsFalse() async throws { // Given let source = try createTestSource() let viewModel = SnapshotFormViewModel(source: source) // When viewModel.date = Date() // Then XCTAssertFalse(viewModel.isDateInFuture) } func testIsDateInFuture_withFutureDate_returnsTrue() async throws { // Given let source = try createTestSource() let viewModel = SnapshotFormViewModel(source: source) // When viewModel.date = Date().adding(days: 1) // Then XCTAssertTrue(viewModel.isDateInFuture) } func testDateWarning_withFutureDate_returnsWarning() async throws { // Given let source = try createTestSource() let viewModel = SnapshotFormViewModel(source: source) // When viewModel.date = Date().adding(days: 1) // Then XCTAssertNotNil(viewModel.dateWarning) XCTAssertEqual(viewModel.dateWarning, "Date is in the future") } }