Merge pull request #2664 from khassel/test-setup

This commit is contained in:
Michael Teeuw 2021-09-28 11:51:19 +02:00 committed by GitHub
commit e10f620cf9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 719 additions and 706 deletions

View File

@ -23,7 +23,6 @@ jobs:
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
- run: | - run: |
node -v
Xvfb :99 -screen 0 1024x768x16 & Xvfb :99 -screen 0 1024x768x16 &
export DISPLAY=:99 export DISPLAY=:99
npm install npm install
@ -32,3 +31,4 @@ jobs:
npm run test:css npm run test:css
npm run test:unit npm run test:unit
npm run test:e2e npm run test:e2e
npm run test:electron

View File

@ -27,6 +27,7 @@ _This release is scheduled to be released on 2021-10-01._
- Refactored methods from weatherproviders into weatherobject (isDaytime, updateSunTime). - Refactored methods from weatherproviders into weatherobject (isDaytime, updateSunTime).
- Use of `logger.js` in jest tests. - Use of `logger.js` in jest tests.
- Run prettier over all relevant files. - Run prettier over all relevant files.
- Move test needing electron in new category `electron`, use `server only` mode in `e2e` tests.
### Fixed ### Fixed

View File

@ -48,6 +48,7 @@ process.on("uncaughtException", function (err) {
*/ */
function App() { function App() {
let nodeHelpers = []; let nodeHelpers = [];
let httpServer;
/** /**
* Loads the config file. Combines it with the defaults, and runs the * Loads the config file. Combines it with the defaults, and runs the
@ -222,7 +223,7 @@ function App() {
} }
loadModules(modules, function () { loadModules(modules, function () {
const server = new Server(config, function (app, io) { httpServer = new Server(config, function (app, io) {
Log.log("Server started ..."); Log.log("Server started ...");
for (let nodeHelper of nodeHelpers) { for (let nodeHelper of nodeHelpers) {
@ -253,6 +254,7 @@ function App() {
nodeHelper.stop(); nodeHelper.stop();
} }
} }
httpServer.close();
}; };
/** /**

View File

@ -9,12 +9,13 @@
*/ */
(function (root, factory) { (function (root, factory) {
if (typeof exports === "object") { if (typeof exports === "object") {
if (process.env.JEST_WORKER_ID === undefined) {
// add timestamps in front of log messages // add timestamps in front of log messages
require("console-stamp")(console, { require("console-stamp")(console, {
pattern: "yyyy-mm-dd HH:MM:ss.l", pattern: "yyyy-mm-dd HH:MM:ss.l",
include: ["debug", "log", "info", "warn", "error"] include: ["debug", "log", "info", "warn", "error"]
}); });
}
// Node, CommonJS-like // Node, CommonJS-like
module.exports = factory(root.config); module.exports = factory(root.config);
} else { } else {
@ -22,7 +23,9 @@
root.Log = factory(root.config); root.Log = factory(root.config);
} }
})(this, function (config) { })(this, function (config) {
let logLevel = { let logLevel;
if ((typeof exports === "object" && process.env.JEST_WORKER_ID === undefined) || typeof exports !== "object") {
logLevel = {
debug: Function.prototype.bind.call(console.debug, console), debug: Function.prototype.bind.call(console.debug, console),
log: Function.prototype.bind.call(console.log, console), log: Function.prototype.bind.call(console.log, console),
info: Function.prototype.bind.call(console.info, console), info: Function.prototype.bind.call(console.info, console),
@ -32,13 +35,10 @@
groupCollapsed: Function.prototype.bind.call(console.groupCollapsed, console), groupCollapsed: Function.prototype.bind.call(console.groupCollapsed, console),
groupEnd: Function.prototype.bind.call(console.groupEnd, console), groupEnd: Function.prototype.bind.call(console.groupEnd, console),
time: Function.prototype.bind.call(console.time, console), time: Function.prototype.bind.call(console.time, console),
timeEnd: Function.prototype.bind.call(console.timeEnd, console) timeEnd: Function.prototype.bind.call(console.timeEnd, console),
timeStamp: Function.prototype.bind.call(console.timeStamp, console)
}; };
if ((typeof exports === "object" && process.env.JEST_WORKER_ID === undefined) || typeof exports !== "object") {
logLevel.timeStamp = Function.prototype.bind.call(console.timeStamp, console);
}
logLevel.setLogLevel = function (newLevel) { logLevel.setLogLevel = function (newLevel) {
if (newLevel) { if (newLevel) {
Object.keys(logLevel).forEach(function (key, index) { Object.keys(logLevel).forEach(function (key, index) {
@ -48,6 +48,23 @@
}); });
} }
}; };
} else {
logLevel = {
debug: function () {},
log: function () {},
info: function () {},
warn: function () {},
error: function () {},
group: function () {},
groupCollapsed: function () {},
groupEnd: function () {},
time: function () {},
timeEnd: function () {},
timeStamp: function () {}
};
logLevel.setLogLevel = function () {};
}
return logLevel; return logLevel;
}); });

View File

@ -92,6 +92,10 @@ function Server(config, callback) {
if (typeof callback === "function") { if (typeof callback === "function") {
callback(app, io); callback(app, io);
} }
this.close = function () {
server.close();
};
} }
module.exports = Server; module.exports = Server;

1016
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -12,6 +12,7 @@
"postinstall": "npm run install-fonts && echo \"MagicMirror installation finished successfully! \n\"", "postinstall": "npm run install-fonts && echo \"MagicMirror installation finished successfully! \n\"",
"test": "NODE_ENV=test jest -i --forceExit", "test": "NODE_ENV=test jest -i --forceExit",
"test:coverage": "NODE_ENV=test nyc --reporter=lcov --reporter=text jest -i --forceExit", "test:coverage": "NODE_ENV=test nyc --reporter=lcov --reporter=text jest -i --forceExit",
"test:electron": "NODE_ENV=test jest --selectProjects electron -i --forceExit",
"test:e2e": "NODE_ENV=test jest --selectProjects e2e -i --forceExit", "test:e2e": "NODE_ENV=test jest --selectProjects e2e -i --forceExit",
"test:unit": "NODE_ENV=test jest --selectProjects unit -i --forceExit", "test:unit": "NODE_ENV=test jest --selectProjects unit -i --forceExit",
"test:prettier": "prettier . --check", "test:prettier": "prettier . --check",
@ -51,11 +52,11 @@
"eslint-plugin-prettier": "^4.0.0", "eslint-plugin-prettier": "^4.0.0",
"express-basic-auth": "^1.2.0", "express-basic-auth": "^1.2.0",
"husky": "^7.0.2", "husky": "^7.0.2",
"jest": "^27.1.1", "jest": "^27.2.0",
"jsdom": "^17.0.0", "jsdom": "^17.0.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"nyc": "^15.1.0", "nyc": "^15.1.0",
"prettier": "^2.4.0", "prettier": "^2.4.1",
"pretty-quick": "^3.1.1", "pretty-quick": "^3.1.1",
"sinon": "^11.1.2", "sinon": "^11.1.2",
"spectron": "^15.0.0", "spectron": "^15.0.0",
@ -66,7 +67,7 @@
"suncalc": "^1.8.0" "suncalc": "^1.8.0"
}, },
"optionalDependencies": { "optionalDependencies": {
"electron": "^13.3.0" "electron": "^13.4.0"
}, },
"dependencies": { "dependencies": {
"colors": "^1.4.0", "colors": "^1.4.0",
@ -106,13 +107,26 @@
"<rootDir>/tests/unit/mocks" "<rootDir>/tests/unit/mocks"
] ]
}, },
{
"displayName": "electron",
"testMatch": [
"**/tests/electron/**/*.[jt]s?(x)"
],
"testPathIgnorePatterns": [
"<rootDir>/tests/electron/modules/mocks",
"<rootDir>/tests/electron/global-setup.js",
"<rootDir>/tests/electron/modules/basic-auth.js"
]
},
{ {
"displayName": "e2e", "displayName": "e2e",
"testMatch": [ "testMatch": [
"**/tests/e2e/**/*.[jt]s?(x)" "**/tests/e2e/**/*.[jt]s?(x)"
], ],
"modulePaths": [
"<rootDir>/js/"
],
"testPathIgnorePatterns": [ "testPathIgnorePatterns": [
"<rootDir>/tests/e2e/modules/mocks",
"<rootDir>/tests/e2e/global-setup.js" "<rootDir>/tests/e2e/global-setup.js"
] ]
} }

View File

@ -1,42 +1,13 @@
const helpers = require("./global-setup");
const fetch = require("node-fetch"); const fetch = require("node-fetch");
const helpers = require("./global-setup");
describe("Electron app environment", function () {
helpers.setupTimeout(this);
let app = null; let app = null;
describe("Electron app environment", function () {
beforeAll(function () { beforeAll(function () {
// Set config sample for use in test app = helpers.startApplication("tests/configs/env.js");
process.env.MM_CONFIG_FILE = "tests/configs/env.js";
}); });
afterAll(function () {
beforeEach(function () { helpers.stopApplication(app);
return helpers
.startApplication({
args: ["js/electron.js"]
})
.then(function (startedApp) {
app = startedApp;
});
});
afterEach(function () {
return helpers.stopApplication(app);
});
it("should open a browserwindow", async function () {
await app.client.waitUntilWindowLoaded();
app.browserWindow.focus();
expect(await app.client.getWindowCount()).toBe(1);
expect(await app.browserWindow.isMinimized()).toBe(false);
expect(await app.browserWindow.isDevToolsOpened()).toBe(false);
expect(await app.browserWindow.isVisible()).toBe(true);
expect(await app.browserWindow.isFocused()).toBe(true);
const bounds = await app.browserWindow.getBounds();
expect(bounds.width).toBeGreaterThan(0);
expect(bounds.height).toBeGreaterThan(0);
expect(await app.browserWindow.getTitle()).toBe("MagicMirror²");
}); });
it("get request from http://localhost:8080 should return 200", function (done) { it("get request from http://localhost:8080 should return 200", function (done) {

View File

@ -1,10 +1,8 @@
const helpers = require("./global-setup");
const fetch = require("node-fetch"); const fetch = require("node-fetch");
const helpers = require("./global-setup");
let app = null;
describe("All font files from roboto.css should be downloadable", function () { describe("All font files from roboto.css should be downloadable", function () {
helpers.setupTimeout(this);
let app;
const fontFiles = []; const fontFiles = [];
// Statements below filters out all 'url' lines in the CSS file // Statements below filters out all 'url' lines in the CSS file
const fileContent = require("fs").readFileSync(__dirname + "/../../fonts/roboto.css", "utf8"); const fileContent = require("fs").readFileSync(__dirname + "/../../fonts/roboto.css", "utf8");
@ -18,20 +16,10 @@ describe("All font files from roboto.css should be downloadable", function () {
} }
beforeAll(function () { beforeAll(function () {
// Set config sample for use in test app = helpers.startApplication("tests/configs/without_modules.js");
process.env.MM_CONFIG_FILE = "tests/configs/without_modules.js";
return helpers
.startApplication({
args: ["js/electron.js"]
})
.then(function (startedApp) {
app = startedApp;
}); });
});
afterAll(function () { afterAll(function () {
return helpers.stopApplication(app); helpers.stopApplication(app);
}); });
test.each(fontFiles)("should return 200 HTTP code for file '%s'", (fontFile, done) => { test.each(fontFiles)("should return 200 HTTP code for file '%s'", (fontFile, done) => {

View File

@ -1,53 +1,16 @@
/* exports.startApplication = function (configFilename, exec) {
* Magic Mirror Global Setup Test Suite jest.resetModules();
* // Set config sample for use in test
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com process.env.MM_CONFIG_FILE = configFilename;
* MIT Licensed. if (exec) exec;
*/ const app = require("app.js");
const Application = require("spectron").Application; app.start();
const assert = require("assert");
const path = require("path");
const EventEmitter = require("events");
exports.getElectronPath = function () {
let electronPath = path.join(__dirname, "..", "..", "node_modules", ".bin", "electron");
if (process.platform === "win32") {
electronPath += ".cmd";
}
return electronPath;
};
// Set timeout - if this is run as CI Job, increase timeout
exports.setupTimeout = function (test) {
if (process.env.CI) {
jest.setTimeout(30000);
} else {
jest.setTimeout(10000);
}
};
exports.startApplication = function (options) {
const emitter = new EventEmitter();
emitter.setMaxListeners(100);
options.path = exports.getElectronPath();
if (process.env.CI) {
options.startTimeout = 30000;
}
const app = new Application(options);
return app.start().then(function () {
assert.strictEqual(app.isRunning(), true);
return app; return app;
});
}; };
exports.stopApplication = function (app) { exports.stopApplication = function (app) {
if (!app || !app.isRunning()) { if (app) {
return; app.stop();
} }
return app.stop().then(function () {
assert.strictEqual(app.isRunning(), false);
});
}; };

View File

@ -1,29 +1,14 @@
const helpers = require("./global-setup");
const fetch = require("node-fetch"); const fetch = require("node-fetch");
const helpers = require("./global-setup");
describe("ipWhitelist directive configuration", function () {
helpers.setupTimeout(this);
let app = null; let app = null;
beforeEach(function () { describe("ipWhitelist directive configuration", function () {
return helpers
.startApplication({
args: ["js/electron.js"]
})
.then(function (startedApp) {
app = startedApp;
});
});
afterEach(function () {
return helpers.stopApplication(app);
});
describe("Set ipWhitelist without access", function () { describe("Set ipWhitelist without access", function () {
beforeAll(function () { beforeAll(function () {
// Set config sample for use in test app = helpers.startApplication("tests/configs/noIpWhiteList.js");
process.env.MM_CONFIG_FILE = "tests/configs/noIpWhiteList.js"; });
afterAll(function () {
helpers.stopApplication(app);
}); });
it("should return 403", function (done) { it("should return 403", function (done) {
@ -36,8 +21,10 @@ describe("ipWhitelist directive configuration", function () {
describe("Set ipWhitelist []", function () { describe("Set ipWhitelist []", function () {
beforeAll(function () { beforeAll(function () {
// Set config sample for use in test app = helpers.startApplication("tests/configs/empty_ipWhiteList.js");
process.env.MM_CONFIG_FILE = "tests/configs/empty_ipWhiteList.js"; });
afterAll(function () {
helpers.stopApplication(app);
}); });
it("should return 200", function (done) { it("should return 200", function (done) {

View File

@ -1,29 +1,14 @@
const helpers = require("./global-setup");
const fetch = require("node-fetch"); const fetch = require("node-fetch");
const helpers = require("./global-setup");
describe("port directive configuration", function () {
helpers.setupTimeout(this);
let app = null; let app = null;
beforeEach(function () { describe("port directive configuration", function () {
return helpers
.startApplication({
args: ["js/electron.js"]
})
.then(function (startedApp) {
app = startedApp;
});
});
afterEach(function () {
return helpers.stopApplication(app);
});
describe("Set port 8090", function () { describe("Set port 8090", function () {
beforeAll(function () { beforeAll(function () {
// Set config sample for use in this test app = helpers.startApplication("tests/configs/port_8090.js");
process.env.MM_CONFIG_FILE = "tests/configs/port_8090.js"; });
afterAll(function () {
helpers.stopApplication(app);
}); });
it("should return 200", function (done) { it("should return 200", function (done) {
@ -36,13 +21,10 @@ describe("port directive configuration", function () {
describe("Set port 8100 on environment variable MM_PORT", function () { describe("Set port 8100 on environment variable MM_PORT", function () {
beforeAll(function () { beforeAll(function () {
process.env.MM_PORT = 8100; app = helpers.startApplication("tests/configs/port_8090.js", (process.env.MM_PORT = 8100));
// Set config sample for use in this test
process.env.MM_CONFIG_FILE = "tests/configs/port_8090.js";
}); });
afterAll(function () { afterAll(function () {
delete process.env.MM_PORT; helpers.stopApplication(app);
}); });
it("should return 200", function (done) { it("should return 200", function (done) {

View File

@ -1,24 +1,13 @@
const helpers = require("./global-setup");
const fetch = require("node-fetch"); const fetch = require("node-fetch");
const helpers = require("./global-setup");
describe("Vendors", function () {
helpers.setupTimeout(this);
let app = null; let app = null;
describe("Vendors", function () {
beforeAll(function () { beforeAll(function () {
process.env.MM_CONFIG_FILE = "tests/configs/env.js"; app = helpers.startApplication("tests/configs/env.js");
return helpers
.startApplication({
args: ["js/electron.js"]
})
.then(function (startedApp) {
app = startedApp;
}); });
});
afterAll(function () { afterAll(function () {
return helpers.stopApplication(app); helpers.stopApplication(app);
}); });
describe("Get list vendors", function () { describe("Get list vendors", function () {

View File

@ -0,0 +1,40 @@
const helpers = require("./global-setup");
describe("Electron app environment", function () {
helpers.setupTimeout(this);
let app = null;
beforeAll(function () {
// Set config sample for use in test
process.env.MM_CONFIG_FILE = "tests/configs/env.js";
});
beforeEach(function () {
return helpers
.startApplication({
args: ["js/electron.js"]
})
.then(function (startedApp) {
app = startedApp;
});
});
afterEach(function () {
return helpers.stopApplication(app);
});
it("should open a browserwindow", async function () {
await app.client.waitUntilWindowLoaded();
app.browserWindow.focus();
expect(await app.client.getWindowCount()).toBe(1);
expect(await app.browserWindow.isMinimized()).toBe(false);
expect(await app.browserWindow.isDevToolsOpened()).toBe(false);
expect(await app.browserWindow.isVisible()).toBe(true);
expect(await app.browserWindow.isFocused()).toBe(true);
const bounds = await app.browserWindow.getBounds();
expect(bounds.width).toBeGreaterThan(0);
expect(bounds.height).toBeGreaterThan(0);
expect(await app.browserWindow.getTitle()).toBe("MagicMirror²");
});
});

View File

@ -0,0 +1,53 @@
/*
* Magic Mirror Global Setup Test Suite
*
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed.
*/
const Application = require("spectron").Application;
const assert = require("assert");
const path = require("path");
const EventEmitter = require("events");
exports.getElectronPath = function () {
let electronPath = path.join(__dirname, "..", "..", "node_modules", ".bin", "electron");
if (process.platform === "win32") {
electronPath += ".cmd";
}
return electronPath;
};
// Set timeout - if this is run as CI Job, increase timeout
exports.setupTimeout = function (test) {
if (process.env.CI) {
jest.setTimeout(30000);
} else {
jest.setTimeout(10000);
}
};
exports.startApplication = function (options) {
const emitter = new EventEmitter();
emitter.setMaxListeners(100);
options.path = exports.getElectronPath();
if (process.env.CI) {
options.startTimeout = 30000;
}
const app = new Application(options);
return app.start().then(function () {
assert.strictEqual(app.isRunning(), true);
return app;
});
};
exports.stopApplication = function (app) {
if (!app || !app.isRunning()) {
return;
}
return app.stop().then(function () {
assert.strictEqual(app.isRunning(), false);
});
};

View File

@ -12,7 +12,7 @@ app.use(basicAuth);
// Set available directories // Set available directories
const directories = ["/tests/configs"]; const directories = ["/tests/configs"];
const rootPath = path.resolve(__dirname + "/../../"); const rootPath = path.resolve(__dirname + "/../../../");
for (let directory of directories) { for (let directory of directories) {
app.use(directory, express.static(path.resolve(rootPath + directory))); app.use(directory, express.static(path.resolve(rootPath + directory)));

View File

@ -1,5 +1,5 @@
const helpers = require("../global-setup"); const helpers = require("../global-setup");
const serverBasicAuth = require("../../servers/basic-auth.js"); const serverBasicAuth = require("./basic-auth.js");
describe("Calendar module", function () { describe("Calendar module", function () {
helpers.setupTimeout(this); helpers.setupTimeout(this);