Hone logo
Hone
Problems

Jest Snapshot Testing: Simulating State Reductions

Snapshot testing in Jest is a powerful tool for verifying UI components and data structures. This challenge focuses on simulating the process of state reduction, a core concept in many state management patterns, and testing it effectively using Jest snapshots. You will create a function that takes an initial state and a series of "actions" that modify the state, and then use Jest to snapshot the resulting state after each action.

Problem Description

Your task is to implement a reducer function that takes an initialState and an array of actions. Each action is an object with a type property and potentially a payload. The reducer function should iterate through the actions and apply transformations to the state based on the action type. You will then use Jest to create snapshot tests that capture the state after each action is applied, effectively demonstrating the "shrinking" or evolution of the state over time.

Key Requirements:

  • The reducer function should accept initialState and an array of actions.
  • It should return an array where each element represents the state after applying a cumulative set of actions.
  • You need to write Jest tests to assert that the output of the reducer function matches the expected snapshots for various scenarios.
  • The "shrinking" concept refers to how the state can be observed to change step-by-step, revealing the outcome of each transformation.

Expected Behavior: The reducer function will return an array of states. The first element of this array will be the initialState. Subsequent elements will be the state after applying the first action, then the first two actions, and so on.

Edge Cases:

  • Empty array of actions.
  • Actions with and without payloads.
  • Different data types within the state.

Examples

Example 1: Simple Counter

Input State:

{
  "count": 0
}

Actions:

[
  { "type": "INCREMENT" },
  { "type": "INCREMENT" },
  { "type": "DECREMENT" }
]

Expected Output (as an array of states):

[
  { "count": 0 },
  { "count": 1 },
  { "count": 2 },
  { "count": 1 }
]

Explanation:

  1. Initial state: { "count": 0 }
  2. After INCREMENT: { "count": 1 }
  3. After second INCREMENT: { "count": 2 }
  4. After DECREMENT: { "count": 1 }

Example 2: User Profile Update

Input State:

{
  "user": {
    "name": "Alice",
    "age": 30,
    "address": {
      "street": "123 Main St",
      "city": "Anytown"
    }
  }
}

Actions:

[
  { "type": "UPDATE_USER_NAME", "payload": "Alice Wonderland" },
  { "type": "UPDATE_USER_CITY", "payload": "Wonderland City" }
]

Expected Output (as an array of states):

[
  {
    "user": {
      "name": "Alice",
      "age": 30,
      "address": {
        "street": "123 Main St",
        "city": "Anytown"
      }
    }
  },
  {
    "user": {
      "name": "Alice Wonderland",
      "age": 30,
      "address": {
        "street": "123 Main St",
        "city": "Anytown"
      }
    }
  },
  {
    "user": {
      "name": "Alice Wonderland",
      "age": 30,
      "address": {
        "street": "123 Main St",
        "city": "Wonderland City"
      }
    }
  }
]

Explanation:

  1. Initial state: Original user profile.
  2. After UPDATE_USER_NAME: User's name is changed.
  3. After UPDATE_USER_CITY: User's city is changed.

Example 3: Empty Actions Array

Input State:

{
  "items": []
}

Actions:

[]

Expected Output (as an array of states):

[
  {
    "items": []
  }
]

Explanation: With no actions, the state remains the initial state.

Constraints

  • The state can be any valid JSON serializable JavaScript object or primitive.
  • Actions will always have a type property.
  • Payloads, if present, can be of any JSON serializable type.
  • The reducer function must be pure: it should not mutate the original state and should always return the same output for the same input.

Notes

  • Consider how to handle unknown action types. A common pattern is to return the current state unchanged.
  • For complex state structures, ensure your reducer correctly updates nested properties without unintended side effects.
  • When writing your Jest tests, use toMatchInlineSnapshot() or toMatchSnapshot() to capture the evolving state. This will allow you to easily verify the state at each step.
  • Think about how to structure your reducer logic to handle different action types efficiently. A switch statement is a common and effective approach.
Loading editor...
typescript