I wanted to use the following code to send a message from the renderer to the main process, which then writes it to a log file using electron-log. My main.js looks like this:
import { app, protocol, BrowserWindow } from 'electron'
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib'
import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
const { ipcMain } = require('electron');
const log = require('electron-log');
const isDevelopment = process.env.NODE_ENV !== 'production'
// Scheme must be registered before the app is ready
protocol.registerSchemesAsPrivileged([
{ scheme: 'app', privileges: { secure: true, standard: true } }
])
async function createWindow() {
// Create the browser window.
const win = new BrowserWindow({
frame: true,
width: 400,
height: 800,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
enableRemoteModule: false
}
})
ipcMain.on('infoLog', (event, args) => {
log.info(args)
});
....
Now I tried to address the IPC in my App.vue accordingly:
import Navbar from '#/components/Navbar'
const { ipcRenderer } = require('electron')
export default {
name: 'App',
components: {
Navbar
},
created: function () {
ipcRenderer.send('infoLog','A async message to main')
}
}
When I start it with yarn electron:serve I see this error in the console of the window:
Uncaught ReferenceError: __dirname is not defined
at eval (webpack-internal:///./node_modules/electron/index.js:4)
at Object../node_modules/electron/index.js (chunk-vendors.js:2778)
at __webpack_require__ (app.js:849)
at fn (app.js:151)
at eval (webpack-internal:///./node_modules/cache-loader/dist/cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader-v16/dist/index.js?!./src/App.vue?vue&type=script&lang=js:5)
at Module../node_modules/cache-loader/dist/cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader-v16/dist/index.js?!./src/App.vue?vue&type=script&lang=js (app.js:938)
at __webpack_require__ (app.js:849)
at fn (app.js:151)
at eval (webpack-internal:///./src/App.vue?vue&type=script&lang=js:2)
at Module../src/App.vue?vue&type=script&lang=js (app.js:1099)
What I don't understand is that I set it up exactly like Electron's doc:
https://www.electronjs.org/docs/api/ipc-main
You have 2 different issues here:
the correct webpack configuration to support node.js code
missing node integration to use node API like require
The stacktrace you are seeing here likely comes from an incorrect webpack configuration. Unless told otherwise, webpack tries to replace __dirname with something different. Here we don't want that - node provides __dirname and we want to use it, so we have to tell webpack to leave __dirname alone.
You'll find an example in the webpack documentation.
For webpack 5 adding a node section should help:
module.exports = {
//...
node: {
global: false,
__filename: false,
__dirname: false,
}
};
After you solved this problem you'll likely fall over the issue that your browser window does not know require. You can reintroduce specific node API like the IPC by using a preload script. Don't activate the full node integration without knowing what you are doing.
For an example, have a look at this answer.
Related
I am looking for a way to disable console.log() for production env. Something like putting the below code to nuxt.config.js or index.js:
if (process.env.NODE_ENV !== "development") {
console.log = () => {};
}
I tried it, but it doesn't work. Any help would be appreciated.
My nuxt.config.js is here
https://gist.github.com/somaria/9a2b0e06497d13a35fe9eee141a15d07
Nuxt's build process includes terser, which can be configured to automatically remove console statements from your production build. You could set build.terser.terserOptions:
// nuxt.config.js
export default {
build: {
terser: {
// https://github.com/terser/terser#compress-options
terserOptions: {
compress: {
drop_console: true
}
}
}
}
}
As an alternative, this can also be done with Plugins.
Under Plugins folder, we can create a file called disableLogs.js which can look like so:
// plugins/disableLogs.js
export function disableLogs() {
console.log = () => {};
// or you can override any other stuff you want
}
process.env.NODE_ENV === "production" ? disableLogs() : null;
Then we can register this plugin to be used inside nuxt.config.js
// nuxt.config.js
plugins: [
{ src: "~/plugins/disableLogs.js" },
{ src: "~/plugins/any-other-plugin.js"
],
This will run before instantiating the root Vue.js Application.
There are other things where you can configure it to run either client or server side, etc. More info here - https://nuxtjs.org/guide/plugins#vue-plugins
I'm struggling to understand how to correctly import ipcRenderer in a .vue file.
I put in /src/background.js file :
webPreferences: {
nodeIntegration:false,
contextIsolation: true, // protects against prototype pollution
preload: path.join(__dirname, "../dist_electron/preload.js"),
}
And, based on https://nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration I put in preload.js :
window.ipcRenderer = ipcRenderer
webpack.config.js :
module.exports = {
entry: './src/background.js',
target: 'node',
output: {
path: path.join(__dirname, 'build'),
filename: 'background.js'
}
}
In order to facilitate the debugging, I created a github repo.
You can git clone the repo from here: https://github.com/raphael10-collab/ElectronVueTypeScriptScaffolding.git
After executing yarn -> yarn electron:serve
you will get the correct page.
But when activating in /src/views/Home.vue this line:
//import { ipcRenderer } from 'electron'
you will get this error:
__dirname is not defined
Environment Info:
System:
OS: Linux 5.4 Ubuntu 18.04.5 LTS (Bionic Beaver)
CPU: (8) x64 Intel(R) Core(TM) i7-4790K CPU # 4.00GHz
Binaries:
Node: 14.5.0 - ~/.nvm/versions/node/v14.5.0/bin/node
Yarn: 1.22.4 - /usr/bin/yarn
npm: 6.14.5 - ~/.nvm/versions/node/v14.5.0/bin/npm
Browsers:
Chrome: 85.0.4183.83
Firefox: 79.0
npmPackages:
#vue/babel-helper-vue-jsx-merge-props: 1.0.0
#vue/babel-plugin-transform-vue-jsx: 1.1.2
#vue/babel-preset-app: 4.4.6
#vue/babel-preset-jsx: 1.1.2
#vue/babel-sugar-functional-vue: 1.1.2
#vue/babel-sugar-inject-h: 1.1.2
#vue/babel-sugar-v-model: 1.1.2
#vue/babel-sugar-v-on: 1.1.2
#vue/cli-overlay: 4.4.6
#vue/cli-plugin-babel: ~4.4.0 => 4.4.6
#vue/cli-plugin-e2e-cypress: ~4.4.0 => 4.4.6
#vue/cli-plugin-router: ~4.4.0 => 4.4.6
#vue/cli-plugin-typescript: ~4.4.0 => 4.4.6
#vue/cli-plugin-unit-mocha: ~4.4.0 => 4.4.6
#vue/cli-plugin-vuex: ~4.4.0 => 4.4.6
#vue/cli-service: ~4.4.0 => 4.4.6
#vue/cli-shared-utils: 4.4.6
#vue/component-compiler-utils: 3.2.0
#vue/preload-webpack-plugin: 1.1.2
#vue/test-utils: ^1.0.3 => 1.0.3
#vue/web-component-wrapper: 1.2.0
babel-helper-vue-jsx-merge-props: 2.0.3
typescript: ^3.9.7 => 3.9.7
vue: ^2.6.11 => 2.6.11
vue-class-component: ^7.2.5 => 7.2.5
vue-cli-plugin-electron-builder: ~2.0.0-rc.4 => 2.0.0-rc.4
vue-hot-reload-api: 2.3.4
vue-i18n: ^8.20.0 => 8.20.0
vue-loader: 15.9.3
vue-property-decorator: ^9.0.0 => 9.0.0
vue-router: ^3.2.0 => 3.3.4
vue-style-loader: 4.1.2
vue-template-compiler: ^2.6.11 => 2.6.11
vue-template-es2015-compiler: 1.9.1
vuex: ^3.5.1 => 3.5.1
vuex-class: ^0.3.2 => 0.3.2
npmGlobalPackages:
#vue/cli: 4.4.6
node version: v14.5.0
Update 1)
I tried to set webPreferences as follows (with nodeIntegration: true) :
webPreferences: {
nodeIntegration: true,
//contextIsolation: true, // protects against prototype pollution
//preload: path.join(__dirname, "../dist_electron/preload.js"),
},
and got this error:
fs.existsSync is not a function
Searching around for info about this kind of problem, I found this post:
How to resolve fs.existsSync is not a function
With this link: https://webpack.js.org/concepts/targets/
But I already specified in webpack.config.js the target ‘node’:
in webpack.config.js :
module.exports = {
entry: './src/background.js',
target: 'node',
output: {
path: path.join(__dirname, 'build'),
filename: 'background.js'
}
}
So... how to solve this new problem?
By the way,
Why must I put
webPreferences: {
nodeIntegration: true,
}
if, for security reasons, it is more secure to have:
webPreferences: {
nodeIntegration:false,
contextIsolation: true, // protects against prototype pollution
preload: path.join(__dirname, "../dist_electron/preload.js"),
}
dist_electron/preload.js :
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) => {
// whitelist channels
let validChannels = ["toMain"];
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, data);
}
},
receive: (channel, func) => {
let validChannels = ["fromMain"];
if (validChannels.includes(channel)) {
// Deliberately strip event as it includes `sender`
ipcRenderer.on(channel, (event, ...args) =>
func(...args));
}
}
}
);
window.ipcRenderer = ipcRenderer
https://www.electronjs.org/docs/tutorial/security#electron-security-warnings
Update 2)
in vue.config.js I've put:
module.exports = {
pluginOptions: {
electronBuilder: {
preload: 'dist_electron/preload.js',
// Or, for multiple preload files:
//preload: { preload: 'src/preload.js', otherPreload:
//'src/preload2.js' }
}
}
}
But I get the same error when I do
yarn electron:serve
UncaughtReferenceError: __dirname is not defined
When setting nodeIntegration: true (but I would prefer to set it to false, and use preload.js file), I get this other error (as above):
Uncaught TypeError: fs.existsSync is not a function
Uncaught TypeError: fs.existsSync is not a function
How to solve the problem?
Looking forward to your kind help
Updated Answer - Nodeintegration disabled and contextIsolation enabled
In order to use the ipcRenderer with Vue CLI plugin Electron Builder you need to first setup electron to utilize a preload.js file.
Inside your vue.config.js file you need to add the preload.js path like this:
// vue.config.js - project root
module.exports = {
pluginOptions: {
electronBuilder: {
preload: 'src/preload.js',
// Or, for multiple preload files:
preload: { preload: 'src/preload.js', otherPreload: 'src/preload2.js' }
}
}
}
Next you need to updated your background.js file to use preload.js in the web preferences like this:
// src/background.js
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
// Use pluginOptions.nodeIntegration, leave this alone
// See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info
nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION,
contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION,
enableRemoteModule: true,
preload: path.join(__dirname, 'preload.js'),
},
})
Note: nodeIntegration is disabled and contextIsolation is enabled by default
Once you have that complete you can create the preload.js file in your src directory.
With contextIsolation enabled you need to import the contextBridge along with ipcRenderer. Then you can can expose the ipcRenderer to your client.
Then add this to the file:
// src/preload.js
import { contextBridge, ipcRenderer } from 'electron'
// Expose ipcRenderer to the client
contextBridge.exposeInMainWorld('ipcRenderer', {
send: (channel, data) => {
let validChannels = ['nameOfClientChannel'] // <-- Array of all ipcRenderer Channels used in the client
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, data)
}
},
receive: (channel, func) => {
let validChannels = ['nameOfElectronChannel'] // <-- Array of all ipcMain Channels used in the electron
if (validChannels.includes(channel)) {
// Deliberately strip event as it includes `sender`
ipcRenderer.on(channel, (event, ...args) => func(...args))
}
}
})
Note: You need to make sure your preload.js file is in the src folder and not dist_electron
To test and make sure the preload file is working you can also create an alert in the preload.js file
// src/preload.js
import { contextBridge, ipcRenderer } from 'electron'
// Expose ipcRenderer to the client
contextBridge.exposeInMainWorld('ipcRenderer', {
send: (channel, data) => {
let validChannels = ['nameOfClientChannel'] // <-- Array of all ipcRenderer Channels used in the client
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, data)
}
},
receive: (channel, func) => {
let validChannels = ['nameOfElectronChannel'] // <-- Array of all ipcMain Channels used in the electron
if (validChannels.includes(channel)) {
// Deliberately strip event as it includes `sender`
ipcRenderer.on(channel, (event, ...args) => func(...args))
}
}
})
alert("It Worked!") // Remove this line once you confirm it worked
When you have verified that your preload script is working correctly, you can access the ipcRenderer from your vue app.
like this:
// src/App.vue
<template>
\\ Some html
</template>
<script>
export default {
name: "App",
methods: {
test(){
window.ipcRenderer.send(channel, args...) // or any other ipcRenderer method you want to invoke
}
};
</script>
In electron you can listen for those events
// background.js
ipcMain.on(channel, (event, args) => {
// Do stuff
});
Sources:
Preload Files
Node Integration
With contextIsolation = true, is it possible to use ipcRenderer?
Original Answer
In order to use the ipcRenderer with Vue CLI plugin Electron Builder you need to first setup electron to utilize a preload.js file.
Inside your vue.config.js file you need to add the preload.js path like this:
// vue.config.js - project root
module.exports = {
pluginOptions: {
electronBuilder: {
preload: 'src/preload.js',
// Or, for multiple preload files:
preload: { preload: 'src/preload.js', otherPreload: 'src/preload2.js' }
}
}
}
Next you need to updated your background.js file to use preload.js in the web preferences like this:
// src/background.js
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
// Use pluginOptions.nodeIntegration, leave this alone
// See nklayman.github.io/vue-cli-plugin-electron-builder/guide/configuration.html#node-integration for more info
nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION,
+ preload: path.join(__dirname, 'preload.js')
}
})
Once you have that complete you can create the preload.js file in your src directory
Then add this to the file:
// src/preload.js
import { ipcRenderer } from 'electron'
window.ipcRenderer = ipcRenderer
Note: You need to make sure your preload.js file is in the src folder and not dist_electron
To test and make sure the preload file is working you can also create an alert in the preload.js file
// src/preload.js
import { ipcRenderer } from 'electron'
window.ipcRenderer = ipcRenderer
alert("It Worked!") // Remove this line once you confirm it worked
When you have verified that your preload script is working correctly, you can access the ipcRenderer from your vue app.
like this:
// src/App.vue
<template>
\\ Some html
</template>
<script>
export default {
name: "App",
methods: {
test(){
window.ipcRenderer.send(channel, args...) // or any other ipcRenderer method you want to invoke
}
};
</script>
Sources:
Preload Files
Node Integration
What worked for me was setting the electron window's contextIsolation to false.
So in your main.js wherever you create a BrowserWindow it would look like this:
const win = new BrowserWindow({
webPreferences: {
contextIsolation: false,
preload: path.join(__dirname, 'preload.js'),
},
})
And then in preload.js you can simply do
const { ipcRenderer } = require('electron')
window.ipcRenderer = ipcRenderer
And then you'll have access to ipcRenderer anywhere in your vue code.
It seems that in the current version of electron contextIsolation defaults to true, which makes the window that preload.js sees different from the one your vue app sees.
You need to set nodeIntegration to true.
This enables NodeJs in the renderer process (i.e the front-end) so you can use stuff like fs (FileSystem) and other NodeJs-only features in your Vue code.
As ipcRenderer requires NodeJs's environnement too (__dirname is a global variable for NodeJs only), it needs to be activated.
I have an angular application wrapped in Electron. I have generated the installer using electron-builder. As per the recommendations for communication with renderer process I have used preload.js as preload script.
The script works fine in the development environment. However, once I package the app and install it, it shows me the error Cannot read property 'exposeInMainWorld' of undefined
Here is my preload.js
window.onload = () => {
const {
contextBridge,
ipcRenderer
} = require("electron");
const validChannels = ['event-1', 'event-2', 'event-3'];
// Expose protected methods that allow the renderer process to use the ipcRenderer without exposing the entire object
contextBridge.exposeInMainWorld(
"api", {
send: (channel, data) => {
// whitelist channels To Main Process
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, data);
}
},
receive: (channel, func) => {
// From Main Process
if (validChannels.includes(channel)) {
console.log('receive: ' + channel);
ipcRenderer.on(channel, (event, ...args) => func(...args));
}
}
}
);
};
My Main.js
this._win = new BrowserWindow({
width: 1024,
height: 768,
webPreferences: {
nodeIntegration: false,
webSecurity: true,
allowEval: false,
allowRunningInsecureContent: false,
contextIsolation: true, // protect against prototype pollution
enableRemoteModule: false, // turn off remote
preload: path.join(__dirname, "./preload.js") // use a preload script
},
title: this._appTitle,
autoHideMenuBar: true,
icon: path.join(__dirname, '/../dist/some-path/favicon.ico')
});
I have kept both main.js and preload.js inside a folder electron at the root level where package.json is available. In case you need to check my builder-config.yaml, it's present at this link.
Please suggest what to do.
P.S. : the preload.js works absolutely fine while in dev mode. The issue is only after packaging the app with electron-builder
I got this kind of error when I wrote:
getPlatform: () => window.remote.getPlatform(),
Then I get Uncaught Error: Uncaught TypeError: Cannot read property 'getPlatform' of undefined
in
console.log('window.remote.getPlatform()', window.remote.getPlatform && window.remote?.getPlatform())
When I change it to
getPlatform: () => process.platform,
everything works just fine.
I got this one working. Actually the version of electron mentioned in my builder-config.yaml was 5.0.13. The contextbridge api was not introduced then. When I changed the version of electron to 9 and above, it works just fine. Thanks
I'm new to frontend world, I would like to write some test using protractor-image-comparison. I followed installation instructions from https://github.com/wswebcreation/protractor-image-comparison. Also I make configuration according to this page.
When I try to use functions form this lib I get following error: "TypeError: Cannot read property 'checkFullPageScreen' of undefined". I'm getting a warrning in protractor.conf.js in
const protractorImageComparison = require('protractor-image-comparison');
"Could not find a declaration file for module
'protractor-image-comparison'.
'/home/rafa/repos/example/src/example/node_modules/protractor-image-comparison/index.js'
implicitly has an 'any' type. Try npm install
#types/protractor-image-comparison if it exists or add a new
declaration (.d.ts) file containing declare module
'protractor-image-comparison';"
So I did, I made simple *.d.ts file with `declare module protractor-image-comparison' in it, but it didn't solve the problem just the warning disappear. It's propably the config issue, but I can't handle it or maybe I made wrong declaration. This is my config file :
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const reporter = require("cucumber-html-reporter");
const path = require("path");
const jsonReports = path.join(process.cwd(), "/reports/json");
const htmlReports = path.join(process.cwd(), "/reports/html");
const targetJson = jsonReports + "/cucumber_report.json";
const cucumberReporterOptions = {
jsonFile: targetJson,
output: htmlReports + "/cucumber_reporter.html",
reportSuiteAsScenarios: true,
theme: "bootstrap",
};
exports.config = {
allScriptsTimeout: 110000,
restartBrowserBetweenTests: true,
//SELENIUM_PROMISE_MANAGER: false,
specs: [
'./e2e/**/login.feature'
],
capabilities: {
'browserName': 'chrome'
},
directConnect: true,
baseUrl: 'http://localhost:4200/',
framework: 'custom',
frameworkPath: require.resolve('protractor-cucumber-framework'),
cucumberOpts: {
format: "json:" + targetJson,
require: ['./e2e/steps/*.ts', "./e2e/timeout.ts"],
},
useAllAngular2AppRoots: true,
onPrepare: () => {
browser.ignoreSynchronization = true;
const protractorImageComparison = require('protractor-image-comparison');
browser.protractorImageComparison = new protractorImageComparison(
{
baselineFolder: "report/screens/baseline",
screenshotPath: "report/screens/actual"
}
);
},
beforeLaunch: function() {
require('ts-node').register({
project: 'e2e'
});
},
onComplete: () => {
reporter.generate(cucumberReporterOptions);
}
};
Ok, I solved it. The reason why I was getting this TypeError is that I lunched few test scenarios and onPrepare was lunched only in the begining. I move config of protractor-image-comparison to cucumber befor hook and everything works fine now.
I have a test that imports a component that in turn imports a helper file that uses the window object to pull out a query string parameter. I get the following error about window:
FAIL src/js/components/__tests__/Controls.test.jsx
● Test suite failed to run
ReferenceError: window is not defined
Controls.jsx:
import { Unwrapped as Controls } from '../Controls'
describe('<MyInterestsControls />', () => {
it('should render the component with the fixture data', () => {
const component = shallow(
<UnwrappedMyInterestControls
dashboardData={dashboardData}
loadingFlags={{ controls: false }}
/>
)
expect(component).toMatchSnapshot()
})
})
Controls.jsx imports ./helpers/services.js which contains the following:
import * as queryString from 'query-string'
const flag = queryString.parse(window.location.search).flag || 'off'
^^^^^^ this seems to be the problem
I have attempted to import jsdom:
import { JSDOM } from 'jsdom'
And implemented the solution presented here at the top of my test file:
const { JSDOM } = require('jsdom');
const jsdom = new JSDOM('<!doctype html><html><body></body></html>');
const { window } = jsdom;
function copyProps(src, target) {
const props = Object.getOwnPropertyNames(src)
.filter(prop => typeof target[prop] === 'undefined')
.map(prop => Object.getOwnPropertyDescriptor(src, prop));
Object.defineProperties(target, props);
}
global.window = window;
global.document = window.document;
global.navigator = {
userAgent: 'node.js',
};
copyProps(window, global);
However, I still get the error and it seems JSDOM's window object isn't exposed to the test.
How can I properly expose global objects like window or document to a Jest test?
Relevant package.json
"scripts": {
"test:watch": "NODE_ENV=test jest --watch"
},
...
"devDependencies": {
...
"jest": "^20.0.4",
"jest-mock": "^21.2.0",
"jsdom": "^11.0.0",
...
},
...
"jest": {
"verbose": true,
"collectCoverageFrom": [
"src/js/helpers/preparePayload.js",
"src/js/components-ni",
"!**/node_modules/**",
"!**/dist/**"
],
"coverageThreshold": {
"global": {
"statements": 50,
"branches": 50,
"functions": 50,
"lines": 75
}
},
"testEnvironment": "jest-environment-node"
}
As mentioned by #RiZKiT in the comment below, since Jest v27.0 the default test environment has changed from "jsdom" to "node".
Your problem relies on the configuration.
In the moment you set:
"testEnvironment": "jest-environment-node"
You are changing the default configuration from Jest which is browser-like to jest-environment-node (Node.js-like) meaning that your test will be run under a Node.js environment
To solve it either you set your testEnvironment to jsdom
Or you remove the testEnvironment from your configuration, so it will take the default value in yourpackage.json:
...
"jest": {
"verbose": true,
"collectCoverageFrom": [
"src/js/helpers/preparePayload.js",
"src/js/components-ni",
"!**/node_modules/**",
"!**/dist/**"
],
"coverageThreshold": {
"global": {
"statements": 50,
"branches": 50,
"functions": 50,
"lines": 75
}
}
}
This is what they say in the documentation
testEnvironment [string] # Default: "jsdom"
The test environment that
will be used for testing. The default environment in Jest is a
browser-like environment through jsdom. If you are building a node
service, you can use the node option to use a node-like environment
instead.
## Do you need the `node` environment?
As I could see, your tests are meant to be run under a browser-like environment.
If you ever need an explicit Node.js environment, better you isolate that case using #jest-environment:
/**
* #jest-environment node
*/
test('use node in this test file', () => {
expect(true).not.toBeNull();
});
Or the other way around, if you are meant to run the tests under a Node.js environment:
/**
* #jest-environment jsdom
*/
test('use jsdom in this test file', () => {
const element = document.createElement('div');
expect(element).not.toBeNull();
});
## Conclusion
With this you can avoid importing jsdom manually and setting global variables. jsdom will mock the DOM implementation automatically.
If you need to change the environment for your tests, use the notation #jest-environment.
You could try doing
global.window = new jsdom.JSDOM().window;
global.document = window.document;
See Expose jsdom to global environment #2460
It seems like one of the contributors declared that he is not planning to expose jsdom to global under the Jest environment.
However, you could use Object.defineProperty(window, 'location', {value: '…'} API to approach it, like the developer from Facebook do. In your case it could be like:
Object.defineProperty(window, 'location', {
value: {
search: ...
},
})
Here you can find examples of how to do this:
DOM Testing React Applications with Jest
For example:
import {jsdom} from 'jsdom';
const documentHTML = '<!doctype html><html><body><div id="root"></div></body></html>';
global.document = jsdom(documentHTML);
global.window = document.parentWindow;
You can simply mock location:
global.location = {search: 'someSearchString'}
Also note, that global in your test is the global context for the file to test (global === window)
Note this will only work if your module make the window.location call after the test has been finishing import all the modules.
export default () => window.location
So if your module looks like this:
const l = window.location
export default l
it will not work. In this case you could mock the module using jest.mock.
I am not sure, but I think you could do it with jest.fn():
global.window = jest.fn(() => {
location: { ... }
})
Maybe even as window = jest.fn(...).
In my case, I was injecting a custom environment file's value into a Vue.js component using Ruby on Rails. So, I was getting a base URL for assets as undefined in a Jest snapshot. The solution was to include a setup file in jest.config.js → setupFiles as an array like below.
jest.config.js
{
setupFiles: [
"./app/javascript/tests/testsConfig.js",
],
}
testsConfig.js
process.env.BASE_DOMAIN = process.env.BASE_DOMAIN || 'http://localhost:3000/'
window.TestsConfig = {
"CLOUD_FRONT_BASE_URL": "https://cdn.site.com"
}