I'm trying to do a snapshot of a component wrapped by Ionic React router and perform deep rendering. But IonicRouterOutlet doesn't seem to deep render in my case. Any suggestions how to fix this issue and perform a deep rendering successfully?
Here's my sample code:
import React from 'react'
import { render } from '#testing-library/react'
import { IonRouterOutlet } from '#ionic/react'
import { IonReactRouter } from '#ionic/react-router'
jest.mock('#capacitor/core', () => ({
Plugins: {
StatusBar: {
setStyle: jest.fn()
},
Storage: {
set: jest.fn(),
get: () => ""
}
},
StatusBarStyle: {
Light: "LIGHT"
},
}));
describe('component', () => {
let component
beforeEach(async () => {
component = render(
<IonReactRouter>
<IonRouterOutlet>
<div />
</IonRouterOutlet>
</IonReactRouter>
)
})
describe('snapshot', () => {
it('should match snapshot', () => {
const { asFragment } = component
expect(asFragment()).toMatchSnapshot()
})
})
})
Here's my snapshot output:
exports[`Login component snapshot should match snapshot 1`] = `
<DocumentFragment>
<ion-router-outlet />
</DocumentFragment>
`;
As you can see, the <div /> is not generated in snapshot output. So how to fix this problem?
I have found this documentation which i think would be helpful https://github.com/crossroads/app.goodchat.hk/wiki/Testing-Routing
Basically, as you have found using IonReactRouter & IonRouterOutlet causes the JSDOM to output <ion-router-outlet /> and not the full tree.
The workaround is to use React Router's Router and MemoryRouter directly, depending on what you want to access e.g history.location.pathname. IonReactRouter uses React Router under the hood so the behaviour and JSDOM output should match.
Related
This one occurred just after a next-auth update. Suddenly it throws this error:
Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
Check the render method of `MyApp`.
So I checked the MyApp file, but there doesn't seem to be anything wrong about it, nothing that I have seen at least.
_app.tsx (_app file in next.js)
import React from "react";
import { start, done } from "nprogress";
import "nprogress/nprogress.css";
import router from "next/router";
import "./styles/patch.css";
import { Provider } from "next-auth/client";
import { ApolloProvider } from "#apollo/client";
import { useApollo } from "../apollo/apolloClient";
router.events.on("routeChangeStart", () => start());
router.events.on("routeChangeComplete", () => done());
router.events.on("routeChangeError", () => done());
const MyApp = ({ Component, pageProps }) => {
const apolloClient = useApollo(pageProps.initialApolloState);
React.useEffect(() => {
// Remove the server-side injected CSS.
const jssStyles = document.querySelector("#jss-server-side");
if (jssStyles) {
jssStyles.parentElement.removeChild(jssStyles);
}
}, []);
return (
<ApolloProvider client={apolloClient}>
<Provider session={pageProps.session}>
<Component {...pageProps} />
</Provider>
</ApolloProvider>
);
};
export default MyApp;
Did I miss something that could've caused this error?
EDIT:
Apparently, it was fine when I used webpack 4. It threw this error upon using webpack 5. But it would be good if there was a solution for webpack 5.
EDIT 2:
When I updated next to version 10.2, it worked perfectly fine. I must've been hasty on things perhaps so I guess there wasn't any problem in the first place. Other than that, I appreciate the answers that had been put in here.
do not use query like this :
React.useEffect(() => {
// this is wrong !!!
const jssStyles = document.querySelector("#jss-server-side");
if (jssStyles) {
jssStyles.parentElement.removeChild(jssStyles);
}
}, [])
try using useRef hook instead ex:
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` points to the mounted text input element
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
This is my component and I am trying to mock method fetchChapterData which would return me the mocked data and would set the state of the component with the returned value but it is not rendering the UI with the mocked data and also re-rendering the component multiple times.
import React, { Component } from 'react'
import LoadingImg from '../../assets/images/loader.gif'
import {fetchChapterData} from '../helpers/common'
class MyComponent extends Component {
constructor(props){
super(props)
this.state={
isDataLoaded:false
}
}
render() {
return (
<div className="abc">
{this.state.isDataLoaded?
<div className="abc">
<div className="abc" dangerouslySetInnerHTML={{__html: this.state.data}}></div>
</div>
:
<div className="loading" key="loading_key">
<img src={LoadingImg} alt ="Loading"/>
</div>
}
</div>
)
}
componentDidMount= async()=> {
let result = await fetchChapterData('IntroductionReportChapter')
/*fetchChapterData is being mocked with data {isDataLoaded:true,data:"<p>hello all</p>"}*/
this.setState({...result});
}
}
export default MyComponent;
This is my Test file
so here I am mocking the method fetchChapterData and asking it to return the fake data
import React from 'react'
import MyComponent from './myComponent'
import { render,screen} from "#testing-library/react"
import {fetchChapterData} from '../helpers/common'
jest.mock('../helpers/common', () => ({
fetchChapterData:jest.fn(),
}));
let container = document.createElement("div");
document.body.appendChild(container);
const fakeData={
"isDataLoaded": true,
"data":"<p>hello</p>"
};
describe("Introduction component testing", () => {
it('Introduction section rendered or not',async()=>{
await fetchChapterData.mockResolvedValueOnce({...fakeData})
render(<MyComponent />)
await expect(fetchChapterData).toHaveBeenCalledTimes(1);
})
});
So I got the solution for the same. We need to use the asynchronous version of act to apply resolved promises while rendering the component in the test file, and it would start executing your mocked data.
describe("My component testing", () => {
it("renders user data", async () => {
const fakeData={isDataLoaded:true,intoductionText:" <p>manisha</p>"}
fetchChapterData.mockResolvedValueOnce({...fakeData})
await act(async () => {
render(<MyComponent />, container);
});
screen.getByText('manisha')
});
})
Hey – try mocking your data helper like so
jest.mock('../helpers/common', () => ({
fetchChapterData:jest.fn(() => Promise.resolve({
//Your data here
}))
}))
Also in your state object at the top of the class, you have false1 instead of false do you want to correct that?
You may also want to take another approach to updating the data loaded state property, maybe instead create a helper function in the class that sets that state like so
const handleData = (data) => {
setState({
isDataLoaded: true,
data
})
}
Finally, call this helper in componentDidMount with the result from your fetchChapterData function
I'm trying to test a simple component, that looks like this
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import styles from './styles.css'
export class App extends PureComponent {
handleClick = (event) => {
const { loadGreetings } = this.props
loadGreetings()
}
render () {
const { hi } = this.props
return (
<section>
<h1 className={styles.earlyDawn}>{hi}</h1>
<button onClick={this.handleClick}>Handshake</button>
</section>
)
}
}
App.propTypes = {
hi: PropTypes.string,
loadGreetings: PropTypes.func
}
export default App
Here is my test file
import React from 'react'
import {App} from './index'
import {shallow} from 'Enzyme'
describe('Testing App container...', () => {
let props
beforeEach(() => {
props = {
loadGreetings: jest.fn().mockName('loadGreetings'),
hi: 'Hi from test'
}
})
test('should handle click on the button', () => {
const wrapper = shallow(<App {...props}/>)
const buttonHi = wrapper.find('button')
const instance = wrapper.instance()
expect(buttonHi.length).toBe(1)
jest.spyOn(instance, 'handleClick')
buttonHi.simulate('click')
expect(props.loadGreetings).toHaveBeenCalled()
expect(instance.handleClick).toHaveBeenCalled()
})
})
So the problem is in the second toHaveBeenCalled assertion that fails all the time. However, first toHaveBeenCalled seems to be working, which bothers me, because props.loadGreetings is called inside instance.handleClick. Could you please help me to find what may be the problem?
Dependencies: "react": "16.9.0", "react-dom": "16.9.0", "babel-jest": "^24.8.0", "enzyme": "^3.10.0", "jest": "^24.8.0",
A simpler approach would be to pass in some initial props and test your component based upon those initial props -- you'll also manipulate those props to add more assertions.
Here's a working example (click on the Tests tab to run the tests):
components/App/index.js
import React, { PureComponent } from "react";
import PropTypes from "prop-types";
class App extends PureComponent {
handleClick = () => {
this.props.loadGreetings();
};
render() {
const { message } = this.props;
return (
<section className="app">
<h1>{message}</h1>
<button onClick={this.handleClick}>Handshake</button>
</section>
);
}
}
App.propTypes = {
message: PropTypes.string,
loadGreetings: PropTypes.func
};
export default App;
components/App/App.test.js
import React from "react";
import { shallow } from "enzyme";
import App from "./index";
// define the passed in function here for easier testing below
const loadGreetings = jest.fn();
// initial props to pass into 'App'
const initProps = {
message: "hi",
loadGreetings
};
describe("Testing App container...", () => {
let wrapper;
beforeEach(() => {
// this resets the wrapper with initial props defined above
wrapper = shallow(<App {...initProps} />);
});
afterEach(() => {
// this clears any calls to the mocked function
// and thereby resets it
loadGreetings.mockClear();
});
it("renders without errors", () => {
expect(wrapper.find(".app").exists()).toBeTruthy();
expect(loadGreetings).toHaveBeenCalledTimes(0);
});
it("initially renders a 'hi' message and then a 'goodbye' message", () => {
expect(wrapper.find("h1").text()).toEqual("hi");
// manipulating the initial 'message' prop
wrapper.setProps({ message: "goodbye" });
expect(wrapper.find("h1").text()).toEqual("goodbye");
});
it("should call 'loadGreetings' when the 'Handshake' button is clicked", () => {
// since we passed in 'loadGreetings' as a jest function
// we expect it to be called when the 'Handshake' button is
// clicked
wrapper.find("button").simulate("click");
expect(loadGreetings).toHaveBeenCalledTimes(1);
});
});
Not recommended (see below), but you can spy on the class method -- you'll have to work with instances, forceUpdate the instance, then invoke the handleClick method either manually, wrapper.instance().handleClick(), or indirectly via some element's event handler: wrapper.find("button").simulate("click") or wrapper.find("button").props().onClick().
The reason I don't recommend this testing strategy is that you're testing a React implementation (testing whether or not the element's event handler invokes your callback function). Instead, you can avoid that by asserting against whether or not a prop function is called and/or a prop/state change happens. This is a more standard and direct approach to testing the component -- as that is what we care about; we care that the props and/or state changes based upon some user action. In other words, by making assertions against the 'loadGreetings' prop being called we're already testing that the onClick event handler works.
Working example:
App.test.js (same testing as above, with the exception of this test):
it("should call 'loadGreetings' when the 'Handshake' button is clicked", () => {
const spy = jest.spyOn(wrapper.instance(), "handleClick"); // spying on the method class
wrapper.instance().forceUpdate(); // required to ensure the spy is placed on the method
wrapper.find("button").simulate("click");
expect(spy).toHaveBeenCalledTimes(1);
const mockedFn = jest.fn(); // setting the method as a mocked fn()
wrapper.instance().handleClick = mockedFn;
wrapper.instance().forceUpdate(); // required to update the method instance with a mocked fn
wrapper.find("button").simulate("click");
expect(mockedFn).toHaveBeenCalledTimes(1);
});
I have the following parent component which has to render a list of dynamic children components:
<template>
<div>
<div v-for="(componentName, index) in supportedComponents" :key="index">
<component v-bind:is="componentName"></component>
</div>
</div>
</template>
<script>
const Component1 = () => import("/components/Component1.vue");
const Component2 = () => import("/components/Component2.vue");
export default {
name: "parentComponent",
components: {
Component1,
Component2
},
props: {
supportedComponents: {
type: Array,
required: true
}
}
};
</script>
The supportedComponents property is a list of component names which I want to render in the parent conponent.
In order to use the children components in the parent I have to import them and register them.
But the only way to do this is to hard code the import paths of the components:
const Component1 = () => import("/components/Component1.vue");
const Component2 = () => import("/components/Component2.vue");
And then register them like this:
components: {
Component1,
Component2
}
I want to keep my parentComponent as generic as possible. This means I have to find a way to avoid hard coded components paths on import statements and registering. I want to inject into the parentComponent what children components it should import and render.
Is this possible in Vue? If yes, then how?
You can load the components inside the created lifecycle and register them according to your array property:
<template>
<div>
<div v-for="(componentName, index) in supportedComponents" :key="index">
<component :is="componentName"></component>
</div>
</div>
</template>
<script>
export default {
name: "parentComponent",
components: {},
props: {
supportedComponents: {
type: Array,
required: true
}
},
created () {
for(let c=0; c<this.supportedComponents.length; c++) {
let componentName = this.supportedComponents[c];
this.$options.components[componentName] = () => import('./' + componentName + '.vue');
}
}
};
</script>
Works pretty well
Here's a working code, just make sure you have some string inside your dynamic import otherwise you'll get "module not found"
<component :is="current" />
export default { data () {
return {
componentToDisplay: null
}
},
computed: {
current () {
if (this.componentToDisplay) {
return () => import('#/components/notices/' + this.componentToDisplay)
}
return () => import('#/components/notices/LoadingNotice.vue')
}
},
mounted () {
this.componentToDisplay = 'Notice' + this.$route.query.id + '.vue'
}
}
Resolving dynamic webpack import() at runtime
You can dynamically set the path of your import() function to load different components depending on component state.
<template>
<component :is="myComponent" />
</template>
<script>
export default {
props: {
component: String,
},
data() {
return {
myComponent: '',
};
},
computed: {
loader() {
return () => import(`../components/${this.component}`);
},
},
created() {
this.loader().then(res => {
// components can be defined as a function that returns a promise;
this.myComponent = () => this.loader();
},
},
}
</script>
Note: JavaScript is compiled by your browser right before it runs. This has nothing to do with how webpack imports are resolved.
I think we need some plugin that can have code and every time it should load automatically. This solution is working for me.
import { App, defineAsyncComponent } from 'vue'
const componentList = ['Button', 'Card']
export const registerComponents = async (app: App): void => {
// import.meta.globEager('../components/Base/*.vue')
componentList.forEach(async (component) => {
const asyncComponent = defineAsyncComponent(
() => import(`../components/Base/${component}.vue`)
)
app.component(component, asyncComponent)
})
}
you can also try glob that also work pretty well but I have checked it for this solution but check this out worth reading
Dynamic import
[Update]
I tried same with import.meta.globEage and it works only issue its little bit lazy loaded you may feel it loading slow but isn't noticeable much.
import { App, defineAsyncComponent } from 'vue'
export const registerComponents = async (app: App): void => {
Object.keys(import.meta.globEager('../components/Base/*.vue')).forEach(
async (component) => {
const asyncComponent = defineAsyncComponent(
() => import(/* #vite-ignore */ component)
)
app.component(
(component && component.split('/').pop()?.split('.')[0]) || '',asyncComponent
)
})
}
I just started doing some unit testing for React JS - using Jest / enzyme.
I would like to know which test (format) is more useful and correct to use for future tests. These are 2 different tests that I'm working on it at the moment.
Unit test 1 : I was writing most of my tests based on this set up
import React from 'react';
import Enzyme from 'enzyme';
import { shallow} from 'enzyme';
import EditWorkflow from './EditWorkflow';
import Adapter from 'enzyme-adapter-react-15';
//render must import shallow = method to show object structure
//Unit Test V
Enzyme.configure({ adapter: new Adapter() })
describe ('<Workflow />', () => {
it( 'renders 1 <Workflow /> Component', () => {
const Component = shallow(<EditWorkflow name= "workflow"/>);
expect(Component).toHaveLength(1);
});
describe('It renders props correctly', () => {
const Component = shallow(<EditWorkflow name= "workflow"/>);
expect(Component.instance().props.name).toBe('workflow');
})
});
**Unit test 2
Different way to write an unit test****
import React from 'react';
import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-15';
import { Login } from './App';
import renderer from 'react-test-renderer';
Enzyme.configure({adapter: new Adapter()});
let wrapper;
let defaultProps = {
getSessionContext: jest.fn(),
checkSession: jest.fn(),
}
let mockCheckSession;
describe('Login', () => {
beforeEach(() => {
mockCheckSession = jest.fn(()=>{return true})
defaultProps = {
getSessionContext: jest.fn(),
checkSession: mockCheckSession,
}
})
it('should render "authorizing..." if theres no session ', () => {
mockCheckSession = jest.fn(()=>{return false})
defaultProps.checkSession = mockCheckSession;
const tree = renderer
.create(<Login {...defaultProps} />)
.toJSON();
expect(tree).toMatchSnapshot();
})
it('should render null if there is a session ', () => {
mockCheckSession = jest.fn(()=>{return true})
defaultProps.checkSession = mockCheckSession;
const tree = renderer
.create(<Login {...defaultProps}/>)
.toJSON();
expect(tree).toMatchSnapshot();
})
})
Since you're not providing the full code, it's hard to help you with your current component. Here are some general tips:
One of the goals of writing (good) unit tests for your React components, is to make sure your component behaves and renders as you want it to do. What I usually do, in this part there is no right or wrong, is start reading the render function from top to bottom and take note of each logical part.
Example #1:
Simply test if the className is set on the right element.
class Screen extends Component {
render() {
return (
<div className={this.props.className}>
<h1>My screen</h1>
</div>
);
}
}
it('should set the className on the first div', () => {
const wrapper = shallow(<Screen className="screen" />);
expect(wrapper.hasClass('screen')).toBeTruthy();
});
Example #2:
If the component renders a part conditionally, you want to test both cases.
class Screen extends Component {
render() {
return (
<div className={this.props.className}>
<h1>My screen</h1>
{this.props.subheading ? <h4>{this.props.subheading}</h4> : null}
</div>
);
}
}
it('should not render the subheading when not given by prop', () => {
const wrapper = shallow(<Screen />);
expect(wrapper.find('h4').exists()).toBeFalsy();
});
it('should render the subheading when given by prop', () => {
const wrapper = shallow(<Screen subheading="My custom subheading!" />);
expect(wrapper.find('h4').exists()).toBeTruthy();
expect(wrapper.find('h4').text()).toEqual('My custom subheading!');
});
I can give some more examples, but I think you'll get the idea.