Hone logo
Hone
Problems

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 a completed status initialized to false.
  • 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 the completed status 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:

  1. Test Suite Structure: Organize your tests logically using describe blocks for each function.
  2. 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.
  3. TypeScript: Utilize TypeScript for your test file and type definitions.
  4. 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, and completed: 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 the completed status 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 id for 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 text for 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 completed status is a boolean.
  • The functions must return new arrays and not mutate the original arrays.

Notes

  • You will need to implement the Todo interface 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 describe blocks. Grouping related tests can improve readability.
  • Your test file should be named test.todo.ts or todo.test.ts to be picked up by Jest.
Loading editor...
typescript