270 lines
7.6 KiB
Swift
270 lines
7.6 KiB
Swift
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<AnyCancellable>!
|
|
|
|
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")
|
|
}
|
|
}
|