📜  基于 ReactJS 创建购物车应用和测试用例执行

📅  最后修改于: 2022-05-13 01:56:14.773000             🧑  作者: Mango

基于 ReactJS 创建购物车应用和测试用例执行

让我们看一个使用 JSX 作为前端的购物车应用程序。它是 React 广泛使用的一种类似 XML/HTML 的语法,它扩展了 ECMAScript,因此类似 XML/HTML 的文本也可以与 JavaScript/React 代码一起应用。

创建 React App 并安装模块:

第 1 步:使用创建反应应用程序

npx create-react-app 
Eg: npx create-react-app shoppingcart

第 2 步:移动到文件夹

cd shoppingcart

第 3 步:安装所需的依赖项

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

或者,我们可以指定 package.json 中的所有内容,而不是一一进行,如下所示,从命令提示符中我们可以给出:

npm install

它将负责安装依赖项中提到的所有包。可以从 package.json 验证使用的包:

包.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"
      }
}

项目文件夹结构:项目应如下所示:

示例:让我们开始项目:

App.js
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 (
        
                     
    ); }    export default App;


App.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
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;


App.jsx
import React from 'react';
import AddItems from './AddItems';
import List from './List';
  
export default class App extends React.Component {
    render() {
        return (
            
                

Available Products

                // List.jsx is enclosed                                               
        );     } }


List.jsx
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 (
                
            );
        });
        return (
            
                  // All the items present in                  // ItemJSON.js is displayed here                 
    {ListItemsList}
            
        );     } }


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 (
            
                
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         
Product NamePriceQuantityAction
                
            
        );     } }


ListItems.jsx
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
            
  •                                                                                                                                                                                                                                                  
                                                                     {product.productName}                                                               {product.productPrice}                                                              {product.productQuantity}                                                               Delete                             
                
  •         );     } }


    App.test.js
    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(
            '');
        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();
            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 deleteing, the item should get deleted', function () {
            const app = TestUtils.renderIntoDocument();
            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();
            expect(app.find('h1').text())
                .to.equal('Available Products');
        });
    });
      
    describe('Enzyme Mount', function () {
        it('Delete An Item', function () {
            let app = mount();
            let itemLength = app.find('li').length;
            app.find('button.delete').at(0).simulate('click');
            expect(app.find('li').length).to.equal(itemLength - 1);
        });
    });


    App.css:用于美化项目

    应用程序.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:由于本项目不涉及任何数据库,所以从“ItemJSON.js”中挑选项目。它位于 src >> app >> Items >> ItemJSON.js

    ItemJSON.js

    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;
    

    启动应用程序:编写以下命令来启动应用程序。项目从3000端口开始

    npm start

    输出:

    接下来,可以从 App.jsx 中找到所需的代码

    应用程序.jsx

    import React from 'react';
    import AddItems from './AddItems';
    import List from './List';
      
    export default class App extends React.Component {
        render() {
            return (
                
                    

    Available Products

                    // List.jsx is enclosed                                               
            );     } }

    List.jsx:我们可以选择添加项目以及删除项目

    列表.jsx

    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 (
                    
                );
            });
            return (
                
                      // All the items present in                  // ItemJSON.js is displayed here                 
      {ListItemsList}
                
            );     } }

    输出:

    输入产品详细信息并单击“添加”按钮后,会出现以下功能:

    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 (
                
                    
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             
    Product NamePriceQuantityAction
                    
                
            );     } }

    删除产品:让我们尝试从上面的列表中删除 Redmi。删除所需的代码在 ListItems.jsx 中

    ListItems.jsx

    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
                
  •                                                                                                                                                                                                                                                  
                                                                     {product.productName}                                                               {product.productPrice}                                                              {product.productQuantity}                                                               Delete                             
                
  •         );     } }

    输出:

    我们可以测试项目的功能如下:

    应用程序.test.js

    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(
            '');
        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();
            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 deleteing, the item should get deleted', function () {
            const app = TestUtils.renderIntoDocument();
            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();
            expect(app.find('h1').text())
                .to.equal('Available Products');
        });
    });
      
    describe('Enzyme Mount', function () {
        it('Delete An Item', function () {
            let app = mount();
            let itemLength = app.find('li').length;
            app.find('button.delete').at(0).simulate('click');
            expect(app.find('li').length).to.equal(itemLength - 1);
        });
    });
    

    可以通过以下方式测试测试脚本:

    npm test

    输出: