How do I access React component's local state using Cypress? - reactjs

I'm using react with redux and testing with cypress,
I was able to access the store using
cy.window().its('store').invoke('getState').then((state) => {}
But how do i access a component's local state rather than the application store?
I tried
cy.get('.simple-component').its('getState')
or
cy.get('.simple-component').invoke('getState')
but Cypress is returning "CypressError: Timed out retrying: cy.invoke() errored because the property: 'getState' does not exist on your subject"
And on the Cypress console (in chrome) it's yeilding:
Yielded:
<div class="simple-component" getstate="[object Object]"></div>
It seems that's caused by React removing the methods from the DOM so i need to access it in React rather than in the DOM?
import React, { Component } from 'react';
class simpleComponent extends Component {
constructor(props) {
super(props)
this.state = {
sample: "hello"
}
}
// getState() just for testing on cypress
getState() {
return this.state
}
render(){
return <div className="simple-component" getState={this.getState()}></div>
}
}
As an alternative can i export the local component state at the end of the simple-component using window.store?

>= version 7.0.0
As of Cypress 7.0, the new Component Test Runner is now bundled with Cypress
From https://www.cypress.io/blog/2021/04/06/cypress-component-testing-react:
We still need to install the react adapter to mount components:
yarn add -D cypress #cypress/react #cypress/webpack-dev-server
add a glob pattern matching your component tests to cypress.json:
{
"component": {
"testFiles": "**/*.test.{js,ts,jsx,tsx}",
"componentFolder": "src"
}
}
Tell Cypress to use #cypress/webpack-dev-server for component tests. in cypress/plugins/index.js:
const injectDevServer = require("#cypress/react/plugins/react-scripts")
module.exports = (on, config) => {
injectDevServer(on, config)
return config
}
This will configure the Cypress Webpack Dev Server to use the same Webpack configuration as Create React App uses.
If you are using a different template, like Next.js, we have some other adapters available. It's also possible to create your own adapter.
< version 7.0.0
There's a Cypress Plugin for that, called react-unit-test. It gives you the ability to mount React components directly (adds a cy.mount() command) and provides access to the component's internal state.
Here's an example from the repo's readme:
// load Cypress TypeScript definitions for IntelliSense
/// <reference types="cypress" />
// import the component you want to test
import { HelloState } from '../../src/hello-x.jsx'
import React from 'react'
describe('HelloState component', () => {
it('works', () => {
// mount the component under test
cy.mount(<HelloState />)
// start testing!
cy.contains('Hello Spider-man!')
// mounted component can be selected via its name, function, or JSX
// e.g. '#HelloState', HelloState, or <HelloState />
cy.get(HelloState)
.invoke('setState', { name: 'React' })
cy.get(HelloState)
.its('state')
.should('deep.equal', { name: 'React' })
// check if GUI has rerendered
cy.contains('Hello React!')
})
})

You can identify the element without mounting the react component. If you are testing your react app in isolation with the source code or writing functional UI test cases, you can consider a Cypress plugin called cypress-react-selector. It helps you identify web elements by component, props, and state even after the minification. You need to use React Dev Tool to identify the component names in that case.
Here is an example:
Suppose your react app:
const MyComponent = ({ someBooleanProp }) => (
<div>My Component {someBooleanProp ? 'show this' : ''} </div>
)
const App = () => (
<div id='root'>
<MyComponent />
<MyComponent name={bob} />
</div>
)
ReactDOM.render(<App />, document.getElementById('root'))
Then you can simply identify the react element like:
cy.getReact('MyComponent', { name: 'bob' } ).getCurrentState();
Find more sample test here
Hope it will help!

Related

How do I make Storybook run both React AND Svelte

I want to write stories for both React and Svelte components. I already have a few React components, and I'm attempting to install Svelte. My closest attempt can either run React OR Svelte depending on whether I comment out my React configuration. If I don't comment it out, I get this message when I look at my Svelte component in storybook:
Error: Objects are not valid as a React child (found: object with keys {Component}). If you meant to render a collection of children, use an array instead.
in unboundStoryFn
in ErrorBoundary
(further stack trace)
This refers to my story stories/test.svelte-stories.js:
import { storiesOf } from '#storybook/svelte';
import TestSvelteComponent from '../src/testComponentGroup/TestSvelteComponent.svelte';
storiesOf('TestSvelteComponent', module)
.add('Svelte Test', () => ({
Component: TestSvelteComponent
}));
My configuration is as follows:
.storybook/config.js:
import './config.react'; // If I comment out this line, I can make the svelte component work in storybook, but of course my react stories won't appear.
import './config.svelte';
.storybook/config.react.js:
import { configure } from '#storybook/react';
const req = require.context('../stories', true, /\.react-stories\.js$/);
function loadStories() {
req.keys().forEach(filename => req(filename));
}
configure(loadStories, module);
.storybook/config.svelte.js:
import { configure } from '#storybook/svelte';
const req = require.context('../stories', true, /\.svelte-stories\.js$/);
function loadStories() {
req.keys().forEach(filename => req(filename));
}
configure(loadStories, module);
.storybook/webpack.config.js:
module.exports = async ({ config, mode }) => {
let j;
// Find svelteloader from the webpack config
const svelteloader = config.module.rules.find((r, i) => {
if (r.loader && r.loader.includes('svelte-loader')) {
j = i;
return true;
}
});
// safely inject preprocess into the config
config.module.rules[j] = {
...svelteloader,
options: {
...svelteloader.options,
}
}
// return the overridden config
return config;
}
src/testComponentGroup/TestSvelteComponent.svelte:
<h1>
Hello
</h1>
It seems as though it's attempting to parse JSX via the Svelte test files, but if I import both React AND Svelte configurations I can still see the React components behaving properly.
See this discussion on github : https://github.com/storybookjs/storybook/issues/3889
It's not possible now and it's planned for the v7.0
The official position now is to create two sets of configuration (preview and manager), instanciate two separates storybook, and then use composition to assemble the two storybook into one.

Adding the properties to the window object and using it before the import of components

I have a Home component, so I am writing a test case for the component the problem is inside the Home Component I am requiring a config.js file and multiple components which inside they use config.js
The file is basically the configuration for the application.
So the values comes from window._config_ object, so to add the properties in my test case. I have tried the below approach.
import React from "react";
import { mount, shallow } from "enzyme";
import Home from "..";
describe("Home Page", () => {
beforeEach(() => {
window._config_ = {
URL: "http://www.sample.com",
};
});
it("should render Home Page", () => {
console.log("here", window._env_);
const wrapper = shallow(<Home />);
expect(wrapper).toMatchSnapshot();
});
});
So since the Home component is imported at the top, it will import the
related config files since inside the describe block only I am adding the
properties
In the above case I am getting the error as TypeError: Cannot read property 'URL' of undefined
If I comment Home component its working, so I have tried importing Home inside the 'it' block but for import should be done at the top level, so then I tried using common js require way but that's giving me the below error:
it("should render Home Page", () => {
console.log("here", window._env_);
const Home = require("..");
const wrapper = shallow(<Home />);
}
ReactShallowRenderer render(): Shallow rendering works only with custom components, but the provided element type was `object`.
I am using CRA for React app.
How should I solve this?
Update
Tried this also but how should I add it, when I added window inside global it says
Out of the box, Create React App only supports overriding these Jest options:
• collectCoverageFrom
• coverageReporters
• coverageThreshold
• coveragePathIgnorePatterns
• extraGlobals
• globalSetup
• globalTeardown
• moduleNameMapper
• resetMocks
• resetModules
• snapshotSerializers
• transform
• transformIgnorePatterns
• watchPathIgnorePatterns.
These options in your package.json Jest configuration are not currently supported by Create React App:
Finally found out, so you can add the global variables in setupTests.js
import { configure } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
global.config = {
URL: "http://www.example.com",
};
configure({ adapter: new Adapter() });
And in every test file you can access like this
it("should render Home Page", () => {
console.log("here", window.config.URL);
const wrapper = shallow(<Home />);
expect(wrapper).toMatchSnapshot();
});

How to run React JS build in React-Native Webview?

I created complete offline ReactJS web application and I want to run it from android application from Web View using React-Native.
I followed the following procedure to do so:
1. I created a compiled ReactJS web application got the build using the following command:
npm run build
Then I created react-native project and placed the build folder with following architecture
I updated App.js with the following content:
import React, {Component} from 'react';
import {Platform, StyleSheet, Text, View, WebView} from 'react-native';
import {roscon} from "./build/index.html";
type Props = {};
export default class App extends Component<Props> {
render() {
return (
<View style={{height: 300, width: 300,overflow:'hidden' }}>
<WebView
source={{uri: roscon}}
scalesPageToFit={true}
domStorageEnabled={true}
javaScriptEnabled={true}
startInLoadingState={true}
/>
</View>
);
}
}
After running this code I expected it to run my ReactJS Web application, instead I got white screen.
Can you please tell what can be the causing issues and how i can make my ReactJS Web App run on react-native?
Note: I was able to run generated build folder using npm command
serve -s build
But I still can't figure out how to port it to react-native project as WebView
After research and testing, I found a solution.
The main issue i found was the compiled build folder is rendered as static html. And it needed a server to serve pages.
So, I followed this link for getting build project to get it up and running
Then, integrating it with nodejs Android Project Samples to get my build folder running in android as a Webview.
Note: I also tried react-snapshot and react-snap but they didn't gave satisfactory results.
Try to require the html file correctly and pass it in to source prop in this way:
<WebView
source={require('./build/index.html')}
/>
Install
npm install react-native-react-bridge
These are used to render React app in WebView
npm install react-dom react-native-webview
Requirements
react 16.8+
react-native 0.60+
Usage
Fix metro.config.js to use babelTransformer from this library.
module.exports = {
transformer: {
babelTransformerPath:
require.resolve('react-native-react- >.
.bridge/lib/plugin'),
...
},
};
Make entry file for React app. web.js
import React, { useState } from "react";
import {
webViewRender,
emit,
useSubscribe,
} from "react-native-react-bridge/lib/web";
const Root = () => {
const [data, setData] = useState("");
// useSubscribe hook receives message from React Native
useSubscribe((message) => {
if (message.type === "success") {
setData(message.data);
}
});
return (
<div>
<div>{data}</div>
<button
onClick={() => {
// emit sends message to React Native
// type: event name
// data: some data which will be serialized by JSON.stringify
emit({ type: "hello", data: 123 });
}}
/>
</div>
);
};
// This statement is detected by babelTransformer as an entry point
// All dependencies are resolved, compressed and stringified into one file
export default webViewRender(<Root />);
Use the entry file in your React Native app with WebView.
import React from "react";
import WebView from "react-native-webview";
import { useBridge } from "react-native-react-bridge";
import webApp from "./WebApp";
const App = () => {
// useBridge hook create props for WebView and handle communication
// 1st argument is the source code of React app
// 2nd argument is callback to receive message from React
const { ref, source, onMessage, emit } = useBridge(webApp, (message) => {
// emit sends message to React
// type: event name
// data: some data which will be serialized by JSON.stringify
if (message.type === "hello" && message.data === 123) {
emit({ type: "success", data: "succeeded!" });
}
});
return (
<WebView
// ref, source and onMessage must be passed to react-native-webview
ref={ref}
source={source}
onMessage={onMessage}
/>
);
};

Getting props through HOC when mounting in Enzyme

I am trying to test some code that uses firebase. I am implementing the firebase-mock library. The problem I'am encountering now is that most all the components I should test get the firebase instance from a HOC (I have a class with the firebase methods I am using that is provided through the context API in the index.js and consumed via a withFirebase HOC, the wrapped component will have firebase in its props).
In this case the code I am trying to test is the following:
// mount.js
import React, { Component } from 'react';
import { withFirebase } from '../../components/Firebase';
class mount extends Component {
state = {
data: null,
};
ref = this.props.firebase.db.ref('/testing');
componentDidMount() {
// Fetch from testing ref
this.ref.on('value', snap => {
this.setState({ data: snap });
});
}
componentWillUnmount() {
this.ref.off('value');
}
render() {
return <div />;
}
}
export default withFirebase(mount);
In my test file I'm doing the following:
describe('Component mount.js', () => {
it.only('fetches', () => {
const wrapper = mount(<Mount />);
console.log(wrapper.prop());
console.log(wrapper.state().data);
});
});
This fails because this.props.firebase is null.
How could I solve this so that I can continue and finally mock firebase calls as i was intending.
I'm guessing that the problem is how to use the Context API in Enzyme, but I'm not sure.
The message you are getting is because enzyme.mount() is returning the withFirebase HOCcomponent, not the <Mount> component that you expect. what you need to do is "find" the contained component. In your example I think myContainedComponent = wrapper.find('Mount') would return the component that you could then do console.log(myContainedComponet.props); There are a lot of answers to similar questions about using Enzyme to test HOC and their enclosed components. I am using React 17 which is not supported by Enzyme.mount() so I have to use shallow. Again there are answers related to doing wrapper = shallow(shallow(<BurgerBuilder/>).get(0)); but these don't work for my setup either.
What is working for me is:
wrapper = shallow(<BurgerBuilder/>);
instance = wrapper.find('BurgerBuilder').dive().instance();
jest.spyOn(instance, 'addIngredientHandler');
NOTE: this is the export for the BurgerBuilder component.
export default withErrorHandler(BurgerBuilder, axiosOrders);
In this example, instance holds the class instance of the contained component rather than the HOC, withErrorHandler.
One of the interesting things about my example is that 'addIngredientHandler' is an arrow function in my class. There are other threads that talk about the complexities of testing class member arrow functions in React. (BTW, you do not need to do instance.forceUpdate(); )
In the interest of full disclosure, I am building my testing skills as I learn React. The components I am testing were developed while running through the Udemy course: React - The Complete Guide (incl Hooks, React Router, Redux)

Why does Jest snapshot show uglified component name instead of exported name

I've got a (private) npm module that exports several React components. The module is bundled by Webpack and in the generated bundle a reference to one of the components (say Warning) looks like this:
t.d(n,"Warning",function(){return ge})
Then I've got a React project importing this module:
import { Warning } from 'my-custom-module';
...
render() {
return (
<Warning>Lorem ipsum</Warning>
);
}
This all works OK, but when I create a Jest snapshot of the component above, I expect the snapshot to look like
<Warning>Lorem ipsum</Warning>
but it looks like:
<ge>Lorem ipsum</ge>
For some reason Jest takes the minified identifier instead of the exported name of the component. How can I see the component name in the Jest snapshot? I'm unsure if I do need to adjust my Webpack config or the Jest setup...
Since you are referring the uglified version of the 'my-custom-module' it will try to render to the uglified names. However, I assume what you actually you need is to shallowly render your component.
You can use the Enzyme libraries's shallow renderer for this.
//MyAwesomeComponent.js
import { Warning } from 'my-custom-module';
export default class MyAwesomeComponent extends Component{
render(){
return (<Warning>Lorem ipsum</Warning>);
}
}
//MyAwesomeComponent.test.js
import { shallow } from 'enzyme';
import MyAwesomeComponent from './MyAwesomeComponent';
it('renders <MyAwesomeComponent />', () => {
const shallowMyComponent = shallow(<MyComponent />);
expect(shallowMyComponent).toMatchSnapshot()
});
This should show your snapshot as Warning without going a level deeper.

Resources