Add a unit testing library (jest) to your project. Write unit tests for
your code. Organize your API call to log in as a thunk if you
haven’t done it before and cover it with the following test
scenarios:
a. Mock request to the server (in unit tests, we shouldn’t
depend on a server, so we simulate its response via
jest.mock()).
b. Write a first unit test that covers the scenario when auth
is a success, then success action is dispatched,
c. Write a second unit test that covers the scenario when
auth is failed, then error/fail action is dispatched. actions.js
export const FETCH_CARDS_SUCCESS = 'FETCH_CARDS_SUCCESS';

export const fetchCardsSuccess = (cards) => ({
type: FETCH_CARDS_SUCCESS,
payload: cards
});

export const fetchCards = (searchValue = '') => {
return async (dispatch) => {
try {
const queryParam = searchValue ? ?search=${encodeURIComponent(searchValue)} : '';
const response = await fetch(http://localhost:5000/api/cards${queryParam});
const data = await response.json();
dispatch(fetchCardsSuccess(data));
} catch (error) {
console.error('Error fetching cards:', error);
}
};
}; reducer.js import {
FETCH_CARDS_SUCCESS,
} from './actions';

const initialState = {
cards: []
};

export const cardsReducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_CARDS_SUCCESS:
return {
...state,
cards: action.payload
};


    default:
        return state;
}
}; store.js import { createStore, combineReducers, applyMiddleware } from 'redux';
import {thunk} from 'redux-thunk';
import { cardsReducer } from './reducer';
import { authReducer } from './authReducer';

const rootReducer = combineReducers({
cards: cardsReducer,
auth: authReducer
});

export const store = createStore(rootReducer, applyMiddleware(thunk)); authReducer.js const initialState = {
    isAuthenticated: false
};

export const authReducer = (state = initialState, action) => {
    switch (action.type) {
        case 'LOGIN':
            return { ...state, isAuthenticated: true };
        case 'LOGOUT':
            return { ...state, isAuthenticated: false };
        default:
            return state;
    }
}; CardSection.jsx import React, { useEffect, useContext, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { fetchCards } from '../../../Redux/actions';
import { SearchInput } from '../SearchInput/SearchInput';
import { Card } from '../Card/Card';
import { ThemeContext } from '../../../Selector/ThemeContext';
import classNames from 'classnames';
import style from './CardSection.module.css';

export function CardSection() {
const dispatch = useDispatch();
const { cards } = useSelector((state) => state.cards);
const { theme } = useContext(ThemeContext);


const [searchValue, setSearchValue] = useState('');
const [debouncedSearchValue, setDebouncedSearchValue] = useState(searchValue);

useEffect(() => {
    const handler = setTimeout(() => {
        setDebouncedSearchValue(searchValue);
    }, 300);

    return () => {
        clearTimeout(handler);
    };
}, [searchValue]);

useEffect(() => {
    dispatch(fetchCards(debouncedSearchValue));
}, [dispatch, debouncedSearchValue]);

const handleSearchChange = (value) => {
    setSearchValue(value);
};

return (
    <div>
        <SearchInput value={searchValue} onSearch={handleSearchChange} />
        <div
            className={classNames(style.cardSection, {
                [style.darkTheme]: theme === 'dark'
            })}
        >
            {cards.length > 0 ? (
                cards.map((cardObj) => (
                    <Card key={cardObj.id} cardObj={cardObj} />
                ))
            ) : (
                <p>No results found</p>
            )}
        </div>
    </div>
);
} Card.jsx import React from 'react';
import PropTypes from 'prop-types';
import style from './Card.module.css';

export const Card = ({ cardObj }) => {
return (


{cardObj.card.image.alt}

{cardObj.card.text.heading}

{cardObj.card.text.description}





);
};
Card.propTypes = {
cardObj: PropTypes.shape({
card: PropTypes.shape({
cardClass: PropTypes.string.isRequired,
image: PropTypes.shape({
imageClass: PropTypes.string.isRequired,
src: PropTypes.string.isRequired,
alt: PropTypes.string.isRequired
}).isRequired,
text: PropTypes.shape({
heading: PropTypes.string.isRequired,
description: PropTypes.string.isRequired
}).isRequired
}).isRequired
}).isRequired
}; index.js: require('dotenv').config();
const express = require('express');
const cors = require('cors');
const cards = require('./cards');
const app = express();
const PORT = 5000;

app.use(cors());
app.use(express.json());
app.use('/img', express.static('img'));

const validUsername = process.env.USERNAME;
const validPassword = process.env.PASSWORD;

app.post('/login', (req, res) => {
const { username, password } = req.body;
if (username === validUsername && password === validPassword) {
res.status(200).json({ message: 'Login successful' });
} else {
res.status(401).json({ message: 'Invalid username or password' });
}
});

app.get('/api/cards', (req, res) => {
const { search } = req.query;


if (search) {
    const searchLower = search.toLowerCase();
    const filteredCards = cards.filter(cardObj => {
        const title = cardObj.card.text.heading.toLowerCase();
        const description = cardObj.card.text.description.toLowerCase();
        return title.includes(searchLower) || description.includes(searchLower);
    });

    return res.status(200).json(filteredCards);
}

res.status(200).json(cards);
});

app.listen(PORT, () => {
console.log(Server is running on port ${PORT});
}); package.json (server) {
  "name": "server",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "test": "jest",
    "dev": "nodemon index.js",
    "start": "node index.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": "",
  "dependencies": {
    "cors": "^2.8.5",
    "dotenv": "^16.4.5",
    "express": "^4.21.0",
    "path": "^0.12.7",
    "redux-thunk": "^3.1.0",
    "server": "file: "
  },
  "devDependencies": {
    "@testing-library/jest-dom": "^6.5.0",
    "@testing-library/react": "^16.0.1",
    "jest": "^29.7.0",
    "nodemon": "^3.1.7",
    "redux-mock-store": "^1.5.4"
  }
}
package.json (client): {
    "name": "react-spring",
    "version": "0.1.0",
    "private": true,
    "scripts": {
        "start": "react-scripts start",
        "build": "react-scripts build",
        "test": "react-scripts test",
        "eject": "react-scripts eject"
    },
    "browserslist": {
        "production": [
            ">0.2%",
            "not dead",
            "not op_mini all"
        ],
        "development": [
            "last 1 chrome version",
            "last 1 firefox version",
            "last 1 safari version"
        ]
    },
    "devDependencies": {
        "@babel/preset-react": "^7.24.7",
        "@eslint/js": "^9.11.1",
        "@testing-library/jest-dom": "^5.17.0",
        "@testing-library/react": "^13.4.0",
        "@testing-library/user-event": "^13.5.0",
        "classnames": "^2.5.1",
        "eslint": "^8.57.1",
        "eslint-plugin-react": "^7.37.0",
        "globals": "^15.9.0",
        "jest": "^27.5.1",
        "prettier": "3.3.3",
        "prop-types": "^15.8.1",
        "react": "^18.3.1",
        "react-dom": "^18.3.1",
        "react-redux": "^9.1.2",
        "react-router-dom": "^6.26.2",
        "react-scripts": "^5.0.1",
        "redux": "^5.0.1",
        "redux-mock-store": "^1.5.4",
        "web-vitals": "^2.1.4",
        "webpack": "^5.95.0"
    },
    "dependencies": {
        "axios": "^1.7.7",
        "redux-thunk": "^3.1.0"
    }
}