Implementing Jest and RTL for beginners (3/3)
Tags: webdev, jest, beginners, react
Published: 2022-07-26 10:17:00

## Table Of Contents
[1. Introduction](#chapter-1)
[2. Examples and Methods](#chapter-2)
[3. Conclusion](#chapter-3)


Alright, another new post! I need to get back to writing since I missed one month of posting. I had to attend to some urgent family matters so I had to miss that period of time.

<a name="chapter-1">Introduction</a>
For this post, we will be completing this Jest testing series with the following content: 
1. How to test for conditional conditions (such as the game win or draw scenario) that renders specific content/elements.
2. How do we test the winning combination of the game 

<a name="chapter-2">Examples and Methods</a>
To test conditional renders, what we will focus on rendering specific messages in this snippet of code taken from the Board component code in the previous post (part 2/3):
```Javascript
      {winner ? (
        <h2 data-testid="winnerMessage">
          {winner === "Draw"
            ? "Round Draw. Restart Round."
            : `Player ${winner} is the Winner!`}
        </h2>
      ) : (
        ""
      )}
```
**testing for conditional renders and attributes**
As shown above, this is a ternary operator nested in another ternary operator. There is a `winner` state which holds a string that has 4 outcomes: `X` or `O` or `Draw` or `""`. If it is empty, the game will continue. If the Winner is `X` or `Y`, a winner message as shown above will be rendered. If it is a `draw`, it will render the draw message.

To test if different rendered messages, we will use simulate different move sets. Refer below for the testing logic:

```Javascript
  test("Winner message for player X should appear when winner is decided and button disabled", () => {
    const { getByTestId } = render(<Board />);
    const moveArr = [0, 5, 1, 6, 2];
    for (let index of moveArr) {
      const button = getByTestId(`squareButton${index}`);
      fireEvent.click(button);
    }
    const playerTurnMsg = getByTestId("winnerMessage");
    expect(playerTurnMsg).toHaveTextContent("Player X is the Winner!");
    expect(getByTestId(`squareButton3`)).toHaveAttribute("disabled");
  });
```
The first line of code is the test description. We are looking to generate a winner message for `X` and when a winner is decided, all buttons from all squares shall be disabled until the reset button is clicked. The move set we mentioned above is as shown: `const moveArr = [0, 5, 1, 6, 2];` The number are the array indexes and we use a `for` loop and a `fireEvent.click` to simulate the test moves. In the backend, the game board should look something like this:

![Game move set for X to be winner](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xfh1m2dfcy1gjkq71yyb.jpg)

This move set will allow player X to win and we will use `getByTestId` to get the ID of the JSX element that displays the winner message and use `toHaveTextContent` matcher to confirm if the winner message is generated. 
Right after that test, we will use the `toHaveAttribute` matcher and get the ID of any unclicked buttons to test if the buttons are indeed `disabled` after a winner is selected.

**testing winning combinations**
To test winning and drawing combinations, a new test file was created called `Winner.test.ts`. The combinations are as shown: 
```Javascript
export const drawCombi = [
  ["X", "O", "X", "X", "O", "O", "O", "X", "X"],
  ["X", "O", "X", "O", "O", "X", "X", "X", "O"],
  ["X", "O", "X", "X", "O", "X", "O", "X", "O"],
  ["O", "X", "O", "X", "O", "X", "X", "O", "X"],
  ["X", "O", "O", "O", "X", "X", "X", "X", "O"],
  ["X", "X", "O", "O", "X", "X", "X", "O", "O"],
  ["X", "X", "O", "O", "O", "X", "X", "O", "X"],
  ["O", "X", "X", "X", "O", "O", "X", "O", "X"],
  ["X", "X", "O", "O", "O", "X", "X", "X", "O"],
  ["O", "X", "X", "X", "O", "O", "O", "X", "X"],
  ["X", "O", "X", "O", "X", "X", "O", "X", "O"],
  ["O", "X", "O", "O", "X", "X", "X", "O", "X"],
];

export const winCombi = [
  ["X", "X", "X", "", "", "", "", "", ""],
  ["", "", "", "X", "X", "X", "", "", ""],
  ["", "", "", "", "", "", "X", "X", "X"],
  ["X", "", "", "X", "", "", "X", "", ""],
  ["", "X", "", "", "X", "", "", "X", ""],
  ["", "", "X", "", "", "X", "", "", "X"],
  ["X", "", "", "", "X", "", "", "", "X"],
  ["", "", "X", "", "X", "", "X", "", ""],
];
```

To decide if there is a winner, a function called `decideIfThereIsWinner` was created as follows:
```Javascript
export const winIndexCombi = [
  [0, 1, 2],
  [3, 4, 5],
  [6, 7, 8],
  [0, 3, 6],
  [1, 4, 7],
  [2, 5, 8],
  [0, 4, 8],
  [2, 4, 6],
];

export function decideIfThereIsWinner(arr: String[], select: String) {
  const playerArr: Number[] = [];
  arr.map((a: String, c: Number) => (a === select ? playerArr.push(c) : ""));
  const filteredCombi = winIndexCombi.filter(
    (comb) => comb.filter((steps) => playerArr.includes(steps)).length >= 3,
  );
  const result = filteredCombi.flat(1).length >= 3;
  return result;
}
```

The function will take all the possible winning index combination and map the array through with a nested filter method. If the newly filter array `filteredCombi` has a length of 3 or more, it will return a `result` variable with a `true` boolean. With all the move pieces set, we will set up our test logic as shown below:

```Javascript
afterEach(cleanup);

describe(Board, () => {
  for (let combi of winCombi) {
    test("Winning Combination for both X and O", () => {
      const arr = combi;
      const result = decideIfThereIsWinner(arr, "X");
      expect(result).toBeTruthy();
    });
  }
  for (let combi of drawCombi) {
    test("Draw Combination check X", () => {
      const arr = combi;
      const result = decideIfThereIsWinner(arr, "X");
      expect(result).toBeFalsy();
    });
  }
  for (let combi of drawCombi) {
    test("Draw Combination check O", () => {
      const arr = combi;
      const result = decideIfThereIsWinner(arr, "O");
      expect(result).toBeFalsy();
    });
  }
});
```
To test the winning combi, since there are only 8 combinations to win, we will expect all 8 scenarios to be return a `true` boolean from `decideIfThereIsWinner` function regardless it is `X` or `O` player. We can use  `toBeTruthy()` to confirm it returns a `true` boolean for each case. However for the draw combination, since `X` always starts first, we need to check both `X` and `O` draw combinations and we can use the `toBeFalsy()` matcher for all cases to confirm the case returns a `false` boolean 
 
And that's it! That's all the test I can come up with. I hope this series provide a little insight on how to start testing your application. This is just the tip of the ice-berg. If you want to learn more, the official documents can be found in https://jestjs.io/. Thank you and until next time!