Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,19 @@ To run markdownlint, use `node --run lint:markdown`.

## Testing

We use [Jest](https://jestjs.io) for JavaScript testing.
We use [Vitest](https://vitest.dev) for JavaScript testing.

To run all tests, use `node --run test`.

The specific test commands are defined in `package.json`.
So you can also run the specific tests with other commands, e.g. `node --run test:unit` or `npx jest tests/e2e/env_spec.js`.
The `package.json` scripts expose finer-grained test commands:

- `test:unit` – run unit tests only
- `test:e2e` – execute browser-driven end-to-end tests
- `test:electron` – launch the Electron-based regression suite
- `test:coverage` – collect coverage while running every suite
- `test:watch` – keep Vitest in watch mode for fast local feedback
- `test:ui` – open the Vitest UI dashboard (needs OS file-watch support enabled)
- `test:calendar` – run the legacy calendar debug helper
- `test:css`, `test:markdown`, `test:prettier`, `test:spelling`, `test:js` – lint-only scripts that enforce formatting, spelling, markdown style, and ESLint.

You can invoke any script with `node --run <script>` (or `npm run <script>`). Individual files can still be targeted directly, e.g. `npx vitest run tests/e2e/env_spec.js`.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,6 @@ js/positions.js
# Ignore lock files other than package-lock.json
pnpm-lock.yaml
yarn.lock

# Vitest temporary test files
tests/**/.tmp/
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ planned for 2026-01-01
- [check_config] refactor: improve error handling (#3927)
- [calendar] test: remove "Recurring event per timezone" test (#3929)
- [calendar] chore: remove `requiresVersion: "2.1.0"` (#3932)
- [tests] migrate from `jest` to `vitest` (#3940)

### Fixed

Expand Down
16 changes: 7 additions & 9 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import {defineConfig, globalIgnores} from "eslint/config";
import globals from "globals";
import {flatConfigs as importX} from "eslint-plugin-import-x";
import jest from "eslint-plugin-jest";
import js from "@eslint/js";
import jsdocPlugin from "eslint-plugin-jsdoc";
import packageJson from "eslint-plugin-package-json";
import stylistic from "@stylistic/eslint-plugin";
import vitest from "eslint-plugin-vitest";

export default defineConfig([
globalIgnores(["config/**", "modules/**/*", "!modules/default/**", "js/positions.js"]),
Expand All @@ -16,15 +16,16 @@ export default defineConfig([
globals: {
...globals.browser,
...globals.node,
...vitest.environments.env.globals,
Log: "readonly",
MM: "readonly",
Module: "readonly",
config: "readonly",
moment: "readonly"
}
},
plugins: {js, stylistic},
extends: [importX.recommended, jest.configs["flat/recommended"], "js/recommended", jsdocPlugin.configs["flat/recommended"], "stylistic/all"],
plugins: {js, stylistic, vitest},
extends: [importX.recommended, vitest.configs.recommended, "js/recommended", jsdocPlugin.configs["flat/recommended"], "stylistic/all"],
rules: {
"@stylistic/array-element-newline": ["error", "consistent"],
"@stylistic/arrow-parens": ["error", "always"],
Expand Down Expand Up @@ -57,12 +58,9 @@ export default defineConfig([
"import-x/newline-after-import": "error",
"import-x/order": "error",
"init-declarations": "off",
"jest/consistent-test-it": "warn",
"jest/no-done-callback": "warn",
"jest/prefer-expect-resolves": "warn",
"jest/prefer-mock-promise-shorthand": "warn",
"jest/prefer-to-be": "warn",
"jest/prefer-to-have-length": "warn",
"vitest/consistent-test-it": "warn",
"vitest/prefer-to-be": "warn",
"vitest/prefer-to-have-length": "warn",
"max-lines-per-function": ["warn", 400],
"max-statements": "off",
"no-global-assign": "off",
Expand Down
8 changes: 4 additions & 4 deletions js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ function App () {
async function loadConfig () {
Log.log("Loading config ...");
const defaults = require(`${__dirname}/defaults`);
if (process.env.JEST_WORKER_ID !== undefined) {
// if we are running with jest
if (global.mmTestMode) {
// if we are running in test mode
defaults.address = "0.0.0.0";
}

Expand Down Expand Up @@ -185,10 +185,10 @@ function App () {

if (defaultModules.includes(moduleName)) {
const defaultModuleFolder = path.resolve(`${global.root_path}/modules/default/`, module);
if (process.env.JEST_WORKER_ID === undefined) {
if (!global.mmTestMode) {
moduleFolder = defaultModuleFolder;
} else {
// running in Jest, allow defaultModules placed under moduleDir for testing
// running in test mode, allow defaultModules placed under moduleDir for testing
if (env.modulesDir === "modules" || env.modulesDir === "tests/mocks") {
moduleFolder = defaultModuleFolder;
}
Expand Down
12 changes: 6 additions & 6 deletions js/electron.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ function createWindow () {

const electronOptions = Object.assign({}, electronOptionsDefaults, config.electronOptions);

if (process.env.JEST_WORKER_ID !== undefined && process.env.MOCK_DATE !== undefined) {
// if we are running with jest and we want to mock the current date
if (process.env.MOCK_DATE !== undefined) {
// if we are running tests and we want to mock the current date
const fakeNow = new Date(process.env.MOCK_DATE).valueOf();
Date = class extends Date {
constructor (...args) {
Expand Down Expand Up @@ -114,8 +114,8 @@ function createWindow () {

// Open the DevTools if run with "node --run start:dev"
if (process.argv.includes("dev")) {
if (process.env.JEST_WORKER_ID !== undefined) {
// if we are running with jest
if (process.env.mmTestMode) {
// if we are running tests
const devtools = new BrowserWindow(electronOptions);
mainWindow.webContents.setDevToolsWebContents(devtools.webContents);
}
Expand Down Expand Up @@ -169,8 +169,8 @@ function createWindow () {

// Quit when all windows are closed.
app.on("window-all-closed", function () {
if (process.env.JEST_WORKER_ID !== undefined) {
// if we are running with jest
if (process.env.mmTestMode) {
// if we are running tests
app.quit();
} else {
createWindow();
Expand Down
2 changes: 1 addition & 1 deletion js/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ const Loader = (function () {
if (window.name !== "jsdom") {
moduleFolder = defaultModuleFolder;
} else {
// running in Jest, allow defaultModules placed under moduleDir for testing
// running in test mode, allow defaultModules placed under moduleDir for testing
if (envVars.modulesDir === "modules") {
moduleFolder = defaultModuleFolder;
}
Expand Down
8 changes: 4 additions & 4 deletions js/logger.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// This logger is very simple, but needs to be extended.
(function (root, factory) {
if (typeof exports === "object") {
if (process.env.JEST_WORKER_ID === undefined) {
if (process.env.mmTestMode !== "true") {
const { styleText } = require("node:util");

// add timestamps in front of log messages
Expand Down Expand Up @@ -78,8 +78,8 @@
let logLevel;
let enableLog;
if (typeof exports === "object") {
// in nodejs and not running with jest
enableLog = process.env.JEST_WORKER_ID === undefined;
// in nodejs and not running in test mode
enableLog = process.env.mmTestMode !== "true";
} else {
// in browser and not running with jsdom
enableLog = typeof window === "object" && window.name !== "jsdom";
Expand All @@ -97,7 +97,7 @@
groupEnd: Function.prototype.bind.call(console.groupEnd, console),
time: Function.prototype.bind.call(console.time, console),
timeEnd: Function.prototype.bind.call(console.timeEnd, console),
timeStamp: Function.prototype.bind.call(console.timeStamp, console)
timeStamp: console.timeStamp ? Function.prototype.bind.call(console.timeStamp, console) : function () {}
};

logLevel.setLogLevel = function (newLevel) {
Expand Down
4 changes: 2 additions & 2 deletions js/module_functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
* @param {Promise} callback function to call when the timer expires
*/
const scheduleTimer = function (timer, intervalMS, callback) {
if (process.env.JEST_WORKER_ID === undefined) {
// only set timer when not running in jest
if (process.env.mmTestMode !== "true") {
// only set timer when not running in test mode
let tmr = timer;
clearTimeout(tmr);
tmr = setTimeout(function () {
Expand Down
8 changes: 5 additions & 3 deletions js/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ module.exports = {
].join("\n");
Log.info(systemDataString);

// Return is currently only for jest
// Return is currently only for tests
return systemDataString;
} catch (error) {
Log.error(error);
Expand Down Expand Up @@ -65,8 +65,10 @@ module.exports = {
if (results && results.length > 0) {
// get the position parts and replace space with underscore
const positionName = results[1].replace(" ", "_");
// add it to the list
modulePositions.push(positionName);
// add it to the list only if not already present (avoid duplicates)
if (!modulePositions.includes(positionName)) {
modulePositions.push(positionName);
}
}
});
try {
Expand Down
Loading
Loading