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