gpt4 book ai didi

reactjs - 使用 Component.WrappedComponent 作为 withRouter 的替代品测试组件在运行测试用例时显示警告

转载 作者:行者123 更新时间:2023-12-03 14:30:45 25 4
gpt4 key购买 nike

我正在尝试测试我的组件“CBreadcrumb”,它使用“withRouter”HOC。运行测试用例时,所有测试用例都通过,但显示警告。

我已经多次尝试检查我的代码。但我无法找到警告的原因。我也尝试过研究这些答案,但没有帮助我。

Can't perform a React state update on an unmounted component

React warning about setState in unmounted component

CBreadcrumb 组件

import React, {PureComponent} from 'react';
import Proptypes from 'prop-types';
import Breadcrumb from "react-bootstrap/Breadcrumb";
import {withRouter} from 'react-router-dom';
import {TryCatchHandler} from "../../../common-utils";

class CBreadcrumb extends PureComponent {
state = {
routes: [],
currentLocation: ""
};

setCurrentLocation = path => {
this.setState({currentLocation: path});
};

setRoutes = routes => {
this.setState({routes: routes});
};

/**
* GETS ARRAY OF PATH(URL) UPTO CURRENT PAGE
* @returns {string[]}
*/
getPathsToInclude = () => {
let currentLocation = !this.state.currentLocation ?
this.props.location.pathname : this.state.currentLocation;

// GET AVAILABLE PATHS IN CURRENT PAGE URL
let pathsToInclude = ((currentLocation).split('/'));

// REMOVE THE FIRST EMPTY ELEMENT FROM ARRAY
pathsToInclude.shift();

// IF ROUTE IS NOT 'home' ADD 'home' AS FIRST PATH
pathsToInclude[0] !== "home" && pathsToInclude.unshift("home");

//INCLUDE '/' IN EACH PATHNAME
for (let i = 0; i < pathsToInclude.length; i++) {
i === 0 || i === 1 ? pathsToInclude[i] = "/".concat(pathsToInclude[i])
: pathsToInclude[i] = pathsToInclude[i - 1] + "/".concat(pathsToInclude[i])

}

return pathsToInclude;
};

/**
*
* @param pathsToInclude
* @returns {Array}
*/
addRoutesByPathsToInclude = pathsToInclude => {
let routes = [];
pathsToInclude.forEach(value => {
routes = routes.concat(
this.props.breadcrumbData
.filter(breadCrumb =>
breadCrumb.path === value
));
});
return routes;
};

filterAndSetRoutesUptoCurrentPage = () => {
this.setRoutes(this.addRoutesByPathsToInclude(this.getPathsToInclude()));
};

setCurrentLocationAndFilterRoutes = async path => {
!path ? await this.setCurrentLocation(this.props.location.pathname)
: await this.setCurrentLocation(path);
this.filterAndSetRoutesUptoCurrentPage();
};

componentDidMount() {
TryCatchHandler.genericTryCatch(this.setCurrentLocationAndFilterRoutes());
}

componentDidUpdate(prevProps, prevState, snapshot) {
/**
* SINCE IT IS LIFECYCLE METHOD , IT RUNS WITH EVERY TEST.
* IN TEST WE DON'T HAVE ACCESS TO 'withRouter' SO IT WILL HAVE TO
* BE SET MANUALLY IN FEW REQUIRED TEST CASES ONLY.
* SO FOR OTHER TESTS WHERE THE PROPS ARE NOT SET,
* 'location' and 'history' OBJECT WILL NOT BE AVAILABLE RESULTING IN WHOLE TEST SUITE FAILURE.
*/
if (prevProps.history) {
const newPath = prevProps.history.location.pathname;
const oldPath = prevProps.location.pathname;
if (newPath !== oldPath) {
TryCatchHandler.genericTryCatch(this.setCurrentLocationAndFilterRoutes(newPath));
} else {
return false
}
} else {
return false
}
}

createBreadcrumbLink = (breadcrumb, index) =>
index !== this.state.routes.length - 1 ?
{'href': "#".concat(breadcrumb.path)} : {'active': true};

getBreadcrumbItemProps = (breadcrumb, index) => {
const {itemAs, title, target, itemBsPrefix, itemChildren} = this.props;
return (
{
'key': "breadcrumb" + breadcrumb.id,
'id': "breadcrumbItem" + breadcrumb.id,
'as': itemAs,
'title': title,
'target': target,
'bsPrefix': itemBsPrefix,
'children': itemChildren,
...this.createBreadcrumbLink(breadcrumb, index)
}
);
};

getBreadcrumbItems = (breadcrumb, index) =>
<Breadcrumb.Item
{...this.getBreadcrumbItemProps(breadcrumb, index)}
>
{breadcrumb.name}
</Breadcrumb.Item>;

render() {
const {as, label, listProps, bsPrefix, children} = this.props;
return (
<Breadcrumb
as={as}
label={label}
listProps={listProps}
bsPrefix={bsPrefix}
children={children}
>
{this.state.routes.map((breadcrumb, index) => (
this.getBreadcrumbItems(breadcrumb, index)
))}
</Breadcrumb>);
}
}

React.propTypes = {
breadcrumbData: Proptypes.array.isRequired,
as: Proptypes.elementType,
label: Proptypes.string,
bsPrefix: Proptypes.string,
listProps: Proptypes.object,
children: Proptypes.array,
title: Proptypes.node,
target: Proptypes.string,
href: Proptypes.string,
active: Proptypes.boolean,
itemAs: Proptypes.elementType,
itemBsPrefix: Proptypes.string,
itemChildren: Proptypes.array
};

/**
* 'withRouter' IS A HIGHER ORDER COMPONENT PROVIDED BY 'react-router-dom'.
* 'withRouter' WILL PASS UPDATED 'match', 'location', and 'history' PROPS
* TO THE WRAPPED COMPONENT WHENEVER IT RENDERS.
* IN BREADCRUMB COMPONENT IT IS USED TO DETECT THE ROUTE CHANGE ALONG WITH 'componentDidUpdate' LIFECYCLE METHOD.
*/
export default withRouter(CBreadcrumb);

CBreadcrumb.test.js

import React from "react";
import CBreadcrumb from '../CBreadcrumb';

expect.addSnapshotSerializer(enzymeSerializer);

describe('CBreadcrumb component tests', () => {
let wrapper, instance;

const dataForBreadCrumb = [
{
id: '1',
name: 'Home',
path: '/home'
},
{
id: '2',
name: 'General Setup',
path: '/generalSetup'
}];

let setWrapperProps = (cWrapper, propsObject) => {
cWrapper.setProps(propsObject);
};

describe('Breadcrumb Component Tests', () => {

beforeEach(() => {
wrapper = shallow(<CBreadcrumb.WrappedComponent/>);
setWrapperProps(wrapper, {breadcrumbData: []});
});

test('if CBreadcrumb component is defined', () => {
expect(wrapper).toBeDefined();
});

test('if renders Breadcrumb component', () => {
expect(wrapper.find('Breadcrumb').length).toEqual(1);
});

test('if Breadcrumb component contains all required props', () => {
let propRequired = [
'as',
'label',
'listProps',
'bsPrefix',
'children'
];
let propsAvailableForBreadcrumb = Object.keys(wrapper.find('Breadcrumb').props());
propRequired.forEach((propAvail, i) => (
expect(propAvail).toContain(propsAvailableForBreadcrumb[i])
));
});
});

describe('CBreadcrumb state tests', () => {

beforeEach(() => {
wrapper = mount(<CBreadcrumb.WrappedComponent/>);
instance = wrapper.instance();
setWrapperProps(wrapper, {breadcrumbData: dataForBreadCrumb});
});

afterEach(() => {
wrapper.unmount();
});

test('if routes state is defined', () => {
expect(wrapper.state('routes')).toBeDefined();
});

test('if currentLocation state is defined', () => {
expect(wrapper.state('currentLocation')).toBeDefined();
});

test('if state`s property currentLocation is set after componentDidMount', () => {
setWrapperProps(wrapper, {
location: {
pathname: '/generalSetup'
},
history: {
location: {
pathname: ''
}
}
});
jest.spyOn(instance, 'setCurrentLocation');
instance.componentDidMount();
expect(instance.setCurrentLocation).toHaveBeenCalled();
});

test('if routes are filtered upto current location and ' +
'state`s property routes is set after componentDidMount ', async () => {
// jest.spyOn(instance, 'setRoutes');
setWrapperProps(wrapper, {location: {pathname: '/generalSetup'}});
await instance.componentDidMount();
// wrapper.update();
expect(wrapper.state('routes').length).not.toBe(0);
});

test('if componentDidUpdate lifecycle will be called and ' +
'routes will be filtered when url changes', async () => {
await instance.componentDidUpdate({
location: {
pathname: '/generalSetup'
},
history: {
location: {
pathname: '/home'
}
}
});
expect(wrapper.state('routes').length).toBe(1);
});

});

describe('BreadcrumbItem Component Tests', () => {

beforeEach(async () => {
wrapper = shallow(<CBreadcrumb.WrappedComponent/>);
instance = wrapper.instance();
setWrapperProps(wrapper, {
breadcrumbData: dataForBreadCrumb,
location: {
pathname: '/generalSetup'
},
});
await instance.componentDidMount();
wrapper.update();
});

test('if renders BreadcrumbItem component', () => {
expect(wrapper.find('#breadcrumbItem1').length).toBe(1);
});

test('if BreadcrumbItem component shows name', () => {
expect(wrapper.find('#breadcrumbItem1').text()).not.toBe('');
});

test('if BreadcrumbItem components except last has href with value', () => {
expect(wrapper.find('#breadcrumbItem1').prop('href')).not.toBe('');
});

test('if last BreadcrumbItem component defined', () => {
expect(wrapper.find('#breadcrumbItem2').length).toBe(1);
});

test('if last BreadcrumbItem component has no href', () => {
expect(wrapper.find('#breadcrumbItem2').prop('href')).not.toBeDefined();
});

test('if last BreadcrumbItem component has prop active', () => {
expect(wrapper.find('#breadcrumbItem2').prop('active')).toBeTruthy();
});

test('if BreadcrumbItem component excluding last contains all required props', () => {
let propRequired = [
'test-id',
'as',
'title',
'target',
'bsPrefix',
'children',
'href'
];

let propsAvailableForBreadcrumbItem = Object.keys(wrapper.find('#breadcrumbItem1').props());
propRequired.forEach((propAvail, i) => (
expect(propAvail).toContain(propsAvailableForBreadcrumbItem[i])
));

});

test('if BreadcrumbItem component including last contains all required props', () => {
let propRequired = [
'test-id',
'as',
'title',
'target',
'bsPrefix',
'children',
'active'
];

let propsAvailableForBreadcrumbItem = Object.keys(wrapper.find('#breadcrumbItem2').props());
propRequired.forEach((propAvail, i) => (
expect(propAvail).toContain(propsAvailableForBreadcrumbItem[i])
));
});
});

describe('Snapshot Testing', () => {
wrapper = shallow(<CBreadcrumb.WrappedComponent breadcrumbData={dataForBreadCrumb}/>);

test('if renders CBreadcrumb component correctly', () => {
expect(wrapper).toMatchSnapshot();
});
});

});


App.js

import React from 'react';
import './App.css';
import {HashRouter, Switch, Route} from 'react-router-dom';

import CBreadcrumb from "./component/CBreadcrumb/CBreadcrumb";
import AdminSetupPage from "./component/AdminSetupPage/AdminSetupPage";
import {AddPage} from "./component/AdminSetupPage/AddPage";

function App() {
const dataForBreadCrumb = [
{
id: '1',
name: 'Home',
path: '/home'
},
{
id: '2',
name: 'General Setup',
path: '/generalSetup'
},
{
id: '3',
name: 'Admin Setup',
path: '/generalSetup/adminSetup'
},
{
id: '4',
name: 'Add Admin',
path: '/generalSetup/adminSetup/add'
},
{
id: '5',
name: 'Manage Admin',
path: '/generalSetup/adminSetup/manage'
},
];
return (
<HashRouter>
<CBreadcrumb breadcrumbData={dataForBreadCrumb}/>
<Switch>
<Route path="/generalSetup/adminSetup" component={AdminSetupPage}/>
<Route path='/generalSetup/adminSetup/add' component={AddPage}/>
</Switch>
</HashRouter>
);
}

export default App;


显示警告如下所示

console.error node_modules/react-dom/cjs/react-dom.development.js:506
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
in CBreadcrumb (created by Context.Consumer)
in withRouter(CBreadcrumb) (created by App)
in Router (created by HashRouter)
in HashRouter (created by App)
in header (created by App)
in div (created by App)
in App

最佳答案

也许你可以引用this article 。在文章的最后,robinwieruh 建议通过 setState() 调用和 Component Unmount 问题解决此问题。

关于reactjs - 使用 Component.WrappedComponent 作为 withRouter 的替代品测试组件在运行测试用例时显示警告,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57388191/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com