React JS Testing: How to set a property nested in an array to true for further Jest testing

28 views Asked by At

I have created the front end to a mock banking application and I am trying to write tests that cover my codes functionality. I have used createContext to store an array of objects that describe the bank's users' information. There are two default users, Jane and John. Their information can be seen in the context.js file. By default, both of the users are are not signed in, which is denoted by the property of currentUser being set to false. In the case that no user is signed in, the deposit page gives the user a message informing them that they need to sign in before attempting to make a deposit. Once they are signed in, then the deposit page will provide functionality for them to make a deposit. Currently I need to set the currentUser property for a specific user to true so that I can further test my code.

How can I access and set a specific user's currentUser property to true when there are multiple users with the same properties in the array?

I know that my code works, because I can run it on a live server and everything renders properly without error on chrome. However, for the sake of becoming a more experienced developer, I want to be able to write tests for my code that show full functionality.

My Code

Within the file below you can find the code for my components and also the default user information array. I have set that data in the UserContext variable.

context.js

import * as ReactRouterDOM from 'react-router-dom';
import { createContext } from 'react'

//Import necessary React functionality to make the app work
const Route             = ReactRouterDOM.Route;
const Link              = ReactRouterDOM.Link;
const HashRouter        = ReactRouterDOM.HashRouter;
//Create context
export const UserContext       = createContext({users: [{name:'jane', email:'[email protected]', password:'secret1', balance:100, currentUser: false}, {name:'john', email:'[email protected]', password:'secret2', balance:100, currentUser: false}]});


//This function defines a card component that can be used throughout the application
function Card(props) {
    function classes() {
        const bg = props.bgcolor ? ' bg-' + props.bgcolor : ' ';
        const txt = props.txtcolor ? ' text-' + props.txtcolor: ' text-white';
        return 'card mb-3' + bg + txt;
    }

    return (
        <div className={classes()} style={{maxWidth:"18rem", display: "inline-block", position: "fixed",
        top: "30%",
        left: "50%",
        "marginTop": "-50px",
        "marginLeft": "-100px"
      }}>
            <div className="card-header text-dark">{props.header}</div>
            <div className="card-body text-dark">
                {props.title && (<h5 className="card-title">{props.title}</h5>)}
                {props.text && (<p className="card-text">{props.text}</p>)}
                {props.body}
                {props.status && (<div className='text-danger' id='createStatus'><br/>{props.status}</div>)}
            </div>
        </div>
    )
}


  //This function defines a button component that can be used on the deposit page
  function DepositButton(props) {

    const { disabled, onChange, value, state, text } = props;
    return (
      <input disabled={disabled} type="submit" value="-&gt; Deposit &lt;-" className="btn btn-light fw-bolder" id="submit_btn" onChange={onChange}/>
    );
  }
  

export { Card, DepositButton };

Within the file below are three functions. The first function, LoadDeposit(), checks to see if a user is signed in. If a user is signed in, then it loads the Deposit() function which allows the user to make a deposit. If no user is singed in, then the toLoginFromDeposit() function is called, which informs the user that they are not signed in and need to do so before making a deposit.

deposit.js

import $ from 'jquery';
import React from 'react';


import { UserContext, Card, Input, LoginButton, DepositButton, WithdrawButton, AccountCreationButton } from '../context/context';

global.$ = $;
global.jQuery = $;

//This function checks if a user is signed
//If a user is signed in, the function returns a function to display their account balance
//and allow them to use an input field and submit button to deposit more funds into their account
//If a user is not signed in, then the function returns a function that informs the user
//that they are not signed in and gives a link to the login page
function LoadDeposit() {
    //Store UserContext inside of a variable
    const ctx                       = React.useContext(UserContext);
    
    //Check to see if the link to the login has been pressed in order to handle 
    //which navbar element is active
    $(document).ready(function() {
        $('.toLoginFromDeposit').click(function() {
            $('.btn-success.active').removeClass("active");
            $('#login').addClass("active");
            window.location.href = "#login"
        });
    });

    //Variable to keep track of whether a user is logged in or not
    let userCheck = false;
    //Use a for loop to check if a user is signed in.
    for (let i = 0; i < ctx.users.length; i++) {
        if (ctx.users[i].currentUser === true) {
            //Assign userCheck to true if a user is signed in
            userCheck = true;
            //Assign a variable to hold the logged in user's account balance
            let balance = ctx.users[i].balance;
            //Return Deposit() function to display balance and allow the user to make deposits
            //Pass in the balance variable to be displayed
            return Deposit(balance);  
        }
        
    }

    //If no user is signed in, return the toLoginFromDeposit() function to inform the user that
    //they are not signed in and provide a link to the login page
    if (userCheck === false) {
        return toLoginFromDeposit();
    }


}

//This function displays the current user's account balance and allows the user to make deposits
function Deposit(initBalance) {
    //Declare necessary variables and their corresponding variable setters and initialize them
    const [status, setStatus]       = React.useState('');
    const [amount, setAmount]       = React.useState('');
    const [balance, setBalance]     = React.useState(initBalance);
    //Store UserContext inside of a variable
    const ctx                       = React.useContext(UserContext);

    //Disables the deposit submit button if the input field is empty
    $(function() {
        if (!amount){
            $('#submit_btn').addClass('disabled');
        } else if (amount){
            $('#invs_btn').addClass('disabled');
            $('#submit_btn').removeClass('disabled');
        }
    });

    //Either outputs an error message if the user tries to click the submit button while the input
    //field is still empty, or calls the makeDeposit() function if the input field is not empty
    function handleSubmitButton() {
        let invs_btn = document.getElementById('invs_btn');
        if (!amount && !$(invs_btn).hasClass('disabled')){
            setStatus('Error: you must enter an amount of money to deposit');
            setTimeout(() => setStatus(''), 4000);
        } else if (amount){
                makeDeposit();
            }
    }

        
    //Checks to make sure the input is a number greater than 0
    //If the input is not a number greater than 0, gives an error message
    function validateDeposit(amount) {

        const numbers = /^[0-9]+$/;
        if (amount <= 0 || !amount.match(numbers)) { 
            setStatus('Error: You must enter a number greater than zero');
            setTimeout(() => setStatus(''), 3000);
            return false;
        }

        return true;
    }

    //Retrieves the user's current balance and adds the users input to it
    //Then updates the user's account balance and displays the new balance
    //Also informs the user that they successfully made a deposit and
    //disables the submit button once a deposit has been made
    function makeDeposit() {
        if (validateDeposit(amount)) {
            for (let i = 0; i < ctx.users.length; i++) {
                if (ctx.users[i].currentUser === true) {
                    ctx.users[i].balance = parseFloat(ctx.users[i].balance) + parseFloat(amount);
                    setAmount('');
                    setBalance(ctx.users[i].balance);
                    setStatus('Success: you successfully deposited $' + amount);
                    setTimeout(() => setStatus(''), 3000);
                    $('#submit_btn').addClass('disabled');
                    break;  
                }
            }
        }
    }

    //Returns a card component displaying the current user's balance and provides a input field and button for submitting a withdrawal
    return (
        <Card
            bgcolor="warning"
            header="Deposit"
            status={status}
            body={(
                <>
                <div data-testid="user_balance">Balance:&emsp; &emsp; &emsp; ${balance}</div><br/><br/>
                <label htmlFor="amount">Deposit Amount:</label><br/>
                <input autoComplete="off" type="input" className="form-control" id="amount" placeholder="Deposit amount" value={amount} onChange={e => setAmount(e.currentTarget.value)}/><br/>
                <span onClick={handleSubmitButton} className="invs_btn" id="invs_btn">
                    <DepositButton type="submit" className="btn btn-light"/>
                </span>
                </>
            )}
        />
    )


}



//This function is called when there is no user signed in
//It returns a card element that informs the user that they are not signed in
//and provides a link that routes the user to the login page
function toLoginFromDeposit() {

    return (
        <Card
            bgcolor="warning"
            header="Error: you are not logged into an account"
            body={(
                <>
                The link below will direct you to the login page!<br/>
                <a type="submit" className="btn btn-warning fw-bolder toLoginFromDeposit" id='toLoginFromDeposit' href="#/login/">
                    -&gt; Go to login page &lt;-
                </a>
                </>
            )}
        />
    )
}


export {LoadDeposit, Deposit, toLoginFromDeposit}

Below is the current code for my tests. In the TODO section I need to be able to set one of the user's currentUser property to true so that I can run the rest of the tests I have written so far. If I can't simulate that a user is logged in, then I can't run the rest of the tests as needed.

deposit.test.js

import React from 'react';
import { fireEvent, render, renderHook } from '@testing-library/react';
import { screen } from '@testing-library/dom'
import userEvent from "@testing-library/user-event";
import { LoadDeposit } from './deposit.js';
import { UserContext } from '../context/context';




//
//---------->  TODO: NEED A TEST THAT SETS ONE OF THE USER'S CURRENTUSER PROPERTY TO TRUE  <----------
//




test('Checking to see if all of the elements of the deposit page are present...', () => {
  render(<LoadDeposit />);

  screen.getByText('Deposit');
  screen.getByText('Balance: $100');
  screen.getByText('Deposit Amount:');
  screen.getByPlaceholderText('Deposit amount')
  screen.getByText('-> Deposit <-');
})

test('Checking to see if the deposit button has functionality using the fireEvent method when a valid input is used...', () => {
  render(<LoadDeposit />);
    screen.getByText('Deposit');
    screen.getByTestId('user_balance');
    screen.getByText('Balance: $100');

    const input = screen.getByPlaceholderText('Deposit amount');
    fireEvent.change(input, {target:{value: 251}});
    fireEvent.click(screen.getByText('-> Deposit <-'));

    screen.getByText('Balance: $351');
    screen.getByText('Success: you successfully deposited $251');

});

test('Checking to see if the deposit button has functionality using the fireEvent method when an invalid input is used...', () => {
  render(<LoadDeposit />);
    screen.getByText('Deposit');
    screen.getByTestId('user_balance');
    screen.getByText('Balance: $351');
    
    const input = screen.getByPlaceholderText('Deposit amount');
    fireEvent.change(input, {target:{value: 'test'}});
    fireEvent.click(screen.getByText('-> Deposit <-'));

  
    screen.getByText('Balance: $351');
    screen.getByText('Error: You must enter a number greater than zero');

});


test("Checking to see if the deposit button has functionality using the userEvent method when a valid input is used...", () => {
  render(<LoadDeposit />);

  const input = screen.getByPlaceholderText('Deposit amount');
  const button = screen.getByText('-> Deposit <-');

  userEvent.type(input, '9');
  userEvent.click(button);

  screen.getByText('Balance: $360');
  screen.getByText('Success: you successfully deposited $9');
});

I have tried writing code that implements setState, but it keeps telling me that it isn't reading the property correctly, which makes me think that I am not accessing the property correctly either. Do I use a for loop or something? I'm pretty confused on how to do this. I also messed around with shallow, but don't fully understand how to implement it.

0

There are 0 answers