I'm trying to setup React testing with Cypress + cypress-react-selector.
Cypress seems to be working fine, but cypress-react-selector not so much.
I can run normal cypress methods.
But when I run cy.react(), it throws: "Could not find instance of React in given element"
I've been googling a lot, but nothing helps.
MyComponent.js
`
import React from 'react';
export const MyComponent = () => {
return <div data-testid="myTestId">my text</div>;
};
`
MyComponent.cy.js
`
import React from 'react';
import { MyComponent } from './MyComponent';
describe('<MyComponent />', () => {
beforeEach(() => {
cy.mount(<MyComponent />);
});
it('renders', () => {
//this works
cy.get('[data-testid=myTestId]').should('have.text', 'my text');
//this seems to be working, but I don't know
cy.waitForReact(5000, '#__cy_root');
//This fails with: "Could not find instance of React in given element"
cy.react('MyComponent');
});
});
`
cypress/support/component.js
`
import 'cypress-react-selector';
import './commands';
import { mount } from 'cypress/react18';
Cypress.Commands.add('mount', mount);`
cypress/support/component-index.html
`
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<title>Components App</title>
</head>
<body>
<div id="__cy_root" data-cy-root></div>
</body>
</html>
`
cypress.config.js
`
const { defineConfig } = require('cypress');
module.exports = defineConfig({
env: {
'cypress-react-selector': {
root: '#__cy_root',
},
},
component: {
devServer: {
framework: 'create-react-app',
bundler: 'webpack',
},
},
});
`
Ask for code, if I haven't provided something.
cypress-react-selector pre-dates cypress component testing, and I don't see any sign that it has been updated to handle component testing - all examples are e2e style testing.
Logically, Cypress built-in component testing and cypress-react-selector do the same thing, so maybe consider using one or the other.
IMO the cypress-react-selector has somewhat better API and more accessible examples, for instance I can't see how to do this in Cypress component testing
cypress-react-selector#sample-tests
Get current state
cy.getReact('MyTextInput', {
props: { field: { name: 'email' } },
}).getCurrentState(); // can return string | boolean | any[] | {}
Related
I am building an app with Express on the backend and React on the frontend with typescript, and I am using Vite to build the frontend (my first time using Vite). My APIs are working fine, yet I am unable to fetch the data on the frontend. The simplified code on the frontend:
React.useEffect(() => {
const fetchData = async () => {
const response = await fetch("/api/data");
const json = await response.json();
if (response.ok) {
setData(json);
}
};
fetchData();
}, []);
It keeps sending me this html in the response:
<!DOCTYPE html>
<html lang="en">
<head>
<script type="module">
import RefreshRuntime from "/#react-refresh"
RefreshRuntime.injectIntoGlobalHook(window)
window.$RefreshReg$ = () => {}
window.$RefreshSig$ = () => (type) => type
window.__vite_plugin_react_preamble_installed__ = true
</script>
<script type="module" src="/#vite/client"></script>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx?t=1675714554862"></script>
</body>
</html>
I have tried to add the mentioned scripts in my html file, but the error persits. I am not sure if it could be maybe in the vite.config.ts:
export default defineConfig({
build: {
outDir: "dist",
},
server: {
port: 3001,
},
plugins: [react()],
});
I have allowed proxy in the package.json file to handle CORS but that doesn't seem to be the problem. I think I am overlooking something important but I am not sure what...Can anybody help?
You'll want to setup a proxy inside your Vite config.
https://vitejs.dev/config/server-options.html#server-proxy
Assuming your API listens on port 3000, add this proxy property to your server object:
server: {
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
secure: false
}
}
}
I'm using the demo code to build a simple Here map using React + Typescript. I'm getting a TypeError: Cannot read properties of undefined (reading 'Platform') at line #11:
8 | React.useLayoutEffect(() => {
9 | if(!mapRef.current) return;
10 |
> 11 | const platform = new H.service.Platform({
12 | ^ apikey: // my API key
13 | });
14 |
And here is the beginning of the typescript file below:
import React from "react";
import * as H from "here-maps";
export function DisplayMap() {
const mapRef = React.useRef(null);
React.useLayoutEffect(() => {
if(!mapRef.current) return;
const platform = new H.service.Platform({
apikey: // my API key
});
const defaultLayers = platform.createDefaultLayers();
...
Any clue what is the problem? I inspected H.service and it is a namespace and Platform is defined inside it. Not sure why it is undefined!
And this is my package.json file:
...
"dependencies": {
"#types/heremaps": "^3.1.5",
"ajv": "^6.12.0",
"here-maps": "^3.0.2",
"react": "^16.13.0",
"react-dom": "^16.13.0"
},
"devDependencies": {
"#types/jest": "^24.9.1",
"#types/node": "^12.12.6",
"#types/react": "^16.9.23",
"#types/react-dom": "^16.9.5",
"react-scripts": "3.4.0",
"typescript": "^3.7.5"
},
...
we can integrate a map into our React component. That said, we’ll need to make some mild adjustments to the instructions to adapt to the way React works, so let’s go step-by-step.
Loading the API Code Libraries
Like mentioned in the quick starting guide, we need to load the core and service modules. These script tags are still added to the head of the document, same as the guide there, but we’ll put them in the /public/index.html file. We can also add the meta-tag to optimize the performance for mobile at the same time:
<head>
...
<title>React App</title>
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
<script
type="text/javascript"
src="https://js.api.here.com/v3/3.1/mapsjs-core.js"
></script>
<script
type="text/javascript"
src="https://js.api.here.com/v3/3.1/mapsjs-service.js"
></script>
</head>
Creating the reusable Map component
Now that we have loaded the HERE Map Libraries, lets create a reusable Map component by creating a file src/DisplayMapClass.js and add the following code:
// src/DisplayMapClass.js
import * as React from 'react';
export class DisplayMapClass extends React.Component {
mapRef = React.createRef();
state = {
// The map instance to use during cleanup
map: null
};
componentDidMount() {
const H = window.H;
const platform = new H.service.Platform({
apikey: "{HERE-API-KEY}"
});
const defaultLayers = platform.createDefaultLayers();
// Create an instance of the map
const map = new H.Map(
this.mapRef.current,
defaultLayers.vector.normal.map,
{
// This map is centered over Europe
center: { lat: 50, lng: 5 },
zoom: 4,
pixelRatio: window.devicePixelRatio || 1
}
);
this.setState({ map });
}
componentWillUnmount() {
// Cleanup after the map to avoid memory leaks when this component exits the page
this.state.map.dispose();
}
render() {
return (
// Set a height on the map so it will display
<div ref={this.mapRef} style={{ height: "500px" }} />
);
}
}
Now, lets import the DisplayMapClass.js component in the App.js to render the map.
//App.js
import React from 'react';
import {DisplayMapClass} from './DisplayMapClass';
function App() {
return (
<DisplayMapClass />
);
}
export default App;
Adding Interactivity
While the previous map may suffice with your business needs, many apps require interactivity. Luckily, the JS SDK gives us ways to add this functionality trivially.
We’ll first start by adding two new imports to our public/index.html file to import the files from the HERE SDK required for it’s related features to function:
<head>
...
<title>React App</title>
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
<script
type="text/javascript"
src="https://js.api.here.com/v3/3.1/mapsjs-core.js"
></script>
<script
type="text/javascript"
src="https://js.api.here.com/v3/3.1/mapsjs-service.js"
></script>
<script
type="text/javascript"
src="https://js.api.here.com/v3/3.1/mapsjs-ui.js"
></script>
<script
type="text/javascript"
src="https://js.api.here.com/v3/3.1/mapsjs-mapevents.js"
></script>
</head>
Then, we can use the new functionality enabled by those imports by making some minor changes to the component we’ve already built.
export default class DisplayMapClass extends React.Component {
mapRef = React.createRef();
state = {
map: null
};
componentDidMount() {
const H = window.H;
const platform = new H.service.Platform({
apikey: "{HERE-API-KEY}"
});
const defaultLayers = platform.createDefaultLayers();
const map = new H.Map(
this.mapRef.current,
defaultLayers.vector.normal.map,
{
center: { lat: 50, lng: 5 },
zoom: 4,
pixelRatio: window.devicePixelRatio || 1
}
);
// MapEvents enables the event system
// Behavior implements default interactions for pan/zoom (also on mobile touch environments)
// This variable is unused and is present for explanatory purposes
const behavior = new H.mapevents.Behavior(new H.mapevents.MapEvents(map));
// Create the default UI components to allow the user to interact with them
// This variable is unused
const ui = H.ui.UI.createDefault(map, defaultLayers);
this.setState({ map });
}
componentWillUnmount() {
this.state.map.dispose();
}
render() {
return <div ref={this.mapRef} style={{ height: "500px" }} />;
}
}
Now we should be able to zoom in and out of maps, pan to locations of our choosing, and even have a convenient API present for our users to utilize
For more details please check the below tutorials. which explain how we can integrate react with the here maps.
https://developer.here.com/tutorials/react/
I am using GatsbyJS. Recently I created a file in which I put this code.
import React from 'react'
import { Helmet } from 'react-helmet'
const CustomScript = () => {
return (
<Helmet>
<script type="application/javascript" src="https://sdki.truepush.com/sdk/v2.0.2/app.js" async></script>
<script>
var truepush = window.truepush || [];
truepush.push(function(){
truepush.Init({
id: "..."
},function(error){
if(error) console.error(error);
})
})
</script>
</Helmet>
)
}
export default CustomScript
But it gives me this error
ERROR #98123 WEBPACK
Generating development JavaScript bundle failed
/home/lilynicole/GitLab/portfolio/src/components/CustomScript.js
12:17 error 'truepush' is not defined no-undef
✖ 1 problem (1 error, 0 warnings)
File: src/components/CustomScript.js
failed Re-building development bundle - 5.401s
I have tried couple of things but nothing seems to work. Any help will be really appreciated.
If you want to insert inline JavaScript (the second <script> tag) with React Helmet, you'd need to quote the entire JavaScript as follows:
import React from 'react'
import { Helmet } from 'react-helmet'
const CustomScript = () => {
return (
<Helmet>
<script type="application/javascript" src="https://sdki.truepush.com/sdk/v2.0.2/app.js" async></script>
<script>{`
var truepush = window.truepush || [];
truepush.push(function(){
truepush.Init({
id: "..."
},function(error){
if(error) console.error(error);
})
})
`}
</script>
</Helmet>
)
}
export default CustomScript
The only change is that I wrapped the entire content of the <script> tag in {` ... `}.
In any case, I'd find it cleaner to write the following:
import React from 'react'
import { Helmet } from 'react-helmet'
import { useEffect } from 'react'
const CustomScript = () => {
useEffect(() => {
var truepush = window.truepush || []
truepush.push(function () {
truepush.Init(
{
id: '...',
},
function (error) {
if (error) console.error(error)
}
)
})
}, [])
return (
<Helmet>
<script
type="application/javascript"
src="https://sdki.truepush.com/sdk/v2.0.2/app.js"
async
></script>
</Helmet>
)
}
export default CustomScript
Here, instead of treating the JavaScript for the initialization as a string you're actually including it in your component code.
To make sure it runs only once you wrap it with useEffect(() => {...}, []).
(Note that I edited out your private Truepush ID and replaced it with ...; you'll need to put it back in)
I am David from Truepush Tech support. Can you please tell me if the project that you are doing is "a WebSite" or "a WebApp".
As if you are building a website then the code please try using the method that #ehrencrona has given out. That is Code by #ehrencrona
But if it is a webapp, we are not supporting them as of now and can't help you with setting it up for them.
We are working on a solution for this from our end as well. We will let you know when we have something that we can give out to help you.
using a electron, react (es6 / jsx), sass, pouchdb and webpack 2 setup. I fail to import or require ipcRenderer to make communication between main and renderer process possible. My setup can be found here: https://github.com/wende60/timeTracker
Any hints how to get the ipcRenderer into a react component?
Cheers, jo
const electron = window.require('electron');
const ipcRenderer = electron.ipcRenderer;
I think it is the better solution because it avoid ejecting the React app.
I had the same problem. This solved that issue for me:
Add in the webpack.config.js:
const webpack = require("webpack");
module.exports = {
plugins: [
new webpack.ExternalsPlugin('commonjs', [
'electron'
])
]
...
}
Then you can use it with
import {ipcRenderer} from "electron";
I suggest you read my response here.
You'll want to set up your app like this:
main.js
const {
app,
BrowserWindow,
ipcMain
} = require("electron");
const path = require("path");
const fs = require("fs");
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let win;
async function createWindow() {
// Create the browser window.
win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: false, // is default value after Electron v5
contextIsolation: true, // protect against prototype pollution
enableRemoteModule: false, // turn off remote
preload: path.join(__dirname, "preload.js") // use a preload script
}
});
// Load app
win.loadFile(path.join(__dirname, "dist/index.html"));
// rest of code..
}
app.on("ready", createWindow);
ipcMain.on("toMain", (event, args) => {
fs.readFile("path/to/file", (error, data) => {
// Do something with file contents
// Send result back to renderer process
win.webContents.send("fromMain", responseObj);
});
});
preload.js
** Update: DO NOT use send key value as property name. It will overwrite on win.webContents.send method and comes to do nothing when you try to call win.webContents.send('your_channel_name') inside your main process main.js. Better to use better names like request and response.
const {
contextBridge,
ipcRenderer
} = require("electron");
// Expose protected methods that allow the renderer process to use
// the ipcRenderer without exposing the entire object
contextBridge.exposeInMainWorld(
"api", {
//send: (channel, data) => {
request: (channel, data) => {
// whitelist channels
let validChannels = ["toMain"];
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, data);
}
},
//receive: (channel, func) => {
response: (channel, func) => {
let validChannels = ["fromMain"];
if (validChannels.includes(channel)) {
// Deliberately strip event as it includes `sender`
ipcRenderer.on(channel, (event, ...args) => func(...args));
}
}
}
);
index.html
<!doctype html>
<html lang="en-US">
<head>
<meta charset="utf-8"/>
<title>Title</title>
</head>
<body>
<script>
window.api.response("fromMain", (data) => {
console.log(`Received ${data} from main process`);
});
window.api.request("toMain", "some data");
</script>
</body>
</html>
As of May 2020, I think Erik Martín Jordán has said it best:
Create a preload.js file:
window.ipcRenderer = require('electron').ipcRenderer;
On main.js:
// Create the browser window.
mainWindow = new BrowserWindow({
alwaysOnTop: true,
frame: false,
fullscreenable: false,
transparent: true,
titleBarStyle: 'customButtonsOnHover',
show: false,
width: 300,
height: 350,
webPreferences: {
// UPDATE: for electron > V12 consider setting contextIsolation and see: https://github.com/electron/electron/issues/9920#issuecomment-797491175
nodeIntegration: true,
preload: __dirname + '/preload.js'
}
});
// Blur window when close o loses focus
mainWindow.webContents.on('did-finish-load', () => mainWindow.webContents.send('ping', '🤘') );
mainWindow variable on this file will preload the preload.js file. Now the React component can call the window.ipcRenderer method.
In the React app.js:
import React, { useEffect, useState } from 'react';
import './App.css';
function App() {
useEffect( () => {
window.ipcRenderer.on('ping', (event, message) => {
console.log(message)
});
}, []);
return (
<div className = 'App'></div>
);
}
export default App;
I've been looking into this topic just recently and I found a solution for doing some some ipc between electrons main.js and the React part of the application. Since both, import {ipcRenderer} from 'electron'; after adding the plugin to the webpack modules and const ipc = require('electron').ipcRenderer; produced some errors I ended up requiring electron in the resulting page and adding it to the window.
In the index.html I did something like this
<body>
...
<script>
window.ipc = require('electron').ipcRenderer;
</script>
<div id="root">
</div>
...
</body>
In reacts index.js I did something like this:
import React from 'react';
import ReactDOM from 'react-dom';
// ...
if(window.ipc)
ipc.on("some-event", (event, someParameter) => {
ReactDOM.render(
<SomeElement value={someParameter} />,
document.getElementById("root")
);
})
// ...
In order to make this work i started the react page from the electron app, in the main.js I did something like that.
const {app, BrowserWindow} = require("electron"};
const exec = require("child_process").exec;
let main;
app.on("ready", () => {
exec("node start", (err, stdout, stderr) => {
if(err) console.log(err);
console.log("" + stdout);
console.log("" + stderr);
});
main = new BrowserWindow();
main.loadURL("http://localhost:3006");
main.on("close", () => {main = null});
});
because electron is running on the same port I added a .env file to my project that contained
PORT=3006
I used the create-react-app my-prj (npm install -g create-react-app) command for my base project it looked something like this
my-prj
|-package.json
|-main.js
|-.env
|-node_modules/
|-public/
|-index.html
|-src/
|-index.js
Hope this post was of any help.
Found a good solution for this issue using webpack-target-electron-renderer So I can develop the web-part in a localhost environment with hot-reloading. electron is required only in the electron environment.
You can see a working example here:
https://github.com/wende60/webpack-web-and-electron-example, forked from acao's webpack-web-and-electron-example and updated for webpack 2 and hot-replacement.
If you are interested in a webpack, electron, react, sass and pouchdb setup have a look here:
https://github.com/wende60/timeTracker
Work is still in progress...
import { ipcRenderer } from "electron"
import { Component } from "react"
...
class MyComponent extends Component {
render(){
ipcRenderer.send("event", "some data")
return (<div>Some JSX</div>)
}
}
I'm writing an isomorphic React app based on :
https://github.com/choonkending/react-webpack-node
Instead of css modules used in the examples I'd like to use scss though. And for some reason I'm having a really hard time getting them to work. My first step was to remove the css webpack loaders from both the server and the client configs replacing them with scss-specific loaders (as well as removing postcss) :
loaders: [
'style-loader',
'css-loader?modules&localIdentName=[name]_[local]_[hash:base64:3]',
'sass-loader?sourceMap',
]
But this throws ReferenceError: window is not defined when building as style-loader is apparently not suitable for server-side rendering. So my next idea was to use isomorphic-style-loader. As far as I understand to get it working I need to decorate my component with their higher order component withStyles:
import React, { PropTypes } from 'react';
import classNames from 'classnames';
import withStyles from 'isomorphic-style-loader/lib/withStyles';
import s from '../assets/scss/common/index.scss';
const App = (props, context) => (
<div className={classNames('app')}>
<h1 className="home_header">Welcome!</h1>
{props.children}
</div>
);
export default withStyles(s)(App);
and then do some trickery in the code rendering page on the server. But the problem is, example from the package docs shows a flux action fired inside Express (https://libraries.io/npm/isomorphic-style-loader#webpack-configuration), and the boilerplate that I'm using uses react-router. So I'm kinda lost as how should I inject this object with insertCss into context. I tried this :
import React from 'react';
import { renderToString } from 'react-dom/server';
import { RouterContext, match, createMemoryHistory } from 'react-router';
import axios from 'axios';
import { Provider } from 'react-redux';
import createRoutes from 'routes.jsx';
import configureStore from 'store/configureStore';
import headconfig from 'components/Meta';
import { fetchComponentDataBeforeRender } from 'api/fetchComponentDataBeforeRender';
const clientConfig = {
host: process.env.HOSTNAME || 'localhost',
port: process.env.PORT || '3001'
};
// configure baseURL for axios requests (for serverside API calls)
axios.defaults.baseURL = `http://${clientConfig.host}:${clientConfig.port}`;
function renderFullPage(renderedContent, initialState, head = {
title: 'cs3',
css: ''
}) {
return `
<!DOCTYPE html>
<html lang="en">
<head>
${head.title}
${head.link}
<style type="text/css">${head.css.join('')}</style>
</head>
<body>
<div id="app">${renderedContent}</div>
<script type="text/javascript">window.__INITIAL_STATE__ = ${JSON.stringify(initialState)};</script>
<script type="text/javascript" charset="utf-8" src="/assets/app.js"></script>
</body>
</html>
`;
}
export default function render(req, res) {
const history = createMemoryHistory();
const store = configureStore({
project: {}
}, history);
const routes = createRoutes(store);
match({ routes, location: req.url }, (error, redirectLocation, renderProps) => {
const css = [];
if (error) {
res.status(500).send(error.message);
} else if (redirectLocation) {
res.redirect(302, redirectLocation.pathname + redirectLocation.search);
} else if (renderProps) {
const context = { insertCss: (styles) => css.push(styles._getCss()) };
const InitialView = (
<Provider context={context} store={store}>
<RouterContext {...renderProps} />
</Provider>
);
fetchComponentDataBeforeRender(store.dispatch, renderProps.components, renderProps.params)
.then(() => {
const componentHTML = renderToString(InitialView);
const initialState = store.getState();
res.status(200).end(renderFullPage(componentHTML, initialState, {
title: 'foo',
css
}));
})
.catch(() => {
res.end(renderFullPage('', {}));
});
} else {
res.status(404).send('Not Found');
}
});
}
but I'm still getting Warning: Failed context type: Required context 'insertCss' was not specified in 'WithStyles(App)'. Any ideas how to tackle this ? And more importantly - is there no easier way to do it ? This seems like a lot of additional work.
There's a few parts to handling scss compilation with webpack when you're doing server-side rendering. First of all, you don't want node trying to import .scss files into your components.
So set a global variable WEBPACK: true in your webpack config:
plugins: [
new webpack.DefinePlugin({
'process.env': {
WEBPACK: JSON.stringify(true),
}
})
],
And in your components, only attempt to import .scss files if the component is being handled by webpack (either during build or development):
if (process.env.WEBPACK) require('../assets/scss/common/index.scss');
If you only have one Sass file per component (you should) then this is always just a one-liner. Any additional Sass files can be imported inside index.scss if you need to.
Then in your config you probably still want the css loader, so it should look like this for your dev server:
{
test: /\.s?css$/,
loaders: ['style', 'css', 'sass']
},
And something like this for you build config:
{
test: /\.s?css$/,
loader: ExtractTextPlugin.extract('style', 'css!sass')
},