Calculator Reducer
Breakdown of the Code
1. JSDoc Type Imports
- These lines use JSDoc to import types from
types.js.
CalculatorState represents the shape of the calculator's state, and CalculatorAction represents the shape of an action dispatched to the reducer.
- These types provide type checking and documentation within the IDE, even though the code is in plain JavaScript.
2. Initial State
initialState defines the starting values for the calculator.
currentValue: Represents the value currently being input or the last calculated result. It starts as '0'.
previousValue: Holds the value before an operator is pressed. Initially null.
operator: Stores the operator (+, -, *, /) currently in use. It starts as null.
waitingForOperand: A boolean flag that indicates if the calculator is waiting for the next operand after an operator is pressed.
3. Reducer Function
- This is a reducer function that takes the current state and an action, and returns the new state based on the type of the action.
- Default value: If no state is provided (e.g., at initialization), it uses
initialState.
4. Handling Action Types
- The
switch statement handles different action.type values.
4.1 Handling 'DIGIT' Action
- When a digit is pressed (
'DIGIT' action):
- If the calculator is waiting for the next operand (
waitingForOperand is true), it replaces the currentValue with the digit and resets the waitingForOperand flag.
- Otherwise, it appends the digit to the
currentValue. If currentValue is '0', it replaces it with the new digit.
4.2 Handling 'OPERATOR' Action
- When an operator is pressed (
'OPERATOR' action):
- If there's already a
previousValue and an operator is set, the function evaluates the current expression and stores the result as the previousValue.
- Otherwise, it sets the
currentValue as the previousValue and assigns the operator from action.payload.
- It also sets
waitingForOperand to true to signal that the calculator is waiting for the next number to be entered.
4.3 Handling 'EQUALS' Action
- When the equals button is pressed (
'EQUALS' action):
- If an operator and a
previousValue are set, it evaluates the current expression and updates currentValue with the result. It also clears the operator and previousValue.
- If the necessary data for evaluation is not present, it simply returns the current state unchanged.
4.4 Handling 'CLEAR' Action
- When the clear button is pressed (
'CLEAR' action):
- It resets the calculator state to the
initialState.
4.5 Default Case
- If an action type is not recognized, the reducer returns the current state without any changes.
5. Evaluate Function
- The
evaluate function takes currentValue, previousValue, and operator to perform the calculation.
- Steps:
- It converts the
previousValue and currentValue from strings to numbers using parseFloat.
- Based on the operator, it performs the corresponding arithmetic operation (
+, -, *, /).
- Divide by zero handling: If the operation is division (
/) and currentValue is 0, it returns 'Error' to avoid division by zero.
- The result of the operation is returned as a string.
Summary
- The
calculatorReducer function handles the state changes of a calculator by interpreting different actions ('DIGIT', 'OPERATOR', 'EQUALS', 'CLEAR').
- The
evaluate helper function performs the arithmetic operations when required.
- The state is updated incrementally as the user presses digits, operators, and other controls, making it suitable for a basic calculator UI.
Breakdown of the Tests
1. Imports and Initial Setup
- Vitest is being imported for testing, using
describe to group the test cases, it for individual test cases, and expect for assertions.
- The
calculatorReducer is imported from a file called calculator.js for testing.
- The
CalculatorState and CalculatorAction types are imported using JSDoc for static type checking.
2. Initial State
- This defines the
initialState, representing the calculator's default state when no input has been provided yet.
3. Test: Handling Digit Input
- What it does: Tests that when the
'DIGIT' action is dispatched with the digit '5', the currentValue is updated to '5'.
- Why: To ensure the reducer correctly updates the state with the inputted digit.
4. Test: Appending Digits to Current Input
- What it does: Tests appending digits
'5' and '3' to form '53'.
- Why: To verify that digits are appended to
currentValue correctly and are not overwritten when a new digit is added.
5. Test: Handling Operator Input
- What it does: Tests pressing an operator (
'+'), ensuring the state updates with the current value as previousValue, sets the operator, and sets waitingForOperand to true.
- Why: To confirm that when an operator is pressed, the reducer correctly prepares for the next operand.
6. Test: Performing Addition
- What it does: Tests the
'EQUALS' action when the operator is '+', and the values are 10 and 5. The result should be '15'.
- Why: To verify that the addition is correctly performed when the equals button is pressed.
7. Test: Performing Subtraction
- What it does: Tests subtraction (
'8 - 3') when the equals button is pressed, with the result being '5'.
- Why: To confirm that subtraction works correctly with the equals action.
8. Test: Performing Multiplication
- What it does: Tests multiplication (
'6 * 4') when the equals button is pressed, expecting the result '24'.
- Why: To ensure the multiplication operation is working correctly.
9. Test: Performing Division
- What it does: Tests division (
'10 / 2') with the expected result of '5'.
- Why: To confirm that division is handled properly.
10. Test: Division by Zero
- What it does: Tests dividing by zero (
'10 / 0') and expects the result to be 'Error'.
- Why: To ensure the reducer correctly handles the edge case of division by zero, avoiding invalid arithmetic results.
11. Test: Clearing the Calculator State
- What it does: Tests that the
'CLEAR' action resets the state back to its initial values (initialState).
- Why: To verify that pressing the clear button resets the entire calculator back to its default state.
Summary
- Each test case ensures that the calculatorReducer handles individual actions correctly.
- The tests cover all possible scenarios, including handling digits, appending digits, performing operations, evaluating expressions, handling edge cases like division by zero, and resetting the state.
- This ensures that the calculator works as expected in all cases and prevents regressions when changes are made to the code.