Jest: Testing Todo List Operations
This challenge focuses on building robust unit tests for a hypothetical todo list application using Jest and TypeScript. You will create tests to ensure that functions responsible for adding, removing, and marking todos as complete behave as expected under various scenarios. Writing comprehensive tests is crucial for maintaining the quality and reliability of any software project.
Problem Description
You are tasked with writing Jest unit tests for a set of TypeScript functions that manage a todo list. The core functionalities you need to test are:
addTodo(todos: Todo[], newTodoText: string): Todo[]: This function should add a new todo item to an existing array of todos. Each todo item should have a unique ID, the text provided, and acompletedstatus initialized tofalse.removeTodo(todos: Todo[], todoId: number): Todo[]: This function should remove a specific todo item from the array based on its ID.toggleTodoComplete(todos: Todo[], todoId: number): Todo[]: This function should toggle thecompletedstatus of a specific todo item. If the todo is not found, the array should remain unchanged.
You will need to create a test.todo.ts file (or similar) to house your Jest tests.
Key Requirements:
- Test Suite Structure: Organize your tests logically using
describeblocks for each function. - Test Cases: Implement a variety of test cases for each function, covering:
- Happy Path: Standard, expected usage.
- Edge Cases: Empty lists, non-existent IDs, invalid inputs (if applicable).
- Immutability: Ensure that the original todo array is not mutated by these functions. They should return new arrays.
- TypeScript: Utilize TypeScript for your test file and type definitions.
- Jest Assertions: Use appropriate Jest matchers (
toEqual,toContainEqual,not.toContainEqual,toBe, etc.).
Expected Behavior:
addTodo: Returns a new array with the added todo. The new todo has a unique ID, the correct text, andcompleted: false.removeTodo: Returns a new array with the specified todo removed. If the ID doesn't exist, the original array (or a copy of it) is returned.toggleTodoComplete: Returns a new array with thecompletedstatus of the specified todo toggled. If the ID doesn't exist, the original array (or a copy of it) is returned.
Important Edge Cases:
- Adding a todo to an empty list.
- Removing a todo that does not exist in the list.
- Toggling the completion status of a todo that does not exist.
- Ensuring that adding, removing, or toggling does not modify the original array passed to the function.
Examples
For the purpose of these examples, let's assume a Todo interface is defined as:
interface Todo {
id: number;
text: string;
completed: boolean;
}
Example 1: addTodo
Input (initial todos): []
Input (newTodoText): "Learn Jest"
Expected Output (new todos): [
{ id: 1, text: "Learn Jest", completed: false }
]
Explanation: Adding a todo to an empty list creates the first todo with ID 1.
Example 2: addTodo (with existing todos)
Input (initial todos): [
{ id: 1, text: "Buy groceries", completed: false }
]
Input (newTodoText): "Walk the dog"
Expected Output (new todos): [
{ id: 1, text: "Buy groceries", completed: false },
{ id: 2, text: "Walk the dog", completed: false }
]
Explanation: A new todo is added with the next available ID (2).
Example 3: removeTodo
Input (todos): [
{ id: 1, text: "Buy groceries", completed: false },
{ id: 2, text: "Walk the dog", completed: false }
]
Input (todoId): 1
Expected Output (new todos): [
{ id: 2, text: "Walk the dog", completed: false }
]
Explanation: The todo with ID 1 is removed.
Example 4: removeTodo (non-existent ID)
Input (todos): [
{ id: 1, text: "Buy groceries", completed: false }
]
Input (todoId): 3
Expected Output (new todos): [
{ id: 1, text: "Buy groceries", completed: false }
]
Explanation: The todo with ID 3 does not exist, so the list remains unchanged.
Example 5: toggleTodoComplete
Input (todos): [
{ id: 1, text: "Buy groceries", completed: false },
{ id: 2, text: "Walk the dog", completed: false }
]
Input (todoId): 1
Expected Output (new todos): [
{ id: 1, text: "Buy groceries", completed: true },
{ id: 2, text: "Walk the dog", completed: false }
]
Explanation: The `completed` status of the todo with ID 1 is toggled from false to true.
Example 6: toggleTodoComplete (already completed)
Input (todos): [
{ id: 1, text: "Buy groceries", completed: true }
]
Input (todoId): 1
Expected Output (new todos): [
{ id: 1, text: "Buy groceries", completed: false }
]
Explanation: The `completed` status of the todo with ID 1 is toggled from true to false.
Constraints
- The
idfor new todos should be a positive integer, incrementing from the highest existing ID. If the list is empty, the first ID should be 1. - The
textfor a todo item must be a non-empty string. (You can assume valid input for the challenge, but it's good practice to consider). - The
completedstatus is a boolean. - The functions must return new arrays and not mutate the original arrays.
Notes
- You will need to implement the
Todointerface and the functions (addTodo,removeTodo,toggleTodoComplete) themselves in a separate file (e.g.,todoUtils.ts) before you can write tests for them. - Pay close attention to testing immutability. You can use Jest's
not.toBe()to check if the returned array is the exact same reference as the input array. - Consider how you will generate unique IDs. A simple counter or finding the max ID in the existing array will suffice for this challenge.
- Think about the order of tests within
describeblocks. Grouping related tests can improve readability. - Your test file should be named
test.todo.tsortodo.test.tsto be picked up by Jest.