Open In App

Create a shopping cart application and test case execution based on ReactJS

Last Updated : 26 Jun, 2023
Improve
Improve
Like Article
Like
Save
Share
Report

ReactJS has gained significant popularity among developers for its efficient and reusable components, making it an ideal choice for building interactive and dynamic web applications. In this article, we will explore how to create a shopping cart application using ReactJS and discuss the importance of test case execution for ensuring the quality of our application.

Let us see a shopping cart application using JSX as the frontend. It is an XML/HTML-like syntax widely got used by React that extends ECMAScript and hence XML/HTML-like text also can be applied with JavaScript/React code. 

Creating React App and Installing Modules:

Step 1: Create the react application by using

npx create-react-app <your foldername>
Eg: npx create-react-app shoppingcart

Step 2: Move to the folder

cd shoppingcart

Step 3: Install the required dependencies

npm install @babel/core
npm i babel-runtime#Here i stands for install
npm i @testing-library/jest-dom
npm i @testing-library/react
npm i @testing-library/user-event
npm i autoprefixer
npm i enzyme
npm i enzyme-adapter-react-16
npm i react
npm i react-dom
npm i react-scripts

OR, Instead of doing one by one, we can specify everything inside the package.json as given below and from the command prompt we can give as:

npm install

It will take care of installing all the packages that got mentioned inside the dependencies. Packages that got used can be verified from package.json:

package.json

{
      "name": "shoppingcart",
      "version": "1.0.0",
      "description": "shoppingcart",
      "main": "app/main.jsx",
      "scripts": {
        "lint": "eslint 'app/**/*.@(js|jsx)'",
        "test": "react-scripts test",
        "start": "react-scripts start",
        "build": "react-scripts build",
        "eject": "react-scripts eject"
      },
      "dependencies": {
        "babel-runtime": "~6.2.0",
        "@testing-library/jest-dom": "^4.2.4",
        "@testing-library/react": "^9.5.0",
        "@testing-library/user-event": "^7.2.1",
        "autoprefixer": "^9.8.0",
        "enzyme": "^3.11.0",
        "enzyme-adapter-react-16": "^1.15.5",
        "react": "^16.14.0",
        "react-dom": "^16.14.0",
        "react-scripts": "3.4.3"
      },
      "eslintConfig": {
        "extends": "react-app"
      },
      "browserslist": {
        "production": [
          ">0.2%",
          "not dead",
          "not op_mini all"
        ],
        "development": [
          "last 1 chrome version",
          "last 1 firefox version",
          "last 1 safari version"
        ]
      },
      "keywords": [
        "react",
        "test",
        "enzyme"
      ],
      "pre-commit": [
        "lint"
      ],
      "devDependencies": {
        "babel-eslint": "~4.1.6",
        "chai": "^3.4.1",
        "html-webpack-plugin": "^5.3.2",
        "react-addons-test-utils": "^15.4.1",
        "webpack": "^5.55.1",
        "webpack-cli": "^4.8.0",
        "webpack-dev-server": "^4.3.0",
        "jsdom": "^7.2.2"
      }
}

Project folder structure: The project should look like this:

 

Example: Let’s Start the project:

App.js

Javascript




import React from 'react';
import './App.css';
 
// That means it is referring the jsx file
// present under src/app/components folder
import App1 from './app/components/App';
 
function App() {
    return (
        <div className="App">
            <App1 />
        </div>
    );
}
 
export default App;


App.css: For beautification of the project

CSS




.App {
    text-align: center;
}
 
.App-logo {
    height: 40vmin;
    pointer-events: none;
}
 
@media (prefers-reduced-motion: no-preference) {
    .App-logo {
        animation: App-logo-spin infinite 20s linear;
    }
}
 
.App-header {
    background-color: #282c34;
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    font-size: calc(10px + 2vmin);
    color: white;
}
 
.App-link {
    color: #006400;
}
 
@keyframes App-logo-spin {
    from {
        transform: rotate(0deg);
    }
    to {
        transform: rotate(360deg);
    }
}
 
.main-wrapper {
    display: flex;
    justify-content: center;
}
 
table {
    margin: 1rem;
}
 
table th td,
table tr td {
    border: 1px solid black;
    border-collapse: collapse;
}
 
form {
    margin: 1rem;
}
 
input {
    line-height: 1.4rem;
    margin-left: 1rem;
}
 
h5 {
    display: block;
}
 
form button {
    line-height: 1.4rem;
    background-color: #ffffff;
    cursor: pointer;
}
 
.cities-wrapper {
    border: 1px solid black;
    margin: 1rem;
    padding: 1rem;
    align-items: flex-end;
    height: fit-content;
}
 
ul {
    float: center;
    margin-top: 0;
}
 
.center {
    margin-left: auto;
    margin-right: auto;
}
 
.shelf-wrapper {
    text-align: left;
    display: flex;
    justify-content: center;
}
 
.shelf-wrapper h4 {
    text-align: center;
}
 
.shelf-wrapper .shelf {
    width: 19%;
    border: 1px solid black;
    display: inline-block;
    min-height: 15rem;
}
 
.shelf span {
    /* width: 60%; */
}
 
.shelf button {
    float: right;
    /* width: 40%; */
}
 
.book-wrapper span {
    width: 100%;
}
 
.shelf table tr td {
    border: none;
}


ItemJSON.js: As this project does not involve any database, let the items be picked from “ItemJSON.js”. It is under src >> app >> Items >> ItemJSON.js

Javascript




import { EventEmitter } from 'events';
import assign from 'object-assign';
 
// Initially specifying the constant items just as an sample
const ProductStore = assign({}, EventEmitter.prototype, {
    items: {
        products: [
            {
                productId: 0, productName: 'Samsung',
                productPrice: 10000, productQuantity: 2
            },
            {
                productId: 1, productName: 'Motorola',
                productPrice: 7000, productQuantity: 3
            },
            {
                productId: 2, productName: 'Redmi',
                productPrice: 8000, productQuantity: 4
            },
        ]
    },
 
    nextproductId: 3,
 
    // To get all the items and display in the screen
    getAll: function getAll() {
        return this.items;
    },
 
    emitChange: function emitChange() {
        this.emit('change');
    },
 
    // When an item is added
    addChangeListener: function addChangeListener(callback) {
        this.on('change', callback);
    },
 
    // When an item is removed
    removeChangeListener: function removeChangeListener(callback) {
        this.removeListener('change', callback);
    },
 
    addNewProducts: function addNewProducts(product) {
        const products = this.items.products;
        if (!products ||
            typeof this.items.products.length !== 'number') {
            this.items.products = [];
        }
        product.productId = this.nextproductId++;
        product.done = false;
        this.items.products.push(product);
    },
 
    deleteProducts: function deleteProducts(productId) {
        this.items.products = this.items.products.filter(
            (product) => product.productId !== productId);
    }
});
 
export default ProductStore;


Start The Application: Write the below command to start the application. The project starts in 3000 port

npm start

Output:

 

In continuation, the Required code can be found from App.jsx

Javascript




import React from 'react';
import AddItems from './AddItems';
import List from './List';
 
export default class App extends React.Component {
    render() {
        return (
            <div>
                <h1>Available Products</h1>
                // List.jsx is enclosed
                <List />
                <AddItems.jsx is enclosed
                <AddItems />
            </div>
        );
    }
}


List.jsx: We have the option to add the items as well as deletion the items

Javascript




import React from 'react';
import ItemJSON from '../Items/ItemJSON';
import ListItems from './ListItems';
 
export default class ProductList extends React.Component {
    constructor(props) {
        super(props);
        this.state = ItemJSON.getAll();
    }
 
    componentDidMount() {
        ItemJSON.addChangeListener(this._onChange.bind(this));
    }
 
    componentWillUnmount() {
        ItemJSON.removeChangeListener(this._onChange.bind(this));
    }
 
    _onChange() {
        this.setState(ItemJSON.getAll());
    }
 
    render() {
        const ListItemsList = this.state.products.map(
            product => {
                return (
                    <ListItems key={product.productId}
                        product={product} />
                );
            });
        return (
            <center>
                  // All the items present in
                // ItemJSON.js is displayed here
                <ul>{ListItemsList}</ul>
            </center>
        );
    }
}


Output:

 

On entering product details and clicking of “add” button, the below functionality occurs:

Javascript




import React from 'react';
import ItemJSON from '../Items/ItemJSON';
 
export default class AddItems extends React.Component {
    //will pick the added product and added
    addItems() {
        const newProductName = this.refs.product.value;
        const newPrice = this.refs.price.value;
        const newQuantity = this.refs.quantity.value;
        if (newProductName) {
            ItemJSON.addNewProducts({
                productName: newProductName,
                productPrice: newPrice,
                productQuantity: newQuantity
            });
            ItemJSON.emitChange();
            this.refs.product.value = '';
            this.refs.price.value = '';
            this.refs.quantity.value = '';
        }
    }
 
    render() {
        return (
            <center>
                <div className="add-todo">
                    <table >
                        <thead>
                            <tr>
                                <th>Product Name</th>
                                <th>Price</th>
                                <th>Quantity</th>
                                <th>Action</th>
                            </tr>
                        </thead>
                        <tbody>
                            <tr>
                                <td><input type="text"
                                    placeholder="Add Product Name"
                                    ref="product" /></td>
                                <td><input type="text"
                                    placeholder="Add Price"
                                    ref="price" /></td>
                                <td><input type="text"
                                    placeholder="Add Quantity"
                                    ref="quantity" /></td>
                                <td><button className="add-button"
                                    onClick={this.addItems.bind(this)}>
                                    Add
                                </button></td>
                            </tr>
                        </tbody>
                    </table>
                </div>
            </center>
        );
    }
}


deletion of a product: Let us try to delete Redmi from the above list. Required code for doing deletion is in ListItems.jsx

Javascript




import React from 'react';
import ItemJSON from '../Items/ItemJSON';
export default class ListItems extends React.Component {
    // This code is meant for deletion
    deleteProduct(e) {
        e.preventDefault();
        ItemJSON.deleteProducts(this.props.product.productId);
        ItemJSON.emitChange();
    }
 
    render() {
        const product = this.props.product;
 
        return (
            //    displaying available products and it
            // is having delete action
            <li>
                <table border="3">
                    <tbody>
                        <tr>
                            <td width="100px">
                                <span className={`todo-text`} >
                                {product.productName} </span>
                            </td>
                            <td width="100px"><span
                                className={`todo-text`}>
                                {product.productPrice}</span>
                            </td>
                            <td width="100px"><span
                                className={`todo-text`}>
                                {product.productQuantity}</span>
                            </td>
                            <td width="100px"><button
                                className="delete"
                                onClick={this.deleteProduct.bind(this)}>
                                Delete </button>
                            </td>
                        </tr>
                    </tbody>
                </table>
            </li>
        );
    }
}


Output:

 

We can test the functionality of the project as below:

App.test.js

Javascript




import React from 'react';
import Adapter from 'enzyme-adapter-react-16';
import { expect } from 'chai';
import { shallow, mount, configure } from 'enzyme';
import TestUtils from 'react-dom/test-utils';
import App from './app/components/App';
import jsdom from 'jsdom';
import { findDOMNode } from 'react-dom';
 
configure({ adapter: new Adapter() });
 
beforeAll(() => {
    global.fetch = jest.fn();
    // window.fetch = jest.fn(); if running browser environment
});
 
let wrapper;
beforeEach(() => {
    wrapper = shallow(< App />, { disableLifecycleMethods: true });
});
 
afterEach(() => {
    wrapper.unmount();
});
 
if (typeof document === 'undefined') {
    global.document = jsdom.jsdom(
        '<!doctype html><html><body></body></html>');
    global.window = document.defaultView;
    global.navigator = global.window.navigator;
}
 
 
describe('DOM Rendering', function () {
    it('Add functionality to add new products
        by clicking add', function () {
        const app = TestUtils.renderIntoDocument(<App />);
        const appDOM = findDOMNode(app);
        let productItemsLength =
            appDOM.querySelectorAll('.todo-text').length;
 
 
        let addInput = appDOM.querySelector('input');
        addInput.value = 'Add item';
        let addButton = appDOM.querySelector('.add-todo button');
        TestUtils.Simulate.click(addButton);
        console.log(appDOM.querySelectorAll('.todo-text').length);
        expect(appDOM.querySelectorAll('.todo-text')
            .length).to.be.equal(productItemsLength + 3); 
            // As after adding we will get additional value 3.
    });
});
 
describe('DOM Rendering', function () {
    it('On deleting, the item should get deleted', function () {
        const app = TestUtils.renderIntoDocument(<App />);
        let productItems = TestUtils
            .scryRenderedDOMComponentsWithTag(app, 'li');
        let productLength = productItems.length;
        let deleteButton = productItems[0]
            .querySelector('button');
        TestUtils.Simulate.click(deleteButton);
        let productItemsAfterClick = TestUtils
            .scryRenderedDOMComponentsWithTag(app, 'li');
        expect(productItemsAfterClick.length)
            .to.equal(productLength - 1);
    });
});
 
describe('Enzyme Shallow', function () {
    it('App\'s title should be Available Products', function () {
        let app = shallow(<App />);
        expect(app.find('h1').text())
            .to.equal('Available Products');
    });
});
 
describe('Enzyme Mount', function () {
    it('Delete An Item', function () {
        let app = mount(<App />);
        let itemLength = app.find('li').length;
        app.find('button.delete').at(0).simulate('click');
        expect(app.find('li').length).to.equal(itemLength - 1);
    });
});


The test script can be tested in the below way:

npm test

Output:

 



Like Article
Suggest improvement
Share your thoughts in the comments

Similar Reads