diff --git a/Calc/CalculatorViewModel.swift b/Calc/CalculatorViewModel.swift index c88202e..7d1215a 100644 --- a/Calc/CalculatorViewModel.swift +++ b/Calc/CalculatorViewModel.swift @@ -23,6 +23,7 @@ final class CalculatorViewModel: ObservableObject { private let engine = CalculatorEngine() private let historyKey = "calc.history" private var didJustEvaluate = false + private var isShowingPlaceholder = true private let displayGroupingSeparator = Locale.current.groupingSeparator ?? "," init() { @@ -38,7 +39,7 @@ final class CalculatorViewModel: ObservableObject { } var canCopyDisplayValue: Bool { - !displayValueToCopy.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty + !isShowingPlaceholder && !displayValueToCopy.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty } func input(_ token: String) { @@ -88,6 +89,7 @@ final class CalculatorViewModel: ObservableObject { expression = "0" errorMessage = nil didJustEvaluate = false + isShowingPlaceholder = true } func deleteBackward() { @@ -97,9 +99,15 @@ final class CalculatorViewModel: ObservableObject { expression = expression.isEmpty ? "0" : expression errorMessage = nil didJustEvaluate = false + isShowingPlaceholder = expression == "0" } func evaluate() { + guard !isShowingPlaceholder else { + errorMessage = nil + return + } + do { let result = try engine.evaluate(expression) let formattedResult = engine.format(result) @@ -119,6 +127,7 @@ final class CalculatorViewModel: ObservableObject { expression = engine.expressionString(result) errorMessage = nil didJustEvaluate = true + isShowingPlaceholder = false } catch let error as CalculatorError { errorMessage = error.errorDescription didJustEvaluate = false @@ -132,6 +141,7 @@ final class CalculatorViewModel: ObservableObject { expression = record.result errorMessage = nil didJustEvaluate = true + isShowingPlaceholder = false } func clearHistory() { @@ -201,10 +211,11 @@ final class CalculatorViewModel: ObservableObject { expression = digit didJustEvaluate = false errorMessage = nil + isShowingPlaceholder = false return } - if expression == "0" { + if isShowingPlaceholder || expression == "0" { expression = digit } else if shouldInsertMultiplicationBeforeNumber { expression += "*\(digit)" @@ -213,6 +224,7 @@ final class CalculatorViewModel: ObservableObject { } errorMessage = nil + isShowingPlaceholder = false } private func appendDecimalPoint() { @@ -220,6 +232,7 @@ final class CalculatorViewModel: ObservableObject { expression = "0." didJustEvaluate = false errorMessage = nil + isShowingPlaceholder = false return } @@ -238,6 +251,7 @@ final class CalculatorViewModel: ObservableObject { } errorMessage = nil + isShowingPlaceholder = false } private func appendOperator(_ `operator`: String) { @@ -245,9 +259,11 @@ final class CalculatorViewModel: ObservableObject { didJustEvaluate = false } - if expression == "0" && `operator` == "-" { + if isShowingPlaceholder { + guard `operator` == "-" else { return } expression = "-" errorMessage = nil + isShowingPlaceholder = false return } @@ -276,6 +292,7 @@ final class CalculatorViewModel: ObservableObject { } errorMessage = nil + isShowingPlaceholder = false } private func appendLeftParenthesis() { @@ -283,10 +300,11 @@ final class CalculatorViewModel: ObservableObject { expression = "(" didJustEvaluate = false errorMessage = nil + isShowingPlaceholder = false return } - if expression == "0" { + if isShowingPlaceholder || expression == "0" { expression = "(" } else if let lastCharacter { if lastCharacter.isLetter { @@ -307,6 +325,7 @@ final class CalculatorViewModel: ObservableObject { } errorMessage = nil + isShowingPlaceholder = false } private func appendRightParenthesis() { @@ -315,6 +334,7 @@ final class CalculatorViewModel: ObservableObject { expression.append(")") errorMessage = nil + isShowingPlaceholder = false } private func currentNumberSegment() -> String { @@ -394,7 +414,7 @@ final class CalculatorViewModel: ObservableObject { if didJustEvaluate { expression = character didJustEvaluate = false - } else if expression == "0" { + } else if isShowingPlaceholder || expression == "0" { expression = character } else if shouldInsertMultiplicationBeforeValue { expression += "*\(character)" @@ -403,6 +423,7 @@ final class CalculatorViewModel: ObservableObject { } errorMessage = nil + isShowingPlaceholder = false } private func appendScientificFunction(_ function: String) { @@ -411,7 +432,7 @@ final class CalculatorViewModel: ObservableObject { if didJustEvaluate { expression = functionToken didJustEvaluate = false - } else if expression == "0" { + } else if isShowingPlaceholder || expression == "0" { expression = functionToken } else if shouldInsertMultiplicationBeforeValue { expression += "*\(functionToken)" @@ -420,13 +441,14 @@ final class CalculatorViewModel: ObservableObject { } errorMessage = nil + isShowingPlaceholder = false } private func appendConstant(_ constant: String) { if didJustEvaluate { expression = constant didJustEvaluate = false - } else if expression == "0" { + } else if isShowingPlaceholder || expression == "0" { expression = constant } else if shouldInsertMultiplicationBeforeValue { expression += "*\(constant)" @@ -435,6 +457,7 @@ final class CalculatorViewModel: ObservableObject { } errorMessage = nil + isShowingPlaceholder = false } private func formatExpressionForDisplay(_ expression: String) -> String { diff --git a/CalcTests/CalculatorEngineTests.swift b/CalcTests/CalculatorEngineTests.swift index 7adf049..d7b0074 100644 --- a/CalcTests/CalculatorEngineTests.swift +++ b/CalcTests/CalculatorEngineTests.swift @@ -120,6 +120,30 @@ final class CalculatorViewModelTests: XCTestCase { XCTAssertTrue(viewModel.canCopyDisplayValue) } + func testPlaceholderZeroDoesNotEvaluate() { + let viewModel = CalculatorViewModel() + let initialHistoryCount = viewModel.history.count + + viewModel.evaluate() + + XCTAssertEqual(viewModel.expression, "0") + XCTAssertEqual(viewModel.history.count, initialHistoryCount) + XCTAssertNil(viewModel.errorMessage) + } + + func testEnteredZeroStillEvaluates() { + let viewModel = CalculatorViewModel() + let initialHistoryCount = viewModel.history.count + + viewModel.input("0") + viewModel.evaluate() + + XCTAssertEqual(viewModel.expression, "0") + XCTAssertEqual(viewModel.history.first?.result, "0") + XCTAssertEqual(viewModel.history.count, initialHistoryCount + 1) + XCTAssertNil(viewModel.errorMessage) + } + func testExpressionDisplayFormatsTypedLargeNumber() { let viewModel = CalculatorViewModel()