├── .gitignore
├── packages
├── jest-electron-runner
│ ├── runner.html
│ ├── electron-runner.js
│ ├── package.json
│ ├── electron-transport.js
│ └── index.js
└── jest-environment-electron
│ ├── index.js
│ └── package.json
├── .flowconfig
├── lerna.json
├── assets
├── jest-electron-full.gif
└── jest-electron-full.mp4
├── .editorconfig
├── example
├── __tests__
│ ├── __snapshots__
│ │ ├── index.test.js.snap
│ │ └── index.test.ts.snap
│ ├── index.test.ts
│ └── index.test.js
├── index.js
├── package.json
└── tsconfig.json
├── package.json
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | coverage
2 |
--------------------------------------------------------------------------------
/packages/jest-electron-runner/runner.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/.flowconfig:
--------------------------------------------------------------------------------
1 | [ignore]
2 |
3 | [include]
4 |
5 | [libs]
6 |
7 | [lints]
8 |
9 | [options]
10 |
--------------------------------------------------------------------------------
/lerna.json:
--------------------------------------------------------------------------------
1 | {
2 | "lerna": "2.0.0",
3 | "packages": ["packages/*"],
4 | "version": "independent"
5 | }
6 |
--------------------------------------------------------------------------------
/assets/jest-electron-full.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/d4rkr00t/jest-electron-runner/HEAD/assets/jest-electron-full.gif
--------------------------------------------------------------------------------
/assets/jest-electron-full.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/d4rkr00t/jest-electron-runner/HEAD/assets/jest-electron-full.mp4
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | end_of_line = lf
7 | indent_size = 2
8 | indent_style = space
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/example/__tests__/__snapshots__/index.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Some test #add test with snapshot 1`] = `
4 | Object {
5 | "a": 3,
6 | "b": 7,
7 | }
8 | `;
9 |
--------------------------------------------------------------------------------
/example/__tests__/__snapshots__/index.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Some test #add test with snapshot 1`] = `
4 | Object {
5 | "a": 3,
6 | "b": 7,
7 | }
8 | `;
9 |
--------------------------------------------------------------------------------
/example/index.js:
--------------------------------------------------------------------------------
1 | exports.add = function add(a, b) {
2 | return a + b;
3 | };
4 |
5 | exports.useDOMApi = function useDOMApi() {
6 | const range = document.createRange();
7 | range.setStart(document.body, 0);
8 | range.setEnd(document.body, 0);
9 | };
10 |
11 | exports.printMe = function printMe(arg) {
12 | console.info(arg);
13 | return arg;
14 | };
15 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jest-electron-runner",
3 | "private": true,
4 | "version": "0.0.0",
5 | "devDependencies": {
6 | "lerna": "^2.1.2"
7 | },
8 | "scripts": {
9 | "test": "echo \"Error: no test specified\" && exit 1"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "git+https://github.com/d4rkr00t/jest-electron-runner.git"
14 | },
15 | "keywords": [],
16 | "author": "Stanislav Sysoev <@d4rkr00t>",
17 | "license": "MIT",
18 | "bugs": {
19 | "url": "https://github.com/d4rkr00t/jest-electron-runner/issues"
20 | },
21 | "homepage": "https://github.com/d4rkr00t/jest-electron-runner#readme"
22 | }
23 |
--------------------------------------------------------------------------------
/packages/jest-environment-electron/index.js:
--------------------------------------------------------------------------------
1 | const { FakeTimers, installCommonGlobals } = require("jest-util");
2 | const mock = require("jest-mock");
3 |
4 | class ElectronEnvironment {
5 | constructor(config) {
6 | const global = (this.global = window);
7 | installCommonGlobals(global, config.globals);
8 | this.moduleMocker = new mock.ModuleMocker(global);
9 | this.fakeTimers = new FakeTimers(global, this.moduleMocker, config);
10 | }
11 |
12 | dispose() {
13 | if (this.fakeTimers) {
14 | this.fakeTimers.dispose();
15 | }
16 | this.fakeTimers = null;
17 | }
18 |
19 | runScript(script) {
20 | return script.runInThisContext();
21 | }
22 | }
23 |
24 | module.exports = ElectronEnvironment;
25 |
--------------------------------------------------------------------------------
/packages/jest-environment-electron/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jest-environment-electron",
3 | "version": "0.0.1",
4 | "description": "Jest environment for Electron",
5 | "main": "index.js",
6 | "dependencies": {
7 | "jest-mock": "^21.0.2",
8 | "jest-util": "^21.0.2"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "git+https://github.com/d4rkr00t/jest-electron-runner.git"
13 | },
14 | "keywords": [
15 | "jest",
16 | "environment",
17 | "jest environment",
18 | "electron"
19 | ],
20 | "author": "Stanislav Sysoev <@d4rkr00t>",
21 | "license": "MIT",
22 | "bugs": {
23 | "url": "https://github.com/d4rkr00t/jest-electron-runner/issues"
24 | },
25 | "homepage": "https://github.com/d4rkr00t/jest-electron-runner#readme"
26 | }
27 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # jest-electron-runner
2 |
3 | Jest test runner that executes tests in Electron's BrowserWindow environment, which gives you access to all browser APIs
4 | available there.
5 |
6 | > EXPERIMENTAL. NOT READY FOR ANY SERIOUS USE !!!!
7 |
8 | 
9 |
10 | ## How to use
11 |
12 | Install required packages:
13 |
14 | ```sh
15 | npm install jest jest-electron-runner jest-environment-electron electron
16 | ```
17 |
18 | Configure jest:
19 | ```js
20 | "jest": {
21 | "runner": "jest-electron-runner",
22 | // Environment is important, otherwise jest will use jsdom by default.
23 | "testEnvironment": "jest-environment-electron"
24 | }
25 | ```
26 |
27 | ## TODO:
28 | - [ ] Coverage
29 | - [ ] Investigate performance with a lot of test suits
30 |
--------------------------------------------------------------------------------
/example/__tests__/index.test.ts:
--------------------------------------------------------------------------------
1 | import { add, useDOMApi, printMe } from "../index";
2 |
3 | describe("Some test", () => {
4 | describe("#add", () => {
5 | test("should add 1 + 2", () => {
6 | expect(add(1, 2)).toBe(3);
7 | });
8 |
9 | test("should add 1 + 1", () => {
10 | expect(add(1, 1)).toBe(3);
11 | });
12 |
13 | test("test with snapshot", () => {
14 | expect({ a: add(1, 2), b: add(3, 4) }).toMatchSnapshot();
15 | });
16 | });
17 |
18 | describe("#useDOMApi", () => {
19 | test("should work", () => {
20 | expect(() => useDOMApi()).not.toThrow();
21 | });
22 | });
23 |
24 | describe("#printMe", () => {
25 | test("shouldn't break transport", () => {
26 | const msg = "BREAK TRANSPORT!";
27 | expect(printMe(msg)).toBe(msg);
28 | });
29 | });
30 | });
31 |
--------------------------------------------------------------------------------
/example/__tests__/index.test.js:
--------------------------------------------------------------------------------
1 | const { add, useDOMApi, printMe } = require("../index");
2 |
3 | describe("Some test", () => {
4 | describe("#add", () => {
5 | test("should add 1 + 2", () => {
6 | expect(add(1, 2)).toBe(3);
7 | });
8 |
9 | test("should add 1 + 1", () => {
10 | expect(add(1, 1)).toBe(3);
11 | });
12 |
13 | test("test with snapshot", () => {
14 | expect({ a: add(1, 2), b: add(3, 4) }).toMatchSnapshot();
15 | });
16 | });
17 |
18 | describe("#useDOMApi", () => {
19 | test("should work", () => {
20 | expect(() => useDOMApi()).not.toThrow();
21 | });
22 | });
23 |
24 | describe("#printMe", () => {
25 | test("shouldn't break transport", () => {
26 | const msg = "BREAK TRANSPORT!";
27 | expect(printMe(msg)).toBe(msg);
28 | });
29 | });
30 | });
31 |
--------------------------------------------------------------------------------
/packages/jest-electron-runner/electron-runner.js:
--------------------------------------------------------------------------------
1 | const os = require("os");
2 | const { ipcRenderer, remote } = require("electron");
3 | const Runtime = require("jest-runtime");
4 | const runTest = require("jest-runner/build/run_test");
5 |
6 | ipcRenderer.on("run", (evt, { file, config, globalConfig }) => {
7 | Runtime.createHasteMap(config, {
8 | maxWorkers: os.cpus().length - 1,
9 | watchman: globalConfig.watchman
10 | })
11 | .build()
12 | .then(hasteMap => {
13 | const resolver = Runtime.createResolver(config, hasteMap.moduleMap);
14 | runTest(file, globalConfig, config, resolver)
15 | .then(results => {
16 | ipcRenderer.send("test-results", results);
17 | window.close();
18 | })
19 | .catch(e => {
20 | ipcRenderer.send(
21 | "error",
22 | e instanceof Error ? e.message : e.toString()
23 | );
24 | window.close();
25 | });
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/packages/jest-electron-runner/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jest-electron-runner",
3 | "version": "0.0.2",
4 | "description": "Jest TestRunner to run tests in Electron environment",
5 | "main": "index.js",
6 | "dependencies": {
7 | "jest-runner": "^21.0.2",
8 | "minimist": "^1.2.0",
9 | "projector-spawn": "^1.0.1",
10 | "supports-color": "^4.4.0",
11 | "throat": "^4.1.0"
12 | },
13 | "peerDependencies": {
14 | "electron": ">= 1.7.5",
15 | "jest-environment-electron": "*"
16 | },
17 | "repository": {
18 | "type": "git",
19 | "url": "git+https://github.com/d4rkr00t/jest-electron-runner.git"
20 | },
21 | "keywords": [
22 | "jest",
23 | "test runner",
24 | "electron"
25 | ],
26 | "author": "Stanislav Sysoev <@d4rkr00t>",
27 | "license": "MIT",
28 | "bugs": {
29 | "url": "https://github.com/d4rkr00t/jest-electron-runner/issues"
30 | },
31 | "homepage": "https://github.com/d4rkr00t/jest-electron-runner#readme"
32 | }
33 |
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example",
3 | "private": true,
4 | "version": "0.0.0",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "jest"
8 | },
9 | "devDependencies": {
10 | "@types/jest": "^20.0.8",
11 | "electron": "^1.7.6",
12 | "jest": "^21.0.1",
13 | "ts-jest": "^21.0.0",
14 | "typescript": "^2.5.2"
15 | },
16 | "keywords": [],
17 | "author": "Stanislav Sysoev <@d4rkr00t>",
18 | "license": "MIT",
19 | "jest": {
20 | "runner": "/../packages/jest-electron-runner/index.js",
21 | "transform": {
22 | "^.+\\.tsx?$": "/node_modules/ts-jest/preprocessor.js"
23 | },
24 | "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
25 | "moduleFileExtensions": [
26 | "ts",
27 | "tsx",
28 | "js",
29 | "jsx",
30 | "json"
31 | ],
32 | "collectCoverageFrom": [
33 | "./example/*.js"
34 | ],
35 | "testEnvironment": "./../packages/jest-environment-electron/index.js"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/packages/jest-electron-runner/electron-transport.js:
--------------------------------------------------------------------------------
1 | const { app, BrowserWindow, ipcMain } = require("electron");
2 | const minimist = require("minimist");
3 |
4 | const args = minimist(process.argv.slice(2));
5 |
6 | function run() {
7 | let runner = new BrowserWindow({
8 | title: "Jest",
9 | show: false,
10 | contextIsolation: true
11 | });
12 |
13 | ipcMain.on("test-results", (evt, result) => {
14 | try {
15 | process.send({ type: "result", data: result });
16 | } catch (e) {}
17 | });
18 |
19 | ipcMain.on("error", (evt, error) => {
20 | try {
21 | process.send({ type: "error", data: error });
22 | } catch (e) {}
23 | });
24 |
25 | runner.loadURL(`file://${__dirname}/runner.html`);
26 |
27 | process.on("message", msg => {
28 | runner.webContents.on("did-finish-load", () => {
29 | runner.webContents.send("run", {
30 | file: msg.file,
31 | globalConfig: msg.globalConfig,
32 | config: msg.config
33 | });
34 | });
35 | });
36 |
37 | process.send({ type: "ready" });
38 | }
39 |
40 | app.on("ready", run);
41 |
42 | app.on("window-all-closed", function() {
43 | app.quit();
44 | });
45 |
46 | if (process.platform === "darwin") {
47 | app.dock.hide();
48 | }
49 |
--------------------------------------------------------------------------------
/packages/jest-electron-runner/index.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const spawn = require("child_process").spawn;
3 | const electron = require("electron");
4 | const throat = require("throat");
5 | const supportsColor = require("supports-color");
6 |
7 | class ElectronTestRunner {
8 | constructor(globalConfig) {
9 | this._globalConfig = globalConfig;
10 | }
11 |
12 | runTests(tests, watcher, onStart, onResult, onFailure, options) {
13 | const mutex = throat(this._globalConfig.maxWorkers);
14 | return Promise.all(
15 | tests.map(test =>
16 | mutex(
17 | () =>
18 | new Promise((resolve, reject) => {
19 | if (watcher.isInterrupted()) {
20 | throw new CancelRun();
21 | }
22 |
23 | const env = Object.assign({}, process.env, {
24 | FORCE_COLOR: supportsColor.level
25 | });
26 |
27 | onStart(test)
28 | .then(() =>
29 | spawn(
30 | electron,
31 | [path.join(__dirname, "electron-transport.js")],
32 | { stdio: ["ipc"], env }
33 | )
34 | )
35 | .then(electron => {
36 | process.on("exit", code => {
37 | electron.kill();
38 | });
39 |
40 | electron.on("message", message => {
41 | if (message.type === "ready") {
42 | return electron.send({
43 | file: test.path,
44 | globalConfig: this._globalConfig,
45 | config: test.context.config
46 | });
47 | }
48 |
49 | if (message.type === "error") {
50 | return reject(new Error(message.data));
51 | }
52 |
53 | resolve(message.data);
54 | });
55 | })
56 | .catch(e => {
57 | reject(e);
58 | });
59 | })
60 | )
61 | .then(result => onResult(test, result))
62 | .catch(e => onFailure(test, e))
63 | )
64 | );
65 | }
66 | }
67 |
68 | class CancelRun extends Error {}
69 |
70 | module.exports = ElectronTestRunner;
71 |
--------------------------------------------------------------------------------
/example/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Basic Options */
4 | "target":
5 | "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */,
6 | "module":
7 | "commonjs" /* Specify module code generation: 'none', commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
8 | // "lib": [], /* Specify library files to be included in the compilation: */
9 | "allowJs": true /* Allow javascript files to be compiled. */,
10 | // "checkJs": true, /* Report errors in .js files. */
11 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
12 | // "declaration": true, /* Generates corresponding '.d.ts' file. */
13 | // "sourceMap": true, /* Generates corresponding '.map' file. */
14 | // "outFile": "./", /* Concatenate and emit output to single file. */
15 | // "outDir": "./", /* Redirect output structure to the directory. */
16 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
17 | // "removeComments": true, /* Do not emit comments to output. */
18 | // "noEmit": true, /* Do not emit outputs. */
19 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */
20 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
21 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
22 |
23 | /* Strict Type-Checking Options */
24 | "strict": true /* Enable all strict type-checking options. */
25 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
26 | // "strictNullChecks": true, /* Enable strict null checks. */
27 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
28 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
29 |
30 | /* Additional Checks */
31 | // "noUnusedLocals": true, /* Report errors on unused locals. */
32 | // "noUnusedParameters": true, /* Report errors on unused parameters. */
33 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
34 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
35 |
36 | /* Module Resolution Options */
37 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
38 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
39 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
40 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
41 | // "typeRoots": [], /* List of folders to include type definitions from. */
42 | // "types": [], /* Type declaration files to be included in compilation. */
43 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
44 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
45 |
46 | /* Source Map Options */
47 | // "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
48 | // "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */
49 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
50 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
51 |
52 | /* Experimental Options */
53 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
54 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
55 | }
56 | }
57 |
--------------------------------------------------------------------------------