From 4fa72f7b43d1c17bb5a90d0299d2e5b5cc8230d6 Mon Sep 17 00:00:00 2001 From: Kamil Piechaczek Date: Thu, 12 Sep 2024 18:18:52 +0200 Subject: [PATCH 01/15] Make release scripts ready for ESM. --- .eslintrc.js => .eslintrc.cjs | 1 + package.json | 1 + scripts/changelog.js | 6 +-- scripts/ci/is-project-ready-to-release.js | 7 +--- scripts/postinstall.js | 46 +++++++++++++---------- scripts/preparepackages.js | 18 ++++----- scripts/publishpackages.js | 15 +++----- scripts/runtest.js | 14 +++---- scripts/utils/constants.js | 14 ++++--- scripts/utils/getlistroptions.js | 6 +-- scripts/utils/parsearguments.js | 11 +++--- scripts/utils/runbuildcommand.js | 17 +++------ 12 files changed, 73 insertions(+), 83 deletions(-) rename .eslintrc.js => .eslintrc.cjs (98%) diff --git a/.eslintrc.js b/.eslintrc.cjs similarity index 98% rename from .eslintrc.js rename to .eslintrc.cjs index a0c5542d9..626d46230 100644 --- a/.eslintrc.js +++ b/.eslintrc.cjs @@ -36,6 +36,7 @@ module.exports = { { files: [ // TODO: add packages as they are migrated to ESM. + './scripts/**/*', './packages/ckeditor5-dev-utils/**/*', './packages/ckeditor5-dev-translations/**/*', './packages/ckeditor5-dev-release-tools/**/*', diff --git a/package.json b/package.json index cabf2e784..11c9d2be8 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "engines": { "node": ">=18.0.0" }, + "type": "module", "devDependencies": { "@ckeditor/ckeditor5-dev-ci": "^43.0.0", "@ckeditor/ckeditor5-dev-release-tools": "^43.0.0", diff --git a/scripts/changelog.js b/scripts/changelog.js index af403d932..8ff5952be 100755 --- a/scripts/changelog.js +++ b/scripts/changelog.js @@ -5,10 +5,8 @@ * For licensing, see LICENSE.md. */ -'use strict'; - -const { generateChangelogForMonoRepository } = require( '@ckeditor/ckeditor5-dev-release-tools' ); -const parseArguments = require( './utils/parsearguments' ); +import { generateChangelogForMonoRepository } from '@ckeditor/ckeditor5-dev-release-tools'; +import parseArguments from './utils/parsearguments.js'; const cliArguments = parseArguments( process.argv.slice( 2 ) ); diff --git a/scripts/ci/is-project-ready-to-release.js b/scripts/ci/is-project-ready-to-release.js index 5f7af4b3c..3d9cf254d 100644 --- a/scripts/ci/is-project-ready-to-release.js +++ b/scripts/ci/is-project-ready-to-release.js @@ -7,15 +7,12 @@ /* eslint-env node */ -'use strict'; - -const releaseTools = require( '@ckeditor/ckeditor5-dev-release-tools' ); -const { name: packageName } = require( '@ckeditor/ckeditor5-dev-release-tools/package.json' ); +import releaseTools from '@ckeditor/ckeditor5-dev-release-tools'; const changelogVersion = releaseTools.getLastFromChangelog(); const npmTag = releaseTools.getNpmTagFromVersion( changelogVersion ); -releaseTools.isVersionPublishableForTag( packageName, changelogVersion, npmTag ) +releaseTools.isVersionPublishableForTag( '@ckeditor/ckeditor5-dev-release-tools', changelogVersion, npmTag ) .then( result => { if ( !result ) { console.error( `The proposed changelog (${ changelogVersion }) version is not higher than the already published one.` ); diff --git a/scripts/postinstall.js b/scripts/postinstall.js index b02721bdd..4335df430 100644 --- a/scripts/postinstall.js +++ b/scripts/postinstall.js @@ -3,28 +3,34 @@ * For licensing, see LICENSE.md. */ -'use strict'; - /* eslint-env node */ -const path = require( 'path' ); -const fs = require( 'fs' ); -const { execSync } = require( 'child_process' ); +import path from 'path'; +import fs from 'fs'; +import { execSync } from 'child_process'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath( import.meta.url ); +const __dirname = path.dirname( __filename ); const ROOT_DIRECTORY = path.join( __dirname, '..' ); -// When installing a repository as a dependency, the `.git` directory does not exist. -// In such a case, husky should not attach its hooks as npm treats it as a package, not a git repository. -if ( fs.existsSync( path.join( ROOT_DIRECTORY, '.git' ) ) ) { - require( 'husky' ).install(); - - execSync( 'npm run postinstall', { - cwd: path.join( ROOT_DIRECTORY, 'packages', 'ckeditor5-dev-tests' ), - stdio: 'inherit' - } ); - - execSync( 'npm run build', { - cwd: path.join( ROOT_DIRECTORY, 'packages', 'ckeditor5-dev-build-tools' ), - stdio: 'inherit' - } ); -} +( async () => { + // When installing a repository as a dependency, the `.git` directory does not exist. + // In such a case, husky should not attach its hooks as npm treats it as a package, not a git repository. + if ( fs.existsSync( path.join( ROOT_DIRECTORY, '.git' ) ) ) { + const husky = ( await import( 'husky' ) ).default; + + husky.install(); + + execSync( 'npm run postinstall', { + cwd: path.join( ROOT_DIRECTORY, 'packages', 'ckeditor5-dev-tests' ), + stdio: 'inherit' + } ); + + execSync( 'npm run build', { + cwd: path.join( ROOT_DIRECTORY, 'packages', 'ckeditor5-dev-build-tools' ), + stdio: 'inherit' + } ); + } +} )(); diff --git a/scripts/preparepackages.js b/scripts/preparepackages.js index 5ebee8453..a6607c080 100644 --- a/scripts/preparepackages.js +++ b/scripts/preparepackages.js @@ -7,16 +7,14 @@ /* eslint-env node */ -'use strict'; - -const upath = require( 'upath' ); -const { Listr } = require( 'listr2' ); -const { globSync } = require( 'glob' ); -const releaseTools = require( '@ckeditor/ckeditor5-dev-release-tools' ); -const parseArguments = require( './utils/parsearguments' ); -const getListrOptions = require( './utils/getlistroptions' ); -const runBuildCommand = require( './utils/runbuildcommand' ); -const { CKEDITOR5_DEV_ROOT, PACKAGES_DIRECTORY, RELEASE_DIRECTORY } = require( './utils/constants' ); +import upath from 'upath'; +import { Listr } from 'listr2'; +import { globSync } from 'glob'; +import * as releaseTools from '@ckeditor/ckeditor5-dev-release-tools'; +import parseArguments from './utils/parsearguments.js'; +import getListrOptions from './utils/getlistroptions.js'; +import runBuildCommand from './utils/runbuildcommand.js'; +import { CKEDITOR5_DEV_ROOT, PACKAGES_DIRECTORY, RELEASE_DIRECTORY } from './utils/constants.js'; const cliArguments = parseArguments( process.argv.slice( 2 ) ); const latestVersion = releaseTools.getLastFromChangelog(); diff --git a/scripts/publishpackages.js b/scripts/publishpackages.js index 44851ae91..75fa01eaa 100644 --- a/scripts/publishpackages.js +++ b/scripts/publishpackages.js @@ -7,14 +7,11 @@ /* eslint-env node */ -'use strict'; - -const { Listr } = require( 'listr2' ); -const releaseTools = require( '@ckeditor/ckeditor5-dev-release-tools' ); -const { provideToken } = require( '@ckeditor/ckeditor5-dev-release-tools/lib/utils/cli' ); -const parseArguments = require( './utils/parsearguments' ); -const getListrOptions = require( './utils/getlistroptions' ); -const { RELEASE_DIRECTORY } = require( './utils/constants' ); +import { Listr } from 'listr2'; +import * as releaseTools from '@ckeditor/ckeditor5-dev-release-tools'; +import parseArguments from './utils/parsearguments.js'; +import getListrOptions from './utils/getlistroptions.js'; +import { RELEASE_DIRECTORY } from './utils/constants.js'; const cliArguments = parseArguments( process.argv.slice( 2 ) ); const latestVersion = releaseTools.getLastFromChangelog(); @@ -92,7 +89,7 @@ const tasks = new Listr( [ if ( process.env.CKE5_RELEASE_TOKEN ) { githubToken = process.env.CKE5_RELEASE_TOKEN; } else { - githubToken = await provideToken(); + githubToken = await releaseTools.provideToken(); } await tasks.run(); diff --git a/scripts/runtest.js b/scripts/runtest.js index 2b9f11970..37cc060e9 100644 --- a/scripts/runtest.js +++ b/scripts/runtest.js @@ -7,14 +7,12 @@ /* eslint-env node */ -'use strict'; - -const path = require( 'path' ); -const { execSync } = require( 'child_process' ); -const fs = require( 'fs-extra' ); -const { globSync } = require( 'glob' ); -const minimist = require( 'minimist' ); -const chalk = require( 'chalk' ); +import path from 'path'; +import { execSync } from 'child_process'; +import fs from 'fs-extra'; +import { globSync } from 'glob'; +import minimist from 'minimist'; +import chalk from 'chalk'; main(); diff --git a/scripts/utils/constants.js b/scripts/utils/constants.js index 3f5f2a0e9..64846ae46 100644 --- a/scripts/utils/constants.js +++ b/scripts/utils/constants.js @@ -3,10 +3,12 @@ * For licensing, see LICENSE.md. */ -const upath = require( 'upath' ); +import upath from 'upath'; +import { fileURLToPath } from 'url'; -module.exports = { - PACKAGES_DIRECTORY: 'packages', - RELEASE_DIRECTORY: 'release', - CKEDITOR5_DEV_ROOT: upath.join( __dirname, '..', '..' ) -}; +const __filename = fileURLToPath( import.meta.url ); +const __dirname = upath.dirname( __filename ); + +export const PACKAGES_DIRECTORY = 'packages'; +export const RELEASE_DIRECTORY = 'release'; +export const CKEDITOR5_DEV_ROOT = upath.join( __dirname, '..', '..' ); diff --git a/scripts/utils/getlistroptions.js b/scripts/utils/getlistroptions.js index 10f018105..9fae9db69 100644 --- a/scripts/utils/getlistroptions.js +++ b/scripts/utils/getlistroptions.js @@ -5,14 +5,12 @@ /* eslint-env node */ -'use strict'; - /** * @param {ReleaseOptions} cliArguments * @returns {Object} */ -module.exports = function getListrOptions( cliArguments ) { +export default function getListrOptions( cliArguments ) { return { renderer: cliArguments.verbose ? 'verbose' : 'default' }; -}; +} diff --git a/scripts/utils/parsearguments.js b/scripts/utils/parsearguments.js index 5ad862594..54e79d220 100644 --- a/scripts/utils/parsearguments.js +++ b/scripts/utils/parsearguments.js @@ -5,15 +5,14 @@ /* eslint-env node */ -'use strict'; - -const minimist = require( 'minimist' ); +import minimist from 'minimist'; +import os from 'os'; /** * @param {Array.} cliArguments * @returns {ReleaseOptions} options */ -module.exports = function parseArguments( cliArguments ) { +export default function parseArguments( cliArguments ) { const config = { boolean: [ 'verbose', @@ -33,7 +32,7 @@ module.exports = function parseArguments( cliArguments ) { ], default: { - concurrency: require( 'os' ).cpus().length / 2, + concurrency: os.cpus().length / 2, packages: null, ci: false, verbose: false, @@ -60,7 +59,7 @@ module.exports = function parseArguments( cliArguments ) { } return options; -}; +} /** * @typedef {Object} ReleaseOptions diff --git a/scripts/utils/runbuildcommand.js b/scripts/utils/runbuildcommand.js index 3b4427186..19926f4e1 100644 --- a/scripts/utils/runbuildcommand.js +++ b/scripts/utils/runbuildcommand.js @@ -3,21 +3,16 @@ * For licensing, see LICENSE.md. */ -/* eslint-env node */ - -'use strict'; - /** * @param {String} packagePath * @returns {Promise} */ -module.exports = async function runBuildCommand( packagePath ) { - const path = require( 'upath' ); - const { tools } = require( '@ckeditor/ckeditor5-dev-utils' ); +export default async function runBuildCommand( packagePath ) { + const path = ( await import( 'upath' ) ).default; + const { readJson } = ( await import( 'fs-extra' ) ).default; + const { tools } = await import( '@ckeditor/ckeditor5-dev-utils' ); - const packageJson = require( - path.join( packagePath, 'package.json' ) - ); + const packageJson = await readJson( path.join( packagePath, 'package.json' ) ); if ( !packageJson.scripts?.build ) { return; @@ -28,5 +23,5 @@ module.exports = async function runBuildCommand( packagePath ) { verbosity: 'error', async: true } ); -}; +} From 33de1525ea4e9149029612ae1bb3ad58e694eb46 Mon Sep 17 00:00:00 2001 From: Kamil Piechaczek Date: Mon, 16 Sep 2024 09:47:21 +0200 Subject: [PATCH 02/15] Dev tests as ESM. --- .eslintrc.js | 1 + .../ckeditor5-dev-tests/bin/postinstall.js | 7 ++-- .../ckeditor5-dev-tests/bin/testautomated.js | 8 ++-- .../ckeditor5-dev-tests/bin/testmanual.js | 8 ++-- packages/ckeditor5-dev-tests/lib/index.js | 10 ++--- .../lib/tasks/runautomatedtests.js | 32 ++++++++-------- .../lib/tasks/runmanualtests.js | 38 +++++++++---------- .../automated-tests/assertions/attribute.js | 2 +- .../assertions/equal-markup.js | 6 +-- .../utils/automated-tests/getkarmaconfig.js | 14 ++++--- .../utils/automated-tests/getwebpackconfig.js | 18 +++++---- .../utils/automated-tests/karmanotifier.js | 10 +++-- .../utils/automated-tests/parsearguments.js | 14 +++---- .../treatwarningsaserrorswebpackplugin.js | 4 +- .../lib/utils/getdefinitionsfromfile.js | 8 ++-- .../lib/utils/getrelativefilepath.js | 8 ++-- .../utils/manual-tests/compilehtmlfiles.js | 30 ++++++++------- .../lib/utils/manual-tests/compilescripts.js | 14 +++---- .../lib/utils/manual-tests/copyassets.js | 14 +++++-- .../lib/utils/manual-tests/createserver.js | 20 +++++----- .../utils/manual-tests/getwebpackconfig.js | 20 +++++----- .../lib/utils/manual-tests/removedir.js | 12 +++--- .../manual-tests/webpacknotifierplugin.js | 8 ++-- .../lib/utils/requiredll.js | 6 +-- .../utils/transformfileoptiontotestglob.js | 10 ++--- packages/ckeditor5-dev-tests/package.json | 16 ++++---- packages/ckeditor5-dev-tests/vitest.config.js | 23 +++++++++++ 27 files changed, 192 insertions(+), 169 deletions(-) create mode 100644 packages/ckeditor5-dev-tests/vitest.config.js diff --git a/.eslintrc.js b/.eslintrc.js index a0c5542d9..5236b531e 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -36,6 +36,7 @@ module.exports = { { files: [ // TODO: add packages as they are migrated to ESM. + './packages/ckeditor5-dev-tests/**/*', './packages/ckeditor5-dev-utils/**/*', './packages/ckeditor5-dev-translations/**/*', './packages/ckeditor5-dev-release-tools/**/*', diff --git a/packages/ckeditor5-dev-tests/bin/postinstall.js b/packages/ckeditor5-dev-tests/bin/postinstall.js index b064e10c0..b206f01e5 100755 --- a/packages/ckeditor5-dev-tests/bin/postinstall.js +++ b/packages/ckeditor5-dev-tests/bin/postinstall.js @@ -5,10 +5,11 @@ * For licensing, see LICENSE.md. */ -'use strict'; +import isWsl from 'is-wsl'; +import { execSync } from 'child_process'; +import { createRequire } from 'module'; -const isWsl = require( 'is-wsl' ); -const { execSync } = require( 'child_process' ); +const require = createRequire( import.meta.url ); if ( isWsl ) { const executables = [ diff --git a/packages/ckeditor5-dev-tests/bin/testautomated.js b/packages/ckeditor5-dev-tests/bin/testautomated.js index 5e745297a..0a71d9fe8 100755 --- a/packages/ckeditor5-dev-tests/bin/testautomated.js +++ b/packages/ckeditor5-dev-tests/bin/testautomated.js @@ -5,11 +5,9 @@ * For licensing, see LICENSE.md. */ -'use strict'; - -const chalk = require( 'chalk' ); -const path = require( 'path' ); -const tests = require( '../lib/index' ); +import chalk from 'chalk'; +import path from 'path'; +import * as tests from '../lib/index.js'; const options = tests.parseArguments( process.argv.slice( 2 ) ); diff --git a/packages/ckeditor5-dev-tests/bin/testmanual.js b/packages/ckeditor5-dev-tests/bin/testmanual.js index 62dbb768d..3f1f98c12 100755 --- a/packages/ckeditor5-dev-tests/bin/testmanual.js +++ b/packages/ckeditor5-dev-tests/bin/testmanual.js @@ -5,11 +5,9 @@ * For licensing, see LICENSE.md. */ -'use strict'; - -const chalk = require( 'chalk' ); -const path = require( 'path' ); -const tests = require( '../lib/index' ); +import chalk from 'chalk'; +import path from 'path'; +import * as tests from '../lib/index.js'; const options = tests.parseArguments( process.argv.slice( 2 ) ); diff --git a/packages/ckeditor5-dev-tests/lib/index.js b/packages/ckeditor5-dev-tests/lib/index.js index ea0d64110..d519ff94e 100644 --- a/packages/ckeditor5-dev-tests/lib/index.js +++ b/packages/ckeditor5-dev-tests/lib/index.js @@ -3,10 +3,6 @@ * For licensing, see LICENSE.md. */ -'use strict'; - -module.exports = { - runAutomatedTests: require( './tasks/runautomatedtests' ), - runManualTests: require( './tasks/runmanualtests' ), - parseArguments: require( './utils/automated-tests/parsearguments' ) -}; +export { default as runAutomatedTests } from './tasks/runautomatedtests.js'; +export { default as runManualTests } from './tasks/runmanualtests.js'; +export { default as parseArguments } from './utils/automated-tests/parsearguments.js'; diff --git a/packages/ckeditor5-dev-tests/lib/tasks/runautomatedtests.js b/packages/ckeditor5-dev-tests/lib/tasks/runautomatedtests.js index 055f52c9c..a60467a7e 100644 --- a/packages/ckeditor5-dev-tests/lib/tasks/runautomatedtests.js +++ b/packages/ckeditor5-dev-tests/lib/tasks/runautomatedtests.js @@ -3,18 +3,21 @@ * For licensing, see LICENSE.md. */ -'use strict'; - -const fs = require( 'fs' ); -const path = require( 'path' ); -const getKarmaConfig = require( '../utils/automated-tests/getkarmaconfig' ); -const chalk = require( 'chalk' ); -const { globSync } = require( 'glob' ); -const minimatch = require( 'minimatch' ); -const mkdirp = require( 'mkdirp' ); -const karmaLogger = require( 'karma/lib/logger.js' ); -const karma = require( 'karma' ); -const transformFileOptionToTestGlob = require( '../utils/transformfileoptiontotestglob' ); +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import { logger } from '@ckeditor/ckeditor5-dev-utils'; +import getKarmaConfig from '../utils/automated-tests/getkarmaconfig.js'; +import chalk from 'chalk'; +import { globSync } from 'glob'; +import minimatch from 'minimatch'; +import mkdirp from 'mkdirp'; +import karmaLogger from 'karma/lib/logger.js'; +import karma from 'karma'; +import transformFileOptionToTestGlob from '../utils/transformfileoptiontotestglob.js'; + +const __filename = fileURLToPath( import.meta.url ); +const __dirname = path.dirname( __filename ); // Glob patterns that should be ignored. It means if a specified test file is located under path // that matches to these patterns, the file will be skipped. @@ -28,7 +31,7 @@ const IGNORE_GLOBS = [ // An absolute path to the entry file that will be passed to Karma. const ENTRY_FILE_PATH = path.posix.join( process.cwd(), 'build', '.automated-tests', 'entry-point.js' ); -module.exports = function runAutomatedTests( options ) { +export default function runAutomatedTests( options ) { return Promise.resolve().then( () => { if ( !options.production ) { console.warn( chalk.yellow( @@ -48,7 +51,7 @@ module.exports = function runAutomatedTests( options ) { return runKarma( optionsForKarma ); } ); -}; +} function transformFilesToTestGlob( files ) { if ( !Array.isArray( files ) || files.length === 0 ) { @@ -211,7 +214,6 @@ function runKarma( options ) { server.on( 'run_complete', () => { // Use timeout to not write to the console in the middle of Karma's status. setTimeout( () => { - const { logger } = require( '@ckeditor/ckeditor5-dev-utils' ); const log = logger(); log.info( `Coverage report saved in '${ chalk.cyan( coveragePath ) }'.` ); diff --git a/packages/ckeditor5-dev-tests/lib/tasks/runmanualtests.js b/packages/ckeditor5-dev-tests/lib/tasks/runmanualtests.js index 1f8862375..58931749d 100644 --- a/packages/ckeditor5-dev-tests/lib/tasks/runmanualtests.js +++ b/packages/ckeditor5-dev-tests/lib/tasks/runmanualtests.js @@ -3,24 +3,22 @@ * For licensing, see LICENSE.md. */ -'use strict'; - -const fs = require( 'fs' ); -const path = require( 'path' ); -const chalk = require( 'chalk' ); -const { globSync } = require( 'glob' ); -const { spawn } = require( 'child_process' ); -const inquirer = require( 'inquirer' ); -const isInteractive = require( 'is-interactive' ); -const { Server: SocketServer } = require( 'socket.io' ); -const { logger } = require( '@ckeditor/ckeditor5-dev-utils' ); -const createManualTestServer = require( '../utils/manual-tests/createserver' ); -const compileManualTestScripts = require( '../utils/manual-tests/compilescripts' ); -const compileManualTestHtmlFiles = require( '../utils/manual-tests/compilehtmlfiles' ); -const copyAssets = require( '../utils/manual-tests/copyassets' ); -const removeDir = require( '../utils/manual-tests/removedir' ); -const transformFileOptionToTestGlob = require( '../utils/transformfileoptiontotestglob' ); -const requireDll = require( '../utils/requiredll' ); +import fs from 'fs'; +import path from 'path'; +import chalk from 'chalk'; +import { globSync } from 'glob'; +import { spawn } from 'child_process'; +import inquirer from 'inquirer'; +import isInteractive from 'is-interactive'; +import { Server as SocketServer } from 'socket.io'; +import { logger } from '@ckeditor/ckeditor5-dev-utils'; +import createManualTestServer from '../utils/manual-tests/createserver.js'; +import compileManualTestScripts from '../utils/manual-tests/compilescripts.js'; +import compileManualTestHtmlFiles from '../utils/manual-tests/compilehtmlfiles.js'; +import copyAssets from '../utils/manual-tests/copyassets.js'; +import removeDir from '../utils/manual-tests/removedir.js'; +import transformFileOptionToTestGlob from '../utils/transformfileoptiontotestglob.js'; +import requireDll from '../utils/requiredll.js'; /** * Main function that runs manual tests. @@ -41,7 +39,7 @@ const requireDll = require( '../utils/requiredll' ); * @param {Boolean} [options.silent=false] Whether to hide files that will be processed by the script. * @returns {Promise} */ -module.exports = function runManualTests( options ) { +export default function runManualTests( options ) { const log = logger(); const cwd = process.cwd(); const buildDir = path.join( cwd, 'build', '.manual-tests' ); @@ -217,4 +215,4 @@ module.exports = function runManualTests( options ) { } ); } ); } -}; +} diff --git a/packages/ckeditor5-dev-tests/lib/utils/automated-tests/assertions/attribute.js b/packages/ckeditor5-dev-tests/lib/utils/automated-tests/assertions/attribute.js index 5976f6b31..c4556ff09 100644 --- a/packages/ckeditor5-dev-tests/lib/utils/automated-tests/assertions/attribute.js +++ b/packages/ckeditor5-dev-tests/lib/utils/automated-tests/assertions/attribute.js @@ -8,7 +8,7 @@ * * @param {Chai} chai */ -module.exports = chai => { +export default chai => { /** * Asserts that the target has an attribute with the given key name. * diff --git a/packages/ckeditor5-dev-tests/lib/utils/automated-tests/assertions/equal-markup.js b/packages/ckeditor5-dev-tests/lib/utils/automated-tests/assertions/equal-markup.js index 8d586ea6a..9dd6b42a0 100644 --- a/packages/ckeditor5-dev-tests/lib/utils/automated-tests/assertions/equal-markup.js +++ b/packages/ckeditor5-dev-tests/lib/utils/automated-tests/assertions/equal-markup.js @@ -3,15 +3,15 @@ * For licensing, see LICENSE.md. */ -const AssertionError = require( 'assertion-error' ); -const { html_beautify: beautify } = require( 'js-beautify/js/lib/beautify-html' ); +import AssertionError from 'assertion-error'; +import { html_beautify as beautify } from 'js-beautify/js/lib/beautify-html.js'; /** * Factory function that registers the `equalMarkup` assertion. * * @param {Chai} chai */ -module.exports = chai => { +export default chai => { /** * Custom assertion that tests whether two given strings containing markup language are equal. * Unlike `expect().to.equal()` form Chai assertion library, this assertion formats the markup before showing a diff. diff --git a/packages/ckeditor5-dev-tests/lib/utils/automated-tests/getkarmaconfig.js b/packages/ckeditor5-dev-tests/lib/utils/automated-tests/getkarmaconfig.js index 8ae15c36d..6e66ed469 100644 --- a/packages/ckeditor5-dev-tests/lib/utils/automated-tests/getkarmaconfig.js +++ b/packages/ckeditor5-dev-tests/lib/utils/automated-tests/getkarmaconfig.js @@ -3,10 +3,14 @@ * For licensing, see LICENSE.md. */ -'use strict'; +import path from 'path'; +import getWebpackConfigForAutomatedTests from './getwebpackconfig.js'; +import { createRequire } from 'module'; +import { fileURLToPath } from 'url'; -const path = require( 'path' ); -const getWebpackConfigForAutomatedTests = require( './getwebpackconfig' ); +const require = createRequire( import.meta.url ); +const __filename = fileURLToPath( import.meta.url ); +const __dirname = path.dirname( __filename ); const AVAILABLE_REPORTERS = [ 'mocha', @@ -17,7 +21,7 @@ const AVAILABLE_REPORTERS = [ * @param {Object} options * @returns {Object} */ -module.exports = function getKarmaConfig( options ) { +export default function getKarmaConfig( options ) { if ( !AVAILABLE_REPORTERS.includes( options.reporter ) ) { throw new Error( `Specified reporter is not supported. Available reporters: ${ AVAILABLE_REPORTERS.join( ', ' ) }.` ); } @@ -199,7 +203,7 @@ module.exports = function getKarmaConfig( options ) { } return karmaConfig; -}; +} // Returns the value of Karma's browser option. // @returns {Array|null} diff --git a/packages/ckeditor5-dev-tests/lib/utils/automated-tests/getwebpackconfig.js b/packages/ckeditor5-dev-tests/lib/utils/automated-tests/getwebpackconfig.js index a15cc5770..1021e5da8 100644 --- a/packages/ckeditor5-dev-tests/lib/utils/automated-tests/getwebpackconfig.js +++ b/packages/ckeditor5-dev-tests/lib/utils/automated-tests/getwebpackconfig.js @@ -3,19 +3,21 @@ * For licensing, see LICENSE.md. */ -'use strict'; +import path from 'path'; +import webpack from 'webpack'; +import { loaders } from '@ckeditor/ckeditor5-dev-utils'; +import getDefinitionsFromFile from '../getdefinitionsfromfile.js'; +import TreatWarningsAsErrorsWebpackPlugin from './treatwarningsaserrorswebpackplugin.js'; +import { fileURLToPath } from 'url'; -const path = require( 'path' ); -const webpack = require( 'webpack' ); -const { loaders } = require( '@ckeditor/ckeditor5-dev-utils' ); -const getDefinitionsFromFile = require( '../getdefinitionsfromfile' ); -const TreatWarningsAsErrorsWebpackPlugin = require( './treatwarningsaserrorswebpackplugin' ); +const __filename = fileURLToPath( import.meta.url ); +const __dirname = path.dirname( __filename ); /** * @param {Object} options * @returns {Object} */ -module.exports = function getWebpackConfigForAutomatedTests( options ) { +export default function getWebpackConfigForAutomatedTests( options ) { const definitions = Object.assign( {}, getDefinitionsFromFile( options.identityFile ) ); const config = { @@ -102,4 +104,4 @@ module.exports = function getWebpackConfigForAutomatedTests( options ) { } return config; -}; +} diff --git a/packages/ckeditor5-dev-tests/lib/utils/automated-tests/karmanotifier.js b/packages/ckeditor5-dev-tests/lib/utils/automated-tests/karmanotifier.js index cfb5a8fde..81a8e100c 100644 --- a/packages/ckeditor5-dev-tests/lib/utils/automated-tests/karmanotifier.js +++ b/packages/ckeditor5-dev-tests/lib/utils/automated-tests/karmanotifier.js @@ -3,8 +3,12 @@ * For licensing, see LICENSE.md. */ -const path = require( 'path' ); -const notifier = require( 'node-notifier' ); +import path from 'path'; +import notifier from 'node-notifier'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath( import.meta.url ); +const __dirname = path.dirname( __filename ); const ckeditor5icon = path.join( __dirname, '..', 'icons', 'ckeditor5.png' ); @@ -14,7 +18,7 @@ const defaultNotifyOptions = { icon: ckeditor5icon }; -module.exports = { 'reporter:karmanotifier': [ 'type', karmaNotifier ] }; +export default { 'reporter:karmanotifier': [ 'type', karmaNotifier ] }; karmaNotifier.$inject = [ 'helper' ]; function karmaNotifier( helper ) { diff --git a/packages/ckeditor5-dev-tests/lib/utils/automated-tests/parsearguments.js b/packages/ckeditor5-dev-tests/lib/utils/automated-tests/parsearguments.js index eefdb774b..567e9eebe 100644 --- a/packages/ckeditor5-dev-tests/lib/utils/automated-tests/parsearguments.js +++ b/packages/ckeditor5-dev-tests/lib/utils/automated-tests/parsearguments.js @@ -3,18 +3,16 @@ * For licensing, see LICENSE.md. */ -'use strict'; - -const fs = require( 'fs' ); -const path = require( 'path' ); -const minimist = require( 'minimist' ); -const { tools, logger } = require( '@ckeditor/ckeditor5-dev-utils' ); +import fs from 'fs'; +import path from 'path'; +import minimist from 'minimist'; +import { tools, logger } from '@ckeditor/ckeditor5-dev-utils'; /** * @param {Array.} args * @returns {Object} */ -module.exports = function parseArguments( args ) { +export default function parseArguments( args ) { const log = logger(); const minimistConfig = { @@ -266,4 +264,4 @@ module.exports = function parseArguments( args ) { return false; } } -}; +} diff --git a/packages/ckeditor5-dev-tests/lib/utils/automated-tests/treatwarningsaserrorswebpackplugin.js b/packages/ckeditor5-dev-tests/lib/utils/automated-tests/treatwarningsaserrorswebpackplugin.js index bcd0a1bcc..718680e25 100644 --- a/packages/ckeditor5-dev-tests/lib/utils/automated-tests/treatwarningsaserrorswebpackplugin.js +++ b/packages/ckeditor5-dev-tests/lib/utils/automated-tests/treatwarningsaserrorswebpackplugin.js @@ -6,7 +6,7 @@ /** * Webpack plugin that reassigns warnings as errors and stops the process if any errors or warnings detected. */ -module.exports = class TreatWarningsAsErrorsWebpackPlugin { +export default class TreatWarningsAsErrorsWebpackPlugin { apply( compiler ) { compiler.hooks.shouldEmit.tap( 'TreatWarningsAsErrorsWebpackPlugin', compilation => { compilation.errors = [ @@ -21,4 +21,4 @@ module.exports = class TreatWarningsAsErrorsWebpackPlugin { } } ); } -}; +} diff --git a/packages/ckeditor5-dev-tests/lib/utils/getdefinitionsfromfile.js b/packages/ckeditor5-dev-tests/lib/utils/getdefinitionsfromfile.js index 7729b456a..f4b552825 100644 --- a/packages/ckeditor5-dev-tests/lib/utils/getdefinitionsfromfile.js +++ b/packages/ckeditor5-dev-tests/lib/utils/getdefinitionsfromfile.js @@ -3,15 +3,13 @@ * For licensing, see LICENSE.md. */ -'use strict'; - -const path = require( 'path' ); +import path from 'path'; /** * @param {String|null} definitionSource * @returns {Object} */ -module.exports = function getDefinitionsFromFile( definitionSource ) { +export default function getDefinitionsFromFile( definitionSource ) { if ( !definitionSource ) { return {}; } @@ -31,7 +29,7 @@ module.exports = function getDefinitionsFromFile( definitionSource ) { return {}; } -}; +} /** * @param {String|null} definitionSource diff --git a/packages/ckeditor5-dev-tests/lib/utils/getrelativefilepath.js b/packages/ckeditor5-dev-tests/lib/utils/getrelativefilepath.js index 80acee08f..431d76ec1 100644 --- a/packages/ckeditor5-dev-tests/lib/utils/getrelativefilepath.js +++ b/packages/ckeditor5-dev-tests/lib/utils/getrelativefilepath.js @@ -3,9 +3,7 @@ * For licensing, see LICENSE.md. */ -'use strict'; - -const path = require( 'path' ); +import path from 'path'; /** * Get a path to a source file which will uniquely identify this file in @@ -21,7 +19,7 @@ const path = require( 'path' ); * @param {String} [cwd=process.cwd()] * @returns {String} */ -module.exports = function getRelativeFilePath( filePath, cwd = process.cwd() ) { +export default function getRelativeFilePath( filePath, cwd = process.cwd() ) { // The path ends with the directory separator. const relativePath = filePath.replace( cwd, '' ).slice( 1 ); @@ -32,4 +30,4 @@ module.exports = function getRelativeFilePath( filePath, cwd = process.cwd() ) { // The main repository. return path.join( 'ckeditor5', relativePath ); -}; +} diff --git a/packages/ckeditor5-dev-tests/lib/utils/manual-tests/compilehtmlfiles.js b/packages/ckeditor5-dev-tests/lib/utils/manual-tests/compilehtmlfiles.js index 796d41fed..a8284dc81 100644 --- a/packages/ckeditor5-dev-tests/lib/utils/manual-tests/compilehtmlfiles.js +++ b/packages/ckeditor5-dev-tests/lib/utils/manual-tests/compilehtmlfiles.js @@ -3,18 +3,20 @@ * For licensing, see LICENSE.md. */ -'use strict'; - -const path = require( 'path' ); -const fs = require( 'fs-extra' ); -const { globSync } = require( 'glob' ); -const _ = require( 'lodash' ); -const chalk = require( 'chalk' ); -const commonmark = require( 'commonmark' ); -const combine = require( 'dom-combiner' ); -const chokidar = require( 'chokidar' ); -const { logger } = require( '@ckeditor/ckeditor5-dev-utils' ); -const getRelativeFilePath = require( '../getrelativefilepath' ); +import path from 'path'; +import fs from 'fs-extra'; +import { globSync } from 'glob'; +import _ from 'lodash'; +import chalk from 'chalk'; +import * as commonmark from 'commonmark'; +import combine from 'dom-combiner'; +import chokidar from 'chokidar'; +import { logger } from '@ckeditor/ckeditor5-dev-utils'; +import getRelativeFilePath from '../getrelativefilepath.js'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath( import.meta.url ); +const __dirname = path.dirname( __filename ); const reader = new commonmark.Parser(); const writer = new commonmark.HtmlRenderer(); @@ -30,7 +32,7 @@ const writer = new commonmark.HtmlRenderer(); * @param {Boolean} [options.silent=false] Whether to hide files that will be processed by the script. * @returns {Promise} */ -module.exports = function compileHtmlFiles( options ) { +export default function compileHtmlFiles( options ) { const buildDir = options.buildDir; const viewTemplate = fs.readFileSync( path.join( __dirname, 'template.html' ), 'utf-8' ); const silent = options.silent || false; @@ -80,7 +82,7 @@ module.exports = function compileHtmlFiles( options ) { } ); }, options.onTestCompilationStatus ); } -}; +} /** * @param {String} buildDir An absolute path to the directory where the processed file should be saved. diff --git a/packages/ckeditor5-dev-tests/lib/utils/manual-tests/compilescripts.js b/packages/ckeditor5-dev-tests/lib/utils/manual-tests/compilescripts.js index 4853448e4..600b8906f 100644 --- a/packages/ckeditor5-dev-tests/lib/utils/manual-tests/compilescripts.js +++ b/packages/ckeditor5-dev-tests/lib/utils/manual-tests/compilescripts.js @@ -3,12 +3,10 @@ * For licensing, see LICENSE.md. */ -'use strict'; - -const webpack = require( 'webpack' ); -const getWebpackConfigForManualTests = require( './getwebpackconfig' ); -const getRelativeFilePath = require( '../getrelativefilepath' ); -const requireDll = require( '../requiredll' ); +import webpack from 'webpack'; +import getWebpackConfigForManualTests from './getwebpackconfig.js'; +import getRelativeFilePath from '../getrelativefilepath.js'; +import requireDll from '../requiredll.js'; /** * @param {Object} options @@ -25,7 +23,7 @@ const requireDll = require( '../requiredll' ); * @param {String} [options.identityFile] A file that provides secret keys used in the test scripts. * @returns {Promise} */ -module.exports = function compileManualTestScripts( options ) { +export default function compileManualTestScripts( options ) { const entryFiles = options.sourceFiles; const entryFilesDLL = entryFiles.filter( entryFile => requireDll( entryFile ) ); const entryFilesNonDll = entryFiles.filter( entryFile => !requireDll( entryFile ) ); @@ -71,7 +69,7 @@ module.exports = function compileManualTestScripts( options ) { const webpackPromises = webpackConfigs.map( config => runWebpack( config ) ); return Promise.all( webpackPromises ); -}; +} /** * @returns {Promise} diff --git a/packages/ckeditor5-dev-tests/lib/utils/manual-tests/copyassets.js b/packages/ckeditor5-dev-tests/lib/utils/manual-tests/copyassets.js index 36c583200..75d71d3a1 100644 --- a/packages/ckeditor5-dev-tests/lib/utils/manual-tests/copyassets.js +++ b/packages/ckeditor5-dev-tests/lib/utils/manual-tests/copyassets.js @@ -3,8 +3,14 @@ * For licensing, see LICENSE.md. */ -const path = require( 'path' ); -const fs = require( 'fs-extra' ); +import path from 'path'; +import fs from 'fs-extra'; +import { createRequire } from 'module'; +import { fileURLToPath } from 'url'; + +const require = createRequire( import.meta.url ); +const __filename = fileURLToPath( import.meta.url ); +const __dirname = path.dirname( __filename ); const assets = [ path.join( __dirname, 'togglesidebar.js' ), @@ -23,9 +29,9 @@ const assets = [ * │ └── ... * ... */ -module.exports = function copyAssets( buildDir ) { +export default function copyAssets( buildDir ) { for ( const assetPath of assets ) { const outputFilePath = path.join( buildDir, 'assets', path.basename( assetPath ) ); fs.copySync( assetPath, outputFilePath ); } -}; +} diff --git a/packages/ckeditor5-dev-tests/lib/utils/manual-tests/createserver.js b/packages/ckeditor5-dev-tests/lib/utils/manual-tests/createserver.js index 29460b290..cb2d08a3d 100644 --- a/packages/ckeditor5-dev-tests/lib/utils/manual-tests/createserver.js +++ b/packages/ckeditor5-dev-tests/lib/utils/manual-tests/createserver.js @@ -3,14 +3,16 @@ * For licensing, see LICENSE.md. */ -'use strict'; +import http from 'http'; +import path from 'path'; +import { globSync } from 'glob'; +import fs from 'fs'; +import combine from 'dom-combiner'; +import { logger } from '@ckeditor/ckeditor5-dev-utils'; +import { fileURLToPath } from 'url'; -const http = require( 'http' ); -const path = require( 'path' ); -const { globSync } = require( 'glob' ); -const fs = require( 'fs' ); -const combine = require( 'dom-combiner' ); -const { logger } = require( '@ckeditor/ckeditor5-dev-utils' ); +const __filename = fileURLToPath( import.meta.url ); +const __dirname = path.dirname( __filename ); /** * Basic HTTP server. @@ -19,7 +21,7 @@ const { logger } = require( '@ckeditor/ckeditor5-dev-utils' ); * @param {Number} [port=8125] Port to listen at. * @param {Function} [onCreate] A callback called with the reference to the HTTP server when it is up and running. */ -module.exports = function createManualTestServer( sourcePath, port = 8125, onCreate ) { +export default function createManualTestServer( sourcePath, port = 8125, onCreate ) { return new Promise( resolve => { const server = http.createServer( ( request, response ) => { onRequest( sourcePath, request, response ); @@ -56,7 +58,7 @@ module.exports = function createManualTestServer( sourcePath, port = 8125, onCre onCreate( server ); } } ); -}; +} function onRequest( sourcePath, request, response ) { response.writeHead( 200, { diff --git a/packages/ckeditor5-dev-tests/lib/utils/manual-tests/getwebpackconfig.js b/packages/ckeditor5-dev-tests/lib/utils/manual-tests/getwebpackconfig.js index 4dbab390b..daa45f37f 100644 --- a/packages/ckeditor5-dev-tests/lib/utils/manual-tests/getwebpackconfig.js +++ b/packages/ckeditor5-dev-tests/lib/utils/manual-tests/getwebpackconfig.js @@ -3,14 +3,16 @@ * For licensing, see LICENSE.md. */ -'use strict'; +import path from 'path'; +import webpack from 'webpack'; +import { CKEditorTranslationsPlugin } from '@ckeditor/ckeditor5-dev-translations'; +import { loaders } from '@ckeditor/ckeditor5-dev-utils'; +import WebpackNotifierPlugin from './webpacknotifierplugin.js'; +import getDefinitionsFromFile from '../getdefinitionsfromfile.js'; +import { fileURLToPath } from 'url'; -const path = require( 'path' ); -const webpack = require( 'webpack' ); -const { CKEditorTranslationsPlugin } = require( '@ckeditor/ckeditor5-dev-translations' ); -const { loaders } = require( '@ckeditor/ckeditor5-dev-utils' ); -const WebpackNotifierPlugin = require( './webpacknotifierplugin' ); -const getDefinitionsFromFile = require( '../getdefinitionsfromfile' ); +const __filename = fileURLToPath( import.meta.url ); +const __dirname = path.dirname( __filename ); /** * @param {Object} options @@ -26,7 +28,7 @@ const getDefinitionsFromFile = require( '../getdefinitionsfromfile' ); * @param {String|null} [options.identityFile] * @returns {Object} */ -module.exports = function getWebpackConfigForManualTests( options ) { +export default function getWebpackConfigForManualTests( options ) { const definitions = Object.assign( {}, getDefinitionsFromFile( options.identityFile ) ); const webpackConfig = { @@ -149,5 +151,5 @@ module.exports = function getWebpackConfigForManualTests( options ) { } return webpackConfig; -}; +} diff --git a/packages/ckeditor5-dev-tests/lib/utils/manual-tests/removedir.js b/packages/ckeditor5-dev-tests/lib/utils/manual-tests/removedir.js index 069926fcd..8189e524c 100644 --- a/packages/ckeditor5-dev-tests/lib/utils/manual-tests/removedir.js +++ b/packages/ckeditor5-dev-tests/lib/utils/manual-tests/removedir.js @@ -3,11 +3,9 @@ * For licensing, see LICENSE.md. */ -'use strict'; - -const del = require( 'del' ); -const { logger } = require( '@ckeditor/ckeditor5-dev-utils' ); -const chalk = require( 'chalk' ); +import del from 'del'; +import { logger } from '@ckeditor/ckeditor5-dev-utils'; +import chalk from 'chalk'; /** * Removes the specified directory. @@ -19,7 +17,7 @@ const chalk = require( 'chalk' ); * @param {Boolean} [options.silent=false] Whether to hide the path to the directory on the console. * @returns {Promise} */ -module.exports = function removeDir( dir, options = {} ) { +export default function removeDir( dir, options = {} ) { return del( dir ).then( () => { if ( !options.silent ) { logger().info( `Removed directory '${ chalk.cyan( dir ) }'` ); @@ -27,4 +25,4 @@ module.exports = function removeDir( dir, options = {} ) { return Promise.resolve(); } ); -}; +} diff --git a/packages/ckeditor5-dev-tests/lib/utils/manual-tests/webpacknotifierplugin.js b/packages/ckeditor5-dev-tests/lib/utils/manual-tests/webpacknotifierplugin.js index 01d1f3b96..48d9e80ed 100644 --- a/packages/ckeditor5-dev-tests/lib/utils/manual-tests/webpacknotifierplugin.js +++ b/packages/ckeditor5-dev-tests/lib/utils/manual-tests/webpacknotifierplugin.js @@ -3,14 +3,12 @@ * For licensing, see LICENSE.md. */ -'use strict'; - -const { logger } = require( '@ckeditor/ckeditor5-dev-utils' ); +import { logger } from '@ckeditor/ckeditor5-dev-utils'; /** * Plugin for Webpack which helps to inform the developer about processes. */ -module.exports = class WebpackNotifierPlugin { +export default class WebpackNotifierPlugin { /** * @param {Object} options * @param {Function} options.onTestCompilationStatus @@ -56,4 +54,4 @@ module.exports = class WebpackNotifierPlugin { this.onTestCompilationStatus( `finished:${ this.processName }` ); } ); } -}; +} diff --git a/packages/ckeditor5-dev-tests/lib/utils/requiredll.js b/packages/ckeditor5-dev-tests/lib/utils/requiredll.js index 023064783..ebf320ce8 100644 --- a/packages/ckeditor5-dev-tests/lib/utils/requiredll.js +++ b/packages/ckeditor5-dev-tests/lib/utils/requiredll.js @@ -3,16 +3,14 @@ * For licensing, see LICENSE.md. */ -'use strict'; - /** * Returns `true` if any of the source files represent a DLL test. * * @param {String|Array.} sourceFiles * @returns {Boolean} */ -module.exports = function requireDll( sourceFiles ) { +export default function requireDll( sourceFiles ) { sourceFiles = Array.isArray( sourceFiles ) ? sourceFiles : [ sourceFiles ]; return sourceFiles.some( filePath => /-dll.[jt]s$/.test( filePath ) ); -}; +} diff --git a/packages/ckeditor5-dev-tests/lib/utils/transformfileoptiontotestglob.js b/packages/ckeditor5-dev-tests/lib/utils/transformfileoptiontotestglob.js index e8382c368..08cedc7b1 100644 --- a/packages/ckeditor5-dev-tests/lib/utils/transformfileoptiontotestglob.js +++ b/packages/ckeditor5-dev-tests/lib/utils/transformfileoptiontotestglob.js @@ -3,10 +3,8 @@ * For licensing, see LICENSE.md. */ -'use strict'; - -const fs = require( 'fs' ); -const path = require( 'path' ); +import fs from 'fs'; +import path from 'path'; const EXTERNAL_DIR_PATH = path.join( process.cwd(), 'external' ); @@ -24,7 +22,7 @@ const EXTERNAL_DIR_PATH = path.join( process.cwd(), 'external' ); * @param {Boolean} [isManualTest=false] Whether the tests are manual or automated. * @returns {Array.} */ -module.exports = function transformFileOptionToTestGlob( pattern, isManualTest = false ) { +export default function transformFileOptionToTestGlob( pattern, isManualTest = false ) { if ( doesPatternMatchExternalRepositoryName( pattern ) ) { return getExternalRepositoryGlob( pattern, { isManualTest } ); } @@ -54,7 +52,7 @@ module.exports = function transformFileOptionToTestGlob( pattern, isManualTest = transformedPathForExternalPackagesWithCKEditorPrefix ] ) ]; -}; +} /** * @param {String} pattern diff --git a/packages/ckeditor5-dev-tests/package.json b/packages/ckeditor5-dev-tests/package.json index 8378830b1..94f0c471d 100644 --- a/packages/ckeditor5-dev-tests/package.json +++ b/packages/ckeditor5-dev-tests/package.json @@ -16,6 +16,7 @@ "node": ">=18.0.0", "npm": ">=5.7.1" }, + "type": "module", "main": "lib/index.js", "files": [ "lib", @@ -74,8 +75,12 @@ "webpack": "^5.94.0" }, "devDependencies": { - "mockery": "^2.1.0", - "proxyquire": "^2.1.3" + "vitest": "^2.0.5" + }, + "scripts": { + "postinstall": "node bin/postinstall.js", + "test": "vitest run --config vitest.config.js", + "coverage": "vitest run --config vitest.config.js --coverage" }, "depcheckIgnore": [ "@babel/core", @@ -88,10 +93,5 @@ "mocha", "process", "typescript" - ], - "scripts": { - "postinstall": "node bin/postinstall.js", - "test": "mocha './tests/**/*.js' --timeout 10000", - "coverage": "nyc --reporter=lcov --reporter=text-summary yarn run test" - } + ] } diff --git a/packages/ckeditor5-dev-tests/vitest.config.js b/packages/ckeditor5-dev-tests/vitest.config.js new file mode 100644 index 000000000..5ad784a28 --- /dev/null +++ b/packages/ckeditor5-dev-tests/vitest.config.js @@ -0,0 +1,23 @@ +/** + * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md. + */ + +import { defineConfig } from 'vitest/config'; + +export default defineConfig( { + test: { + testTimeout: 10000, + restoreMocks: true, + include: [ + 'tests/**/*.js' + ], + coverage: { + provider: 'v8', + include: [ + 'lib/**' + ], + reporter: [ 'text', 'json', 'html', 'lcov' ] + } + } +} ); From e352edf7786824941e429f87157d50908d2a9601 Mon Sep 17 00:00:00 2001 From: Kamil Piechaczek Date: Mon, 16 Sep 2024 10:29:12 +0200 Subject: [PATCH 03/15] Support for running maunal tests. --- .../lib/findmessages.js | 7 ++- .../lib/servetranslations.js | 5 ++- .../ckeditor5-dev-translations/package.json | 1 + .../tests/findmessages.js | 43 ++++++++++++++++++- 4 files changed, 50 insertions(+), 6 deletions(-) diff --git a/packages/ckeditor5-dev-translations/lib/findmessages.js b/packages/ckeditor5-dev-translations/lib/findmessages.js index 631bbed1b..5e1369bd9 100644 --- a/packages/ckeditor5-dev-translations/lib/findmessages.js +++ b/packages/ckeditor5-dev-translations/lib/findmessages.js @@ -4,7 +4,7 @@ */ import parser from '@babel/parser'; -import traverse from '@babel/traverse'; +import { default as traverse } from '@babel/traverse'; /** * Parses source and finds messages from the first argument of `t()` calls. @@ -24,7 +24,10 @@ export default function findMessages( source, sourceFile, onMessageFound, onErro ] } ); - traverse( ast, { + // Support for a non-`type=module` project. + const traverseCallable = typeof traverse === 'function' ? traverse : traverse.default; + + traverseCallable( ast, { CallExpression: ( { node } ) => { try { findMessagesInNode( node ); diff --git a/packages/ckeditor5-dev-translations/lib/servetranslations.js b/packages/ckeditor5-dev-translations/lib/servetranslations.js index d6334a0a2..1e058140e 100644 --- a/packages/ckeditor5-dev-translations/lib/servetranslations.js +++ b/packages/ckeditor5-dev-translations/lib/servetranslations.js @@ -3,7 +3,7 @@ * For licensing, see LICENSE.md. */ -import fs from 'fs'; +import fs from 'fs-extra'; import path from 'path'; import { fileURLToPath } from 'url'; import chalk from 'chalk'; @@ -88,7 +88,7 @@ export default function serveTranslations( compiler, options, translationService } // Add all context messages found in the core package. - const contexts = require( pathToResource ); + const contexts = fs.readJsonSync( pathToResource ); for ( const item of Object.keys( contexts ) ) { translationService.addIdMessage( item ); @@ -107,6 +107,7 @@ export default function serveTranslations( compiler, options, translationService // after any potential TypeScript file has already been compiled. module.loaders.unshift( { loader: path.join( __dirname, 'translatesourceloader.js' ), + type: 'module', options: { translateSource } } ); diff --git a/packages/ckeditor5-dev-translations/package.json b/packages/ckeditor5-dev-translations/package.json index f70a06f44..c4cc85f0f 100644 --- a/packages/ckeditor5-dev-translations/package.json +++ b/packages/ckeditor5-dev-translations/package.json @@ -25,6 +25,7 @@ "@babel/parser": "^7.18.9", "@babel/traverse": "^7.18.9", "chalk": "^4.0.0", + "fs-extra": "^11.2.0", "rimraf": "^3.0.2", "webpack-sources": "^2.0.1", "pofile": "^1.0.9" diff --git a/packages/ckeditor5-dev-translations/tests/findmessages.js b/packages/ckeditor5-dev-translations/tests/findmessages.js index b0c975df6..332cfe634 100644 --- a/packages/ckeditor5-dev-translations/tests/findmessages.js +++ b/packages/ckeditor5-dev-translations/tests/findmessages.js @@ -3,10 +3,16 @@ * For licensing, see LICENSE.md. */ -import { describe, expect, it } from 'vitest'; -import findMessages from '../lib/findmessages.js'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import traverse from '@babel/traverse'; describe( 'findMessages', () => { + let findMessages; + + beforeEach( async () => { + findMessages = ( await import( '../lib/findmessages.js' ) ).default; + } ); + it( 'should parse provided code and find messages from `t()` function calls on string literals', () => { const messages = []; @@ -207,4 +213,37 @@ describe( 'findMessages', () => { 'First t() call argument should be a string literal or an object literal (foo.js).' ] ); } ); + + describe( 'a non-type=module project support', () => { + beforeEach( async () => { + vi.resetAllMocks(); + vi.clearAllMocks(); + vi.resetModules(); + + vi.doMock( '@babel/traverse', () => ( { + default: { + default: traverse + } + } ) ); + + findMessages = ( await import( '../lib/findmessages.js' ) ).default; + } ); + + it( 'should parse provided code and find messages from `t()` function calls on string literals', () => { + const messages = []; + + findMessages( + `function x() { + const t = this.t; + t( 'Image' ); + t( 'CKEditor' ); + g( 'Some other function' ); + }`, + 'foo.js', + message => messages.push( message ) + ); + + expect( messages ).to.deep.equal( [ { id: 'Image', string: 'Image' }, { id: 'CKEditor', string: 'CKEditor' } ] ); + } ); + } ); } ); From 5d1f8e4226d96832b14ade0bcecce4247051c504 Mon Sep 17 00:00:00 2001 From: Kamil Piechaczek Date: Mon, 16 Sep 2024 10:51:47 +0200 Subject: [PATCH 04/15] Support for a custom Karma configuratiom for an IntelliJ IDE. --- packages/ckeditor5-dev-tests/bin/intellijkarmarunner/README.md | 2 +- packages/ckeditor5-dev-tests/bin/intellijkarmarunner/bin/karma | 2 +- .../intellijkarmarunner/{karma.config.js => karma.config.cjs} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename packages/ckeditor5-dev-tests/bin/intellijkarmarunner/{karma.config.js => karma.config.cjs} (100%) diff --git a/packages/ckeditor5-dev-tests/bin/intellijkarmarunner/README.md b/packages/ckeditor5-dev-tests/bin/intellijkarmarunner/README.md index f86154942..c936ef936 100644 --- a/packages/ckeditor5-dev-tests/bin/intellijkarmarunner/README.md +++ b/packages/ckeditor5-dev-tests/bin/intellijkarmarunner/README.md @@ -18,7 +18,7 @@ yarn run test --files=engine,basic-styles 1. In the IDE, go to _Run_ > _Edit configurations..._: 1. Add a new configuration of type "**Karma**" and a name of your preference. -1. In "Configuration file", selected the "**node\_modules/ckeditor5-dev-tests/bin/intellijkarmarunner/karma.config.js**" file. +1. In "Configuration file", selected the "**node\_modules/ckeditor5-dev-tests/bin/intellijkarmarunner/karma.config.cjs**" file. 1. In "Karma Package", selected the "**node\_modules/ckeditor5-dev-tests/bin/intellijkarmarunner**" directory. 1. In "Karma options", input the CKEditor 5 tests arguments. E.g. `--files=engine,basic-styles`. 1. In "Working directory", select the base `ckeditor5` directory. diff --git a/packages/ckeditor5-dev-tests/bin/intellijkarmarunner/bin/karma b/packages/ckeditor5-dev-tests/bin/intellijkarmarunner/bin/karma index c583d22db..1e9a7c965 100755 --- a/packages/ckeditor5-dev-tests/bin/intellijkarmarunner/bin/karma +++ b/packages/ckeditor5-dev-tests/bin/intellijkarmarunner/bin/karma @@ -33,4 +33,4 @@ const intellijConfig = process.argv.find( item => item.includes( 'intellij.conf. process.argv.push( '--karma-config-overrides=' + intellijConfig ); // Now running the tests. -require( '../../testautomated' ); +import( '../../testautomated.js' ); diff --git a/packages/ckeditor5-dev-tests/bin/intellijkarmarunner/karma.config.js b/packages/ckeditor5-dev-tests/bin/intellijkarmarunner/karma.config.cjs similarity index 100% rename from packages/ckeditor5-dev-tests/bin/intellijkarmarunner/karma.config.js rename to packages/ckeditor5-dev-tests/bin/intellijkarmarunner/karma.config.cjs From bdd79d9ab29c9db1d9432f185d4000a4d70db8d1 Mon Sep 17 00:00:00 2001 From: Kamil Piechaczek Date: Mon, 16 Sep 2024 11:30:02 +0200 Subject: [PATCH 05/15] CircleCI: Support for running package tests in separate steps. --- .circleci/config.yml | 269 ++---------------- .circleci/template.yml | 266 +++++++++++++++++ .eslintrc.js => .eslintrc.cjs | 0 .gitignore | 3 + package.json | 2 + scripts/ci/combine-coverage-lcov.js | 30 ++ scripts/ci/generate-circleci-configuration.js | 136 +++++++++ scripts/runtest.js | 32 +-- 8 files changed, 476 insertions(+), 262 deletions(-) create mode 100644 .circleci/template.yml rename .eslintrc.js => .eslintrc.cjs (100%) create mode 100644 scripts/ci/combine-coverage-lcov.js create mode 100644 scripts/ci/generate-circleci-configuration.js diff --git a/.circleci/config.yml b/.circleci/config.yml index 502462aa1..8aaa9b41a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,5 +1,21 @@ +# This is the based configuration required by CircleCI to run a build. +# +# The repository uses the dynamic configuration to generate +# tasks for executing tests and checking the code coverage. +# +# This configuration aims to prepare a complete design and continue checking +# the repository in a new workflow. +# +# To modify the commands to execute on CI, review the following files: +# - scripts/ci/generate-circleci-configuration.js - the script that creates the `config-tests.yml` file used on the new workflow. +# - .circleci/template.yml - the template filled with data to execute. +# +# Useful resources: +# - https://circleci.com/docs/using-dynamic-configuration/ version: 2.1 +setup: true + parameters: triggerCommitHash: type: string @@ -11,16 +27,10 @@ parameters: type: boolean default: false -commands: - bootstrap_repository_command: - description: "Bootstrap the repository" - steps: - - install_ssh_keys_command - - run: - name: Install dependencies - command: yarn install - - prepare_environment_variables_commands +orbs: + continuation: circleci/continuation@0.1.2 +commands: install_ssh_keys_command: description: "Install SSH keys" steps: @@ -28,244 +38,23 @@ commands: fingerprints: - "a0:41:a2:56:c8:7d:3f:29:41:d1:87:92:fd:50:2b:6b" - npm_login_command: - description: "Enable interacting with `npm` using an auth token" - steps: - - run: - name: Login to the npm registry using '.npmrc' file - command: echo "//registry.npmjs.org/:_authToken=\${CKE5_NPM_TOKEN}" > ~/.npmrc - - git_credentials_command: - description: "Setup git configuration" - steps: - - run: - name: Setup git configuration - command: | - git config --global user.email "ckeditor-bot@cksource.com" - git config --global user.name "CKEditorBot" - - prepare_environment_variables_commands: - description: "Prepare non-secret environment variables" - steps: - - run: - name: Prepare environment variables - command: | - #!/bin/bash - - # Non-secret environment variables needed for the pipeline scripts. - CKE5_GITHUB_ORGANIZATION="ckeditor" - CKE5_GITHUB_REPOSITORY="ckeditor5-dev" - CKE5_CIRCLE_APPROVAL_JOB_NAME="release_approval" - CKE5_GITHUB_RELEASE_BRANCH="master" - - echo export CKE5_CIRCLE_APPROVAL_JOB_NAME=$CKE5_CIRCLE_APPROVAL_JOB_NAME >> $BASH_ENV - echo export CKE5_GITHUB_RELEASE_BRANCH=$CKE5_GITHUB_RELEASE_BRANCH >> $BASH_ENV - echo export CKE5_GITHUB_ORGANIZATION=$CKE5_GITHUB_ORGANIZATION >> $BASH_ENV - echo export CKE5_GITHUB_REPOSITORY=$CKE5_GITHUB_REPOSITORY >> $BASH_ENV - echo export CKE5_GITHUB_REPOSITORY_SLUG="$CKE5_GITHUB_ORGANIZATION/$CKE5_GITHUB_REPOSITORY" >> $BASH_ENV - echo export CKE5_COMMIT_SHA1=$CIRCLE_SHA1 >> $BASH_ENV - jobs: - notify_ci_failure: - machine: true - parameters: - hideAuthor: - type: string - default: "false" - steps: - - checkout - - bootstrap_repository_command - - run: - # In the PRs that comes from forked repositories, we do not share secret variables. - # Hence, some of the scripts will not be executed. - name: 👤 Verify if the build was triggered by community - Check if the build should continue - command: | - #!/bin/bash - - if [[ -z ${COVERALLS_REPO_TOKEN} ]]; - then - circleci-agent step halt - fi - - run: - environment: - CKE5_SLACK_NOTIFY_HIDE_AUTHOR: << parameters.hideAuthor >> - CKE5_PIPELINE_NUMBER: << pipeline.number >> - name: Waiting for other jobs to finish and sending notification on failure - command: yarn ckeditor5-dev-ci-circle-workflow-notifier - no_output_timeout: 1h - - validate_and_tests: - machine: true - resource_class: large - steps: - - checkout - - bootstrap_repository_command - - run: - name: Execute ESLint - command: yarn run lint - - run: - environment: - TZ: Europe/Warsaw - name: Run unit tests - command: yarn run coverage - - unless: - # Upload the code coverage results for non-nightly builds only. - condition: << pipeline.parameters.isNightly >> - steps: - - run: - # In the PRs that comes from forked repositories, we do not share secret variables. - # Hence, some of the scripts will not be executed. - name: 👤 Verify if the build was triggered by community - Check if the build should continue - command: | - #!/bin/bash - - if [[ -z ${COVERALLS_REPO_TOKEN} ]]; - then - circleci-agent step halt - fi - - run: - name: Upload code coverage - command: cat coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js - - release_prepare: - machine: true - resource_class: large - steps: - - checkout - - bootstrap_repository_command - - run: - name: Check if packages are ready to be released - command: yarn run release:prepare-packages --verbose --compile-only - - trigger_release_process: - machine: true - resource_class: large - steps: - - checkout - - bootstrap_repository_command - - run: - name: Verify if the project is ready to release - command: | - #!/bin/bash - - # Do not fail if the Node script ends with non-zero exit code. - set +e - - node scripts/ci/is-project-ready-to-release.js - EXIT_CODE=$( echo $? ) - - if [ ${EXIT_CODE} -eq 1 ]; - then - circleci-agent step halt - fi - - run: - name: Trigger the release pipeline - command: yarn ckeditor5-dev-ci-trigger-circle-build - - release_project: + generate_configuration: machine: true - resource_class: large steps: - checkout - - bootstrap_repository_command - - run: - name: Verify the trigger commit from the repository - command: | - #!/bin/bash - - CKE5_LATEST_COMMIT_HASH=$( git log -n 1 --pretty=format:%H origin/master ) - CKE5_TRIGGER_COMMIT_HASH=<< pipeline.parameters.triggerCommitHash >> - - if [[ "${CKE5_LATEST_COMMIT_HASH}" != "${CKE5_TRIGGER_COMMIT_HASH}" ]]; then - echo "There is a newer commit in the repository on the \`#master\` branch. Use its build to start the release." - circleci-agent step halt - fi - - npm_login_command - - git_credentials_command - - run: - name: Verify if a releaser triggered the job - command: | - #!/bin/bash - - # Do not fail if the Node script ends with non-zero exit code. - set +e - - yarn ckeditor5-dev-ci-is-job-triggered-by-member - EXIT_CODE=$( echo $? ) - - if [ ${EXIT_CODE} -ne 0 ]; - then - echo "Aborting the release due to failed verification of the approver (no rights to release)." - circleci-agent step halt - fi - - run: - name: Disable the redundant workflows option - command: yarn ckeditor5-dev-ci-circle-disable-auto-cancel-builds - - run: - name: Prepare the new version to release - command: npm run release:prepare-packages -- --verbose - - run: - name: Publish the packages - command: npm run release:publish-packages -- --verbose + - install_ssh_keys_command - run: - name: Enable the redundant workflows option - command: yarn ckeditor5-dev-ci-circle-enable-auto-cancel-builds - when: always + name: Install dependencies + command: yarn install - run: - name: Pack the "release/" directory (in case of failure) - command: | - zip -r ./release.zip ./release - when: always - - store_artifacts: - path: ./release.zip - when: always + name: Generate a new configuration to check all packages in the repository + command: node scripts/ci/generate-circleci-configuration.js + - continuation/continue: + configuration_path: .circleci/config-tests.yml workflows: version: 2 - main: - when: - and: - - equal: [ false, << pipeline.parameters.isNightly >> ] - - equal: [ false, << pipeline.parameters.isRelease >> ] - jobs: - - validate_and_tests - - release_prepare - - trigger_release_process: - requires: - - validate_and_tests - - release_prepare - filters: - branches: - only: - - master - - notify_ci_failure: - filters: - branches: - only: - - master - - release: - when: - and: - - equal: [ false, << pipeline.parameters.isNightly >> ] - - equal: [ true, << pipeline.parameters.isRelease >> ] - jobs: - - release_approval: - type: approval - - release_project: - requires: - - release_approval - - nightly: - when: - and: - - equal: [ true, << pipeline.parameters.isNightly >> ] - - equal: [ false, << pipeline.parameters.isRelease >> ] + config: jobs: - - validate_and_tests - - notify_ci_failure: - hideAuthor: "true" - filters: - branches: - only: - - master + - generate_configuration diff --git a/.circleci/template.yml b/.circleci/template.yml new file mode 100644 index 000000000..aa1e6488d --- /dev/null +++ b/.circleci/template.yml @@ -0,0 +1,266 @@ +version: 2.1 + +parameters: + triggerCommitHash: + type: string + default: "" + isNightly: + type: boolean + default: false + isRelease: + type: boolean + default: false + +commands: + bootstrap_repository_command: + description: "Bootstrap the repository" + steps: + - install_ssh_keys_command + - run: + name: Install dependencies + command: yarn install + - prepare_environment_variables_commands + + install_ssh_keys_command: + description: "Install SSH keys" + steps: + - add_ssh_keys: + fingerprints: + - "a0:41:a2:56:c8:7d:3f:29:41:d1:87:92:fd:50:2b:6b" + + npm_login_command: + description: "Enable interacting with `npm` using an auth token" + steps: + - run: + name: Login to the npm registry using '.npmrc' file + command: echo "//registry.npmjs.org/:_authToken=\${CKE5_NPM_TOKEN}" > ~/.npmrc + + git_credentials_command: + description: "Setup git configuration" + steps: + - run: + name: Setup git configuration + command: | + git config --global user.email "ckeditor-bot@cksource.com" + git config --global user.name "CKEditorBot" + + prepare_environment_variables_commands: + description: "Prepare non-secret environment variables" + steps: + - run: + name: Prepare environment variables + command: | + #!/bin/bash + + # Non-secret environment variables needed for the pipeline scripts. + CKE5_GITHUB_ORGANIZATION="ckeditor" + CKE5_GITHUB_REPOSITORY="ckeditor5-dev" + CKE5_CIRCLE_APPROVAL_JOB_NAME="release_approval" + CKE5_GITHUB_RELEASE_BRANCH="master" + + echo export CKE5_CIRCLE_APPROVAL_JOB_NAME=$CKE5_CIRCLE_APPROVAL_JOB_NAME >> $BASH_ENV + echo export CKE5_GITHUB_RELEASE_BRANCH=$CKE5_GITHUB_RELEASE_BRANCH >> $BASH_ENV + echo export CKE5_GITHUB_ORGANIZATION=$CKE5_GITHUB_ORGANIZATION >> $BASH_ENV + echo export CKE5_GITHUB_REPOSITORY=$CKE5_GITHUB_REPOSITORY >> $BASH_ENV + echo export CKE5_GITHUB_REPOSITORY_SLUG="$CKE5_GITHUB_ORGANIZATION/$CKE5_GITHUB_REPOSITORY" >> $BASH_ENV + echo export CKE5_COMMIT_SHA1=$CIRCLE_SHA1 >> $BASH_ENV + +jobs: + notify_ci_failure: + machine: true + parameters: + hideAuthor: + type: string + default: "false" + steps: + - checkout + - bootstrap_repository_command + - run: + # In the PRs that comes from forked repositories, we do not share secret variables. + # Hence, some of the scripts will not be executed. + name: 👤 Verify if the build was triggered by community - Check if the build should continue + command: | + #!/bin/bash + + if [[ -z ${COVERALLS_REPO_TOKEN} ]]; + then + circleci-agent step halt + fi + - run: + environment: + CKE5_SLACK_NOTIFY_HIDE_AUTHOR: << parameters.hideAuthor >> + CKE5_PIPELINE_NUMBER: << pipeline.number >> + name: Waiting for other jobs to finish and sending notification on failure + command: yarn ckeditor5-dev-ci-circle-workflow-notifier + no_output_timeout: 1h + + validate_and_tests: + machine: true + resource_class: large + steps: + - checkout + - bootstrap_repository_command + - run: + name: Execute ESLint + command: yarn run lint + - unless: + # Upload the code coverage results for non-nightly builds only. + condition: << pipeline.parameters.isNightly >> + steps: + - run: + # In the PRs that comes from forked repositories, we do not share secret variables. + # Hence, some of the scripts will not be executed. + name: 👤 Verify if the build was triggered by community - Check if the build should continue + command: | + #!/bin/bash + + if [[ -z ${COVERALLS_REPO_TOKEN} ]]; + then + circleci-agent step halt + fi + - run: + name: Upload code coverage + command: cat coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js + + release_prepare: + machine: true + resource_class: large + steps: + - checkout + - bootstrap_repository_command + - run: + name: Check if packages are ready to be released + command: yarn run release:prepare-packages --verbose --compile-only + + trigger_release_process: + machine: true + resource_class: large + steps: + - checkout + - bootstrap_repository_command + - run: + name: Verify if the project is ready to release + command: | + #!/bin/bash + + # Do not fail if the Node script ends with non-zero exit code. + set +e + + node scripts/ci/is-project-ready-to-release.js + EXIT_CODE=$( echo $? ) + + if [ ${EXIT_CODE} -eq 1 ]; + then + circleci-agent step halt + fi + - run: + name: Trigger the release pipeline + command: yarn ckeditor5-dev-ci-trigger-circle-build + + release_project: + machine: true + resource_class: large + steps: + - checkout + - bootstrap_repository_command + - run: + name: Verify the trigger commit from the repository + command: | + #!/bin/bash + + CKE5_LATEST_COMMIT_HASH=$( git log -n 1 --pretty=format:%H origin/master ) + CKE5_TRIGGER_COMMIT_HASH=<< pipeline.parameters.triggerCommitHash >> + + if [[ "${CKE5_LATEST_COMMIT_HASH}" != "${CKE5_TRIGGER_COMMIT_HASH}" ]]; then + echo "There is a newer commit in the repository on the \`#master\` branch. Use its build to start the release." + circleci-agent step halt + fi + - npm_login_command + - git_credentials_command + - run: + name: Verify if a releaser triggered the job + command: | + #!/bin/bash + + # Do not fail if the Node script ends with non-zero exit code. + set +e + + yarn ckeditor5-dev-ci-is-job-triggered-by-member + EXIT_CODE=$( echo $? ) + + if [ ${EXIT_CODE} -ne 0 ]; + then + echo "Aborting the release due to failed verification of the approver (no rights to release)." + circleci-agent step halt + fi + - run: + name: Disable the redundant workflows option + command: yarn ckeditor5-dev-ci-circle-disable-auto-cancel-builds + - run: + name: Prepare the new version to release + command: npm run release:prepare-packages -- --verbose + - run: + name: Publish the packages + command: npm run release:publish-packages -- --verbose + - run: + name: Enable the redundant workflows option + command: yarn ckeditor5-dev-ci-circle-enable-auto-cancel-builds + when: always + - run: + name: Pack the "release/" directory (in case of failure) + command: | + zip -r ./release.zip ./release + when: always + - store_artifacts: + path: ./release.zip + when: always + +workflows: + version: 2 + main: + when: + and: + - equal: [ false, << pipeline.parameters.isNightly >> ] + - equal: [ false, << pipeline.parameters.isRelease >> ] + jobs: + - validate_and_tests + - release_prepare + - trigger_release_process: + requires: + - validate_and_tests + - release_prepare + filters: + branches: + only: + - master + - notify_ci_failure: + filters: + branches: + only: + - master + + release: + when: + and: + - equal: [ false, << pipeline.parameters.isNightly >> ] + - equal: [ true, << pipeline.parameters.isRelease >> ] + jobs: + - release_approval: + type: approval + - release_project: + requires: + - release_approval + + nightly: + when: + and: + - equal: [ true, << pipeline.parameters.isNightly >> ] + - equal: [ false, << pipeline.parameters.isRelease >> ] + jobs: + - validate_and_tests + - notify_ci_failure: + hideAuthor: "true" + filters: + branches: + only: + - master diff --git a/.eslintrc.js b/.eslintrc.cjs similarity index 100% rename from .eslintrc.js rename to .eslintrc.cjs diff --git a/.gitignore b/.gitignore index 1b81873e8..faaf9bd36 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,6 @@ packages/ckeditor5-dev-build-tools/dist packages/ckeditor5-dev-release-tools/tests/test-fixtures/** !packages/ckeditor5-dev-release-tools/tests/test-fixtures/.gitkeep + +# Generated automatically via CircleCI. +.circleci/config-tests.yml diff --git a/package.json b/package.json index cabf2e784..dceda2c5c 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "engines": { "node": ">=18.0.0" }, + "type": "module", "devDependencies": { "@ckeditor/ckeditor5-dev-ci": "^43.0.0", "@ckeditor/ckeditor5-dev-release-tools": "^43.0.0", @@ -23,6 +24,7 @@ "fs-extra": "^11.2.0", "glob": "^10.2.5", "husky": "^8.0.2", + "js-yaml": "^4.1.0", "lint-staged": "^10.2.4", "listr2": "^6.5.0", "minimist": "^1.2.8", diff --git a/scripts/ci/combine-coverage-lcov.js b/scripts/ci/combine-coverage-lcov.js new file mode 100644 index 000000000..651242948 --- /dev/null +++ b/scripts/ci/combine-coverage-lcov.js @@ -0,0 +1,30 @@ +#!/usr/bin/env node + +/** + * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/* eslint-env node */ + +import path from 'path'; +import fs from 'fs-extra'; +import { globSync } from 'glob'; +import chalk from 'chalk'; + +const cwd = process.cwd(); +const coverageFile = path.join( cwd, 'coverage', 'lcov.info' ); + +fs.emptyDirSync( path.join( coverageFile, '..' ) ); +fs.ensureFileSync( path.join( coverageFile ) ); + +// Merge separate reports into a single file that would be sent to Coveralls. +for ( const lcovPath of globSync( './packages/*/coverage/lcov.info' ) ) { + const relativePackagePath = path.join( lcovPath, '..', '..' ); + const content = fs.readFileSync( lcovPath, 'utf-8' ) + .replaceAll( /^(SF:)/gm, `$1${ relativePackagePath }/` ); + + fs.writeFileSync( coverageFile, content, { flag: 'as' } ); +} + +console.log( chalk.cyan( `Coverage status stored in "${ chalk.underline( coverageFile ) }".` ) ); diff --git a/scripts/ci/generate-circleci-configuration.js b/scripts/ci/generate-circleci-configuration.js new file mode 100644 index 000000000..05542cc7c --- /dev/null +++ b/scripts/ci/generate-circleci-configuration.js @@ -0,0 +1,136 @@ +#!/usr/bin/env node + +/** + * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/* eslint-env node */ + +// The script assumes that it is executed from the CKEditor 5 Commercial directory and aims to load +// the template file (`.circleci/template.yml`) and store it under the `.circleci/config-tests.yml` path, +// a source for a new workflow triggered from the main thread when a new build starts. +// +// See: https://circleci.com/docs/using-dynamic-configuration/. + +import fs from 'fs/promises'; +import { fileURLToPath } from 'url'; +import upath from 'upath'; +import { glob } from 'glob'; +import yaml from 'js-yaml'; + +const __filename = fileURLToPath( import.meta.url ); +const __dirname = upath.dirname( __filename ); + +const ROOT_DIRECTORY = upath.join( __dirname, '..', '..' ); +const CIRCLECI_CONFIGURATION_DIRECTORY = upath.join( ROOT_DIRECTORY, '.circleci' ); + +( async () => { + const packages = await glob( '*/', { cwd: upath.join( ROOT_DIRECTORY, 'packages' ) } ); + + packages.sort(); + + /** + * @type CircleCIConfiguration + */ + const config = yaml.load( + await fs.readFile( upath.join( CIRCLECI_CONFIGURATION_DIRECTORY, 'template.yml' ) ) + ); + + config.jobs.validate_and_tests.steps.splice( 3, 0, ...generateTestSteps( packages ) ); + config.jobs.validate_and_tests.steps.splice( -1, 0, { + run: { + name: 'Combine coverage reports into a single file', + command: 'node scripts/ci/combine-coverage-lcov.js' + } + } ); + + await fs.writeFile( + upath.join( CIRCLECI_CONFIGURATION_DIRECTORY, 'config-tests.yml' ), + yaml.dump( config, { lineWidth: -1 } ) + ); +} )(); + +function generateTestSteps( packages ) { + return packages.map( packageName => { + return { + run: { + environment: { + TZ: 'Europe/Warsaw' + }, + // When a previous package failed, we still want to check the entire repository. + when: 'always', + name: `Execute tests for "${ packageName }"`, + cwd: upath.join( 'packages', packageName ), + command: 'yarn run coverage' + } + }; + } ); +} + +/** + * This type partially covers supported options on CircleCI. + * To see the complete guide, follow: https://circleci.com/docs/configuration-reference. + * + * @typedef {Object} CircleCIConfiguration + * + * @property {String} version + * + * @property {Object.} parameters + * + * @property {Object.} jobs + * + * @property {Object.} command + * + * @property {Object} workflows + * + * @property {Boolean} [setup] + */ + +/** + * @typedef {Object} CircleCIParameter + * + * @property {'string'|'boolean'|'integer'|'enum'} type + * + * @property {String|Number|Boolean} default + */ + +/** + * @typedef {Object} CircleCIJob + * + * @property {Boolean} machine + * + * @property {Array.} steps + * + * @property {Object.} [parameters] + */ + +/** + * @typedef {Object} CircleCICommand + * + * @property {String} description + * + * @property {Array.} steps + * + * @property {Object.} [parameters] + */ + +/** + * @typedef {Object} CircleCITask + * + * @property {Object} [persist_to_workspace] + * + * @property {String} [persist_to_workspace.root] + * + * @property {Array.} [persist_to_workspace.paths] + * + * @property {Object} [run] + * + * @property {String} [run.name] + * + * @property {String} [run.command] + * + * @property {String} [run.when] + * + * @property {String} [run.working_directory] + */ diff --git a/scripts/runtest.js b/scripts/runtest.js index 2b9f11970..cad1be884 100644 --- a/scripts/runtest.js +++ b/scripts/runtest.js @@ -7,21 +7,18 @@ /* eslint-env node */ -'use strict'; - -const path = require( 'path' ); -const { execSync } = require( 'child_process' ); -const fs = require( 'fs-extra' ); -const { globSync } = require( 'glob' ); -const minimist = require( 'minimist' ); -const chalk = require( 'chalk' ); +import path from 'path'; +import { execSync } from 'child_process'; +import fs from 'fs-extra'; +import { globSync } from 'glob'; +import minimist from 'minimist'; +import chalk from 'chalk'; main(); function main() { let hasError = false; const cwd = process.cwd(); - const coverageFile = path.join( cwd, 'coverage', 'lcov.info' ); const { coverage } = parseArguments( process.argv.slice( 2 ) ); const packages = globSync( './packages/*/package.json' ) @@ -49,19 +46,10 @@ function main() { } if ( coverage ) { - fs.emptyDirSync( path.join( coverageFile, '..' ) ); - fs.ensureFileSync( path.join( coverageFile ) ); - - // Merge separate reports into a single file that would be sent to Coveralls. - for ( const lcovPath of globSync( './packages/*/coverage/lcov.info' ) ) { - const relativePackagePath = path.join( lcovPath, '..', '..' ); - const content = fs.readFileSync( lcovPath, 'utf-8' ) - .replaceAll( /^(SF:)/gm, `$1${ relativePackagePath }/` ); - - fs.writeFileSync( coverageFile, content, { flag: 'as' } ); - } - - console.log( chalk.cyan( `\nCoverage status stored in "${ chalk.underline( coverageFile ) }".` ) ); + execSync( 'node scripts/ci/combine-coverage-lcov.js', { + cwd, + stdio: 'inherit' + } ); } if ( ignoredPackages.length ) { From 74b3c3cab32568c0885b706e83451509cd571f5f Mon Sep 17 00:00:00 2001 From: Kamil Piechaczek Date: Mon, 16 Sep 2024 11:37:13 +0200 Subject: [PATCH 06/15] s/cwd/working_directory --- scripts/ci/generate-circleci-configuration.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/ci/generate-circleci-configuration.js b/scripts/ci/generate-circleci-configuration.js index 05542cc7c..08148acf2 100644 --- a/scripts/ci/generate-circleci-configuration.js +++ b/scripts/ci/generate-circleci-configuration.js @@ -61,7 +61,7 @@ function generateTestSteps( packages ) { // When a previous package failed, we still want to check the entire repository. when: 'always', name: `Execute tests for "${ packageName }"`, - cwd: upath.join( 'packages', packageName ), + working_directory: upath.join( 'packages', packageName ), command: 'yarn run coverage' } }; From 6e451e2dac0b57578c9a64ed408dff98fc94f1e2 Mon Sep 17 00:00:00 2001 From: Kamil Piechaczek Date: Mon, 16 Sep 2024 11:51:55 +0200 Subject: [PATCH 07/15] Filter out packages without tests. --- scripts/ci/generate-circleci-configuration.js | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/scripts/ci/generate-circleci-configuration.js b/scripts/ci/generate-circleci-configuration.js index 08148acf2..f55f26f76 100644 --- a/scripts/ci/generate-circleci-configuration.js +++ b/scripts/ci/generate-circleci-configuration.js @@ -13,7 +13,7 @@ // // See: https://circleci.com/docs/using-dynamic-configuration/. -import fs from 'fs/promises'; +import fs from 'fs-extra'; import { fileURLToPath } from 'url'; import upath from 'upath'; import { glob } from 'glob'; @@ -27,8 +27,15 @@ const CIRCLECI_CONFIGURATION_DIRECTORY = upath.join( ROOT_DIRECTORY, '.circleci' ( async () => { const packages = await glob( '*/', { cwd: upath.join( ROOT_DIRECTORY, 'packages' ) } ); + const packagesWithTests = await asyncFilter( packages, async packageName => { + const pkgJson = await fs.readJson( + upath.join( ROOT_DIRECTORY, 'packages', packageName, 'package.json' ) + ); - packages.sort(); + return pkgJson?.scripts?.coverage; + } ); + + packagesWithTests.sort(); /** * @type CircleCIConfiguration @@ -37,7 +44,7 @@ const CIRCLECI_CONFIGURATION_DIRECTORY = upath.join( ROOT_DIRECTORY, '.circleci' await fs.readFile( upath.join( CIRCLECI_CONFIGURATION_DIRECTORY, 'template.yml' ) ) ); - config.jobs.validate_and_tests.steps.splice( 3, 0, ...generateTestSteps( packages ) ); + config.jobs.validate_and_tests.steps.splice( 3, 0, ...generateTestSteps( packagesWithTests ) ); config.jobs.validate_and_tests.steps.splice( -1, 0, { run: { name: 'Combine coverage reports into a single file', @@ -51,6 +58,15 @@ const CIRCLECI_CONFIGURATION_DIRECTORY = upath.join( ROOT_DIRECTORY, '.circleci' ); } )(); +async function asyncFilter( items, predicate ) { + return items.reduce( async ( results, item ) => { + return [ + ...await results, + ...await predicate( item ) ? [ item ] : [] + ]; + }, [] ); +} + function generateTestSteps( packages ) { return packages.map( packageName => { return { From 9563123e27b1b9ed323b44ab7a0c4cf072878b7f Mon Sep 17 00:00:00 2001 From: Kamil Piechaczek Date: Mon, 16 Sep 2024 13:18:24 +0200 Subject: [PATCH 08/15] First tests. --- packages/ckeditor5-dev-tests/package.json | 1 + .../tests/_utils/testsetup.js | 9 + packages/ckeditor5-dev-tests/tests/index.js | 37 ++ .../tests/tasks/runautomatedtests.js | 506 +++++++++--------- packages/ckeditor5-dev-tests/vitest.config.js | 7 + 5 files changed, 310 insertions(+), 250 deletions(-) create mode 100644 packages/ckeditor5-dev-tests/tests/_utils/testsetup.js create mode 100644 packages/ckeditor5-dev-tests/tests/index.js diff --git a/packages/ckeditor5-dev-tests/package.json b/packages/ckeditor5-dev-tests/package.json index 94f0c471d..9bc232159 100644 --- a/packages/ckeditor5-dev-tests/package.json +++ b/packages/ckeditor5-dev-tests/package.json @@ -75,6 +75,7 @@ "webpack": "^5.94.0" }, "devDependencies": { + "jest-extended": "^4.0.2", "vitest": "^2.0.5" }, "scripts": { diff --git a/packages/ckeditor5-dev-tests/tests/_utils/testsetup.js b/packages/ckeditor5-dev-tests/tests/_utils/testsetup.js new file mode 100644 index 000000000..52e540b96 --- /dev/null +++ b/packages/ckeditor5-dev-tests/tests/_utils/testsetup.js @@ -0,0 +1,9 @@ +/** + * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md. + */ + +import { expect } from 'vitest'; +import * as matchers from 'jest-extended'; + +expect.extend( matchers ); diff --git a/packages/ckeditor5-dev-tests/tests/index.js b/packages/ckeditor5-dev-tests/tests/index.js new file mode 100644 index 000000000..15b9b0fc6 --- /dev/null +++ b/packages/ckeditor5-dev-tests/tests/index.js @@ -0,0 +1,37 @@ +/** + * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md. + */ + +import { describe, expect, it, vi } from 'vitest'; +import * as index from '../lib/index.js'; +import runAutomatedTests from '../lib/tasks/runautomatedtests.js'; +import runManualTests from '../lib/tasks/runmanualtests.js'; +import parseArguments from '../lib/utils/automated-tests/parsearguments.js'; + +vi.mock( '../lib/tasks/runautomatedtests.js' ); +vi.mock( '../lib/tasks/runmanualtests.js' ); +vi.mock( '../lib/utils/automated-tests/parsearguments.js' ); + +describe( 'index.js', () => { + describe( 'runAutomatedTests()', () => { + it( 'should be a function', () => { + expect( index.runAutomatedTests ).to.be.a( 'function' ); + expect( index.runAutomatedTests ).toEqual( runAutomatedTests ); + } ); + } ); + + describe( 'runManualTests()', () => { + it( 'should be a function', () => { + expect( index.runManualTests ).to.be.a( 'function' ); + expect( index.runManualTests ).toEqual( runManualTests ); + } ); + } ); + + describe( 'parseArguments()', () => { + it( 'should be a function', () => { + expect( index.parseArguments ).to.be.a( 'function' ); + expect( index.parseArguments ).toEqual( parseArguments ); + } ); + } ); +} ); diff --git a/packages/ckeditor5-dev-tests/tests/tasks/runautomatedtests.js b/packages/ckeditor5-dev-tests/tests/tasks/runautomatedtests.js index 6ee2f09a9..b1eb0f5cb 100644 --- a/packages/ckeditor5-dev-tests/tests/tasks/runautomatedtests.js +++ b/packages/ckeditor5-dev-tests/tests/tasks/runautomatedtests.js @@ -3,95 +3,82 @@ * For licensing, see LICENSE.md. */ -'use strict'; - -const mockery = require( 'mockery' ); -const sinon = require( 'sinon' ); -const proxyquire = require( 'proxyquire' ); -const expect = require( 'chai' ).expect; -const chalk = require( 'chalk' ); -const path = require( 'path' ); - -describe( 'runAutomatedTests', () => { - let sandbox, stubs, runAutomatedTests, karmaServerCallback; +import path from 'path'; +import fs from 'fs'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { globSync } from 'glob'; +import mkdirp from 'mkdirp'; +import chalk from 'chalk'; +import karma from 'karma'; +import karmaLogger from 'karma/lib/logger.js'; +import transformFileOptionToTestGlob from '../../lib/utils/transformfileoptiontotestglob.js'; + +const stubs = vi.hoisted( () => ( { + log: { + log: vi.fn(), + error: vi.fn(), + warn: vi.fn(), + info: vi.fn() + }, + karma: { + server: { + constructor: vi.fn(), + on: vi.fn(), + start: vi.fn() + } + } +} ) ); + +vi.mock( 'karma', () => ( { + default: { + Server: class KarmaServer { + constructor( ...args ) { + stubs.karma.server.constructor( ...args ); + } - beforeEach( () => { - karmaServerCallback = null; - sandbox = sinon.createSandbox(); + on( ...args ) { + return stubs.karma.server.on( ...args ); + } - mockery.enable( { - useCleanCache: true, - warnOnReplace: false, - warnOnUnregistered: false - } ); + start( ...args ) { + return stubs.karma.server.start( ...args ); + } + }, + config: { + parseConfig: vi.fn() + } + } +} ) ); + +vi.mock( 'chalk', () => ( { + default: { + yellow: vi.fn() + } +} ) ); + +vi.mock( 'fs' ); +vi.mock( 'mkdirp' ); +vi.mock( 'glob' ); +vi.mock( 'karma/lib/logger.js' ); +vi.mock( '../../lib/utils/automated-tests/getkarmaconfig.js' ); +vi.mock( '../../lib/utils/transformfileoptiontotestglob.js' ); - stubs = { - fs: { - writeFileSync: sandbox.stub(), - utimesSync: sandbox.stub(), - readdirSync: sandbox.stub() - }, - log: { - info: sandbox.stub(), - warn: sandbox.stub(), - error: sandbox.stub() - }, - mkdirp: { - sync: sandbox.stub() - }, - glob: { - globSync: sandbox.stub() - }, - karma: { - Server: class KarmaServer { - constructor( config, callback ) { - karmaServerCallback = callback; - } - - on( ...args ) { - return stubs.karma.karmaServerOn( ...args ); - } - - start( ...args ) { - return stubs.karma.karmaServerOn( ...args ); - } - }, - karmaServerOn: sandbox.stub(), - karmaServerStart: sandbox.stub(), - config: { - parseConfig: sandbox.stub() - } - }, - getKarmaConfig: sandbox.stub(), - transformFileOptionToTestGlob: sandbox.stub() - }; +describe( 'runAutomatedTests', () => { + let runAutomatedTests; - sandbox.stub( process, 'cwd' ).returns( '/workspace' ); + beforeEach( async () => { + vi.spyOn( process, 'cwd' ).mockReturnValue( '/workspace' ); - mockery.registerMock( 'mkdirp', stubs.mkdirp ); - mockery.registerMock( 'karma', stubs.karma ); - mockery.registerMock( 'karma/lib/logger.js', { - setupFromConfig: sandbox.spy(), - create( name ) { - expect( name ).to.equal( 'config' ); - return stubs.log; - } - } ); - mockery.registerMock( '../utils/automated-tests/getkarmaconfig', stubs.getKarmaConfig ); - mockery.registerMock( '../utils/transformfileoptiontotestglob', stubs.transformFileOptionToTestGlob ); + vi.mocked( karmaLogger ).create.mockImplementation( name => { + expect( name ).to.equal( 'config' ); - runAutomatedTests = proxyquire( '../../lib/tasks/runautomatedtests', { - fs: stubs.fs, - glob: stubs.glob + return stubs.log; } ); - } ); - afterEach( () => { - sandbox.restore(); - mockery.disable(); + runAutomatedTests = ( await import( '../../lib/tasks/runautomatedtests.js' ) ).default; } ); - it( 'should create an entry file before tests execution', done => { + it( 'should create an entry file before tests execution', async () => { const options = { files: [ 'basic-styles' @@ -99,56 +86,51 @@ describe( 'runAutomatedTests', () => { production: true }; - stubs.fs.readdirSync.returns( [] ); + vi.mocked( fs ).readdirSync.mockReturnValue( [] ); - stubs.transformFileOptionToTestGlob.returns( [ + vi.mocked( transformFileOptionToTestGlob ).mockReturnValue( [ '/workspace/packages/ckeditor5-basic-styles/tests/**/*.js', '/workspace/packages/ckeditor-basic-styles/tests/**/*.js' ] ); - stubs.glob.globSync.onFirstCall().returns( [ - '/workspace/packages/ckeditor5-basic-styles/tests/bold.js', - '/workspace/packages/ckeditor5-basic-styles/tests/italic.js' - ] ); - - stubs.glob.globSync.onSecondCall().returns( [] ); + vi.mocked( globSync ) + .mockReturnValue( [] ) + .mockReturnValueOnce( [ + '/workspace/packages/ckeditor5-basic-styles/tests/bold.js', + '/workspace/packages/ckeditor5-basic-styles/tests/italic.js' + ] ); const expectedEntryPointContent = [ 'import "/workspace/packages/ckeditor5-basic-styles/tests/bold.js";', - 'import "/workspace/packages/ckeditor5-basic-styles/tests/italic.js";', - '' + 'import "/workspace/packages/ckeditor5-basic-styles/tests/italic.js";' ].join( '\n' ); + const promise = runAutomatedTests( options ); + setTimeout( () => { - karmaServerCallback( 0 ); - } ); + expect( stubs.karma.server.constructor ).toHaveBeenCalledOnce(); + + const [ firstCall ] = stubs.karma.server.constructor.mock.calls; + const [ , exitCallback ] = firstCall; - runAutomatedTests( options ) - .then( () => { - expect( stubs.mkdirp.sync.calledOnce ).to.equal( true ); - expect( stubs.mkdirp.sync.firstCall.args[ 0 ] ).to.equal( '/workspace/build/.automated-tests' ); + exitCallback( 0 ); + } ); - expect( stubs.fs.writeFileSync.calledOnce ).to.equal( true ); - expect( stubs.fs.writeFileSync.firstCall.args[ 0 ] ).to.equal( '/workspace/build/.automated-tests/entry-point.js' ); - expect( stubs.fs.writeFileSync.firstCall.args[ 1 ] ).to.include( expectedEntryPointContent ); + await promise; - done(); - } ); + expect( vi.mocked( mkdirp ).sync ).toHaveBeenCalledExactlyOnceWith( '/workspace/build/.automated-tests' ); + expect( vi.mocked( fs ).writeFileSync ).toHaveBeenCalledExactlyOnceWith( + '/workspace/build/.automated-tests/entry-point.js', + expect.stringContaining( expectedEntryPointContent ) + ); } ); - it( 'throws when files are not specified', () => { - return runAutomatedTests( { production: true } ) - .then( - () => { - throw new Error( 'Expected to be rejected.' ); - }, - err => { - expect( err.message ).to.equal( 'Karma requires files to tests. `options.files` has to be non-empty array.' ); - } - ); + it( 'throws when files are not specified', async () => { + await expect( runAutomatedTests( { production: true } ) ) + .rejects.toThrow( 'Karma requires files to tests. `options.files` has to be non-empty array.' ); } ); - it( 'throws when specified files are invalid', () => { + it( 'throws when specified files are invalid', async () => { const options = { files: [ 'basic-foo', @@ -157,38 +139,29 @@ describe( 'runAutomatedTests', () => { production: true }; - stubs.fs.readdirSync.returns( [] ); + vi.mocked( fs ).readdirSync.mockReturnValue( [] ); - stubs.transformFileOptionToTestGlob.onFirstCall().returns( [ - '/workspace/packages/ckeditor5-basic-foo/tests/**/*.js', - '/workspace/packages/ckeditor-basic-foo/tests/**/*.js' - ] ); + vi.mocked( transformFileOptionToTestGlob ) + .mockReturnValueOnce( [ + '/workspace/packages/ckeditor5-basic-foo/tests/**/*.js', + '/workspace/packages/ckeditor-basic-foo/tests/**/*.js' + ] ) + .mockReturnValueOnce( [ + '/workspace/packages/ckeditor5-bar-core/tests/**/*.js', + '/workspace/packages/ckeditor-bar-core/tests/**/*.js' + ] ); - stubs.transformFileOptionToTestGlob.onSecondCall().returns( [ - '/workspace/packages/ckeditor5-bar-core/tests/**/*.js', - '/workspace/packages/ckeditor-bar-core/tests/**/*.js' - ] ); + vi.mocked( globSync ).mockReturnValue( [] ); + + await expect( runAutomatedTests( options ) ) + .rejects.toThrow( 'Not found files to tests. Specified patterns are invalid.' ); - stubs.glob.globSync.returns( [] ); - - return runAutomatedTests( options ) - .then( - () => { - throw new Error( 'Expected to be rejected.' ); - }, - err => { - expect( stubs.log.warn.calledTwice ).to.equal( true ); - expect( stubs.log.warn.firstCall.args[ 0 ] ).to.equal( 'Pattern "%s" does not match any file.' ); - expect( stubs.log.warn.firstCall.args[ 1 ] ).to.equal( 'basic-foo' ); - expect( stubs.log.warn.secondCall.args[ 0 ] ).to.equal( 'Pattern "%s" does not match any file.' ); - expect( stubs.log.warn.secondCall.args[ 1 ] ).to.equal( 'bar-core' ); - - expect( err.message ).to.equal( 'Not found files to tests. Specified patterns are invalid.' ); - } - ); + expect( stubs.log.warn ).toHaveBeenCalledTimes( 2 ); + expect( stubs.log.warn ).toHaveBeenCalledWith( 'Pattern "%s" does not match any file.', 'basic-foo' ); + expect( stubs.log.warn ).toHaveBeenCalledWith( 'Pattern "%s" does not match any file.', 'bar-core' ); } ); - it( 'throws when Karma config parser throws', () => { + it( 'throws when Karma config parser throws', async () => { const options = { files: [ 'basic-styles' @@ -196,34 +169,34 @@ describe( 'runAutomatedTests', () => { production: true }; - stubs.fs.readdirSync.returns( [] ); - - stubs.transformFileOptionToTestGlob.returns( [ - '/workspace/packages/ckeditor5-basic-styles/tests/**/*.js', - '/workspace/packages/ckeditor-basic-styles/tests/**/*.js' - ] ); - - stubs.glob.globSync.onFirstCall().returns( [ - '/workspace/packages/ckeditor5-basic-styles/tests/bold.js', - '/workspace/packages/ckeditor5-basic-styles/tests/italic.js' - ] ); - - stubs.glob.globSync.onSecondCall().returns( [] ); - - stubs.karma.config.parseConfig.throws( new Error( 'Example error from Karma config parser.' ) ); + vi.mocked( fs ).readdirSync.mockReturnValue( [] ); + + vi.mocked( transformFileOptionToTestGlob ) + .mockReturnValueOnce( [ + '/workspace/packages/ckeditor5-basic-foo/tests/**/*.js', + '/workspace/packages/ckeditor-basic-foo/tests/**/*.js' + ] ) + .mockReturnValueOnce( [ + '/workspace/packages/ckeditor5-bar-core/tests/**/*.js', + '/workspace/packages/ckeditor-bar-core/tests/**/*.js' + ] ); + + vi.mocked( globSync ) + .mockReturnValue( [] ) + .mockReturnValueOnce( [ + '/workspace/packages/ckeditor5-basic-styles/tests/bold.js', + '/workspace/packages/ckeditor5-basic-styles/tests/italic.js' + ] ); + + vi.mocked( karma ).config.parseConfig.mockImplementation( () => { + throw new Error( 'Example error from Karma config parser.' ); + } ); - return runAutomatedTests( options ) - .then( - () => { - throw new Error( 'Expected to be rejected.' ); - }, - err => { - expect( err.message ).to.equal( 'Example error from Karma config parser.' ); - } - ); + await expect( runAutomatedTests( options ) ) + .rejects.toThrow( 'Example error from Karma config parser.' ); } ); - it( 'should warn when the `production` option is set to `false`', () => { + it( 'should warn when the `production` option is set to `false`', async () => { const options = { files: [ 'basic-styles' @@ -231,37 +204,45 @@ describe( 'runAutomatedTests', () => { production: false }; - const consoleWarnStub = sandbox.stub( console, 'warn' ); + const consoleWarnStub = vi.spyOn( console, 'warn' ).mockImplementation( () => { + } ); - stubs.fs.readdirSync.returns( [] ); + vi.mocked( chalk ).yellow.mockReturnValue( 'chalk.yellow: warn' ); + vi.mocked( fs ).readdirSync.mockReturnValue( [] ); - stubs.transformFileOptionToTestGlob.returns( [ + vi.mocked( transformFileOptionToTestGlob ).mockReturnValue( [ '/workspace/packages/ckeditor5-basic-styles/tests/**/*.js', '/workspace/packages/ckeditor-basic-styles/tests/**/*.js' ] ); - stubs.glob.globSync.onFirstCall().returns( [ - '/workspace/packages/ckeditor5-basic-styles/tests/bold.js', - '/workspace/packages/ckeditor5-basic-styles/tests/italic.js' - ] ); + vi.mocked( globSync ) + .mockReturnValue( [] ) + .mockReturnValueOnce( [ + '/workspace/packages/ckeditor5-basic-styles/tests/bold.js', + '/workspace/packages/ckeditor5-basic-styles/tests/italic.js' + ] ); - stubs.glob.globSync.onSecondCall().returns( [] ); + const promise = runAutomatedTests( options ); setTimeout( () => { - karmaServerCallback( 0 ); + expect( stubs.karma.server.constructor ).toHaveBeenCalledOnce(); + + const [ firstCall ] = stubs.karma.server.constructor.mock.calls; + const [ , exitCallback ] = firstCall; + + exitCallback( 0 ); } ); - return runAutomatedTests( options ) - .then( () => { - expect( consoleWarnStub ).to.be.calledOnce; - expect( consoleWarnStub ).to.be.calledWith( - chalk.yellow( '⚠ You\'re running tests in dev mode - some error protections are loose. ' + - 'Use the `--production` flag to use strictest verification methods.' ) - ); - } ); + await promise; + + expect( consoleWarnStub ).toHaveBeenCalledExactlyOnceWith( 'chalk.yellow: warn' ); + expect( vi.mocked( chalk ).yellow ).toHaveBeenCalledExactlyOnceWith( + '⚠ You\'re running tests in dev mode - some error protections are loose. ' + + 'Use the `--production` flag to use strictest verification methods.' + ); } ); - it( 'should not add the code making console use throw an error when the `production` option is set to false', () => { + it( 'should not add the code making console use throw an error when the `production` option is set to false', async () => { const options = { files: [ 'basic-styles' @@ -269,33 +250,42 @@ describe( 'runAutomatedTests', () => { production: false }; - sandbox.stub( console, 'warn' ); - - stubs.fs.readdirSync.returns( [] ); + vi.spyOn( console, 'warn' ).mockImplementation( () => { + } ); + vi.mocked( fs ).readdirSync.mockReturnValue( [] ); - stubs.transformFileOptionToTestGlob.returns( [ + vi.mocked( transformFileOptionToTestGlob ).mockReturnValue( [ '/workspace/packages/ckeditor5-basic-styles/tests/**/*.js', '/workspace/packages/ckeditor-basic-styles/tests/**/*.js' ] ); - stubs.glob.globSync.onFirstCall().returns( [ - '/workspace/packages/ckeditor5-basic-styles/tests/bold.js', - '/workspace/packages/ckeditor5-basic-styles/tests/italic.js' - ] ); + vi.mocked( globSync ) + .mockReturnValue( [] ) + .mockReturnValueOnce( [ + '/workspace/packages/ckeditor5-basic-styles/tests/bold.js', + '/workspace/packages/ckeditor5-basic-styles/tests/italic.js' + ] ); - stubs.glob.globSync.onSecondCall().returns( [] ); + const promise = runAutomatedTests( options ); setTimeout( () => { - karmaServerCallback( 0 ); + expect( stubs.karma.server.constructor ).toHaveBeenCalledOnce(); + + const [ firstCall ] = stubs.karma.server.constructor.mock.calls; + const [ , exitCallback ] = firstCall; + + exitCallback( 0 ); } ); - return runAutomatedTests( options ) - .then( () => { - expect( stubs.fs.writeFileSync.firstCall.args[ 1 ] ).to.not.include( '// Make using any method from the console to fail.' ); - } ); + await promise; + + expect( vi.mocked( fs ).writeFileSync ).toHaveBeenCalledExactlyOnceWith( + expect.any( String ), + expect.not.stringContaining( '// Make using any method from the console to fail.' ) + ); } ); - it( 'should add the code making console use throw an error when the `production` option is set to true', () => { + it( 'should add the code making console use throw an error when the `production` option is set to true', async () => { const options = { files: [ 'basic-styles' @@ -303,31 +293,42 @@ describe( 'runAutomatedTests', () => { production: true }; - stubs.fs.readdirSync.returns( [] ); + vi.spyOn( console, 'warn' ).mockImplementation( () => { + } ); + vi.mocked( fs ).readdirSync.mockReturnValue( [] ); - stubs.transformFileOptionToTestGlob.returns( [ + vi.mocked( transformFileOptionToTestGlob ).mockReturnValue( [ '/workspace/packages/ckeditor5-basic-styles/tests/**/*.js', '/workspace/packages/ckeditor-basic-styles/tests/**/*.js' ] ); - stubs.glob.globSync.onFirstCall().returns( [ - '/workspace/packages/ckeditor5-basic-styles/tests/bold.js', - '/workspace/packages/ckeditor5-basic-styles/tests/italic.js' - ] ); + vi.mocked( globSync ) + .mockReturnValue( [] ) + .mockReturnValueOnce( [ + '/workspace/packages/ckeditor5-basic-styles/tests/bold.js', + '/workspace/packages/ckeditor5-basic-styles/tests/italic.js' + ] ); - stubs.glob.globSync.onSecondCall().returns( [] ); + const promise = runAutomatedTests( options ); setTimeout( () => { - karmaServerCallback( 0 ); + expect( stubs.karma.server.constructor ).toHaveBeenCalledOnce(); + + const [ firstCall ] = stubs.karma.server.constructor.mock.calls; + const [ , exitCallback ] = firstCall; + + exitCallback( 0 ); } ); - return runAutomatedTests( options ) - .then( () => { - expect( stubs.fs.writeFileSync.firstCall.args[ 1 ] ).to.include( '// Make using any method from the console to fail.' ); - } ); + await promise; + + expect( vi.mocked( fs ).writeFileSync ).toHaveBeenCalledExactlyOnceWith( + expect.any( String ), + expect.stringContaining( '// Make using any method from the console to fail.' ) + ); } ); - it( 'should load custom assertions automatically (camelCase)', done => { + it( 'should load custom assertions automatically (camelCase in paths)', async () => { const options = { files: [ 'basic-styles' @@ -335,19 +336,19 @@ describe( 'runAutomatedTests', () => { production: true }; - stubs.fs.readdirSync.returns( [ 'assertionA.js', 'assertionB.js' ] ); + vi.mocked( fs ).readdirSync.mockReturnValue( [ 'assertionA.js', 'assertionB.js' ] ); - stubs.transformFileOptionToTestGlob.returns( [ + vi.mocked( transformFileOptionToTestGlob ).mockReturnValue( [ '/workspace/packages/ckeditor5-basic-styles/tests/**/*.js', '/workspace/packages/ckeditor-basic-styles/tests/**/*.js' ] ); - stubs.glob.globSync.onFirstCall().returns( [ - '/workspace/packages/ckeditor5-basic-styles/tests/bold.js', - '/workspace/packages/ckeditor5-basic-styles/tests/italic.js' - ] ); - - stubs.glob.globSync.onSecondCall().returns( [] ); + vi.mocked( globSync ) + .mockReturnValue( [] ) + .mockReturnValueOnce( [ + '/workspace/packages/ckeditor5-basic-styles/tests/bold.js', + '/workspace/packages/ckeditor5-basic-styles/tests/italic.js' + ] ); const assertionsDir = path.join( __dirname, '..', '..', 'lib', 'utils', 'automated-tests', 'assertions' ).replace( /\\/g, '/' ); @@ -355,28 +356,30 @@ describe( 'runAutomatedTests', () => { `import assertionAFactory from "${ assertionsDir }/assertionA.js";`, `import assertionBFactory from "${ assertionsDir }/assertionB.js";`, 'assertionAFactory( chai );', - 'assertionBFactory( chai );', - '' + 'assertionBFactory( chai );' ].join( '\n' ); + const promise = runAutomatedTests( options ); + setTimeout( () => { - karmaServerCallback( 0 ); - } ); + expect( stubs.karma.server.constructor ).toHaveBeenCalledOnce(); - runAutomatedTests( options ) - .then( () => { - expect( stubs.mkdirp.sync.calledOnce ).to.equal( true ); - expect( stubs.mkdirp.sync.firstCall.args[ 0 ] ).to.equal( '/workspace/build/.automated-tests' ); + const [ firstCall ] = stubs.karma.server.constructor.mock.calls; + const [ , exitCallback ] = firstCall; - expect( stubs.fs.writeFileSync.calledOnce ).to.equal( true ); - expect( stubs.fs.writeFileSync.firstCall.args[ 0 ] ).to.equal( '/workspace/build/.automated-tests/entry-point.js' ); - expect( stubs.fs.writeFileSync.firstCall.args[ 1 ] ).to.include( expectedEntryPointContent ); + exitCallback( 0 ); + } ); - done(); - } ); + await promise; + + expect( vi.mocked( mkdirp ).sync ).toHaveBeenCalledExactlyOnceWith( '/workspace/build/.automated-tests' ); + expect( vi.mocked( fs ).writeFileSync ).toHaveBeenCalledExactlyOnceWith( + '/workspace/build/.automated-tests/entry-point.js', + expect.stringContaining( expectedEntryPointContent ) + ); } ); - it( 'should load custom assertions automatically (kebab-case)', done => { + it( 'should load custom assertions automatically (kebab-case in paths)', async () => { const options = { files: [ 'basic-styles' @@ -384,19 +387,19 @@ describe( 'runAutomatedTests', () => { production: true }; - stubs.fs.readdirSync.returns( [ 'assertion-a.js', 'assertion-b.js' ] ); + vi.mocked( fs ).readdirSync.mockReturnValue( [ 'assertion-a.js', 'assertion-b.js' ] ); - stubs.transformFileOptionToTestGlob.returns( [ + vi.mocked( transformFileOptionToTestGlob ).mockReturnValue( [ '/workspace/packages/ckeditor5-basic-styles/tests/**/*.js', '/workspace/packages/ckeditor-basic-styles/tests/**/*.js' ] ); - stubs.glob.globSync.onFirstCall().returns( [ - '/workspace/packages/ckeditor5-basic-styles/tests/bold.js', - '/workspace/packages/ckeditor5-basic-styles/tests/italic.js' - ] ); - - stubs.glob.globSync.onSecondCall().returns( [] ); + vi.mocked( globSync ) + .mockReturnValue( [] ) + .mockReturnValueOnce( [ + '/workspace/packages/ckeditor5-basic-styles/tests/bold.js', + '/workspace/packages/ckeditor5-basic-styles/tests/italic.js' + ] ); const assertionsDir = path.join( __dirname, '..', '..', 'lib', 'utils', 'automated-tests', 'assertions' ).replace( /\\/g, '/' ); @@ -408,20 +411,23 @@ describe( 'runAutomatedTests', () => { '' ].join( '\n' ); + const promise = runAutomatedTests( options ); + setTimeout( () => { - karmaServerCallback( 0 ); - } ); + expect( stubs.karma.server.constructor ).toHaveBeenCalledOnce(); + + const [ firstCall ] = stubs.karma.server.constructor.mock.calls; + const [ , exitCallback ] = firstCall; - runAutomatedTests( options ) - .then( () => { - expect( stubs.mkdirp.sync.calledOnce ).to.equal( true ); - expect( stubs.mkdirp.sync.firstCall.args[ 0 ] ).to.equal( '/workspace/build/.automated-tests' ); + exitCallback( 0 ); + } ); - expect( stubs.fs.writeFileSync.calledOnce ).to.equal( true ); - expect( stubs.fs.writeFileSync.firstCall.args[ 0 ] ).to.equal( '/workspace/build/.automated-tests/entry-point.js' ); - expect( stubs.fs.writeFileSync.firstCall.args[ 1 ] ).to.include( expectedEntryPointContent ); + await promise; - done(); - } ); + expect( vi.mocked( mkdirp ).sync ).toHaveBeenCalledExactlyOnceWith( '/workspace/build/.automated-tests' ); + expect( vi.mocked( fs ).writeFileSync ).toHaveBeenCalledExactlyOnceWith( + '/workspace/build/.automated-tests/entry-point.js', + expect.stringContaining( expectedEntryPointContent ) + ); } ); } ); diff --git a/packages/ckeditor5-dev-tests/vitest.config.js b/packages/ckeditor5-dev-tests/vitest.config.js index 5ad784a28..075897d63 100644 --- a/packages/ckeditor5-dev-tests/vitest.config.js +++ b/packages/ckeditor5-dev-tests/vitest.config.js @@ -7,11 +7,18 @@ import { defineConfig } from 'vitest/config'; export default defineConfig( { test: { + setupFiles: [ + './tests/_utils/testsetup.js' + ], testTimeout: 10000, + mockReset: true, restoreMocks: true, include: [ 'tests/**/*.js' ], + exclude: [ + './tests/_utils/**/*.js' + ], coverage: { provider: 'v8', include: [ From 2696d95382e1f44832916250175dfa92517c36c3 Mon Sep 17 00:00:00 2001 From: Kamil Piechaczek Date: Tue, 17 Sep 2024 15:12:28 +0200 Subject: [PATCH 09/15] Tests for "runManualTests()". --- .../lib/tasks/runmanualtests.js | 2 +- .../lib/utils/manual-tests/createserver.js | 2 +- .../tests/tasks/runautomatedtests.js | 2 +- .../tests/tasks/runmanualtests.js | 1571 ++++++----------- 4 files changed, 573 insertions(+), 1004 deletions(-) diff --git a/packages/ckeditor5-dev-tests/lib/tasks/runmanualtests.js b/packages/ckeditor5-dev-tests/lib/tasks/runmanualtests.js index 58931749d..bcb315fc1 100644 --- a/packages/ckeditor5-dev-tests/lib/tasks/runmanualtests.js +++ b/packages/ckeditor5-dev-tests/lib/tasks/runmanualtests.js @@ -5,9 +5,9 @@ import fs from 'fs'; import path from 'path'; +import { spawn } from 'child_process'; import chalk from 'chalk'; import { globSync } from 'glob'; -import { spawn } from 'child_process'; import inquirer from 'inquirer'; import isInteractive from 'is-interactive'; import { Server as SocketServer } from 'socket.io'; diff --git a/packages/ckeditor5-dev-tests/lib/utils/manual-tests/createserver.js b/packages/ckeditor5-dev-tests/lib/utils/manual-tests/createserver.js index cb2d08a3d..bafe7dfc4 100644 --- a/packages/ckeditor5-dev-tests/lib/utils/manual-tests/createserver.js +++ b/packages/ckeditor5-dev-tests/lib/utils/manual-tests/createserver.js @@ -27,7 +27,7 @@ export default function createManualTestServer( sourcePath, port = 8125, onCreat onRequest( sourcePath, request, response ); } ).listen( port ); - // SIGINT isn't caught on Windows in process. However CTRL+C can be catch + // SIGINT isn't caught on Windows in process. However, `CTRL+C` can be caught // by `readline` module. After that we can emit SIGINT to the process manually. if ( process.platform === 'win32' ) { const readline = require( 'readline' ).createInterface( { diff --git a/packages/ckeditor5-dev-tests/tests/tasks/runautomatedtests.js b/packages/ckeditor5-dev-tests/tests/tasks/runautomatedtests.js index b1eb0f5cb..61099ca15 100644 --- a/packages/ckeditor5-dev-tests/tests/tasks/runautomatedtests.js +++ b/packages/ckeditor5-dev-tests/tests/tasks/runautomatedtests.js @@ -63,7 +63,7 @@ vi.mock( 'karma/lib/logger.js' ); vi.mock( '../../lib/utils/automated-tests/getkarmaconfig.js' ); vi.mock( '../../lib/utils/transformfileoptiontotestglob.js' ); -describe( 'runAutomatedTests', () => { +describe( 'runAutomatedTests()', () => { let runAutomatedTests; beforeEach( async () => { diff --git a/packages/ckeditor5-dev-tests/tests/tasks/runmanualtests.js b/packages/ckeditor5-dev-tests/tests/tasks/runmanualtests.js index f59ca777d..ab8791403 100644 --- a/packages/ckeditor5-dev-tests/tests/tasks/runmanualtests.js +++ b/packages/ckeditor5-dev-tests/tests/tasks/runmanualtests.js @@ -3,235 +3,211 @@ * For licensing, see LICENSE.md. */ -'use strict'; +import fs from 'fs'; +import path from 'path'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { Server } from 'socket.io'; +import { spawn } from 'child_process'; +import { globSync } from 'glob'; +import chalk from 'chalk'; +import inquirer from 'inquirer'; +import isInteractive from 'is-interactive'; +import { logger } from '@ckeditor/ckeditor5-dev-utils'; +import requireDll from '../../lib/utils/requiredll.js'; +import createManualTestServer from '../../lib/utils/manual-tests/createserver.js'; +import compileManualTestScripts from '../../lib/utils/manual-tests/compilescripts.js'; +import compileManualTestHtmlFiles from '../../lib/utils/manual-tests/compilehtmlfiles.js'; +import transformFileOptionToTestGlob from '../../lib/utils/transformfileoptiontotestglob.js'; +import removeDir from '../../lib/utils/manual-tests/removedir.js'; +import runManualTests from '../../lib/tasks/runmanualtests.js'; + +const stubs = vi.hoisted( () => ( { + log: { + log: vi.fn(), + error: vi.fn(), + warning: vi.fn(), + info: vi.fn() + }, + spawn: { + spawnReturnValue: { + on: ( event, callback ) => { + if ( !stubs.spawn.spawnEvents[ event ] ) { + stubs.spawn.spawnEvents[ event ] = []; + } -const mockery = require( 'mockery' ); -const sinon = require( 'sinon' ); -const expect = require( 'chai' ).expect; + stubs.spawn.spawnEvents[ event ].push( callback ); + + // Return the same object containing the `on()` method to allow method chaining: `.on( ... ).on( ... )`. + return stubs.spawn.spawnReturnValue; + } + }, + spawnExitCode: 0, + spawnEvents: {}, + spawnTriggerEvent: ( event, data ) => { + if ( stubs.spawn.spawnEvents[ event ] ) { + for ( const callback of stubs.spawn.spawnEvents[ event ] ) { + callback( data ); + } -describe( 'runManualTests', () => { - let sandbox, spies, runManualTests, defaultOptions; + delete stubs.spawn.spawnEvents[ event ]; + } + } + } +} ) ); + +vi.mock( 'socket.io' ); +vi.mock( 'child_process' ); +vi.mock( 'inquirer' ); +vi.mock( 'glob' ); +vi.mock( 'chalk', () => ( { + default: { + bold: vi.fn( input => input ) + } +} ) ); +vi.mock( 'path' ); +vi.mock( 'fs' ); +vi.mock( 'is-interactive' ); +vi.mock( '@ckeditor/ckeditor5-dev-utils' ); +vi.mock( '../../lib/utils/manual-tests/createserver.js' ); +vi.mock( '../../lib/utils/manual-tests/compilehtmlfiles.js' ); +vi.mock( '../../lib/utils/manual-tests/compilescripts.js' ); +vi.mock( '../../lib/utils/manual-tests/removedir.js' ); +vi.mock( '../../lib/utils/manual-tests/copyassets.js' ); +vi.mock( '../../lib/utils/transformfileoptiontotestglob.js' ); +vi.mock( '../../lib/utils/requiredll.js' ); + +describe( 'runManualTests()', () => { + let defaultOptions; beforeEach( () => { - sandbox = sinon.createSandbox(); + stubs.spawn.spawnExitCode = 0; + + vi.mocked( spawn ).mockImplementation( () => { + // Simulate closing a new process. It does not matter that this simulation ends the child process immediately. + // All that matters is that the `close` event is emitted with specified exit code. + process.nextTick( () => { + stubs.spawn.spawnTriggerEvent( 'close', stubs.spawn.spawnExitCode ); + } ); - mockery.enable( { - useCleanCache: true, - warnOnReplace: false, - warnOnUnregistered: false + return stubs.spawn.spawnReturnValue; } ); - spies = { - socketIO: { - Server: sandbox.stub().returns( new ( class {} )() ) - }, - childProcess: { - spawn: sandbox.stub().callsFake( () => { - // Simulate closing a new process. It does not matter that this simulation ends the child process immediately. - // All that matters is that the `close` event is emitted with specified exit code. - process.nextTick( () => { - spies.childProcess.spawnTriggerEvent( 'close', spies.childProcess.spawnExitCode ); - } ); + vi.mocked( globSync ).mockImplementation( pattern => { + const patterns = { + // Valid pattern for manual tests. + 'workspace/packages/ckeditor5-*/tests/**/manual/**/*.js': [ + 'workspace/packages/ckeditor5-foo/tests/manual/feature-a.js', + 'workspace/packages/ckeditor5-bar/tests/manual/feature-b.js' + ], + // Another valid pattern for manual tests. + 'workspace/packages/ckeditor-*/tests/**/manual/**/*.js': [ + 'workspace/packages/ckeditor-foo/tests/manual/feature-c.js', + 'workspace/packages/ckeditor-bar/tests/manual/feature-d.js' + ], + // Invalid pattern for manual tests (points to `/_utils/` subdirectory). + 'workspace/packages/ckeditor-*/tests/**/manual/_utils/**/*.js': [ + 'workspace/packages/ckeditor-foo/tests/manual/_utils/feature-e.js', + 'workspace/packages/ckeditor-bar/tests/manual/_utils/feature-f.js' + ], + // Invalid pattern for manual tests (points outside manual test directory). + 'workspace/packages/ckeditor-*/tests/**/outside/**/*.js': [ + 'workspace/packages/ckeditor-foo/tests/outside/feature-g.js', + 'workspace/packages/ckeditor-bar/tests/outside/feature-h.js' + ], + // Valid pattern for manual tests that require DLLs. + 'workspace/packages/ckeditor5-*/tests/**/manual/dll/**/*.js': [ + 'workspace/packages/ckeditor5-foo/tests/manual/dll/feature-i-dll.js', + 'workspace/packages/ckeditor5-bar/tests/manual/dll/feature-j-dll.js' + ], + // Pattern for finding `package.json` in all repositories. + // External repositories are first, then the root repository. + '{,external/*/}package.json': [ + 'workspace/ckeditor5/external/ckeditor5-internal/package.json', + 'workspace/ckeditor5/external/collaboration-features/package.json', + 'workspace/ckeditor5/package.json' + ] + }; - return spies.childProcess.spawnReturnValue; - } ), - spawnReturnValue: { - on: ( event, callback ) => { - if ( !spies.childProcess.spawnEvents[ event ] ) { - spies.childProcess.spawnEvents[ event ] = []; - } + const separator = process.platform === 'win32' ? '\\' : '/'; + const result = patterns[ pattern ] || []; - spies.childProcess.spawnEvents[ event ].push( callback ); + return result.map( p => p.split( '/' ).join( separator ) ); + } ); - // Return the same object containing the `on()` method to allow method chaining: `.on( ... ).on( ... )`. - return spies.childProcess.spawnReturnValue; - } - }, - spawnExitCode: 0, - spawnEvents: {}, - spawnTriggerEvent: ( event, data ) => { - if ( spies.childProcess.spawnEvents[ event ] ) { - for ( const callback of spies.childProcess.spawnEvents[ event ] ) { - callback( data ); - } + vi.mocked( path ).join.mockImplementation( ( ...chunks ) => chunks.join( '/' ) ); + vi.mocked( path ).resolve.mockImplementation( path => '/absolute/path/to/' + path ); + vi.mocked( path ).basename.mockImplementation( path => path.split( /[\\/]/ ).pop() ); + vi.mocked( path ).dirname.mockImplementation( path => { + const chunks = path.split( /[\\/]/ ); - delete spies.childProcess.spawnEvents[ event ]; - } - } - }, - inquirer: { - prompt: sandbox.stub() - }, - glob: { - globSync: sandbox.stub().callsFake( pattern => { - const patterns = { - // Valid pattern for manual tests. - 'workspace/packages/ckeditor5-*/tests/**/manual/**/*.js': [ - 'workspace/packages/ckeditor5-foo/tests/manual/feature-a.js', - 'workspace/packages/ckeditor5-bar/tests/manual/feature-b.js' - ], - // Another valid pattern for manual tests. - 'workspace/packages/ckeditor-*/tests/**/manual/**/*.js': [ - 'workspace/packages/ckeditor-foo/tests/manual/feature-c.js', - 'workspace/packages/ckeditor-bar/tests/manual/feature-d.js' - ], - // Invalid pattern for manual tests (points to `/_utils/` subdirectory). - 'workspace/packages/ckeditor-*/tests/**/manual/_utils/**/*.js': [ - 'workspace/packages/ckeditor-foo/tests/manual/_utils/feature-e.js', - 'workspace/packages/ckeditor-bar/tests/manual/_utils/feature-f.js' - ], - // Invalid pattern for manual tests (points outside manual test directory). - 'workspace/packages/ckeditor-*/tests/**/outside/**/*.js': [ - 'workspace/packages/ckeditor-foo/tests/outside/feature-g.js', - 'workspace/packages/ckeditor-bar/tests/outside/feature-h.js' - ], - // Valid pattern for manual tests that require DLLs. - 'workspace/packages/ckeditor5-*/tests/**/manual/dll/**/*.js': [ - 'workspace/packages/ckeditor5-foo/tests/manual/dll/feature-i-dll.js', - 'workspace/packages/ckeditor5-bar/tests/manual/dll/feature-j-dll.js' - ], - // Pattern for finding `package.json` in all repositories. - // External repositories are first, then the root repository. - '{,external/*/}package.json': [ - 'workspace/ckeditor5/external/ckeditor5-internal/package.json', - 'workspace/ckeditor5/external/collaboration-features/package.json', - 'workspace/ckeditor5/package.json' - ] - }; - - const separator = process.platform === 'win32' ? '\\' : '/'; - const result = patterns[ pattern ] || []; - - return result.map( p => p.split( '/' ).join( separator ) ); - } ) - }, - chalk: { - bold: sandbox.stub().callsFake( msg => msg ) - }, - fs: { - readFileSync: sandbox.stub() - }, - path: { - join: sandbox.stub().callsFake( ( ...chunks ) => chunks.join( '/' ) ), - resolve: sandbox.stub().callsFake( path => '/absolute/path/to/' + path ), - basename: sandbox.stub().callsFake( path => path.split( /[\\/]/ ).pop() ), - dirname: sandbox.stub().callsFake( path => { - const chunks = path.split( /[\\/]/ ); - - chunks.pop(); - - return chunks.join( '/' ); - } ) - }, - devUtils: { - logger: sandbox.stub().callsFake( () => ( { - warning: spies.devUtils.logWarning, - info: spies.devUtils.logInfo - } ) ), - logWarning: sandbox.stub(), - logInfo: sandbox.stub() - }, - isInteractive: sandbox.stub(), - requireDll: sandbox.stub().callsFake( sourceFiles => { - return sourceFiles.some( filePath => /-dll.[jt]s$/.test( filePath ) ); - } ), - server: sandbox.stub(), - htmlFileCompiler: sandbox.spy( () => Promise.resolve() ), - scriptCompiler: sandbox.spy( () => Promise.resolve() ), - removeDir: sandbox.spy( () => Promise.resolve() ), - copyAssets: sandbox.spy(), - transformFileOptionToTestGlob: sandbox.stub().returns( [] ) - }; + chunks.pop(); + + return chunks.join( '/' ); + } ); - mockery.registerMock( 'socket.io', spies.socketIO ); - mockery.registerMock( 'child_process', spies.childProcess ); - mockery.registerMock( 'inquirer', spies.inquirer ); - mockery.registerMock( 'glob', spies.glob ); - mockery.registerMock( 'chalk', spies.chalk ); - mockery.registerMock( 'path', spies.path ); - mockery.registerMock( 'fs', spies.fs ); - mockery.registerMock( 'is-interactive', spies.isInteractive ); - mockery.registerMock( '@ckeditor/ckeditor5-dev-utils', spies.devUtils ); - mockery.registerMock( '../utils/manual-tests/createserver', spies.server ); - mockery.registerMock( '../utils/manual-tests/compilehtmlfiles', spies.htmlFileCompiler ); - mockery.registerMock( '../utils/manual-tests/compilescripts', spies.scriptCompiler ); - mockery.registerMock( '../utils/manual-tests/removedir', spies.removeDir ); - mockery.registerMock( '../utils/manual-tests/copyassets', spies.copyAssets ); - mockery.registerMock( '../utils/transformfileoptiontotestglob', spies.transformFileOptionToTestGlob ); - mockery.registerMock( '../utils/requiredll', spies.requireDll ); - - sandbox.stub( process, 'cwd' ).returns( 'workspace' ); + vi.mocked( logger ).mockImplementation( () => stubs.log ); + vi.mocked( requireDll ).mockImplementation( sourceFiles => { + return sourceFiles.some( filePath => /-dll.[jt]s$/.test( filePath ) ); + } ); + + vi.mocked( compileManualTestScripts ).mockResolvedValue(); + vi.mocked( compileManualTestHtmlFiles ).mockResolvedValue(); + vi.mocked( removeDir ).mockResolvedValue(); + vi.mocked( transformFileOptionToTestGlob ).mockReturnValue( [] ); + + vi.spyOn( process, 'cwd' ).mockReturnValue( 'workspace' ); // The `glob` util returns paths in format depending on the platform. - sandbox.stub( process, 'platform' ).value( 'linux' ); + vi.spyOn( process, 'platform', 'get' ).mockReturnValue( 'linux' ); defaultOptions = { dll: null }; - - runManualTests = require( '../../lib/tasks/runmanualtests' ); } ); - afterEach( () => { - sandbox.restore(); - mockery.disable(); - } ); - - it( 'should run all manual tests and return promise', () => { - spies.transformFileOptionToTestGlob.returns( [ + it( 'should run all manual tests and return promise', async () => { + vi.mocked( transformFileOptionToTestGlob ).mockReturnValue( [ 'workspace/packages/ckeditor5-*/tests/**/manual/**/*.js', 'workspace/packages/ckeditor-*/tests/**/manual/**/*.js' ] ); - return runManualTests( defaultOptions ) - .then( () => { - expect( spies.removeDir.calledOnce ).to.equal( true ); - expect( spies.removeDir.firstCall.args[ 0 ] ).to.equal( 'workspace/build/.manual-tests' ); - - expect( spies.htmlFileCompiler.calledOnce ).to.equal( true ); - sinon.assert.calledWith( spies.htmlFileCompiler.firstCall, { - buildDir: 'workspace/build/.manual-tests', - sourceFiles: [ - 'workspace/packages/ckeditor5-foo/tests/manual/feature-a.js', - 'workspace/packages/ckeditor5-bar/tests/manual/feature-b.js', - 'workspace/packages/ckeditor-foo/tests/manual/feature-c.js', - 'workspace/packages/ckeditor-bar/tests/manual/feature-d.js' - ], - language: undefined, - onTestCompilationStatus: sinon.match.func, - additionalLanguages: undefined, - disableWatch: true, - silent: false - } ); - - expect( spies.scriptCompiler.calledOnce ).to.equal( true ); - sinon.assert.calledWith( spies.scriptCompiler.firstCall, { - cwd: 'workspace', - buildDir: 'workspace/build/.manual-tests', - sourceFiles: [ - 'workspace/packages/ckeditor5-foo/tests/manual/feature-a.js', - 'workspace/packages/ckeditor5-bar/tests/manual/feature-b.js', - 'workspace/packages/ckeditor-foo/tests/manual/feature-c.js', - 'workspace/packages/ckeditor-bar/tests/manual/feature-d.js' - ], - themePath: null, - language: undefined, - tsconfig: undefined, - onTestCompilationStatus: sinon.match.func, - additionalLanguages: undefined, - debug: undefined, - disableWatch: true, - identityFile: undefined - } ); - - expect( spies.server.calledOnce ).to.equal( true ); - expect( spies.server.firstCall.args[ 0 ] ).to.equal( 'workspace/build/.manual-tests' ); - } ); + await runManualTests( defaultOptions ); + + expect( vi.mocked( removeDir ) ).toHaveBeenCalledExactlyOnceWith( 'workspace/build/.manual-tests', expect.any( Object ) ); + expect( vi.mocked( createManualTestServer ) ).toHaveBeenCalledExactlyOnceWith( + 'workspace/build/.manual-tests', + undefined, + expect.any( Function ) + ); + expect( vi.mocked( compileManualTestHtmlFiles ) ).toHaveBeenCalledExactlyOnceWith( expect.objectContaining( { + buildDir: 'workspace/build/.manual-tests', + sourceFiles: [ + 'workspace/packages/ckeditor5-foo/tests/manual/feature-a.js', + 'workspace/packages/ckeditor5-bar/tests/manual/feature-b.js', + 'workspace/packages/ckeditor-foo/tests/manual/feature-c.js', + 'workspace/packages/ckeditor-bar/tests/manual/feature-d.js' + ], + onTestCompilationStatus: expect.any( Function ), + silent: false + } ) ); + expect( vi.mocked( compileManualTestScripts ) ).toHaveBeenCalledExactlyOnceWith( expect.objectContaining( { + cwd: 'workspace', + buildDir: 'workspace/build/.manual-tests', + sourceFiles: [ + 'workspace/packages/ckeditor5-foo/tests/manual/feature-a.js', + 'workspace/packages/ckeditor5-bar/tests/manual/feature-b.js', + 'workspace/packages/ckeditor-foo/tests/manual/feature-c.js', + 'workspace/packages/ckeditor-bar/tests/manual/feature-d.js' + ], + themePath: null, + onTestCompilationStatus: expect.any( Function ) + } ) ); } ); - it( 'runs specified manual tests', () => { - spies.transformFileOptionToTestGlob.onFirstCall().returns( [ 'workspace/packages/ckeditor5-*/tests/**/manual/**/*.js' ] ); - spies.transformFileOptionToTestGlob.onSecondCall().returns( [ 'workspace/packages/ckeditor-*/tests/**/manual/**/*.js' ] ); + it( 'runs specified manual tests', async () => { + vi.mocked( transformFileOptionToTestGlob ) + .mockReturnValueOnce( [ 'workspace/packages/ckeditor5-*/tests/**/manual/**/*.js' ] ) + .mockReturnValueOnce( [ 'workspace/packages/ckeditor-*/tests/**/manual/**/*.js' ] ); const options = { files: [ @@ -242,62 +218,31 @@ describe( 'runManualTests', () => { debug: [ 'CK_DEBUG' ] }; - return runManualTests( { ...defaultOptions, ...options } ) - .then( () => { - expect( spies.removeDir.calledOnce ).to.equal( true ); - expect( spies.removeDir.firstCall.args[ 0 ] ).to.equal( 'workspace/build/.manual-tests' ); - - expect( spies.transformFileOptionToTestGlob.calledTwice ).to.equal( true ); - expect( spies.transformFileOptionToTestGlob.firstCall.args[ 0 ] ).to.equal( 'ckeditor5-classic' ); - expect( spies.transformFileOptionToTestGlob.firstCall.args[ 1 ] ).to.equal( true ); - expect( spies.transformFileOptionToTestGlob.secondCall.args[ 0 ] ).to.equal( 'ckeditor-classic/manual/classic.js' ); - expect( spies.transformFileOptionToTestGlob.secondCall.args[ 1 ] ).to.equal( true ); - - expect( spies.htmlFileCompiler.calledOnce ).to.equal( true ); - sinon.assert.calledWith( spies.htmlFileCompiler.firstCall, { - buildDir: 'workspace/build/.manual-tests', - sourceFiles: [ - 'workspace/packages/ckeditor5-foo/tests/manual/feature-a.js', - 'workspace/packages/ckeditor5-bar/tests/manual/feature-b.js', - 'workspace/packages/ckeditor-foo/tests/manual/feature-c.js', - 'workspace/packages/ckeditor-bar/tests/manual/feature-d.js' - ], - language: undefined, - onTestCompilationStatus: sinon.match.func, - additionalLanguages: undefined, - disableWatch: false, - silent: false - } ); - - expect( spies.scriptCompiler.calledOnce ).to.equal( true ); - sinon.assert.calledWith( spies.scriptCompiler.firstCall, { - cwd: 'workspace', - buildDir: 'workspace/build/.manual-tests', - sourceFiles: [ - 'workspace/packages/ckeditor5-foo/tests/manual/feature-a.js', - 'workspace/packages/ckeditor5-bar/tests/manual/feature-b.js', - 'workspace/packages/ckeditor-foo/tests/manual/feature-c.js', - 'workspace/packages/ckeditor-bar/tests/manual/feature-d.js' - ], - themePath: 'path/to/theme', - language: undefined, - tsconfig: undefined, - onTestCompilationStatus: sinon.match.func, - additionalLanguages: undefined, - debug: [ 'CK_DEBUG' ], - disableWatch: false, - identityFile: undefined - } ); - - expect( spies.server.calledOnce ).to.equal( true ); - expect( spies.server.firstCall.args[ 0 ] ).to.equal( 'workspace/build/.manual-tests' ); - } ); + await runManualTests( { ...defaultOptions, ...options } ); + + expect( vi.mocked( transformFileOptionToTestGlob ) ).toHaveBeenCalledTimes( 2 ); + expect( vi.mocked( transformFileOptionToTestGlob ) ).toHaveBeenCalledWith( 'ckeditor5-classic', true ); + expect( vi.mocked( transformFileOptionToTestGlob ) ).toHaveBeenCalledWith( 'ckeditor-classic/manual/classic.js', true ); + expect( vi.mocked( compileManualTestHtmlFiles ) ).toHaveBeenCalledExactlyOnceWith( expect.objectContaining( { + sourceFiles: [ + 'workspace/packages/ckeditor5-foo/tests/manual/feature-a.js', + 'workspace/packages/ckeditor5-bar/tests/manual/feature-b.js', + 'workspace/packages/ckeditor-foo/tests/manual/feature-c.js', + 'workspace/packages/ckeditor-bar/tests/manual/feature-d.js' + ] + } ) ); + expect( vi.mocked( compileManualTestScripts ) ).toHaveBeenCalledExactlyOnceWith( expect.objectContaining( { + sourceFiles: [ + 'workspace/packages/ckeditor5-foo/tests/manual/feature-a.js', + 'workspace/packages/ckeditor5-bar/tests/manual/feature-b.js', + 'workspace/packages/ckeditor-foo/tests/manual/feature-c.js', + 'workspace/packages/ckeditor-bar/tests/manual/feature-d.js' + ], + debug: [ 'CK_DEBUG' ] + } ) ); } ); - it( 'allows specifying language and additionalLanguages (to CKEditorTranslationsPlugin)', () => { - spies.transformFileOptionToTestGlob.onFirstCall().returns( [ 'workspace/packages/ckeditor5-*/tests/**/manual/**/*.js' ] ); - spies.transformFileOptionToTestGlob.onSecondCall().returns( [ 'workspace/packages/ckeditor-*/tests/**/manual/**/*.js' ] ); - + it( 'allows specifying language and additionalLanguages (to `CKEditorTranslationsPlugin`)', async () => { const options = { files: [ 'ckeditor5-classic', @@ -312,55 +257,17 @@ describe( 'runManualTests', () => { debug: [ 'CK_DEBUG' ] }; - return runManualTests( { ...defaultOptions, ...options } ) - .then( () => { - expect( spies.removeDir.calledOnce ).to.equal( true ); - expect( spies.removeDir.firstCall.args[ 0 ] ).to.equal( 'workspace/build/.manual-tests' ); - - expect( spies.htmlFileCompiler.calledOnce ).to.equal( true ); - sinon.assert.calledWith( spies.htmlFileCompiler.firstCall, { - buildDir: 'workspace/build/.manual-tests', - sourceFiles: [ - 'workspace/packages/ckeditor5-foo/tests/manual/feature-a.js', - 'workspace/packages/ckeditor5-bar/tests/manual/feature-b.js', - 'workspace/packages/ckeditor-foo/tests/manual/feature-c.js', - 'workspace/packages/ckeditor-bar/tests/manual/feature-d.js' - ], - language: 'pl', - onTestCompilationStatus: sinon.match.func, - additionalLanguages: [ 'ar', 'en' ], - disableWatch: false, - silent: false - } ); - - expect( spies.scriptCompiler.calledOnce ).to.equal( true ); - sinon.assert.calledWith( spies.scriptCompiler.firstCall, { - cwd: 'workspace', - buildDir: 'workspace/build/.manual-tests', - sourceFiles: [ - 'workspace/packages/ckeditor5-foo/tests/manual/feature-a.js', - 'workspace/packages/ckeditor5-bar/tests/manual/feature-b.js', - 'workspace/packages/ckeditor-foo/tests/manual/feature-c.js', - 'workspace/packages/ckeditor-bar/tests/manual/feature-d.js' - ], - themePath: 'path/to/theme', - language: 'pl', - onTestCompilationStatus: sinon.match.func, - additionalLanguages: [ 'ar', 'en' ], - debug: [ 'CK_DEBUG' ], - disableWatch: false, - tsconfig: undefined, - identityFile: undefined - } ); + await runManualTests( { ...defaultOptions, ...options } ); - expect( spies.server.calledOnce ).to.equal( true ); - expect( spies.server.firstCall.args[ 0 ] ).to.equal( 'workspace/build/.manual-tests' ); - } ); + expect( vi.mocked( compileManualTestScripts ) ).toHaveBeenCalledExactlyOnceWith( expect.objectContaining( { + additionalLanguages: [ 'ar', 'en' ] + } ) ); } ); - it( 'allows specifying port', () => { - spies.transformFileOptionToTestGlob.onFirstCall().returns( [ 'workspace/packages/ckeditor5-*/tests/**/manual/**/*.js' ] ); - spies.transformFileOptionToTestGlob.onSecondCall().returns( [ 'workspace/packages/ckeditor-*/tests/**/manual/**/*.js' ] ); + it( 'allows specifying port', async () => { + vi.mocked( transformFileOptionToTestGlob ) + .mockReturnValueOnce( [ 'workspace/packages/ckeditor5-*/tests/**/manual/**/*.js' ] ) + .mockReturnValueOnce( [ 'workspace/packages/ckeditor-*/tests/**/manual/**/*.js' ] ); const options = { files: [ @@ -370,18 +277,16 @@ describe( 'runManualTests', () => { port: 8888 }; - return runManualTests( { ...defaultOptions, ...options } ) - .then( () => { - expect( spies.server.calledOnce ).to.equal( true ); - expect( spies.server.firstCall.args[ 0 ] ).to.equal( 'workspace/build/.manual-tests' ); - expect( spies.server.firstCall.args[ 1 ] ).to.equal( 8888 ); - } ); - } ); + await runManualTests( { ...defaultOptions, ...options } ); - it( 'allows specifying identity file (absolute path)', () => { - spies.transformFileOptionToTestGlob.onFirstCall().returns( [ 'workspace/packages/ckeditor5-*/tests/**/manual/**/*.js' ] ); - spies.transformFileOptionToTestGlob.onSecondCall().returns( [ 'workspace/packages/ckeditor-*/tests/**/manual/**/*.js' ] ); + expect( vi.mocked( createManualTestServer ) ).toHaveBeenCalledExactlyOnceWith( + expect.any( String ), + 8888, + expect.any( Function ) + ); + } ); + it( 'allows specifying identity file (an absolute path)', async () => { const options = { files: [ 'ckeditor5-classic', @@ -390,36 +295,14 @@ describe( 'runManualTests', () => { identityFile: '/absolute/path/to/secrets.js' }; - return runManualTests( { ...defaultOptions, ...options } ) - .then( () => { - expect( spies.server.calledOnce ).to.equal( true ); - expect( spies.server.firstCall.args[ 0 ] ).to.equal( 'workspace/build/.manual-tests' ); - - sinon.assert.calledWith( spies.scriptCompiler.firstCall, { - cwd: 'workspace', - buildDir: 'workspace/build/.manual-tests', - sourceFiles: [ - 'workspace/packages/ckeditor5-foo/tests/manual/feature-a.js', - 'workspace/packages/ckeditor5-bar/tests/manual/feature-b.js', - 'workspace/packages/ckeditor-foo/tests/manual/feature-c.js', - 'workspace/packages/ckeditor-bar/tests/manual/feature-d.js' - ], - themePath: null, - language: undefined, - tsconfig: undefined, - onTestCompilationStatus: sinon.match.func, - debug: undefined, - additionalLanguages: undefined, - disableWatch: false, - identityFile: '/absolute/path/to/secrets.js' - } ); - } ); - } ); + await runManualTests( { ...defaultOptions, ...options } ); - it( 'allows specifying identity file (relative path)', () => { - spies.transformFileOptionToTestGlob.onFirstCall().returns( [ 'workspace/packages/ckeditor5-*/tests/**/manual/**/*.js' ] ); - spies.transformFileOptionToTestGlob.onSecondCall().returns( [ 'workspace/packages/ckeditor-*/tests/**/manual/**/*.js' ] ); + expect( vi.mocked( compileManualTestScripts ) ).toHaveBeenCalledExactlyOnceWith( expect.objectContaining( { + identityFile: '/absolute/path/to/secrets.js' + } ) ); + } ); + it( 'allows specifying identity file (a relative path)', async () => { const options = { files: [ 'ckeditor5-classic', @@ -428,34 +311,15 @@ describe( 'runManualTests', () => { identityFile: 'path/to/secrets.js' }; - return runManualTests( { ...defaultOptions, ...options } ) - .then( () => { - expect( spies.server.calledOnce ).to.equal( true ); - expect( spies.server.firstCall.args[ 0 ] ).to.equal( 'workspace/build/.manual-tests' ); - - sinon.assert.calledWith( spies.scriptCompiler.firstCall, { - cwd: 'workspace', - buildDir: 'workspace/build/.manual-tests', - sourceFiles: [ - 'workspace/packages/ckeditor5-foo/tests/manual/feature-a.js', - 'workspace/packages/ckeditor5-bar/tests/manual/feature-b.js', - 'workspace/packages/ckeditor-foo/tests/manual/feature-c.js', - 'workspace/packages/ckeditor-bar/tests/manual/feature-d.js' - ], - themePath: null, - language: undefined, - tsconfig: undefined, - onTestCompilationStatus: sinon.match.func, - debug: undefined, - additionalLanguages: undefined, - disableWatch: false, - identityFile: 'path/to/secrets.js' - } ); - } ); + await runManualTests( { ...defaultOptions, ...options } ); + + expect( vi.mocked( compileManualTestScripts ) ).toHaveBeenCalledExactlyOnceWith( expect.objectContaining( { + identityFile: 'path/to/secrets.js' + } ) ); } ); - it( 'should allow hiding processed files in the console', () => { - spies.transformFileOptionToTestGlob.returns( [ + it( 'should allow hiding processed files in the console', async () => { + vi.mocked( transformFileOptionToTestGlob ).mockReturnValue( [ 'workspace/packages/ckeditor5-*/tests/**/manual/**/*.js', 'workspace/packages/ckeditor-*/tests/**/manual/**/*.js' ] ); @@ -464,55 +328,18 @@ describe( 'runManualTests', () => { silent: true }; - return runManualTests( { ...defaultOptions, ...options } ) - .then( () => { - expect( spies.removeDir.calledOnce ).to.equal( true ); - expect( spies.removeDir.firstCall.args[ 0 ] ).to.equal( 'workspace/build/.manual-tests' ); - expect( spies.removeDir.firstCall.args[ 1 ] ).to.deep.equal( { silent: true } ); - - expect( spies.htmlFileCompiler.calledOnce ).to.equal( true ); - sinon.assert.calledWith( spies.htmlFileCompiler.firstCall, { - buildDir: 'workspace/build/.manual-tests', - sourceFiles: [ - 'workspace/packages/ckeditor5-foo/tests/manual/feature-a.js', - 'workspace/packages/ckeditor5-bar/tests/manual/feature-b.js', - 'workspace/packages/ckeditor-foo/tests/manual/feature-c.js', - 'workspace/packages/ckeditor-bar/tests/manual/feature-d.js' - ], - language: undefined, - onTestCompilationStatus: sinon.match.func, - additionalLanguages: undefined, - disableWatch: true, - silent: true - } ); + await runManualTests( { ...defaultOptions, ...options } ); - expect( spies.scriptCompiler.calledOnce ).to.equal( true ); - sinon.assert.calledWith( spies.scriptCompiler.firstCall, { - cwd: 'workspace', - buildDir: 'workspace/build/.manual-tests', - sourceFiles: [ - 'workspace/packages/ckeditor5-foo/tests/manual/feature-a.js', - 'workspace/packages/ckeditor5-bar/tests/manual/feature-b.js', - 'workspace/packages/ckeditor-foo/tests/manual/feature-c.js', - 'workspace/packages/ckeditor-bar/tests/manual/feature-d.js' - ], - themePath: null, - language: undefined, - tsconfig: undefined, - onTestCompilationStatus: sinon.match.func, - additionalLanguages: undefined, - debug: undefined, - disableWatch: true, - identityFile: undefined - } ); - - expect( spies.server.calledOnce ).to.equal( true ); - expect( spies.server.firstCall.args[ 0 ] ).to.equal( 'workspace/build/.manual-tests' ); - } ); + expect( vi.mocked( removeDir ) ).toHaveBeenCalledExactlyOnceWith( expect.any( String ), expect.objectContaining( { + silent: true + } ) ); + expect( vi.mocked( compileManualTestHtmlFiles ) ).toHaveBeenCalledExactlyOnceWith( expect.objectContaining( { + silent: true + } ) ); } ); - it( 'should allow disabling listening for changes in source files', () => { - spies.transformFileOptionToTestGlob.returns( [ + it( 'should allow disabling listening for changes in source files', async () => { + vi.mocked( transformFileOptionToTestGlob ).mockReturnValue( [ 'workspace/packages/ckeditor5-*/tests/**/manual/**/*.js', 'workspace/packages/ckeditor-*/tests/**/manual/**/*.js' ] ); @@ -521,51 +348,18 @@ describe( 'runManualTests', () => { disableWatch: true }; - return runManualTests( { ...defaultOptions, ...options } ) - .then( () => { - expect( spies.htmlFileCompiler.calledOnce ).to.equal( true ); - sinon.assert.calledWith( spies.htmlFileCompiler.firstCall, { - buildDir: 'workspace/build/.manual-tests', - sourceFiles: [ - 'workspace/packages/ckeditor5-foo/tests/manual/feature-a.js', - 'workspace/packages/ckeditor5-bar/tests/manual/feature-b.js', - 'workspace/packages/ckeditor-foo/tests/manual/feature-c.js', - 'workspace/packages/ckeditor-bar/tests/manual/feature-d.js' - ], - language: undefined, - onTestCompilationStatus: sinon.match.func, - additionalLanguages: undefined, - disableWatch: true, - silent: false - } ); + await runManualTests( { ...defaultOptions, ...options } ); - expect( spies.scriptCompiler.calledOnce ).to.equal( true ); - sinon.assert.calledWith( spies.scriptCompiler.firstCall, { - cwd: 'workspace', - buildDir: 'workspace/build/.manual-tests', - sourceFiles: [ - 'workspace/packages/ckeditor5-foo/tests/manual/feature-a.js', - 'workspace/packages/ckeditor5-bar/tests/manual/feature-b.js', - 'workspace/packages/ckeditor-foo/tests/manual/feature-c.js', - 'workspace/packages/ckeditor-bar/tests/manual/feature-d.js' - ], - themePath: null, - language: undefined, - tsconfig: undefined, - onTestCompilationStatus: sinon.match.func, - additionalLanguages: undefined, - debug: undefined, - disableWatch: true, - identityFile: undefined - } ); - - expect( spies.server.calledOnce ).to.equal( true ); - expect( spies.server.firstCall.args[ 0 ] ).to.equal( 'workspace/build/.manual-tests' ); - } ); + expect( vi.mocked( compileManualTestHtmlFiles ) ).toHaveBeenCalledExactlyOnceWith( expect.objectContaining( { + disableWatch: true + } ) ); + expect( vi.mocked( compileManualTestScripts ) ).toHaveBeenCalledExactlyOnceWith( expect.objectContaining( { + disableWatch: true + } ) ); } ); - it( 'compiles only manual test files (ignores utils and files outside the manual directory)', () => { - spies.transformFileOptionToTestGlob.returns( [ + it( 'compiles only manual test files (ignores utils and files outside the manual directory)', async () => { + vi.mocked( transformFileOptionToTestGlob ).mockReturnValue( [ 'workspace/packages/ckeditor-*/tests/**/manual/**/*.js', 'workspace/packages/ckeditor-*/tests/**/manual/_utils/**/*.js', 'workspace/packages/ckeditor-*/tests/**/outside/**/*.js' @@ -575,70 +369,49 @@ describe( 'runManualTests', () => { disableWatch: true }; - return runManualTests( { ...defaultOptions, ...options } ) - .then( () => { - expect( spies.htmlFileCompiler.calledOnce ).to.equal( true ); - sinon.assert.calledWith( spies.htmlFileCompiler.firstCall, { - buildDir: 'workspace/build/.manual-tests', - sourceFiles: [ - 'workspace/packages/ckeditor-foo/tests/manual/feature-c.js', - 'workspace/packages/ckeditor-bar/tests/manual/feature-d.js' - ], - language: undefined, - onTestCompilationStatus: sinon.match.func, - additionalLanguages: undefined, - disableWatch: true, - silent: false - } ); - - expect( spies.scriptCompiler.calledOnce ).to.equal( true ); - sinon.assert.calledWith( spies.scriptCompiler.firstCall, { - cwd: 'workspace', - buildDir: 'workspace/build/.manual-tests', - sourceFiles: [ - 'workspace/packages/ckeditor-foo/tests/manual/feature-c.js', - 'workspace/packages/ckeditor-bar/tests/manual/feature-d.js' - ], - themePath: null, - language: undefined, - tsconfig: undefined, - onTestCompilationStatus: sinon.match.func, - additionalLanguages: undefined, - debug: undefined, - disableWatch: true, - identityFile: undefined - } ); + await runManualTests( { ...defaultOptions, ...options } ); - expect( spies.server.calledOnce ).to.equal( true ); - expect( spies.server.firstCall.args[ 0 ] ).to.equal( 'workspace/build/.manual-tests' ); - } ); + expect( vi.mocked( compileManualTestHtmlFiles ) ).toHaveBeenCalledExactlyOnceWith( expect.objectContaining( { + sourceFiles: [ + 'workspace/packages/ckeditor-foo/tests/manual/feature-c.js', + 'workspace/packages/ckeditor-bar/tests/manual/feature-d.js' + ] + } ) ); + expect( vi.mocked( compileManualTestScripts ) ).toHaveBeenCalledExactlyOnceWith( expect.objectContaining( { + sourceFiles: [ + 'workspace/packages/ckeditor-foo/tests/manual/feature-c.js', + 'workspace/packages/ckeditor-bar/tests/manual/feature-d.js' + ] + } ) ); } ); - it( 'should start a socket.io server as soon as the http server is up and running', () => { - spies.transformFileOptionToTestGlob.returns( [ + it( 'should start a socket.io server as soon as the http server is up and running', async () => { + vi.mocked( transformFileOptionToTestGlob ).mockReturnValue( [ 'workspace/packages/ckeditor5-*/tests/**/manual/**/*.js', 'workspace/packages/ckeditor-*/tests/**/manual/**/*.js' ] ); - const httpServerMock = sinon.spy(); + const httpServerMock = vi.fn(); - spies.server.callsFake( ( buildDire, port, onCreate ) => onCreate( httpServerMock ) ); + vi.mocked( createManualTestServer ).mockImplementation( ( buildDire, port, onCreate ) => onCreate( httpServerMock ) ); - return runManualTests( defaultOptions ) - .then( () => { - sinon.assert.calledOnce( spies.socketIO.Server ); - sinon.assert.calledWithExactly( spies.socketIO.Server, httpServerMock ); - } ); + await runManualTests( defaultOptions ); + + expect( vi.mocked( Server ) ).toHaveBeenCalledExactlyOnceWith( httpServerMock ); } ); - it( 'should set disableWatch to true if files flag is not provided', () => { - return runManualTests( defaultOptions ) - .then( () => { - sinon.assert.calledWith( spies.scriptCompiler.firstCall, sinon.match.has( 'disableWatch', true ) ); - } ); + it( 'should set disableWatch to true if files flag is not provided', async () => { + await runManualTests( defaultOptions ); + + expect( vi.mocked( compileManualTestHtmlFiles ) ).toHaveBeenCalledExactlyOnceWith( expect.objectContaining( { + disableWatch: true + } ) ); + expect( vi.mocked( compileManualTestScripts ) ).toHaveBeenCalledExactlyOnceWith( expect.objectContaining( { + disableWatch: true + } ) ); } ); - it( 'should set disableWatch to false if files flag is provided', () => { + it( 'should set disableWatch to false if files flag is provided', async () => { const options = { files: [ 'ckeditor5-classic', @@ -646,13 +419,17 @@ describe( 'runManualTests', () => { ] }; - return runManualTests( { ...defaultOptions, ...options } ) - .then( () => { - sinon.assert.calledWith( spies.scriptCompiler.firstCall, sinon.match.has( 'disableWatch', false ) ); - } ); + await runManualTests( { ...defaultOptions, ...options } ); + + expect( vi.mocked( compileManualTestHtmlFiles ) ).toHaveBeenCalledExactlyOnceWith( expect.objectContaining( { + disableWatch: false + } ) ); + expect( vi.mocked( compileManualTestScripts ) ).toHaveBeenCalledExactlyOnceWith( expect.objectContaining( { + disableWatch: false + } ) ); } ); - it( 'should read disableWatch flag value even if files flag is provided', () => { + it( 'should read disableWatch flag value even if files flag is provided', async () => { const options = { files: [ 'ckeditor5-classic', @@ -661,563 +438,374 @@ describe( 'runManualTests', () => { disableWatch: true }; - return runManualTests( { ...defaultOptions, ...options } ) - .then( () => { - sinon.assert.calledWith( spies.scriptCompiler.firstCall, sinon.match.has( 'disableWatch', true ) ); - } ); - } ); + await runManualTests( { ...defaultOptions, ...options } ); - it( 'should set default values for files', () => { - return runManualTests( defaultOptions ) - .then( () => { - expect( spies.transformFileOptionToTestGlob.calledTwice ).to.equal( true ); - expect( spies.transformFileOptionToTestGlob.firstCall.args[ 0 ] ).to.equal( '*' ); - expect( spies.transformFileOptionToTestGlob.secondCall.args[ 0 ] ).to.equal( 'ckeditor5' ); - } ); + expect( vi.mocked( compileManualTestHtmlFiles ) ).toHaveBeenCalledExactlyOnceWith( expect.objectContaining( { + disableWatch: true + } ) ); + expect( vi.mocked( compileManualTestScripts ) ).toHaveBeenCalledExactlyOnceWith( expect.objectContaining( { + disableWatch: true + } ) ); } ); - it( 'should transform provided files to glob', () => { - const options = { - files: [ - 'ckeditor5-classic', - 'ckeditor-classic/manual/classic.js' - ] - }; + it( 'should find all CKEditor 5 manual tests if the `files` option is not defined', async () => { + await runManualTests( defaultOptions ); - return runManualTests( { ...defaultOptions, ...options } ) - .then( () => { - expect( spies.transformFileOptionToTestGlob.calledTwice ).to.equal( true ); - expect( spies.transformFileOptionToTestGlob.firstCall.args[ 0 ] ).to.equal( 'ckeditor5-classic' ); - expect( spies.transformFileOptionToTestGlob.secondCall.args[ 0 ] ).to.equal( 'ckeditor-classic/manual/classic.js' ); - } ); + expect( vi.mocked( transformFileOptionToTestGlob ) ).toHaveBeenCalledTimes( 2 ); + expect( vi.mocked( transformFileOptionToTestGlob ) ).toHaveBeenCalledWith( '*', true ); + expect( vi.mocked( transformFileOptionToTestGlob ) ).toHaveBeenCalledWith( 'ckeditor5', true ); } ); - it( 'should not duplicate glob files in the final sourceFiles array', () => { - spies.transformFileOptionToTestGlob.returns( [ + it( 'should not duplicate glob files in the final sourceFiles array', async () => { + vi.mocked( transformFileOptionToTestGlob ).mockReturnValue( [ 'workspace/packages/ckeditor-*/tests/**/manual/**/*.js', 'workspace/packages/ckeditor-*/tests/**/manual/**/*.js' ] ); - return runManualTests( defaultOptions ) - .then( () => { - expect( spies.scriptCompiler.firstCall.args[ 0 ].sourceFiles ).to.deep.equal( [ - 'workspace/packages/ckeditor-foo/tests/manual/feature-c.js', - 'workspace/packages/ckeditor-bar/tests/manual/feature-d.js' - ] ); - } ); + await runManualTests( defaultOptions ); + + expect( vi.mocked( transformFileOptionToTestGlob ) ).toHaveBeenCalledTimes( 2 ); + + expect( vi.mocked( compileManualTestHtmlFiles ) ).toHaveBeenCalledExactlyOnceWith( expect.objectContaining( { + sourceFiles: [ + 'workspace/packages/ckeditor-foo/tests/manual/feature-c.js', + 'workspace/packages/ckeditor-bar/tests/manual/feature-d.js' + ] + } ) ); + expect( vi.mocked( compileManualTestScripts ) ).toHaveBeenCalledExactlyOnceWith( expect.objectContaining( { + sourceFiles: [ + 'workspace/packages/ckeditor-foo/tests/manual/feature-c.js', + 'workspace/packages/ckeditor-bar/tests/manual/feature-d.js' + ] + } ) ); } ); describe( 'DLLs', () => { - it( 'should not build the DLLs if there are no DLL-related manual tests', () => { - spies.transformFileOptionToTestGlob.returns( [ - 'workspace/packages/ckeditor5-*/tests/**/manual/**/*.js', - 'workspace/packages/ckeditor-*/tests/**/manual/**/*.js' - ] ); + beforeEach( () => { + vi.mocked( isInteractive ).mockReturnValue( true ); - return runManualTests( defaultOptions ) - .then( () => { - sinon.assert.notCalled( spies.childProcess.spawn ); - sinon.assert.notCalled( spies.devUtils.logInfo ); - sinon.assert.notCalled( spies.devUtils.logWarning ); - sinon.assert.notCalled( spies.inquirer.prompt ); - sinon.assert.notCalled( spies.path.resolve ); - sinon.assert.notCalled( spies.fs.readFileSync ); - } ); - } ); - - it( 'should not build the DLLs if the console is not interactive', () => { - spies.isInteractive.returns( false ); - spies.transformFileOptionToTestGlob.returns( [ + vi.mocked( transformFileOptionToTestGlob ).mockReturnValue( [ 'workspace/packages/ckeditor5-*/tests/**/manual/**/*.js', 'workspace/packages/ckeditor-*/tests/**/manual/**/*.js', 'workspace/packages/ckeditor5-*/tests/**/manual/dll/**/*.js' ] ); - - return runManualTests( defaultOptions ) - .then( () => { - sinon.assert.notCalled( spies.childProcess.spawn ); - sinon.assert.notCalled( spies.devUtils.logInfo ); - sinon.assert.notCalled( spies.devUtils.logWarning ); - sinon.assert.notCalled( spies.inquirer.prompt ); - sinon.assert.notCalled( spies.path.resolve ); - sinon.assert.notCalled( spies.fs.readFileSync ); - } ); } ); - it( 'should not build the DLLs and not ask user if `--dll` flag is `false`, even if console is interactive', () => { - spies.isInteractive.returns( true ); - spies.transformFileOptionToTestGlob.returns( [ + it( 'should not build the DLLs if there are no DLL-related manual tests', async () => { + vi.mocked( transformFileOptionToTestGlob ).mockReturnValue( [ 'workspace/packages/ckeditor5-*/tests/**/manual/**/*.js', - 'workspace/packages/ckeditor-*/tests/**/manual/**/*.js', - 'workspace/packages/ckeditor5-*/tests/**/manual/dll/**/*.js' + 'workspace/packages/ckeditor-*/tests/**/manual/**/*.js' ] ); + await runManualTests( defaultOptions ); + + expect( vi.mocked( spawn ) ).not.toHaveBeenCalled(); + expect( vi.mocked( inquirer ).prompt ).not.toHaveBeenCalled(); + expect( stubs.log.info ).not.toHaveBeenCalled(); + expect( stubs.log.warning ).not.toHaveBeenCalled(); + } ); + + it( 'should not build the DLLs if the console is not interactive', async () => { + vi.mocked( isInteractive ).mockReturnValue( false ); + + await runManualTests( defaultOptions ); + + expect( vi.mocked( spawn ) ).not.toHaveBeenCalled(); + expect( vi.mocked( inquirer ).prompt ).not.toHaveBeenCalled(); + expect( stubs.log.info ).not.toHaveBeenCalled(); + expect( stubs.log.warning ).not.toHaveBeenCalled(); + } ); + + it( 'should not build the DLLs and not ask user if `--dll` flag is `false`, even if console is interactive', async () => { const options = { dll: false }; - return runManualTests( { ...defaultOptions, ...options } ) - .then( () => { - sinon.assert.notCalled( spies.childProcess.spawn ); - sinon.assert.notCalled( spies.devUtils.logInfo ); - sinon.assert.notCalled( spies.devUtils.logWarning ); - sinon.assert.notCalled( spies.inquirer.prompt ); - sinon.assert.notCalled( spies.path.resolve ); - sinon.assert.notCalled( spies.fs.readFileSync ); - } ); + await runManualTests( { ...defaultOptions, ...options } ); + + expect( vi.mocked( spawn ) ).not.toHaveBeenCalled(); + expect( vi.mocked( inquirer ).prompt ).not.toHaveBeenCalled(); + expect( stubs.log.info ).not.toHaveBeenCalled(); + expect( stubs.log.warning ).not.toHaveBeenCalled(); } ); - it( 'should not build the DLLs if user declined the question', () => { - spies.isInteractive.returns( true ); - spies.inquirer.prompt.resolves( { confirm: false } ); - spies.transformFileOptionToTestGlob.returns( [ - 'workspace/packages/ckeditor5-*/tests/**/manual/**/*.js', - 'workspace/packages/ckeditor-*/tests/**/manual/**/*.js', - 'workspace/packages/ckeditor5-*/tests/**/manual/dll/**/*.js' - ] ); + it( 'should not build the DLLs if user declined the question', async () => { + vi.mocked( inquirer ).prompt.mockResolvedValue( { confirm: false } ); - return runManualTests( defaultOptions ) - .then( () => { - sinon.assert.notCalled( spies.childProcess.spawn ); - - sinon.assert.calledOnce( spies.devUtils.logWarning ); - sinon.assert.calledWith( spies.devUtils.logWarning.firstCall, - '\n⚠ Some tests require DLL builds.\n' - ); - - sinon.assert.calledTwice( spies.devUtils.logInfo ); - sinon.assert.calledWith( spies.devUtils.logInfo.firstCall, - 'You don\'t have to update these builds every time unless you want to check changes in DLL tests.' - ); - sinon.assert.calledWith( spies.devUtils.logInfo.secondCall, - 'You can use the following flags to skip this prompt in the future: --dll / --no-dll.\n' - ); - - sinon.assert.calledOnce( spies.inquirer.prompt ); - sinon.assert.calledWith( spies.inquirer.prompt.firstCall, [ { - message: 'Create the DLL builds now?', - type: 'confirm', - name: 'confirm', - default: false - } ] ); - - sinon.assert.notCalled( spies.path.resolve ); - sinon.assert.notCalled( spies.fs.readFileSync ); - } ); + await runManualTests( defaultOptions ); + + expect( vi.mocked( spawn ) ).not.toHaveBeenCalled(); + expect( vi.mocked( inquirer ).prompt ).toHaveBeenCalledExactlyOnceWith( [ + { + message: 'Create the DLL builds now?', + type: 'confirm', + name: 'confirm', + default: false + } + ] ); + expect( stubs.log.warning ).toHaveBeenCalledExactlyOnceWith( '\n⚠ Some tests require DLL builds.\n' ); + expect( stubs.log.info ).toHaveBeenCalledTimes( 2 ); + expect( stubs.log.info ).toHaveBeenCalledWith( + 'You don\'t have to update these builds every time unless you want to check changes in DLL tests.' + ); + expect( stubs.log.info ).toHaveBeenCalledWith( + 'You can use the following flags to skip this prompt in the future: --dll / --no-dll.\n' + ); + expect( vi.mocked( chalk ).bold ).toHaveBeenCalledTimes( 3 ); } ); - it( 'should open the package.json in each repository in proper order (root repository first, then external ones)', () => { - spies.isInteractive.returns( true ); - spies.inquirer.prompt.resolves( { confirm: true } ); - spies.fs.readFileSync.returns( JSON.stringify( { + it( 'should open the package.json in each repository in proper order (root repository first, then external ones)', async () => { + vi.mocked( inquirer ).prompt.mockResolvedValue( { confirm: true } ); + vi.mocked( fs ).readFileSync.mockReturnValue( JSON.stringify( { name: 'ckeditor5-example-package' } ) ); - spies.transformFileOptionToTestGlob.returns( [ - 'workspace/packages/ckeditor5-*/tests/**/manual/**/*.js', - 'workspace/packages/ckeditor-*/tests/**/manual/**/*.js', - 'workspace/packages/ckeditor5-*/tests/**/manual/dll/**/*.js' - ] ); - const consoleStub = sinon.stub( console, 'log' ); - - return runManualTests( defaultOptions ) - .then( () => { - consoleStub.restore(); - - expect( consoleStub.callCount ).to.equal( 1 ); - expect( consoleStub.firstCall.firstArg ).to.equal( '\n📍 DLL building complete.\n' ); - - sinon.assert.notCalled( spies.childProcess.spawn ); - - sinon.assert.calledOnce( spies.devUtils.logWarning ); - sinon.assert.calledWith( spies.devUtils.logWarning.firstCall, - '\n⚠ Some tests require DLL builds.\n' - ); - - sinon.assert.calledTwice( spies.devUtils.logInfo ); - sinon.assert.calledWith( spies.devUtils.logInfo.firstCall, - 'You don\'t have to update these builds every time unless you want to check changes in DLL tests.' - ); - sinon.assert.calledWith( spies.devUtils.logInfo.secondCall, - 'You can use the following flags to skip this prompt in the future: --dll / --no-dll.\n' - ); - - sinon.assert.calledOnce( spies.inquirer.prompt ); - sinon.assert.calledWith( spies.inquirer.prompt.firstCall, [ { - message: 'Create the DLL builds now?', - type: 'confirm', - name: 'confirm', - default: false - } ] ); - - // The `path.resolve()` calls are not sorted, so it is called in the same order as data returned from `glob`. - sinon.assert.calledThrice( spies.path.resolve ); - sinon.assert.calledWith( spies.path.resolve.firstCall, - 'workspace/ckeditor5/external/ckeditor5-internal/package.json' - ); - sinon.assert.calledWith( spies.path.resolve.secondCall, - 'workspace/ckeditor5/external/collaboration-features/package.json' - ); - sinon.assert.calledWith( spies.path.resolve.thirdCall, - 'workspace/ckeditor5/package.json' - ); - - // The `fs.readFileSync()` calls are sorted: root repository first, then external ones. - sinon.assert.calledThrice( spies.fs.readFileSync ); - sinon.assert.calledWith( spies.fs.readFileSync.firstCall, - '/absolute/path/to/workspace/ckeditor5/package.json' - ); - sinon.assert.calledWith( spies.fs.readFileSync.secondCall, - '/absolute/path/to/workspace/ckeditor5/external/ckeditor5-internal/package.json' - ); - sinon.assert.calledWith( spies.fs.readFileSync.thirdCall, - '/absolute/path/to/workspace/ckeditor5/external/collaboration-features/package.json' - ); - } ); + const consoleStub = vi.spyOn( console, 'log' ).mockImplementation( () => {} ); + + await runManualTests( defaultOptions ); + + expect( consoleStub ).toHaveBeenCalledExactlyOnceWith( '\n📍 DLL building complete.\n' ); + consoleStub.mockRestore(); + + // The `path.resolve()` calls are not sorted, so it is called in the same order as data returned from `glob`. + expect( vi.mocked( path ).resolve ).toHaveBeenCalledTimes( 3 ); + expect( vi.mocked( path ).resolve ).toHaveBeenCalledWith( 'workspace/ckeditor5/external/ckeditor5-internal/package.json' ); + expect( vi.mocked( path ).resolve ).toHaveBeenCalledWith( 'workspace/ckeditor5/external/collaboration-features/package.json' ); + expect( vi.mocked( path ).resolve ).toHaveBeenCalledWith( 'workspace/ckeditor5/package.json' ); + + // The `fs.readFileSync()` calls are sorted: root repository first, then external ones. + expect( vi.mocked( fs ).readFileSync ).toHaveBeenCalledTimes( 3 ); + expect( vi.mocked( fs ).readFileSync ).toHaveBeenNthCalledWith( + 1, + '/absolute/path/to/workspace/ckeditor5/package.json', + 'utf-8' + ); + + expect( vi.mocked( fs ).readFileSync ).toHaveBeenNthCalledWith( + 2, + '/absolute/path/to/workspace/ckeditor5/external/ckeditor5-internal/package.json', + 'utf-8' + ); + expect( vi.mocked( fs ).readFileSync ).toHaveBeenNthCalledWith( + 3, + '/absolute/path/to/workspace/ckeditor5/external/collaboration-features/package.json', + 'utf-8' + ); } ); - it( 'should not build the DLLs if no repository has scripts in package.json', () => { - spies.isInteractive.returns( true ); - spies.inquirer.prompt.resolves( { confirm: true } ); - spies.fs.readFileSync.returns( JSON.stringify( { + it( 'should not build the DLLs if no repository has scripts in package.json', async () => { + vi.mocked( inquirer ).prompt.mockResolvedValue( { confirm: true } ); + vi.mocked( fs ).readFileSync.mockReturnValue( JSON.stringify( { name: 'ckeditor5-example-package' } ) ); - spies.transformFileOptionToTestGlob.returns( [ - 'workspace/packages/ckeditor5-*/tests/**/manual/**/*.js', - 'workspace/packages/ckeditor-*/tests/**/manual/**/*.js', - 'workspace/packages/ckeditor5-*/tests/**/manual/dll/**/*.js' - ] ); - - const consoleStub = sinon.stub( console, 'log' ); - return runManualTests( defaultOptions ) - .then( () => { - consoleStub.restore(); + vi.spyOn( console, 'log' ).mockImplementation( () => {} ); - expect( consoleStub.callCount ).to.equal( 1 ); - expect( consoleStub.firstCall.firstArg ).to.equal( '\n📍 DLL building complete.\n' ); + await runManualTests( defaultOptions ); - sinon.assert.notCalled( spies.childProcess.spawn ); - } ); + expect( vi.mocked( spawn ) ).not.toHaveBeenCalled(); } ); - it( 'should not build the DLLs if no repository has script to build DLLs in package.json', () => { - spies.isInteractive.returns( true ); - spies.inquirer.prompt.resolves( { confirm: true } ); - spies.fs.readFileSync.returns( JSON.stringify( { + it( 'should not build the DLLs if no repository has script to build DLLs in package.json', async () => { + vi.mocked( inquirer ).prompt.mockResolvedValue( { confirm: true } ); + vi.mocked( fs ).readFileSync.mockReturnValue( JSON.stringify( { name: 'ckeditor5-example-package', scripts: { 'build': 'node ./scripts/build' } } ) ); - spies.transformFileOptionToTestGlob.returns( [ - 'workspace/packages/ckeditor5-*/tests/**/manual/**/*.js', - 'workspace/packages/ckeditor-*/tests/**/manual/**/*.js', - 'workspace/packages/ckeditor5-*/tests/**/manual/dll/**/*.js' - ] ); - const consoleStub = sinon.stub( console, 'log' ); + vi.spyOn( console, 'log' ).mockImplementation( () => {} ); - return runManualTests( defaultOptions ) - .then( () => { - consoleStub.restore(); + await runManualTests( defaultOptions ); - expect( consoleStub.callCount ).to.equal( 1 ); - expect( consoleStub.firstCall.firstArg ).to.equal( '\n📍 DLL building complete.\n' ); - - sinon.assert.notCalled( spies.childProcess.spawn ); - } ); + expect( vi.mocked( spawn ) ).not.toHaveBeenCalled(); } ); - it( 'should build the DLLs in each repository that has script to build DLLs in package.json', () => { - spies.isInteractive.returns( true ); - spies.inquirer.prompt.resolves( { confirm: true } ); - spies.fs.readFileSync - .returns( JSON.stringify( { + it( 'should build the DLLs in each repository that has script to build DLLs in package.json', async () => { + vi.mocked( inquirer ).prompt.mockResolvedValue( { confirm: true } ); + vi.mocked( fs ).readFileSync.mockImplementation( input => { + if ( input === '/absolute/path/to/workspace/ckeditor5/external/collaboration-features/package.json' ) { + return JSON.stringify( { + name: 'ckeditor5-example-package', + scripts: { + 'build': 'node ./scripts/build' + } + } ); + } + + return JSON.stringify( { name: 'ckeditor5-example-package', scripts: { 'dll:build': 'node ./scripts/build-dll' } - } ) ) - .withArgs( '/absolute/path/to/workspace/ckeditor5/external/collaboration-features/package.json' ) - .returns( JSON.stringify( { - name: 'ckeditor5-example-package', - scripts: { - 'build': 'node ./scripts/build' - } - } ) ); - spies.transformFileOptionToTestGlob.returns( [ - 'workspace/packages/ckeditor5-*/tests/**/manual/**/*.js', - 'workspace/packages/ckeditor-*/tests/**/manual/**/*.js', - 'workspace/packages/ckeditor5-*/tests/**/manual/dll/**/*.js' - ] ); - - const consoleStub = sinon.stub( console, 'log' ); + } ); + } ); - return runManualTests( defaultOptions ) - .then( () => { - consoleStub.restore(); + vi.spyOn( console, 'log' ).mockImplementation( () => {} ); - expect( consoleStub.callCount ).to.equal( 1 ); - expect( consoleStub.firstCall.firstArg ).to.equal( '\n📍 DLL building complete.\n' ); + await runManualTests( defaultOptions ); - sinon.assert.calledTwice( spies.childProcess.spawn ); + expect( vi.mocked( spawn ) ).toHaveBeenCalledTimes( 2 ); + expect( vi.mocked( spawn ) ).toHaveBeenCalledWith( + 'yarnpkg', + [ 'run', 'dll:build' ], + { + encoding: 'utf8', + shell: true, + cwd: '/absolute/path/to/workspace/ckeditor5', + stdio: 'inherit' + } + ); + expect( vi.mocked( spawn ) ).toHaveBeenCalledWith( + 'yarnpkg', + [ 'run', 'dll:build' ], + { + encoding: 'utf8', + shell: true, + cwd: '/absolute/path/to/workspace/ckeditor5/external/ckeditor5-internal', + stdio: 'inherit' + } + ); + } ); - sinon.assert.calledWith( spies.childProcess.spawn.firstCall, - 'yarnpkg', - [ 'run', 'dll:build' ], - { - encoding: 'utf8', - shell: true, - cwd: '/absolute/path/to/workspace/ckeditor5', - stdio: 'inherit' - } - ); - sinon.assert.calledWith( spies.childProcess.spawn.secondCall, - 'yarnpkg', - [ 'run', 'dll:build' ], - { - encoding: 'utf8', - shell: true, - cwd: '/absolute/path/to/workspace/ckeditor5/external/ckeditor5-internal', - stdio: 'inherit' + it( 'should build the DLLs automatically and not ask user if `--dll` flag is `true`, even if console is interactive', async () => { + vi.mocked( fs ).readFileSync.mockImplementation( input => { + if ( input === '/absolute/path/to/workspace/ckeditor5/external/collaboration-features/package.json' ) { + return JSON.stringify( { + name: 'ckeditor5-example-package', + scripts: { + 'build': 'node ./scripts/build' } - ); - - sinon.assert.calledOnce( spies.devUtils.logWarning ); - sinon.assert.calledWith( spies.devUtils.logWarning.firstCall, - '\n⚠ Some tests require DLL builds.\n' - ); - - sinon.assert.callCount( spies.devUtils.logInfo, 4 ); - sinon.assert.calledWith( spies.devUtils.logInfo.getCall( 0 ), - 'You don\'t have to update these builds every time unless you want to check changes in DLL tests.' - ); - sinon.assert.calledWith( spies.devUtils.logInfo.getCall( 1 ), - 'You can use the following flags to skip this prompt in the future: --dll / --no-dll.\n' - ); - sinon.assert.calledWith( spies.devUtils.logInfo.getCall( 2 ), - '\n📍 Building DLLs in ckeditor5...\n' - ); - sinon.assert.calledWith( spies.devUtils.logInfo.getCall( 3 ), - '\n📍 Building DLLs in ckeditor5-internal...\n' - ); - } ); - } ); + } ); + } - it( 'should build the DLLs automatically and not ask user if `--dll` flag is `true`, even if console is interactive', () => { - spies.isInteractive.returns( true ); - spies.fs.readFileSync - .returns( JSON.stringify( { + return JSON.stringify( { name: 'ckeditor5-example-package', scripts: { 'dll:build': 'node ./scripts/build-dll' } - } ) ) - .withArgs( '/absolute/path/to/workspace/ckeditor5/external/collaboration-features/package.json' ) - .returns( JSON.stringify( { - name: 'ckeditor5-example-package', - scripts: { - 'build': 'node ./scripts/build' - } - } ) ); - spies.transformFileOptionToTestGlob.returns( [ - 'workspace/packages/ckeditor5-*/tests/**/manual/**/*.js', - 'workspace/packages/ckeditor-*/tests/**/manual/**/*.js', - 'workspace/packages/ckeditor5-*/tests/**/manual/dll/**/*.js' - ] ); + } ); + } ); const options = { dll: true }; - const consoleStub = sinon.stub( console, 'log' ); - - return runManualTests( { ...defaultOptions, ...options } ) - .then( () => { - consoleStub.restore(); + vi.spyOn( console, 'log' ).mockImplementation( () => {} ); - expect( consoleStub.callCount ).to.equal( 1 ); - expect( consoleStub.firstCall.firstArg ).to.equal( '\n📍 DLL building complete.\n' ); + await runManualTests( { ...defaultOptions, ...options } ); - sinon.assert.notCalled( spies.inquirer.prompt ); + expect( vi.mocked( spawn ) ).toHaveBeenCalledTimes( 2 ); + expect( vi.mocked( spawn ) ).toHaveBeenCalledWith( + 'yarnpkg', + [ 'run', 'dll:build' ], + { + encoding: 'utf8', + shell: true, + cwd: '/absolute/path/to/workspace/ckeditor5', + stdio: 'inherit' + } + ); + expect( vi.mocked( spawn ) ).toHaveBeenCalledWith( + 'yarnpkg', + [ 'run', 'dll:build' ], + { + encoding: 'utf8', + shell: true, + cwd: '/absolute/path/to/workspace/ckeditor5/external/ckeditor5-internal', + stdio: 'inherit' + } + ); + } ); - sinon.assert.calledTwice( spies.childProcess.spawn ); - sinon.assert.calledWith( spies.childProcess.spawn.firstCall, - 'yarnpkg', - [ 'run', 'dll:build' ], - { - encoding: 'utf8', - shell: true, - cwd: '/absolute/path/to/workspace/ckeditor5', - stdio: 'inherit' + it( 'should reject a promise if building DLLs has failed', async () => { + vi.mocked( inquirer ).prompt.mockResolvedValue( { confirm: true } ); + vi.mocked( fs ).readFileSync.mockImplementation( input => { + if ( input === '/absolute/path/to/workspace/ckeditor5/external/collaboration-features/package.json' ) { + return JSON.stringify( { + name: 'ckeditor5-example-package', + scripts: { + 'build': 'node ./scripts/build' } - ); - sinon.assert.calledWith( spies.childProcess.spawn.secondCall, - 'yarnpkg', - [ 'run', 'dll:build' ], - { - encoding: 'utf8', - shell: true, - cwd: '/absolute/path/to/workspace/ckeditor5/external/ckeditor5-internal', - stdio: 'inherit' - } - ); - - sinon.assert.notCalled( spies.devUtils.logWarning ); + } ); + } - sinon.assert.calledTwice( spies.devUtils.logInfo ); - sinon.assert.calledWith( spies.devUtils.logInfo.firstCall, - '\n📍 Building DLLs in ckeditor5...\n' - ); - sinon.assert.calledWith( spies.devUtils.logInfo.secondCall, - '\n📍 Building DLLs in ckeditor5-internal...\n' - ); + return JSON.stringify( { + name: 'ckeditor5-example-package', + scripts: { + 'dll:build': 'node ./scripts/build-dll' + } } ); - } ); - - it( 'should reject a promise if building DLLs has failed', () => { - spies.isInteractive.returns( true ); - spies.inquirer.prompt.resolves( { confirm: true } ); - spies.fs.readFileSync.returns( JSON.stringify( { - name: 'ckeditor5-example-package', - scripts: { - 'dll:build': 'node ./scripts/build-dll' + } ); + stubs.spawn.spawnExitCode = 1; + + await expect( runManualTests( defaultOptions ) ) + .rejects.toThrow( 'Building DLLs in ckeditor5 finished with an error.' ); + + expect( vi.mocked( spawn ) ).toHaveBeenCalledExactlyOnceWith( + 'yarnpkg', + [ 'run', 'dll:build' ], + { + encoding: 'utf8', + shell: true, + cwd: '/absolute/path/to/workspace/ckeditor5', + stdio: 'inherit' } - } ) ); - spies.transformFileOptionToTestGlob.returns( [ - 'workspace/packages/ckeditor5-*/tests/**/manual/**/*.js', - 'workspace/packages/ckeditor-*/tests/**/manual/**/*.js', - 'workspace/packages/ckeditor5-*/tests/**/manual/dll/**/*.js' - ] ); - spies.childProcess.spawnExitCode = 1; - - return runManualTests( defaultOptions ) - .then( - () => { - throw new Error( 'Expected to be rejected.' ); - }, - error => { - expect( error.message ).to.equal( 'Building DLLs in ckeditor5 finished with an error.' ); - - sinon.assert.calledOnce( spies.childProcess.spawn ); - sinon.assert.calledWith( spies.childProcess.spawn.firstCall, - 'yarnpkg', - [ 'run', 'dll:build' ], - { - encoding: 'utf8', - shell: true, - cwd: '/absolute/path/to/workspace/ckeditor5', - stdio: 'inherit' - } - ); - } - ); + ); } ); - it( 'should build the DLLs in each repository for Windows environment', () => { - sandbox.stub( process, 'platform' ).value( 'win32' ); + it( 'should build the DLLs in each repository for Windows environment', async () => { + vi.spyOn( process, 'platform', 'get' ).mockReturnValue( 'win32' ); - spies.isInteractive.returns( true ); - spies.inquirer.prompt.resolves( { confirm: true } ); - spies.fs.readFileSync.returns( JSON.stringify( { + vi.mocked( inquirer ).prompt.mockResolvedValue( { confirm: true } ); + vi.mocked( fs ).readFileSync.mockReturnValue( JSON.stringify( { name: 'ckeditor5-example-package', scripts: { 'dll:build': 'node ./scripts/build-dll' } } ) ); - spies.transformFileOptionToTestGlob.returns( [ - 'workspace/packages/ckeditor5-*/tests/**/manual/**/*.js', - 'workspace/packages/ckeditor-*/tests/**/manual/**/*.js', - 'workspace/packages/ckeditor5-*/tests/**/manual/dll/**/*.js' - ] ); - - const consoleStub = sinon.stub( console, 'log' ); - - return runManualTests( defaultOptions ) - .then( () => { - consoleStub.restore(); - - expect( consoleStub.callCount ).to.equal( 1 ); - expect( consoleStub.firstCall.firstArg ).to.equal( '\n📍 DLL building complete.\n' ); - - sinon.assert.calledOnce( spies.scriptCompiler ); - sinon.assert.calledWith( spies.scriptCompiler.firstCall, { - cwd: 'workspace', - buildDir: 'workspace/build/.manual-tests', - sourceFiles: [ - 'workspace\\packages\\ckeditor5-foo\\tests\\manual\\feature-a.js', - 'workspace\\packages\\ckeditor5-bar\\tests\\manual\\feature-b.js', - 'workspace\\packages\\ckeditor-foo\\tests\\manual\\feature-c.js', - 'workspace\\packages\\ckeditor-bar\\tests\\manual\\feature-d.js', - 'workspace\\packages\\ckeditor5-foo\\tests\\manual\\dll\\feature-i-dll.js', - 'workspace\\packages\\ckeditor5-bar\\tests\\manual\\dll\\feature-j-dll.js' - ], - themePath: null, - language: undefined, - tsconfig: undefined, - onTestCompilationStatus: sinon.match.func, - additionalLanguages: undefined, - debug: undefined, - disableWatch: true, - identityFile: undefined - } ); - sinon.assert.calledOnce( spies.htmlFileCompiler ); - sinon.assert.calledWith( spies.htmlFileCompiler.firstCall, { - buildDir: 'workspace/build/.manual-tests', - sourceFiles: [ - 'workspace\\packages\\ckeditor5-foo\\tests\\manual\\feature-a.js', - 'workspace\\packages\\ckeditor5-bar\\tests\\manual\\feature-b.js', - 'workspace\\packages\\ckeditor-foo\\tests\\manual\\feature-c.js', - 'workspace\\packages\\ckeditor-bar\\tests\\manual\\feature-d.js', - 'workspace\\packages\\ckeditor5-foo\\tests\\manual\\dll\\feature-i-dll.js', - 'workspace\\packages\\ckeditor5-bar\\tests\\manual\\dll\\feature-j-dll.js' - ], - language: undefined, - onTestCompilationStatus: sinon.match.func, - additionalLanguages: undefined, - disableWatch: true, - silent: false - } ); - - sinon.assert.calledThrice( spies.childProcess.spawn ); - sinon.assert.calledWith( spies.childProcess.spawn.firstCall, - 'yarnpkg', - [ 'run', 'dll:build' ], - { - encoding: 'utf8', - shell: true, - cwd: '/absolute/path/to/workspace/ckeditor5', - stdio: 'inherit' - } - ); - sinon.assert.calledWith( spies.childProcess.spawn.secondCall, - 'yarnpkg', - [ 'run', 'dll:build' ], - { - encoding: 'utf8', - shell: true, - cwd: '/absolute/path/to/workspace/ckeditor5/external/ckeditor5-internal', - stdio: 'inherit' - } - ); - sinon.assert.calledWith( spies.childProcess.spawn.thirdCall, - 'yarnpkg', - [ 'run', 'dll:build' ], - { - encoding: 'utf8', - shell: true, - cwd: '/absolute/path/to/workspace/ckeditor5/external/collaboration-features', - stdio: 'inherit' - } - ); - } ); + vi.spyOn( console, 'log' ).mockImplementation( () => {} ); + + await runManualTests( defaultOptions ); + expect( vi.mocked( spawn ) ).toHaveBeenCalledTimes( 3 ); + expect( vi.mocked( spawn ) ).toHaveBeenCalledWith( + 'yarnpkg', + [ 'run', 'dll:build' ], + { + encoding: 'utf8', + shell: true, + cwd: '/absolute/path/to/workspace/ckeditor5', + stdio: 'inherit' + } + ); + expect( vi.mocked( spawn ) ).toHaveBeenCalledWith( + 'yarnpkg', + [ 'run', 'dll:build' ], + { + encoding: 'utf8', + shell: true, + cwd: '/absolute/path/to/workspace/ckeditor5/external/ckeditor5-internal', + stdio: 'inherit' + } + ); + expect( vi.mocked( spawn ) ).toHaveBeenCalledWith( + 'yarnpkg', + [ 'run', 'dll:build' ], + { + encoding: 'utf8', + shell: true, + cwd: '/absolute/path/to/workspace/ckeditor5/external/collaboration-features', + stdio: 'inherit' + } + ); } ); - it( 'allows specifying tsconfig file', () => { - spies.transformFileOptionToTestGlob.onFirstCall().returns( [ 'workspace/packages/ckeditor5-*/tests/**/manual/**/*.js' ] ); - spies.transformFileOptionToTestGlob.onSecondCall().returns( [ 'workspace/packages/ckeditor-*/tests/**/manual/**/*.js' ] ); + it( 'allows specifying tsconfig file', async () => { + vi.mocked( transformFileOptionToTestGlob ) + .mockReturnValueOnce( [ 'workspace/packages/ckeditor5-*/tests/**/manual/**/*.js' ] ) + .mockReturnValueOnce( [ 'workspace/packages/ckeditor-*/tests/**/manual/**/*.js' ] ); const options = { files: [ @@ -1227,30 +815,11 @@ describe( 'runManualTests', () => { tsconfig: '/absolute/path/to/tsconfig.json' }; - return runManualTests( { ...defaultOptions, ...options } ) - .then( () => { - expect( spies.server.calledOnce ).to.equal( true ); - expect( spies.server.firstCall.args[ 0 ] ).to.equal( 'workspace/build/.manual-tests' ); - - sinon.assert.calledWith( spies.scriptCompiler.firstCall, { - cwd: 'workspace', - buildDir: 'workspace/build/.manual-tests', - sourceFiles: [ - 'workspace/packages/ckeditor5-foo/tests/manual/feature-a.js', - 'workspace/packages/ckeditor5-bar/tests/manual/feature-b.js', - 'workspace/packages/ckeditor-foo/tests/manual/feature-c.js', - 'workspace/packages/ckeditor-bar/tests/manual/feature-d.js' - ], - themePath: null, - language: undefined, - onTestCompilationStatus: sinon.match.func, - debug: undefined, - additionalLanguages: undefined, - disableWatch: false, - tsconfig: '/absolute/path/to/tsconfig.json', - identityFile: undefined - } ); - } ); + await runManualTests( { ...defaultOptions, ...options } ); + + expect( vi.mocked( compileManualTestScripts ) ).toHaveBeenCalledExactlyOnceWith( expect.objectContaining( { + tsconfig: '/absolute/path/to/tsconfig.json' + } ) ); } ); } ); } ); From c9c067ccb70c5c0902f5b9661c8bf854ddaa5fa2 Mon Sep 17 00:00:00 2001 From: Kamil Piechaczek Date: Tue, 17 Sep 2024 22:49:20 +0200 Subject: [PATCH 10/15] Converted attribute tests. --- .../tests/utils/automated-tests/assertions/attribute.js | 7 +++---- .../tests/utils/automated-tests/assertions/equal-markup.js | 7 +++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/ckeditor5-dev-tests/tests/utils/automated-tests/assertions/attribute.js b/packages/ckeditor5-dev-tests/tests/utils/automated-tests/assertions/attribute.js index 99d4514b5..7684962e2 100644 --- a/packages/ckeditor5-dev-tests/tests/utils/automated-tests/assertions/attribute.js +++ b/packages/ckeditor5-dev-tests/tests/utils/automated-tests/assertions/attribute.js @@ -3,12 +3,11 @@ * For licensing, see LICENSE.md. */ -const chai = require( 'chai' ); -const expect = chai.expect; -const attributeFactory = require( '../../../../lib/utils/automated-tests/assertions/attribute' ); +import { beforeAll, describe, expect, it, chai } from 'vitest'; +import attributeFactory from '../../../../lib/utils/automated-tests/assertions/attribute.js'; describe( 'attribute chai assertion', () => { - before( () => { + beforeAll( () => { attributeFactory( chai ); } ); diff --git a/packages/ckeditor5-dev-tests/tests/utils/automated-tests/assertions/equal-markup.js b/packages/ckeditor5-dev-tests/tests/utils/automated-tests/assertions/equal-markup.js index 302fc3b0c..de9bc8fa1 100644 --- a/packages/ckeditor5-dev-tests/tests/utils/automated-tests/assertions/equal-markup.js +++ b/packages/ckeditor5-dev-tests/tests/utils/automated-tests/assertions/equal-markup.js @@ -3,12 +3,11 @@ * For licensing, see LICENSE.md. */ -const chai = require( 'chai' ); -const expect = chai.expect; -const equalMarkupFactory = require( '../../../../lib/utils/automated-tests/assertions/equal-markup' ); +import { beforeAll, describe, expect, it, chai } from 'vitest'; +import equalMarkupFactory from '../../../../lib/utils/automated-tests/assertions/equal-markup.js'; describe( 'equalMarkup chai assertion', () => { - before( () => { + beforeAll( () => { equalMarkupFactory( chai ); } ); From 14c8f2caaa9277d7f34973ed3e4931c589649723 Mon Sep 17 00:00:00 2001 From: Kamil Piechaczek Date: Wed, 18 Sep 2024 10:58:45 +0200 Subject: [PATCH 11/15] More tests. --- .../ckeditor5-dev-tests/bin/testmanual.js | 2 +- .../utils/automated-tests/parsearguments.js | 3 + .../lib/utils/getdefinitionsfromfile.js | 3 + .../utils/manual-tests/getwebpackconfig.js | 5 +- .../utils/transformfileoptiontotestglob.js | 16 +- .../getdefinitionsfromfile/secret.cjs | 12 + .../fixtures/karma-config-overrides/noop.cjs | 7 + .../karma-config-overrides/removecoverage.cjs | 8 + .../entrypoint.cjs} | 0 .../tests/tasks/runautomatedtests.js | 3 +- .../utils/automated-tests/getkarmaconfig.js | 224 ++++++++++-------- .../utils/automated-tests/getwebpackconfig.js | 92 +++---- .../utils/automated-tests/parsearguments.js | 20 +- .../treatwarningsaserrorswebpackplugin.js | 83 ++++--- .../tests/utils/getdefinitionsfromfile.js | 67 ++---- .../tests/utils/getrelativefilepath.js | 198 +++++++--------- .../utils/manual-tests/compilehtmlfiles.js | 12 +- .../tests/utils/requiredll.js | 134 +++++------ .../utils/transformfileoptiontotestglob.js | 44 ++-- packages/ckeditor5-dev-tests/vitest.config.js | 3 +- 20 files changed, 449 insertions(+), 487 deletions(-) create mode 100644 packages/ckeditor5-dev-tests/tests/fixtures/getdefinitionsfromfile/secret.cjs create mode 100644 packages/ckeditor5-dev-tests/tests/fixtures/karma-config-overrides/noop.cjs create mode 100644 packages/ckeditor5-dev-tests/tests/fixtures/karma-config-overrides/removecoverage.cjs rename packages/ckeditor5-dev-tests/tests/{utils/automated-tests/fixtures/file.js => fixtures/treatwarningsaserrorswebpackplugin/entrypoint.cjs} (100%) diff --git a/packages/ckeditor5-dev-tests/bin/testmanual.js b/packages/ckeditor5-dev-tests/bin/testmanual.js index 3f1f98c12..97fd6dbfc 100755 --- a/packages/ckeditor5-dev-tests/bin/testmanual.js +++ b/packages/ckeditor5-dev-tests/bin/testmanual.js @@ -24,5 +24,5 @@ tests.runManualTests( options ) // Mark result of this task as invalid. process.exitCode = 1; - console.log( chalk.red( error ) ); + console.log( chalk.red( error.stack ) ); } ); diff --git a/packages/ckeditor5-dev-tests/lib/utils/automated-tests/parsearguments.js b/packages/ckeditor5-dev-tests/lib/utils/automated-tests/parsearguments.js index 567e9eebe..fe654960b 100644 --- a/packages/ckeditor5-dev-tests/lib/utils/automated-tests/parsearguments.js +++ b/packages/ckeditor5-dev-tests/lib/utils/automated-tests/parsearguments.js @@ -5,9 +5,12 @@ import fs from 'fs'; import path from 'path'; +import { createRequire } from 'module'; import minimist from 'minimist'; import { tools, logger } from '@ckeditor/ckeditor5-dev-utils'; +const require = createRequire( import.meta.url ); + /** * @param {Array.} args * @returns {Object} diff --git a/packages/ckeditor5-dev-tests/lib/utils/getdefinitionsfromfile.js b/packages/ckeditor5-dev-tests/lib/utils/getdefinitionsfromfile.js index f4b552825..fdca410ff 100644 --- a/packages/ckeditor5-dev-tests/lib/utils/getdefinitionsfromfile.js +++ b/packages/ckeditor5-dev-tests/lib/utils/getdefinitionsfromfile.js @@ -4,6 +4,9 @@ */ import path from 'path'; +import { createRequire } from 'module'; + +const require = createRequire( import.meta.url ); /** * @param {String|null} definitionSource diff --git a/packages/ckeditor5-dev-tests/lib/utils/manual-tests/getwebpackconfig.js b/packages/ckeditor5-dev-tests/lib/utils/manual-tests/getwebpackconfig.js index daa45f37f..5f918c110 100644 --- a/packages/ckeditor5-dev-tests/lib/utils/manual-tests/getwebpackconfig.js +++ b/packages/ckeditor5-dev-tests/lib/utils/manual-tests/getwebpackconfig.js @@ -4,16 +4,19 @@ */ import path from 'path'; +import { fileURLToPath } from 'url'; +import { createRequire } from 'module'; import webpack from 'webpack'; import { CKEditorTranslationsPlugin } from '@ckeditor/ckeditor5-dev-translations'; import { loaders } from '@ckeditor/ckeditor5-dev-utils'; import WebpackNotifierPlugin from './webpacknotifierplugin.js'; import getDefinitionsFromFile from '../getdefinitionsfromfile.js'; -import { fileURLToPath } from 'url'; const __filename = fileURLToPath( import.meta.url ); const __dirname = path.dirname( __filename ); +const require = createRequire( import.meta.url ); + /** * @param {Object} options * @param {String} options.cwd Current working directory. Usually it points to the CKEditor 5 root directory. diff --git a/packages/ckeditor5-dev-tests/lib/utils/transformfileoptiontotestglob.js b/packages/ckeditor5-dev-tests/lib/utils/transformfileoptiontotestglob.js index 08cedc7b1..7d60c4da6 100644 --- a/packages/ckeditor5-dev-tests/lib/utils/transformfileoptiontotestglob.js +++ b/packages/ckeditor5-dev-tests/lib/utils/transformfileoptiontotestglob.js @@ -6,7 +6,7 @@ import fs from 'fs'; import path from 'path'; -const EXTERNAL_DIR_PATH = path.join( process.cwd(), 'external' ); +const EXTERNAL_DIR_NAME = 'external'; /** * Converts values of `--files` argument to proper globs. Handles both JS and TS files. These are the supported types of values: @@ -61,9 +61,11 @@ export default function transformFileOptionToTestGlob( pattern, isManualTest = f * @returns {Array.} */ function getExternalRepositoryGlob( pattern, { isManualTest } ) { + const externalPath = path.join( process.cwd(), EXTERNAL_DIR_NAME ); + const repositoryGlob = isManualTest ? - path.join( EXTERNAL_DIR_PATH, pattern, 'tests', 'manual', '**', '*' ) + '.{js,ts}' : - path.join( EXTERNAL_DIR_PATH, pattern, 'tests', '**', '*' ) + '.{js,ts}'; + path.join( externalPath, pattern, 'tests', 'manual', '**', '*' ) + '.{js,ts}' : + path.join( externalPath, pattern, 'tests', '**', '*' ) + '.{js,ts}'; return [ repositoryGlob.split( path.sep ).join( path.posix.sep ) @@ -75,12 +77,14 @@ function getExternalRepositoryGlob( pattern, { isManualTest } ) { * @returns {Boolean} */ function doesPatternMatchExternalRepositoryName( pattern ) { - if ( !fs.existsSync( EXTERNAL_DIR_PATH ) ) { + const externalPath = path.join( process.cwd(), EXTERNAL_DIR_NAME ); + + if ( !fs.existsSync( externalPath ) ) { return false; } - return fs.readdirSync( EXTERNAL_DIR_PATH ) - .filter( externalDir => fs.statSync( path.join( EXTERNAL_DIR_PATH, externalDir ) ).isDirectory() ) + return fs.readdirSync( externalPath ) + .filter( externalDir => fs.statSync( path.join( externalPath, externalDir ) ).isDirectory() ) .includes( pattern ); } diff --git a/packages/ckeditor5-dev-tests/tests/fixtures/getdefinitionsfromfile/secret.cjs b/packages/ckeditor5-dev-tests/tests/fixtures/getdefinitionsfromfile/secret.cjs new file mode 100644 index 000000000..3f6701c37 --- /dev/null +++ b/packages/ckeditor5-dev-tests/tests/fixtures/getdefinitionsfromfile/secret.cjs @@ -0,0 +1,12 @@ +/** + * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md. + */ + +module.exports = { + SECRET: 'secret', + ANOTHER_SECRET: 'another-secret', + NON_PRIMITIVE_SECRET: { + foo: [ 'bar', 'baz' ] + } +}; diff --git a/packages/ckeditor5-dev-tests/tests/fixtures/karma-config-overrides/noop.cjs b/packages/ckeditor5-dev-tests/tests/fixtures/karma-config-overrides/noop.cjs new file mode 100644 index 000000000..0b81095cf --- /dev/null +++ b/packages/ckeditor5-dev-tests/tests/fixtures/karma-config-overrides/noop.cjs @@ -0,0 +1,7 @@ +/** + * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md. + */ + +module.exports = () => { +}; diff --git a/packages/ckeditor5-dev-tests/tests/fixtures/karma-config-overrides/removecoverage.cjs b/packages/ckeditor5-dev-tests/tests/fixtures/karma-config-overrides/removecoverage.cjs new file mode 100644 index 000000000..0828ebaed --- /dev/null +++ b/packages/ckeditor5-dev-tests/tests/fixtures/karma-config-overrides/removecoverage.cjs @@ -0,0 +1,8 @@ +/** + * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md. + */ + +module.exports = config => { + config.reporters.splice( config.reporters.indexOf( 'coverage' ), 1 ); +} diff --git a/packages/ckeditor5-dev-tests/tests/utils/automated-tests/fixtures/file.js b/packages/ckeditor5-dev-tests/tests/fixtures/treatwarningsaserrorswebpackplugin/entrypoint.cjs similarity index 100% rename from packages/ckeditor5-dev-tests/tests/utils/automated-tests/fixtures/file.js rename to packages/ckeditor5-dev-tests/tests/fixtures/treatwarningsaserrorswebpackplugin/entrypoint.cjs diff --git a/packages/ckeditor5-dev-tests/tests/tasks/runautomatedtests.js b/packages/ckeditor5-dev-tests/tests/tasks/runautomatedtests.js index 61099ca15..7cc0c9288 100644 --- a/packages/ckeditor5-dev-tests/tests/tasks/runautomatedtests.js +++ b/packages/ckeditor5-dev-tests/tests/tasks/runautomatedtests.js @@ -204,8 +204,7 @@ describe( 'runAutomatedTests()', () => { production: false }; - const consoleWarnStub = vi.spyOn( console, 'warn' ).mockImplementation( () => { - } ); + const consoleWarnStub = vi.spyOn( console, 'warn' ).mockImplementation( () => {} ); vi.mocked( chalk ).yellow.mockReturnValue( 'chalk.yellow: warn' ); vi.mocked( fs ).readdirSync.mockReturnValue( [] ); diff --git a/packages/ckeditor5-dev-tests/tests/utils/automated-tests/getkarmaconfig.js b/packages/ckeditor5-dev-tests/tests/utils/automated-tests/getkarmaconfig.js index 975c18cc4..e028cf62b 100644 --- a/packages/ckeditor5-dev-tests/tests/utils/automated-tests/getkarmaconfig.js +++ b/packages/ckeditor5-dev-tests/tests/utils/automated-tests/getkarmaconfig.js @@ -3,49 +3,41 @@ * For licensing, see LICENSE.md. */ -'use strict'; - -const mockery = require( 'mockery' ); -const { expect } = require( 'chai' ); -const sinon = require( 'sinon' ); -const path = require( 'path' ); +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import getWebpackConfigForAutomatedTests from '../../../lib/utils/automated-tests/getwebpackconfig.js'; +import getKarmaConfig from '../../../lib/utils/automated-tests/getkarmaconfig.js'; + +vi.mock( 'path', () => ( { + default: { + join: vi.fn( ( ...chunks ) => chunks.join( '/' ) ), + dirname: vi.fn() + } +} ) ); +vi.mock( '../../../lib/utils/automated-tests/getwebpackconfig.js' ); describe( 'getKarmaConfig()', () => { - let getKarmaConfig, sandbox, karmaConfigOverrides; - const originalEnv = process.env; + const karmaConfigOverrides = { + // A relative path according to the tested file. + // From: /ckeditor5-dev/packages/ckeditor5-dev-tests/lib/utils/automated-tests/getkarmaconfig.js + // To: /ckeditor5-dev/packages/ckeditor5-dev-tests/tests/utils/automated-tests/fixtures/karma-config-overrides/*.cjs + noop: '../../../tests/fixtures/karma-config-overrides/noop.cjs', + removeCoverage: '../../../tests/fixtures/karma-config-overrides/removecoverage.cjs' + }; beforeEach( () => { - sandbox = sinon.createSandbox(); - - karmaConfigOverrides = sandbox.spy(); - sandbox.stub( process, 'cwd' ).returns( 'workspace' ); - sandbox.stub( path, 'join' ).callsFake( ( ...chunks ) => chunks.join( '/' ) ); + vi.spyOn( process, 'cwd' ).mockReturnValue( 'workspace' ); - // Sinon cannot stub non-existing props. process.env = Object.assign( {}, originalEnv, { CI: false } ); - - mockery.enable( { - useCleanCache: true, - warnOnReplace: false, - warnOnUnregistered: false - } ); - - mockery.registerMock( './getwebpackconfig', options => options ); - mockery.registerMock( 'karma-config-overrides', karmaConfigOverrides ); - - getKarmaConfig = require( '../../../lib/utils/automated-tests/getkarmaconfig' ); } ); afterEach( () => { - sandbox.restore(); - mockery.disable(); - mockery.deregisterAll(); - process.env = originalEnv; } ); it( 'should return basic karma config for all tested files', () => { + vi.mocked( getWebpackConfigForAutomatedTests ).mockReturnValue( { webpackConfig: true } ); + const options = { files: [ '*' ], reporter: 'mocha', @@ -63,16 +55,28 @@ describe( 'getKarmaConfig()', () => { const karmaConfig = getKarmaConfig( options ); - expect( karmaConfig ).to.have.own.property( 'basePath', 'workspace' ); - expect( karmaConfig ).to.have.own.property( 'frameworks' ); - expect( karmaConfig ).to.have.own.property( 'files' ); - expect( karmaConfig ).to.have.own.property( 'preprocessors' ); - expect( karmaConfig ).to.have.own.property( 'webpack' ); - expect( karmaConfig.webpack ).to.deep.equal( { ...options, files: [ 'workspace/packages/ckeditor5-*/tests/**/*.js' ] } ); - expect( karmaConfig ).to.have.own.property( 'webpackMiddleware' ); - expect( karmaConfig ).to.have.own.property( 'reporters' ); - expect( karmaConfig ).to.have.own.property( 'browsers' ); - expect( karmaConfig ).to.have.own.property( 'singleRun', true ); + expect( vi.mocked( getWebpackConfigForAutomatedTests ) ).toHaveBeenCalledExactlyOnceWith( { + ...options, + files: [ + 'workspace/packages/ckeditor5-*/tests/**/*.js' + ] + } ); + + expect( karmaConfig ).toEqual( expect.objectContaining( { + basePath: 'workspace', + frameworks: expect.any( Array ), + files: expect.any( Array ), + preprocessors: expect.any( Object ), + webpack: expect.any( Object ), + webpackMiddleware: expect.any( Object ), + reporters: expect.any( Array ), + browsers: expect.any( Array ), + singleRun: true + } ) ); + + expect( karmaConfig.webpack ).toEqual( expect.objectContaining( { + webpackConfig: true + } ) ); } ); // See: https://github.com/ckeditor/ckeditor5/issues/8823 @@ -92,15 +96,23 @@ describe( 'getKarmaConfig()', () => { } } ); - expect( karmaConfig ).to.have.own.property( 'proxies' ); - expect( karmaConfig.proxies ).to.have.own.property( '/assets/' ); - expect( karmaConfig.proxies ).to.have.own.property( '/example.com/image.png' ); - expect( karmaConfig.proxies ).to.have.own.property( '/www.example.com/image.png' ); - - expect( karmaConfig.files ).to.be.an( 'array' ); - expect( karmaConfig.files.length ).to.equal( 2 ); - expect( karmaConfig.files[ 0 ] ).to.equal( 'workspace/entry-file.js' ); - expect( karmaConfig.files[ 1 ].pattern ).to.equal( 'packages/ckeditor5-utils/tests/_assets/**/*' ); + expect( karmaConfig ).toEqual( expect.objectContaining( { + proxies: expect.any( Object ), + files: expect.any( Array ) + } ) ); + expect( karmaConfig.proxies ).toEqual( expect.objectContaining( { + '/assets/': expect.any( String ), + '/example.com/image.png': expect.any( String ), + '/www.example.com/image.png': expect.any( String ) + } ) ); + + expect( karmaConfig.files ).toHaveLength( 2 ); + expect( karmaConfig.files ).toEqual( expect.arrayContaining( [ + 'workspace/entry-file.js', + expect.objectContaining( { + pattern: 'packages/ckeditor5-utils/tests/_assets/**/*' + } ) + ] ) ); } ); it( 'should contain a list of available plugins', () => { @@ -119,50 +131,53 @@ describe( 'getKarmaConfig()', () => { } } ); - expect( karmaConfig.plugins ).to.be.an( 'array' ); - expect( karmaConfig.plugins ).to.have.lengthOf.above( 0 ); + expect( karmaConfig ).toEqual( expect.objectContaining( { + files: expect.any( Array ) + } ) ); + expect( karmaConfig.files ).not.toHaveLength( 0 ); } ); it( 'should enable webpack watcher when passed the "karmaConfigOverrides" option (execute in Intellij)', () => { + vi.mocked( getWebpackConfigForAutomatedTests ).mockReturnValue( { watch: null } ); + const karmaConfig = getKarmaConfig( { files: [ '*' ], reporter: 'mocha', - karmaConfigOverrides: 'karma-config-overrides', + karmaConfigOverrides: karmaConfigOverrides.noop, globPatterns: { '*': 'workspace/packages/ckeditor5-*/tests/**/*.js' } } ); - expect( karmaConfig.webpack ).to.contain.property( 'watch', true ); + expect( karmaConfig.webpack ).toEqual( expect.objectContaining( { + watch: true + } ) ); } ); it( 'should configure coverage reporter', () => { + vi.mocked( getWebpackConfigForAutomatedTests ).mockReturnValue( { } ); + const karmaConfig = getKarmaConfig( { files: [ '*' ], reporter: 'mocha', - karmaConfigOverrides: 'karma-config-overrides', + karmaConfigOverrides: karmaConfigOverrides.noop, globPatterns: { '*': 'workspace/packages/ckeditor5-*/tests/**/*.js' }, coverage: true } ); - expect( karmaConfig.reporters ).to.contain( 'coverage' ); - expect( karmaConfig.coverageReporter ).to.contain.property( 'reporters' ); + expect( karmaConfig ).toEqual( expect.objectContaining( { + reporters: expect.arrayContaining( [ 'coverage' ] ), + coverageReporter: { + reporters: expect.any( Array ), + watermarks: expect.any( Object ) + } + } ) ); } ); it( 'should remove webpack babel-loader if coverage reporter is removed by overrides', () => { - mockery.registerMock( 'karma-config-overrides-remove-coverage', config => { - config.reporters.splice( config.reporters.indexOf( 'coverage' ), 1 ); - } ); - - const karmaConfig = getKarmaConfig( { - files: [ '*' ], - reporter: 'mocha', - karmaConfigOverrides: 'karma-config-overrides-remove-coverage', - globPatterns: { - '*': 'workspace/packages/ckeditor5-*/tests/**/*.js' - }, + vi.mocked( getWebpackConfigForAutomatedTests ).mockReturnValue( { module: { rules: [ { @@ -172,20 +187,33 @@ describe( 'getKarmaConfig()', () => { loader: 'other-loader' } ] + } + } ); + + const karmaConfig = getKarmaConfig( { + files: [ '*' ], + reporter: 'mocha', + karmaConfigOverrides: karmaConfigOverrides.removeCoverage, + globPatterns: { + '*': 'workspace/packages/ckeditor5-*/tests/**/*.js' }, coverage: true } ); const loaders = karmaConfig.webpack.module.rules.map( rule => rule.loader ); - expect( karmaConfig.reporters ).to.not.contain( 'coverage' ); - expect( loaders ).to.not.contain( 'babel-loader' ); - expect( loaders ).to.contain( 'other-loader' ); + expect( karmaConfig ).not.toEqual( expect.objectContaining( { + reporters: expect.arrayContaining( [ 'coverage' ] ) + } ) ); + + expect( loaders ).not.toEqual( expect.arrayContaining( [ 'babel-loader' ] ) ); + expect( loaders ).toEqual( expect.arrayContaining( [ 'other-loader' ] ) ); } ); - it( 'should return custom launchers with flags', () => { + it( 'should return custom browser launchers with flags', () => { const options = { reporter: 'mocha', + files: [ '*' ], globPatterns: { '*': 'workspace/packages/ckeditor5-*/tests/**/*.js' } @@ -193,30 +221,36 @@ describe( 'getKarmaConfig()', () => { const karmaConfig = getKarmaConfig( options ); - expect( karmaConfig ).to.have.own.property( 'customLaunchers' ); - expect( karmaConfig.customLaunchers ).to.have.own.property( 'CHROME_CI' ); - expect( karmaConfig.customLaunchers ).to.have.own.property( 'CHROME_LOCAL' ); - - expect( karmaConfig.customLaunchers.CHROME_CI ).to.have.own.property( 'base', 'Chrome' ); - expect( karmaConfig.customLaunchers.CHROME_CI ).to.have.own.property( 'flags' ); - expect( karmaConfig.customLaunchers.CHROME_CI.flags ).to.deep.equal( [ - '--disable-background-timer-throttling', - '--js-flags="--expose-gc"', - '--disable-renderer-backgrounding', - '--disable-backgrounding-occluded-windows', - '--disable-search-engine-choice-screen', - '--no-sandbox' - ] ); - - expect( karmaConfig.customLaunchers.CHROME_LOCAL ).to.have.own.property( 'base', 'Chrome' ); - expect( karmaConfig.customLaunchers.CHROME_LOCAL ).to.have.own.property( 'flags' ); - expect( karmaConfig.customLaunchers.CHROME_LOCAL.flags ).to.deep.equal( [ - '--disable-background-timer-throttling', - '--js-flags="--expose-gc"', - '--disable-renderer-backgrounding', - '--disable-backgrounding-occluded-windows', - '--disable-search-engine-choice-screen', - '--remote-debugging-port=9222' - ] ); + expect( karmaConfig ).toEqual( expect.objectContaining( { + customLaunchers: expect.any( Object ) + } ) ); + + expect( karmaConfig.customLaunchers ).toEqual( expect.objectContaining( { + CHROME_CI: expect.objectContaining( { + base: 'Chrome', + flags: [ + '--disable-background-timer-throttling', + '--js-flags="--expose-gc"', + '--disable-renderer-backgrounding', + '--disable-backgrounding-occluded-windows', + '--disable-search-engine-choice-screen', + '--no-sandbox' + ] + } ) + } ) ); + + expect( karmaConfig.customLaunchers ).toEqual( expect.objectContaining( { + CHROME_LOCAL: expect.objectContaining( { + base: 'Chrome', + flags: [ + '--disable-background-timer-throttling', + '--js-flags="--expose-gc"', + '--disable-renderer-backgrounding', + '--disable-backgrounding-occluded-windows', + '--disable-search-engine-choice-screen', + '--remote-debugging-port=9222' + ] + } ) + } ) ); } ); } ); diff --git a/packages/ckeditor5-dev-tests/tests/utils/automated-tests/getwebpackconfig.js b/packages/ckeditor5-dev-tests/tests/utils/automated-tests/getwebpackconfig.js index c4831526e..3838ece6f 100644 --- a/packages/ckeditor5-dev-tests/tests/utils/automated-tests/getwebpackconfig.js +++ b/packages/ckeditor5-dev-tests/tests/utils/automated-tests/getwebpackconfig.js @@ -3,54 +3,22 @@ * For licensing, see LICENSE.md. */ -'use strict'; - -const mockery = require( 'mockery' ); -const sinon = require( 'sinon' ); -const { expect } = require( 'chai' ); +import { describe, expect, it, vi } from 'vitest'; +import { loaders } from '@ckeditor/ckeditor5-dev-utils'; +import TreatWarningsAsErrorsWebpackPlugin from '../../../lib/utils/automated-tests/treatwarningsaserrorswebpackplugin.js'; +import getWebpackConfigForAutomatedTests from '../../../lib/utils/automated-tests/getwebpackconfig.js'; +import getDefinitionsFromFile from '../../../lib/utils/getdefinitionsfromfile.js'; + +vi.mock( '@ckeditor/ckeditor5-dev-utils' ); +vi.mock( '../../../lib/utils/getdefinitionsfromfile.js' ); +vi.mock( '../../../lib/utils/automated-tests/treatwarningsaserrorswebpackplugin', () => ( { + default: class TreatWarningsAsErrorsWebpackPlugin {} +} ) ); describe( 'getWebpackConfigForAutomatedTests()', () => { - let getWebpackConfigForAutomatedTests, stubs; - - beforeEach( () => { - mockery.enable( { - useCleanCache: true, - warnOnReplace: false, - warnOnUnregistered: false - } ); - - stubs = { - getDefinitionsFromFile: sinon.stub().returns( {} ), - loaders: { - getIconsLoader: sinon.stub().returns( {} ), - getStylesLoader: sinon.stub().returns( {} ), - getTypeScriptLoader: sinon.stub().returns( {} ), - getFormattedTextLoader: sinon.stub().returns( {} ), - getCoverageLoader: sinon.stub().returns( {} ), - getJavaScriptLoader: sinon.stub().returns( {} ) - }, - TreatWarningsAsErrorsWebpackPlugin: class TreatWarningsAsErrorsWebpackPlugin {} - }; - - mockery.registerMock( '@ckeditor/ckeditor5-dev-utils', { loaders: stubs.loaders } ); - - mockery.registerMock( '../getdefinitionsfromfile', stubs.getDefinitionsFromFile ); - - mockery.registerMock( './treatwarningsaserrorswebpackplugin', stubs.TreatWarningsAsErrorsWebpackPlugin ); - - getWebpackConfigForAutomatedTests = require( '../../../lib/utils/automated-tests/getwebpackconfig' ); - } ); - - afterEach( () => { - sinon.restore(); - mockery.disable(); - mockery.deregisterAll(); - } ); - it( 'should return basic webpack configuration object', () => { - const debug = []; const webpackConfig = getWebpackConfigForAutomatedTests( { - debug, + debug: [], themePath: '/theme/path', tsconfig: '/tsconfig/path' } ); @@ -58,18 +26,16 @@ describe( 'getWebpackConfigForAutomatedTests()', () => { expect( webpackConfig.resolve.extensions ).to.deep.equal( [ '.ts', '.js', '.json' ] ); expect( webpackConfig.resolve.fallback.timers ).to.equal( false ); - expect( stubs.loaders.getIconsLoader.calledOnce ).to.equal( true ); - - expect( stubs.loaders.getStylesLoader.calledOnce ).to.equal( true ); - expect( stubs.loaders.getStylesLoader.firstCall.args[ 0 ] ).to.have.property( 'themePath', '/theme/path' ); - expect( stubs.loaders.getStylesLoader.firstCall.args[ 0 ] ).to.have.property( 'minify', true ); - - expect( stubs.loaders.getTypeScriptLoader.calledOnce ).to.equal( true ); - expect( stubs.loaders.getTypeScriptLoader.firstCall.args[ 0 ] ).to.have.property( 'configFile', '/tsconfig/path' ); - - expect( stubs.loaders.getFormattedTextLoader.calledOnce ).to.equal( true ); - - expect( stubs.loaders.getCoverageLoader.called ).to.equal( false ); + expect( vi.mocked( loaders.getIconsLoader ) ).toHaveBeenCalledOnce(); + expect( vi.mocked( loaders.getFormattedTextLoader ) ).toHaveBeenCalledOnce(); + expect( vi.mocked( loaders.getCoverageLoader ) ).not.toHaveBeenCalledOnce(); + expect( vi.mocked( loaders.getStylesLoader ) ).toHaveBeenCalledExactlyOnceWith( { + themePath: '/theme/path', + minify: true + } ); + expect( vi.mocked( loaders.getTypeScriptLoader ) ).toHaveBeenCalledExactlyOnceWith( { + configFile: '/tsconfig/path' + } ); expect( webpackConfig.resolveLoader.modules[ 0 ] ).to.equal( 'node_modules' ); expect( webpackConfig.devtool ).to.equal( undefined ); @@ -87,7 +53,8 @@ describe( 'getWebpackConfigForAutomatedTests()', () => { getWebpackConfigForAutomatedTests( { files: [ '**/*.js' ] } ); - expect( stubs.loaders.getJavaScriptLoader.called ).to.equal( false ); + + expect( vi.mocked( loaders.getJavaScriptLoader ) ).not.toHaveBeenCalledOnce(); } ); it( 'should return webpack configuration containing a loader for measuring the coverage', () => { @@ -96,7 +63,7 @@ describe( 'getWebpackConfigForAutomatedTests()', () => { files: [ '**/*.js' ] } ); - expect( stubs.loaders.getCoverageLoader.called ).to.equal( true ); + expect( vi.mocked( loaders.getCoverageLoader ) ).toHaveBeenCalledOnce(); } ); it( 'should return webpack configuration with source map support', () => { @@ -124,7 +91,7 @@ describe( 'getWebpackConfigForAutomatedTests()', () => { } ); it( 'should return webpack configuration with loaded identity file', () => { - stubs.getDefinitionsFromFile.returns( { LICENSE_KEY: 'secret' } ); + vi.mocked( getDefinitionsFromFile ).mockReturnValue( { LICENSE_KEY: 'secret' } ); const webpackConfig = getWebpackConfigForAutomatedTests( { identityFile: 'path/to/secrets.js' @@ -132,7 +99,7 @@ describe( 'getWebpackConfigForAutomatedTests()', () => { const plugin = webpackConfig.plugins[ 0 ]; - expect( stubs.getDefinitionsFromFile.firstCall.args[ 0 ] ).to.equal( 'path/to/secrets.js' ); + expect( vi.mocked( getDefinitionsFromFile ) ).toHaveBeenCalledExactlyOnceWith( 'path/to/secrets.js' ); expect( plugin.definitions.LICENSE_KEY ).to.equal( 'secret' ); } ); @@ -169,8 +136,9 @@ describe( 'getWebpackConfigForAutomatedTests()', () => { production: true } ); - expect( webpackConfig.plugins.filter( plugin => plugin instanceof stubs.TreatWarningsAsErrorsWebpackPlugin ) ) - .to.have.lengthOf( 1 ); + const plugin = webpackConfig.plugins.find( plugin => plugin instanceof TreatWarningsAsErrorsWebpackPlugin ); + + expect( plugin ).toBeTruthy(); } ); it( 'should load TypeScript files first when importing JS files', () => { diff --git a/packages/ckeditor5-dev-tests/tests/utils/automated-tests/parsearguments.js b/packages/ckeditor5-dev-tests/tests/utils/automated-tests/parsearguments.js index d76d9eb57..75c66178a 100644 --- a/packages/ckeditor5-dev-tests/tests/utils/automated-tests/parsearguments.js +++ b/packages/ckeditor5-dev-tests/tests/utils/automated-tests/parsearguments.js @@ -3,28 +3,18 @@ * For licensing, see LICENSE.md. */ -'use strict'; - -const fs = require( 'fs' ); -const path = require( 'path' ); -const mockery = require( 'mockery' ); -const { expect } = require( 'chai' ); -const sinon = require( 'sinon' ); -const proxyquire = require( 'proxyquire' ); +import { describe, expect, it, vi } from 'vitest'; const originalPosixJoin = path.posix.join; +vi.mock( 'fs' ); +vi.mock( '@ckeditor/ckeditor5-dev-utils' ); + describe( 'parseArguments()', () => { let parseArguments, sandbox, stubs, packageName; beforeEach( () => { - sandbox = sinon.createSandbox(); - - mockery.enable( { - useCleanCache: true, - warnOnReplace: false, - warnOnUnregistered: false - } ); + vi.spyOn( process, 'cwd' ).mockReturnValue( '/' ); stubs = { cwd: sandbox.stub( process, 'cwd' ).callsFake( () => '/' ), diff --git a/packages/ckeditor5-dev-tests/tests/utils/automated-tests/treatwarningsaserrorswebpackplugin.js b/packages/ckeditor5-dev-tests/tests/utils/automated-tests/treatwarningsaserrorswebpackplugin.js index 326cb611c..76b876755 100644 --- a/packages/ckeditor5-dev-tests/tests/utils/automated-tests/treatwarningsaserrorswebpackplugin.js +++ b/packages/ckeditor5-dev-tests/tests/utils/automated-tests/treatwarningsaserrorswebpackplugin.js @@ -3,59 +3,68 @@ * For licensing, see LICENSE.md. */ -'use strict'; +import { describe, expect, it } from 'vitest'; +import webpack from 'webpack'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import TreatWarningsAsErrorsWebpackPlugin from '../../../lib/utils/automated-tests/treatwarningsaserrorswebpackplugin.js'; -const webpack = require( 'webpack' ); -const path = require( 'path' ); -const { expect } = require( 'chai' ); +const __filename = fileURLToPath( import.meta.url ); +const __dirname = path.dirname( __filename ); describe( 'TreatWarningsAsErrorsWebpackPlugin()', () => { - let TreatWarningsAsErrorsWebpackPlugin; + it( 'should reassign warnings to errors and not emit the code when errors are present', () => { + return new Promise( ( resolve, reject ) => { + runCompiler( + { + mode: 'development', + entry: './treatwarningsaserrorswebpackplugin/entrypoint.cjs', + plugins: [ + { + apply( compiler ) { + compiler.hooks.make.tap( 'MakeCompilationWarning', compilation => { + compilation.errors.push( new Error( 'Compilation error 1' ) ); + compilation.errors.push( new Error( 'Compilation error 2' ) ); + compilation.warnings.push( new Error( 'Compilation warning 1' ) ); + compilation.warnings.push( new Error( 'Compilation warning 2' ) ); + } ); + } + }, + new TreatWarningsAsErrorsWebpackPlugin() + ] + }, + ( err, stats ) => { + if ( err ) { + return reject( err ); + } - beforeEach( () => { - TreatWarningsAsErrorsWebpackPlugin = require( '../../../lib/utils/automated-tests/treatwarningsaserrorswebpackplugin' ); - } ); + try { + const statsJson = stats.toJson( { errorDetails: false } ); - it( 'should reassign warnings to errors and not emit the code when errors are present', done => { - runCompiler( { - mode: 'development', - entry: './file', - plugins: [ - { - apply( compiler ) { - compiler.hooks.make.tap( 'MakeCompilationWarning', compilation => { - compilation.errors.push( new Error( 'Compilation error 1' ) ); - compilation.errors.push( new Error( 'Compilation error 2' ) ); - compilation.warnings.push( new Error( 'Compilation warning 1' ) ); - compilation.warnings.push( new Error( 'Compilation warning 2' ) ); - } ); + expect( statsJson.errors.length ).to.equal( 4 ); + expect( statsJson.warnings.length ).to.equal( 0 ); + expect( statsJson.errors[ 0 ].message ).to.equal( 'Compilation error 1' ); + expect( statsJson.errors[ 1 ].message ).to.equal( 'Compilation error 2' ); + expect( statsJson.errors[ 2 ].message ).to.equal( 'Compilation warning 1' ); + expect( statsJson.errors[ 3 ].message ).to.equal( 'Compilation warning 2' ); + expect( statsJson.assets[ 0 ].emitted ).to.equal( false ); + resolve(); + } catch ( error ) { + reject( error ); } - }, - new TreatWarningsAsErrorsWebpackPlugin() - ] - }, stats => { - const statsJson = stats.toJson( { errorDetails: false } ); - - expect( statsJson.errors.length ).to.equal( 4 ); - expect( statsJson.warnings.length ).to.equal( 0 ); - expect( statsJson.errors[ 0 ].message ).to.equal( 'Compilation error 1' ); - expect( statsJson.errors[ 1 ].message ).to.equal( 'Compilation error 2' ); - expect( statsJson.errors[ 2 ].message ).to.equal( 'Compilation warning 1' ); - expect( statsJson.errors[ 3 ].message ).to.equal( 'Compilation warning 2' ); - expect( statsJson.assets[ 0 ].emitted ).to.equal( false ); - done(); + } ); } ); } ); } ); function runCompiler( options, callback ) { - options.context = path.join( __dirname, 'fixtures' ); + options.context = path.join( __dirname, '..', '..', 'fixtures' ); const compiler = webpack( options ); compiler.outputFileSystem = {}; compiler.run( ( err, stats ) => { - callback( stats ); + callback( err, stats ); } ); } diff --git a/packages/ckeditor5-dev-tests/tests/utils/getdefinitionsfromfile.js b/packages/ckeditor5-dev-tests/tests/utils/getdefinitionsfromfile.js index 2525b1846..28e88923b 100644 --- a/packages/ckeditor5-dev-tests/tests/utils/getdefinitionsfromfile.js +++ b/packages/ckeditor5-dev-tests/tests/utils/getdefinitionsfromfile.js @@ -3,49 +3,20 @@ * For licensing, see LICENSE.md. */ -'use strict'; - -const mockery = require( 'mockery' ); -const sinon = require( 'sinon' ); -const { expect } = require( 'chai' ); +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import path from 'path'; +import getDefinitionsFromFile from '../../lib/utils/getdefinitionsfromfile.js'; describe( 'getDefinitionsFromFile()', () => { - let getDefinitionsFromFile, consoleStub; - beforeEach( () => { - consoleStub = sinon.stub( console, 'error' ); - sinon.stub( process, 'cwd' ).returns( '/workspace' ); - - mockery.enable( { - useCleanCache: true, - warnOnReplace: false, - warnOnUnregistered: false - } ); - - mockery.registerMock( 'path', { - join: sinon.stub().callsFake( ( ...chunks ) => chunks.join( '/' ).replace( '/./', '/' ) ), - isAbsolute: sinon.stub().callsFake( path => path.startsWith( '/' ) ) - } ); - - mockery.registerMock( '/workspace/path/to/secret.js', { - SECRET: 'secret', - ANOTHER_SECRET: 'another-secret', - NON_PRIMITIVE_SECRET: { - foo: [ 'bar', 'baz' ] - } - } ); - - getDefinitionsFromFile = require( '../../lib/utils/getdefinitionsfromfile' ); - } ); - - afterEach( () => { - sinon.restore(); - mockery.disable(); - mockery.deregisterAll(); + vi.spyOn( path, 'join' ).mockImplementation( ( ...chunks ) => chunks.join( '/' ).replace( '/./', '/' ) ); } ); it( 'should return definition object if path to identity file is relative', () => { - const definitions = getDefinitionsFromFile( './path/to/secret.js' ); + const definitions = getDefinitionsFromFile( + // A relative path according to a package root. + path.join( '.', 'tests', 'fixtures', 'getdefinitionsfromfile', 'secret.cjs' ) + ); expect( definitions ).to.deep.equal( { SECRET: '"secret"', @@ -55,7 +26,9 @@ describe( 'getDefinitionsFromFile()', () => { } ); it( 'should return definition object if path to identity file is absolute', () => { - const definitions = getDefinitionsFromFile( '/workspace/path/to/secret.js' ); + const definitions = getDefinitionsFromFile( + path.join( __dirname, '..', 'fixtures', 'getdefinitionsfromfile', 'secret.cjs' ) + ); expect( definitions ).to.deep.equal( { SECRET: '"secret"', @@ -71,28 +44,32 @@ describe( 'getDefinitionsFromFile()', () => { } ); it( 'should not throw an error and return empty object if path to identity file is not valid', () => { + const consoleStub = vi.spyOn( console, 'error' ).mockImplementation( () => {} ); let definitions; expect( () => { definitions = getDefinitionsFromFile( 'foo.js' ); } ).to.not.throw(); - expect( consoleStub.callCount ).to.equal( 1 ); - expect( consoleStub.firstCall.args[ 0 ] ).to.satisfy( msg => msg.startsWith( 'Cannot find module \'/workspace/foo.js\'' ) ); + expect( consoleStub ).toHaveBeenCalledExactlyOnceWith( + expect.stringContaining( 'Cannot find module' ) + ); expect( definitions ).to.deep.equal( {} ); } ); - it( 'should not throw an error and return empty object if stringifying the identity file has failed', () => { - sinon.stub( JSON, 'stringify' ).throws( new Error( 'Example error.' ) ); + it( 'should not throw an error and return empty object if stringifies the identity file has failed', () => { + const consoleStub = vi.spyOn( console, 'error' ).mockImplementation( () => {} ); + vi.spyOn( JSON, 'stringify' ).mockImplementation( () => { + throw new Error( 'Example error.' ); + } ); let definitions; expect( () => { - definitions = getDefinitionsFromFile( '/workspace/path/to/secret.js' ); + definitions = getDefinitionsFromFile( path.join( '.', 'tests', 'fixtures', 'getdefinitionsfromfile', 'secret.cjs' ) ); } ).to.not.throw(); - expect( consoleStub.callCount ).to.equal( 1 ); - expect( consoleStub.firstCall.args[ 0 ] ).to.equal( 'Example error.' ); + expect( consoleStub ).toHaveBeenCalledExactlyOnceWith( 'Example error.' ); expect( definitions ).to.deep.equal( {} ); } ); } ); diff --git a/packages/ckeditor5-dev-tests/tests/utils/getrelativefilepath.js b/packages/ckeditor5-dev-tests/tests/utils/getrelativefilepath.js index d1d763179..8e110a742 100644 --- a/packages/ckeditor5-dev-tests/tests/utils/getrelativefilepath.js +++ b/packages/ckeditor5-dev-tests/tests/utils/getrelativefilepath.js @@ -3,142 +3,122 @@ * For licensing, see LICENSE.md. */ -'use strict'; - -const path = require( 'path' ); -const sinon = require( 'sinon' ); -const { expect } = require( 'chai' ); - -describe( 'dev-tests/utils', () => { - let getRelativeFilePath; - - beforeEach( () => { - getRelativeFilePath = require( '../../lib/utils/getrelativefilepath' ); - } ); - - describe( 'getRelativeFilePath()', () => { - let sandbox; +import path from 'path'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import getRelativeFilePath from '../../lib/utils/getrelativefilepath.js'; +describe( 'getRelativeFilePath()', () => { + describe( 'Unix paths', () => { beforeEach( () => { - sandbox = sinon.createSandbox(); - } ); - - afterEach( () => { - sandbox.restore(); + vi.spyOn( path, 'join' ).mockImplementation( ( ...args ) => args.join( '/' ) ); } ); - describe( 'Unix paths', () => { - beforeEach( () => { - sandbox.stub( path, 'join' ).callsFake( ( ...args ) => args.join( '/' ) ); - } ); + it( 'returns path which starts with package name (simple check)', () => { + vi.spyOn( process, 'cwd' ).mockReturnValue( '/Users/foo' ); - it( 'returns path which starts with package name (simple check)', () => { - sandbox.stub( process, 'cwd' ).returns( '/Users/foo' ); + checkPath( '/Users/foo/packages/ckeditor5-foo/tests/manual/foo.js', 'ckeditor5-foo/tests/manual/foo.js' ); + } ); - checkPath( '/Users/foo/packages/ckeditor5-foo/tests/manual/foo.js', 'ckeditor5-foo/tests/manual/foo.js' ); - } ); + it( 'returns path which starts with package name (workspace directory looks like package name)', () => { + vi.spyOn( process, 'cwd' ).mockReturnValue( '/Users/foo/ckeditor5-workspace/ckeditor5' ); - it( 'returns path which starts with package name (workspace directory looks like package name)', () => { - sandbox.stub( process, 'cwd' ).returns( '/Users/foo/ckeditor5-workspace/ckeditor5' ); + checkPath( + '/Users/foo/ckeditor5-workspace/ckeditor5/packages/ckeditor5-foo/tests/manual/foo.js', + 'ckeditor5-foo/tests/manual/foo.js' + ); + } ); - checkPath( - '/Users/foo/ckeditor5-workspace/ckeditor5/packages/ckeditor5-foo/tests/manual/foo.js', - 'ckeditor5-foo/tests/manual/foo.js' - ); - } ); + it( 'returns a proper path for "ckeditor-" prefix', () => { + vi.spyOn( process, 'cwd' ).mockReturnValue( '/work/space' ); - it( 'returns a proper path for "ckeditor-" prefix', () => { - sandbox.stub( process, 'cwd' ).returns( '/work/space' ); + checkPath( '/work/space/packages/ckeditor-foo/tests/manual/foo.js', 'ckeditor-foo/tests/manual/foo.js' ); + } ); - checkPath( '/work/space/packages/ckeditor-foo/tests/manual/foo.js', 'ckeditor-foo/tests/manual/foo.js' ); - } ); + it( 'returns a proper path for "ckeditor-" prefix and "ckeditor.js" file', () => { + vi.spyOn( process, 'cwd' ).mockReturnValue( '/work/space' ); - it( 'returns a proper path for "ckeditor-" prefix and "ckeditor.js" file', () => { - sandbox.stub( process, 'cwd' ).returns( '/work/space' ); + checkPath( '/work/space/packages/ckeditor-foo/tests/manual/ckeditor.js', 'ckeditor-foo/tests/manual/ckeditor.js' ); + } ); - checkPath( '/work/space/packages/ckeditor-foo/tests/manual/ckeditor.js', 'ckeditor-foo/tests/manual/ckeditor.js' ); - } ); + it( 'returns a proper path to from the main (root) package', () => { + vi.spyOn( process, 'cwd' ).mockReturnValue( '/work/space' ); + checkPath( '/work/space/packages/ckeditor5/tests/manual/foo.js', 'ckeditor5/tests/manual/foo.js' ); + } ); - it( 'returns a proper path to from the main (root) package', () => { - sandbox.stub( process, 'cwd' ).returns( '/work/space' ); - checkPath( '/work/space/packages/ckeditor5/tests/manual/foo.js', 'ckeditor5/tests/manual/foo.js' ); - } ); + it( 'returns a proper path for "ckeditor5.js" file', () => { + vi.spyOn( process, 'cwd' ).mockReturnValue( '/work/space' ); + checkPath( + '/work/space/packages/ckeditor5-build-a/tests/manual/ckeditor5.js', + 'ckeditor5-build-a/tests/manual/ckeditor5.js' + ); + } ); - it( 'returns a proper path for "ckeditor5.js" file', () => { - sandbox.stub( process, 'cwd' ).returns( '/work/space' ); - checkPath( - '/work/space/packages/ckeditor5-build-a/tests/manual/ckeditor5.js', - 'ckeditor5-build-a/tests/manual/ckeditor5.js' - ); - } ); + it( 'returns a proper path for "ckeditor.js" file', () => { + vi.spyOn( process, 'cwd' ).mockReturnValue( '/work/space' ); + checkPath( + '/work/space/packages/ckeditor5-build-a/tests/manual/ckeditor.js', + 'ckeditor5-build-a/tests/manual/ckeditor.js' ); + } ); + } ); - it( 'returns a proper path for "ckeditor.js" file', () => { - sandbox.stub( process, 'cwd' ).returns( '/work/space' ); - checkPath( - '/work/space/packages/ckeditor5-build-a/tests/manual/ckeditor.js', - 'ckeditor5-build-a/tests/manual/ckeditor.js' ); - } ); + describe( 'Windows paths', () => { + beforeEach( () => { + vi.spyOn( path, 'join' ).mockImplementation( ( ...args ) => args.join( '\\' ) ); } ); - describe( 'Windows paths', () => { - beforeEach( () => { - sandbox.stub( path, 'join' ).callsFake( ( ...args ) => args.join( '\\' ) ); - } ); + it( 'returns path which starts with package name (simple check)', () => { + vi.spyOn( process, 'cwd' ).mockReturnValue( 'C:\\work\\space' ); - it( 'returns path which starts with package name (simple check)', () => { - sandbox.stub( process, 'cwd' ).returns( 'C:\\work\\space' ); + checkPath( 'C:\\work\\space\\packages\\ckeditor5-foo\\tests\\manual\\foo.js', 'ckeditor5-foo\\tests\\manual\\foo.js' ); + } ); - checkPath( 'C:\\work\\space\\packages\\ckeditor5-foo\\tests\\manual\\foo.js', 'ckeditor5-foo\\tests\\manual\\foo.js' ); - } ); + it( 'returns path which starts with package name (workspace directory looks like package name)', () => { + vi.spyOn( process, 'cwd' ).mockReturnValue( 'C:\\Document and settings\\foo\\ckeditor5-workspace\\ckeditor5' ); - it( 'returns path which starts with package name (workspace directory looks like package name)', () => { - sandbox.stub( process, 'cwd' ).returns( 'C:\\Document and settings\\foo\\ckeditor5-workspace\\ckeditor5' ); + checkPath( + 'C:\\Document and settings\\foo\\ckeditor5-workspace\\ckeditor5\\packages\\ckeditor5-foo\\tests\\manual\\foo.js', + 'ckeditor5-foo\\tests\\manual\\foo.js' + ); + } ); - checkPath( - 'C:\\Document and settings\\foo\\ckeditor5-workspace\\ckeditor5\\packages\\ckeditor5-foo\\tests\\manual\\foo.js', - 'ckeditor5-foo\\tests\\manual\\foo.js' - ); - } ); + it( 'returns a proper path for "ckeditor-" prefix', () => { + vi.spyOn( process, 'cwd' ).mockReturnValue( 'C:\\work\\space' ); - it( 'returns a proper path for "ckeditor-" prefix', () => { - sandbox.stub( process, 'cwd' ).returns( 'C:\\work\\space' ); + checkPath( 'C:\\work\\space\\packages\\ckeditor-foo\\tests\\manual\\foo.js', 'ckeditor-foo\\tests\\manual\\foo.js' ); + } ); - checkPath( 'C:\\work\\space\\packages\\ckeditor-foo\\tests\\manual\\foo.js', 'ckeditor-foo\\tests\\manual\\foo.js' ); - } ); + it( 'returns a proper path for "ckeditor-" prefix and "ckeditor.js" file', () => { + vi.spyOn( process, 'cwd' ).mockReturnValue( 'C:\\work\\space' ); - it( 'returns a proper path for "ckeditor-" prefix and "ckeditor.js" file', () => { - sandbox.stub( process, 'cwd' ).returns( 'C:\\work\\space' ); + checkPath( + 'C:\\work\\space\\packages\\ckeditor-foo\\tests\\manual\\ckeditor.js', + 'ckeditor-foo\\tests\\manual\\ckeditor.js' + ); + } ); - checkPath( - 'C:\\work\\space\\packages\\ckeditor-foo\\tests\\manual\\ckeditor.js', - 'ckeditor-foo\\tests\\manual\\ckeditor.js' - ); - } ); + it( 'returns a proper path to from the main (root) package', () => { + vi.spyOn( process, 'cwd' ).mockReturnValue( 'C:\\work\\space' ); + checkPath( 'C:\\work\\space\\tests\\manual\\foo.js', 'ckeditor5\\tests\\manual\\foo.js' ); + } ); - it( 'returns a proper path to from the main (root) package', () => { - sandbox.stub( process, 'cwd' ).returns( 'C:\\work\\space' ); - checkPath( 'C:\\work\\space\\tests\\manual\\foo.js', 'ckeditor5\\tests\\manual\\foo.js' ); - } ); + it( 'returns a proper path for "ckeditor5.js" file', () => { + vi.spyOn( process, 'cwd' ).mockReturnValue( 'C:\\work\\space' ); + checkPath( + 'C:\\work\\space\\packages\\ckeditor5-build-a\\tests\\manual\\ckeditor5.js', + 'ckeditor5-build-a\\tests\\manual\\ckeditor5.js' + ); + } ); - it( 'returns a proper path for "ckeditor5.js" file', () => { - sandbox.stub( process, 'cwd' ).returns( 'C:\\work\\space' ); - checkPath( - 'C:\\work\\space\\packages\\ckeditor5-build-a\\tests\\manual\\ckeditor5.js', - 'ckeditor5-build-a\\tests\\manual\\ckeditor5.js' - ); - } ); - - it( 'returns a proper path for "ckeditor.js" file', () => { - sandbox.stub( process, 'cwd' ).returns( 'C:\\work\\space' ); - checkPath( - 'C:\\work\\space\\packages\\ckeditor5-build-a\\tests\\manual\\ckeditor.js', - 'ckeditor5-build-a\\tests\\manual\\ckeditor.js' - ); - } ); + it( 'returns a proper path for "ckeditor.js" file', () => { + vi.spyOn( process, 'cwd' ).mockReturnValue( 'C:\\work\\space' ); + checkPath( + 'C:\\work\\space\\packages\\ckeditor5-build-a\\tests\\manual\\ckeditor.js', + 'ckeditor5-build-a\\tests\\manual\\ckeditor.js' + ); } ); } ); - - function checkPath( filePath, expectedPath ) { - expect( getRelativeFilePath( filePath ) ).to.equal( expectedPath ); - } } ); + +function checkPath( filePath, expectedPath ) { + expect( getRelativeFilePath( filePath ) ).to.equal( expectedPath ); +} diff --git a/packages/ckeditor5-dev-tests/tests/utils/manual-tests/compilehtmlfiles.js b/packages/ckeditor5-dev-tests/tests/utils/manual-tests/compilehtmlfiles.js index e94a529da..7753418d2 100644 --- a/packages/ckeditor5-dev-tests/tests/utils/manual-tests/compilehtmlfiles.js +++ b/packages/ckeditor5-dev-tests/tests/utils/manual-tests/compilehtmlfiles.js @@ -3,16 +3,10 @@ * For licensing, see LICENSE.md. */ -'use strict'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { createRequire } from 'module'; -const path = require( 'path' ); -const mockery = require( 'mockery' ); -const sinon = require( 'sinon' ); -const { use, expect } = require( 'chai' ); -const chokidar = require( 'chokidar' ); -const sinonChai = require( 'sinon-chai' ); - -use( sinonChai ); +const require = createRequire( import.meta.url ); const fakeDirname = path.dirname( require.resolve( '../../../lib/utils/manual-tests/compilehtmlfiles' ) ); diff --git a/packages/ckeditor5-dev-tests/tests/utils/requiredll.js b/packages/ckeditor5-dev-tests/tests/utils/requiredll.js index b4a13f5ef..a0e129973 100644 --- a/packages/ckeditor5-dev-tests/tests/utils/requiredll.js +++ b/packages/ckeditor5-dev-tests/tests/utils/requiredll.js @@ -3,103 +3,83 @@ * For licensing, see LICENSE.md. */ -'use strict'; +import { describe, expect, it } from 'vitest'; +import requireDll from '../../lib/utils/requiredll.js'; -const sinon = require( 'sinon' ); -const { expect } = require( 'chai' ); +describe( 'requireDll()', () => { + it( 'should return true when loads JavaScript DLL file (Unix)', () => { + const files = [ + '/workspace/ckeditor5/tests/manual/all-features-dll.js' + ]; -describe( 'dev-tests/utils', () => { - let requireDll; - - beforeEach( () => { - requireDll = require( '../../lib/utils/requiredll' ); + expect( requireDll( files ) ).to.equal( true ); } ); - describe( 'requireDll()', () => { - let sandbox; - - beforeEach( () => { - sandbox = sinon.createSandbox(); - } ); - - afterEach( () => { - sandbox.restore(); - } ); - - it( 'should return true when loads JavaScript DLL file (Unix)', () => { - const files = [ - '/workspace/ckeditor5/tests/manual/all-features-dll.js' - ]; + it( 'should return true when loads single JavaScript DLL file (Unix)', () => { + const file = '/workspace/ckeditor5/tests/manual/all-features-dll.js'; - expect( requireDll( files ) ).to.equal( true ); - } ); - - it( 'should return true when loads single JavaScript DLL file (Unix)', () => { - const file = '/workspace/ckeditor5/tests/manual/all-features-dll.js'; - - expect( requireDll( file ) ).to.equal( true ); - } ); + expect( requireDll( file ) ).to.equal( true ); + } ); - it( 'should return true when loads TypeScript DLL file (Unix)', () => { - const files = [ - '/workspace/ckeditor5/tests/manual/all-features-dll.ts' - ]; + it( 'should return true when loads TypeScript DLL file (Unix)', () => { + const files = [ + '/workspace/ckeditor5/tests/manual/all-features-dll.ts' + ]; - expect( requireDll( files ) ).to.equal( true ); - } ); + expect( requireDll( files ) ).to.equal( true ); + } ); - it( 'should return true when loads JavaScript DLL file (Windows)', () => { - const files = [ - 'C:\\workspace\\ckeditor5\\tests\\manual\\all-features-dll.js' - ]; + it( 'should return true when loads JavaScript DLL file (Windows)', () => { + const files = [ + 'C:\\workspace\\ckeditor5\\tests\\manual\\all-features-dll.js' + ]; - expect( requireDll( files ) ).to.equal( true ); - } ); + expect( requireDll( files ) ).to.equal( true ); + } ); - it( 'should return true when loads TypeScript DLL file (Windows)', () => { - const files = [ - 'C:\\workspace\\ckeditor5\\tests\\manual\\all-features-dll.ts' - ]; + it( 'should return true when loads TypeScript DLL file (Windows)', () => { + const files = [ + 'C:\\workspace\\ckeditor5\\tests\\manual\\all-features-dll.ts' + ]; - expect( requireDll( files ) ).to.equal( true ); - } ); + expect( requireDll( files ) ).to.equal( true ); + } ); - it( 'should return false when loads JavaScript non-DLL file (Unix)', () => { - const files = [ - '/workspace/ckeditor5/tests/manual/article.js' - ]; + it( 'should return false when loads JavaScript non-DLL file (Unix)', () => { + const files = [ + '/workspace/ckeditor5/tests/manual/article.js' + ]; - expect( requireDll( files ) ).to.equal( false ); - } ); + expect( requireDll( files ) ).to.equal( false ); + } ); - it( 'should return false when loads single JavaScript non-DLL file (Unix)', () => { - const file = '/workspace/ckeditor5/tests/manual/article.js'; + it( 'should return false when loads single JavaScript non-DLL file (Unix)', () => { + const file = '/workspace/ckeditor5/tests/manual/article.js'; - expect( requireDll( file ) ).to.equal( false ); - } ); + expect( requireDll( file ) ).to.equal( false ); + } ); - it( 'should return false when loads TypeScript non-DLL file (Unix)', () => { - const files = [ - '/workspace/ckeditor5/tests/manual/article.ts' - ]; + it( 'should return false when loads TypeScript non-DLL file (Unix)', () => { + const files = [ + '/workspace/ckeditor5/tests/manual/article.ts' + ]; - expect( requireDll( files ) ).to.equal( false ); - } ); + expect( requireDll( files ) ).to.equal( false ); + } ); - it( 'should return false when loads JavaScript non-DLL file (Windows)', () => { - const files = [ - 'C:\\workspace\\ckeditor5\\tests\\manual\\article.js' - ]; + it( 'should return false when loads JavaScript non-DLL file (Windows)', () => { + const files = [ + 'C:\\workspace\\ckeditor5\\tests\\manual\\article.js' + ]; - expect( requireDll( files ) ).to.equal( false ); - } ); + expect( requireDll( files ) ).to.equal( false ); + } ); - it( 'should return false when loads TypeScript non-DLL file (Windows)', () => { - const files = [ - 'C:\\workspace\\ckeditor5\\tests\\manual\\article.ts' - ]; + it( 'should return false when loads TypeScript non-DLL file (Windows)', () => { + const files = [ + 'C:\\workspace\\ckeditor5\\tests\\manual\\article.ts' + ]; - expect( requireDll( files ) ).to.equal( false ); - } ); + expect( requireDll( files ) ).to.equal( false ); } ); } ); diff --git a/packages/ckeditor5-dev-tests/tests/utils/transformfileoptiontotestglob.js b/packages/ckeditor5-dev-tests/tests/utils/transformfileoptiontotestglob.js index 179494936..c302c926a 100644 --- a/packages/ckeditor5-dev-tests/tests/utils/transformfileoptiontotestglob.js +++ b/packages/ckeditor5-dev-tests/tests/utils/transformfileoptiontotestglob.js @@ -3,30 +3,20 @@ * For licensing, see LICENSE.md. */ -'use strict'; +import path from 'path'; +import fs from 'fs'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import transformFileOptionToTestGlob from '../../lib/utils/transformfileoptiontotestglob.js'; -const path = require( 'path' ); -const { expect } = require( 'chai' ); -const sinon = require( 'sinon' ); -const fs = require( 'fs' ); - -describe( 'dev-tests/utils', () => { - let transformFileOptionToTestGlob, sandbox, readdirSyncStub, existsSyncStub, statSyncStub; +vi.mock( 'fs' ); +describe( 'transformFileOptionToTestGlob()', () => { beforeEach( () => { - sandbox = sinon.createSandbox(); - - sandbox.stub( path, 'join' ).callsFake( ( ...chunks ) => chunks.join( '/' ) ); - sandbox.stub( process, 'cwd' ).returns( '/workspace' ); - statSyncStub = sandbox.stub( fs, 'statSync' ).returns( { isDirectory: () => true } ); - readdirSyncStub = sandbox.stub( fs, 'readdirSync' ).returns( [ 'external-directory' ] ); - existsSyncStub = sandbox.stub( fs, 'existsSync' ).returns( true ); - - transformFileOptionToTestGlob = require( '../../lib/utils/transformfileoptiontotestglob' ); - } ); - - afterEach( () => { - sandbox.restore(); + vi.spyOn( path, 'join' ).mockImplementation( ( ...chunks ) => chunks.join( '/' ) ); + vi.spyOn( process, 'cwd' ).mockReturnValue( '/workspace' ); + vi.mocked( fs ).statSync.mockReturnValue( { isDirectory: () => true } ); + vi.mocked( fs ).readdirSync.mockReturnValue( [ 'external-directory' ] ); + vi.mocked( fs ).existsSync.mockReturnValue( true ); } ); describe( 'converts "ckeditor5" to pattern matching all root package tests', () => { @@ -255,7 +245,7 @@ describe( 'dev-tests/utils', () => { describe( 'should return correct glob for external dirs when external dir name passed', () => { it( 'for automated tests', () => { - readdirSyncStub.returns( [ 'test-external-directory' ] ); + vi.mocked( fs ).readdirSync.mockReturnValue( [ 'test-external-directory' ] ); expect( transformFileOptionToTestGlob( 'test-external-directory' ) ).to.deep.equal( [ '/workspace/external/test-external-directory/tests/**/*.{js,ts}' @@ -263,7 +253,7 @@ describe( 'dev-tests/utils', () => { } ); it( 'for manual tests', () => { - readdirSyncStub.returns( [ 'test-external-directory' ] ); + vi.mocked( fs ).readdirSync.mockReturnValue( [ 'test-external-directory' ] ); expect( transformFileOptionToTestGlob( 'test-external-directory', true ) ).to.deep.equal( [ '/workspace/external/test-external-directory/tests/manual/**/*.{js,ts}' @@ -271,8 +261,8 @@ describe( 'dev-tests/utils', () => { } ); it( 'should not match external directory when isDirectory returns false', () => { - statSyncStub.returns( { isDirectory: () => false } ); - readdirSyncStub.returns( [ 'test-external-file' ] ); + vi.mocked( fs ).statSync.mockReturnValue( { isDirectory: () => false } ); + vi.mocked( fs ).readdirSync.mockReturnValue( [ 'test-external-file' ] ); expect( transformFileOptionToTestGlob( 'test-external-directory', true ) ).to.deep.equal( [ '/workspace/packages/ckeditor5-test-external-directory/tests/manual/**/*.{js,ts}', @@ -284,10 +274,10 @@ describe( 'dev-tests/utils', () => { } ); it( 'should not call readdirSync if directory does not exist', () => { - existsSyncStub.returns( false ); + vi.mocked( fs ).existsSync.mockReturnValue( false ); transformFileOptionToTestGlob( 'test-random-directory' ); - expect( readdirSyncStub.called ).to.equal( false ); + expect( vi.mocked( fs ).readdirSync ).not.toHaveBeenCalled(); } ); } ); diff --git a/packages/ckeditor5-dev-tests/vitest.config.js b/packages/ckeditor5-dev-tests/vitest.config.js index 075897d63..450eeda30 100644 --- a/packages/ckeditor5-dev-tests/vitest.config.js +++ b/packages/ckeditor5-dev-tests/vitest.config.js @@ -17,7 +17,8 @@ export default defineConfig( { 'tests/**/*.js' ], exclude: [ - './tests/_utils/**/*.js' + './tests/_utils/**/*.js', + './tests/fixtures/**/*.js' ], coverage: { provider: 'v8', From 7ee4e4676a179927c10ce835a0976a3f9e2c5f3b Mon Sep 17 00:00:00 2001 From: Kamil Piechaczek Date: Wed, 18 Sep 2024 13:24:33 +0200 Subject: [PATCH 12/15] Even more tests. --- .../utils/manual-tests/compilehtmlfiles.js | 2 +- .../treatwarningsaserrorswebpackplugin.js | 2 +- .../utils/manual-tests/compilehtmlfiles.js | 463 +++++++++--------- .../utils/manual-tests/compilescripts.js | 330 ++++--------- .../tests/utils/manual-tests/createserver.js | 72 ++- .../utils/manual-tests/getwebpackconfig.js | 155 +++--- .../tests/utils/manual-tests/removedir.js | 96 ++-- 7 files changed, 465 insertions(+), 655 deletions(-) diff --git a/packages/ckeditor5-dev-tests/lib/utils/manual-tests/compilehtmlfiles.js b/packages/ckeditor5-dev-tests/lib/utils/manual-tests/compilehtmlfiles.js index a8284dc81..ee1eb102b 100644 --- a/packages/ckeditor5-dev-tests/lib/utils/manual-tests/compilehtmlfiles.js +++ b/packages/ckeditor5-dev-tests/lib/utils/manual-tests/compilehtmlfiles.js @@ -45,7 +45,7 @@ export default function compileHtmlFiles( options ) { const staticFiles = sourceDirs .flatMap( sourceDir => { - const globPattern = path.join( sourceDir, '**', '*.!(js|html|md)' ).split( path.sep ).join( '/' ); + const globPattern = path.join( sourceDir, '**', '*.!(js|html|md)' ).split( /[\\/]/ ).join( '/' ); return globSync( globPattern ); } ) diff --git a/packages/ckeditor5-dev-tests/tests/utils/automated-tests/treatwarningsaserrorswebpackplugin.js b/packages/ckeditor5-dev-tests/tests/utils/automated-tests/treatwarningsaserrorswebpackplugin.js index 76b876755..9b323263c 100644 --- a/packages/ckeditor5-dev-tests/tests/utils/automated-tests/treatwarningsaserrorswebpackplugin.js +++ b/packages/ckeditor5-dev-tests/tests/utils/automated-tests/treatwarningsaserrorswebpackplugin.js @@ -12,7 +12,7 @@ import TreatWarningsAsErrorsWebpackPlugin from '../../../lib/utils/automated-tes const __filename = fileURLToPath( import.meta.url ); const __dirname = path.dirname( __filename ); -describe( 'TreatWarningsAsErrorsWebpackPlugin()', () => { +describe( 'TreatWarningsAsErrorsWebpackPlugin', () => { it( 'should reassign warnings to errors and not emit the code when errors are present', () => { return new Promise( ( resolve, reject ) => { runCompiler( diff --git a/packages/ckeditor5-dev-tests/tests/utils/manual-tests/compilehtmlfiles.js b/packages/ckeditor5-dev-tests/tests/utils/manual-tests/compilehtmlfiles.js index 7753418d2..cc2931fb0 100644 --- a/packages/ckeditor5-dev-tests/tests/utils/manual-tests/compilehtmlfiles.js +++ b/packages/ckeditor5-dev-tests/tests/utils/manual-tests/compilehtmlfiles.js @@ -3,122 +3,109 @@ * For licensing, see LICENSE.md. */ +import path from 'path'; +import fs from 'fs-extra'; import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { createRequire } from 'module'; - -const require = createRequire( import.meta.url ); - -const fakeDirname = path.dirname( require.resolve( '../../../lib/utils/manual-tests/compilehtmlfiles' ) ); - -describe( 'compileHtmlFiles', () => { - let sandbox, stubs, files, compileHtmlFiles; +import { logger } from '@ckeditor/ckeditor5-dev-utils'; +import { globSync } from 'glob'; +import chokidar from 'chokidar'; +import chalk from 'chalk'; +import domCombiner from 'dom-combiner'; +import compileHtmlFiles from '../../../lib/utils/manual-tests/compilehtmlfiles.js'; +import getRelativeFilePath from '../../../lib/utils/getrelativefilepath.js'; + +const stubs = vi.hoisted( () => ( { + commonmark: { + parser: { + parse: vi.fn() + }, + htmlRenderer: { + render: vi.fn() + } + }, + log: { + info: vi.fn() + } +} ) ); + +vi.mock( 'path' ); +vi.mock( 'commonmark', () => ( { + Parser: class Parser { + parse( ...args ) { + return stubs.commonmark.parser.parse( ...args ); + } + }, + + HtmlRenderer: class HtmlRenderer { + render( ...args ) { + return stubs.commonmark.htmlRenderer.render( ...args ); + } + } +} ) ); +vi.mock( 'path' ); +vi.mock( 'glob' ); +vi.mock( 'fs-extra' ); +vi.mock( 'chokidar' ); +vi.mock( 'chalk', () => ( { + default: { + cyan: vi.fn() + } +} ) ); +vi.mock( 'dom-combiner' ); +vi.mock( '@ckeditor/ckeditor5-dev-utils' ); +vi.mock( '../../../lib/utils/getrelativefilepath.js' ); + +describe( 'compileHtmlFiles()', () => { + let files; let patternFiles = {}; let separator = '/'; beforeEach( () => { - mockery.enable( { - useCleanCache: true, - warnOnReplace: false, - warnOnUnregistered: false - } ); - - sandbox = sinon.createSandbox(); - - stubs = { - fs: { - readFileSync: sandbox.spy( pathToFile => files[ pathToFile ] ), - ensureDirSync: sandbox.stub(), - outputFileSync: sandbox.stub(), - copySync: sandbox.stub() - }, - - path: { - join: sandbox.stub().callsFake( ( ...chunks ) => chunks.join( separator ) ), - parse: sandbox.stub().callsFake( pathToParse => { - const chunks = pathToParse.split( separator ); - const fileName = chunks.pop(); - - return { - dir: chunks.join( separator ), - name: fileName.split( '.' ).slice( 0, -1 ).join( '.' ) - }; - } ), - dirname: sandbox.stub().callsFake( pathToParse => { - return pathToParse.split( separator ).slice( 0, -1 ).join( separator ); - } ), - sep: separator - }, - - logger: { - info: sandbox.stub(), - warning: sandbox.stub(), - error: sandbox.stub() - }, - - commonmark: { - parse: sandbox.spy(), - render: sandbox.spy( () => '

Markdown header

' ) - }, - - chalk: { - cyan: sandbox.spy( text => text ) - }, - - chokidar: { - watch: sandbox.stub( chokidar, 'watch' ).callsFake( () => ( { - on: () => { - } - } ) ) - }, - - getRelativeFilePath: sandbox.spy( pathToFile => pathToFile ), - glob: { - globSync: sandbox.spy( pattern => patternFiles[ pattern ] ) - }, - domCombiner: sandbox.spy( ( ...args ) => args.join( '\n' ) ) - }; - - mockery.registerMock( 'path', stubs.path ); - mockery.registerMock( 'commonmark', { - Parser: class Parser { - parse( ...args ) { - return stubs.commonmark.parse( ...args ); - } - }, - - HtmlRenderer: class HtmlRenderer { - render( ...args ) { - return stubs.commonmark.render( ...args ); - } - } + stubs.commonmark.htmlRenderer.render.mockReturnValue( '

Markdown header

' ); + vi.mocked( logger ).mockReturnValue( stubs.log ); + vi.mocked( chalk ).cyan.mockImplementation( input => input ); + vi.mocked( fs ).readFileSync.mockImplementation( pathToFile => files[ pathToFile ] ); + vi.mocked( path ).join.mockImplementation( ( ...chunks ) => chunks.join( separator ) ); + vi.mocked( path ).parse.mockImplementation( pathToParse => { + const chunks = pathToParse.split( separator ); + const fileName = chunks.pop(); + + return { + dir: chunks.join( separator ), + name: fileName.split( '.' ).slice( 0, -1 ).join( '.' ) + }; } ); - mockery.registerMock( 'glob', stubs.glob ); - mockery.registerMock( 'fs-extra', stubs.fs ); - mockery.registerMock( 'chokidar', stubs.chokidar ); - mockery.registerMock( 'dom-combiner', stubs.domCombiner ); - mockery.registerMock( '@ckeditor/ckeditor5-dev-utils', { - logger() { - return stubs.logger; - } + vi.mocked( path ).dirname.mockImplementation( pathToParse => { + return pathToParse.split( separator ).slice( 0, -1 ).join( separator ); } ); - mockery.registerMock( '../getrelativefilepath', stubs.getRelativeFilePath ); - } ); - - afterEach( () => { - sandbox.restore(); - mockery.deregisterAll(); - mockery.disable(); + vi.mocked( chokidar ).watch.mockImplementation( () => ( { + on: vi.fn() + } ) ); + vi.mocked( getRelativeFilePath ).mockImplementation( pathToFile => pathToFile ); + vi.mocked( globSync ).mockImplementation( pattern => patternFiles[ pattern ] ); + vi.mocked( domCombiner ).mockImplementation( ( ...args ) => args.join( '\n' ) ); } ); describe( 'Unix environment', () => { beforeEach( () => { separator = '/'; - compileHtmlFiles = require( '../../../lib/utils/manual-tests/compilehtmlfiles' ); + } ); + + it( 'creates a build directory where compiled files are saved', () => { + files = {}; + + compileHtmlFiles( { + buildDir: 'buildDir', + language: 'en', + sourceFiles: [] + } ); + + expect( vi.mocked( fs ).ensureDirSync ).toHaveBeenCalledExactlyOnceWith( 'buildDir' ); } ); it( 'should compile md and html files to the output html file', () => { files = { - [ `${ fakeDirname }/template.html` ]: '
template html content
', + '/template.html': '
template html content
', 'path/to/manual/file.md': '## Markdown header', 'path/to/manual/file.html': '
html file content
' }; @@ -133,50 +120,93 @@ describe( 'compileHtmlFiles', () => { sourceFiles: [ 'path/to/manual/file.js' ] } ); - expect( stubs.commonmark.parse ).to.be.calledWithExactly( '## Markdown header' ); - expect( stubs.fs.ensureDirSync ).to.be.calledWithExactly( 'buildDir' ); + expect( stubs.commonmark.parser.parse ).toHaveBeenCalledExactlyOnceWith( '## Markdown header' ); - /* eslint-disable max-len */ - expect( stubs.fs.outputFileSync ).to.be.calledWithExactly( + expect( vi.mocked( fs ).outputFileSync ).toHaveBeenCalledExactlyOnceWith( 'buildDir/path/to/manual/file.html', [ '
template html content
', '

Markdown header

', '', '' + - '' + + '' + '', '
html file content
', '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + '' ].join( '\n' ) ); - /* eslint-enable max-len */ - expect( stubs.chokidar.watch ).to.be.calledWithExactly( + expect( stubs.log.info ).toHaveBeenCalledTimes( 2 ); + expect( stubs.log.info ).toHaveBeenCalledWith( expect.stringMatching( /^Processing/ ) ); + expect( stubs.log.info ).toHaveBeenCalledWith( expect.stringMatching( /^Finished writing/ ) ); + } ); + + it( 'should listen to changes in source files', () => { + files = { + '/template.html': '
template html content
', + 'path/to/manual/file.md': '## Markdown header', + 'path/to/manual/file.html': '
html file content
' + }; + + patternFiles = { + 'path/to/manual/**/*.!(js|html|md)': [ 'static-file.png' ] + }; + + compileHtmlFiles( { + buildDir: 'buildDir', + language: 'en', + sourceFiles: [ 'path/to/manual/file.js' ] + } ); + + expect( vi.mocked( chokidar ).watch ).toHaveBeenCalledTimes( 2 ); + expect( vi.mocked( chokidar ).watch ).toHaveBeenCalledWith( 'path/to/manual/file.md', { ignoreInitial: true } ); - expect( stubs.chokidar.watch ).to.be.calledWithExactly( + expect( vi.mocked( chokidar ).watch ).toHaveBeenCalledWith( 'path/to/manual/file.html', { ignoreInitial: true } ); - expect( stubs.fs.copySync ).to.be.calledWithExactly( - 'static-file.png', 'buildDir/static-file.png' - ); + } ); + + it( 'should copy static resources', () => { + files = { + '/template.html': '
template html content
', + 'path/to/manual/file.md': '## Markdown header', + 'path/to/manual/file.html': '
html file content
' + }; + + patternFiles = { + 'path/to/manual/**/*.!(js|html|md)': [ 'static-file.png' ] + }; + + compileHtmlFiles( { + buildDir: 'buildDir', + language: 'en', + sourceFiles: [ 'path/to/manual/file.js' ] + } ); - expect( stubs.logger.info.callCount ).to.equal( 2 ); - expect( stubs.logger.info.firstCall.args[ 0 ] ).to.match( /^Processing/ ); - expect( stubs.logger.info.secondCall.args[ 0 ] ).to.match( /^Finished writing/ ); + expect( vi.mocked( fs ).copySync ).toHaveBeenCalledExactlyOnceWith( 'static-file.png', 'buildDir/static-file.png' ); } ); it( 'should compile files with options#language specified', () => { + files = { + '/template.html': '
template html content
', + 'path/to/manual/file.md': '## Markdown header', + 'path/to/manual/file.html': '
html file content
' + }; + + patternFiles = { + 'path/to/manual/**/*.!(js|html|md)': [] + }; + compileHtmlFiles( { buildDir: 'buildDir', language: 'en', @@ -184,38 +214,36 @@ describe( 'compileHtmlFiles', () => { sourceFiles: [ 'path/to/manual/file.js' ] } ); - /* eslint-disable max-len */ - expect( stubs.fs.outputFileSync ).to.be.calledWithExactly( + expect( vi.mocked( fs ).outputFileSync ).toHaveBeenCalledExactlyOnceWith( 'buildDir/path/to/manual/file.html', [ '
template html content
', '

Markdown header

', '', '
' + - '' + + '' + '', '
html file content
', '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + '' ].join( '\n' ) ); - /* eslint-enable max-len */ } ); it( 'should work with files containing dots in their names', () => { files = { - [ `${ fakeDirname }/template.html` ]: '
template html content
', + '/template.html': '
template html content
', 'path/to/manual/file.abc.md': '## Markdown header', 'path/to/manual/file.abc.html': '
html file content
' }; @@ -229,35 +257,15 @@ describe( 'compileHtmlFiles', () => { sourceFiles: [ 'path/to/manual/file.abc.js' ] } ); - /* eslint-disable max-len */ - expect( stubs.fs.outputFileSync ).to.be.calledWith( - 'buildDir/path/to/manual/file.abc.html', [ - '
template html content
', - '

Markdown header

', - '', - '
' + - '' + - '', - '
html file content
', - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' - ].join( '\n' ) + expect( vi.mocked( fs ).outputFileSync ).toHaveBeenCalledExactlyOnceWith( + 'buildDir/path/to/manual/file.abc.html', + expect.stringContaining( '' ) ); - /* eslint-enable max-len */ } ); it( 'should work with a few entry points patterns', () => { files = { - [ `${ fakeDirname }/template.html` ]: '
template html content
', + '/template.html': '
template html content
', 'path/to/manual/file.md': '## Markdown header', 'path/to/manual/file.html': '
html file content
', 'path/to/another/manual/file.md': '## Markdown header', @@ -277,15 +285,22 @@ describe( 'compileHtmlFiles', () => { ] } ); - expect( stubs.chokidar.watch ).to.be.calledWithExactly( 'path/to/manual/file.md', { ignoreInitial: true } ); - expect( stubs.chokidar.watch ).to.be.calledWithExactly( 'path/to/manual/file.html', { ignoreInitial: true } ); - expect( stubs.chokidar.watch ).to.be.calledWithExactly( 'path/to/another/manual/file.html', { ignoreInitial: true } ); - expect( stubs.chokidar.watch ).to.be.calledWithExactly( 'path/to/another/manual/file.html', { ignoreInitial: true } ); + expect( vi.mocked( fs ).outputFileSync ).toHaveBeenCalledTimes( 2 ); + + expect( vi.mocked( fs ).outputFileSync ).toHaveBeenCalledWith( + 'buildDir/path/to/manual/file.html', + expect.stringContaining( '' ) + ); + + expect( vi.mocked( fs ).outputFileSync ).toHaveBeenCalledWith( + 'buildDir/path/to/another/manual/file.html', + expect.stringContaining( '' ) + ); } ); it( 'should not copy md files containing dots in their file names', () => { files = { - [ `${ fakeDirname }/template.html` ]: '
template html content
', + '/template.html': '
template html content
', 'path/to/manual/file.md': '## Markdown header', 'path/to/manual/file.html': '
html file content
' }; @@ -300,42 +315,12 @@ describe( 'compileHtmlFiles', () => { sourceFiles: [ 'path/to/manual/file.js' ] } ); - expect( stubs.commonmark.parse ).to.be.calledWithExactly( '## Markdown header' ); - expect( stubs.fs.ensureDirSync ).to.be.calledWithExactly( 'buildDir' ); - - /* eslint-disable max-len */ - expect( stubs.fs.outputFileSync ).to.be.calledWithExactly( - 'buildDir/path/to/manual/file.html', [ - '
template html content
', - '

Markdown header

', - '', - '
' + - '' + - '', - '
html file content
', - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' - ].join( '\n' ) - ); - /* eslint-enable max-len */ - - expect( stubs.chokidar.watch ).to.be.calledWithExactly( 'path/to/manual/file.md', { ignoreInitial: true } ); - expect( stubs.chokidar.watch ).to.be.calledWithExactly( 'path/to/manual/file.html', { ignoreInitial: true } ); - expect( stubs.fs.copySync ).not.to.be.calledWith( 'some.file.md', 'buildDir/some.file.md' ); + expect( vi.mocked( fs ).copySync ).not.toHaveBeenCalled(); } ); it( 'should compile the manual test and do not inform about the processed file', () => { files = { - [ `${ fakeDirname }/template.html` ]: '
template html content
', + '/template.html': '
template html content
', 'path/to/manual/file.md': '## Markdown header', 'path/to/manual/file.html': '
html file content
' }; @@ -351,27 +336,22 @@ describe( 'compileHtmlFiles', () => { silent: true } ); - expect( stubs.commonmark.parse ).to.be.calledWithExactly( '## Markdown header' ); - expect( stubs.fs.ensureDirSync ).to.be.calledWithExactly( 'buildDir' ); - - expect( stubs.logger.info.callCount ).to.equal( 0 ); + expect( stubs.log.info ).not.toHaveBeenCalled(); } ); } ); describe( 'Windows environment', () => { beforeEach( () => { separator = '\\'; - compileHtmlFiles = require( '../../../lib/utils/manual-tests/compilehtmlfiles' ); } ); it( 'should work on Windows environments', () => { - // Our wrapper on Glob returns proper paths for Unix and Windows. patternFiles = { - 'path\\to\\manual\\**\\*.!(js|html|md)': [ 'static-file.png' ] + 'path/to/manual/**/*.!(js|html|md)': [ 'static-file.png' ] }; files = { - [ fakeDirname + '\\template.html' ]: '
template html content
', + '\\template.html': '
template html content
', 'path\\to\\manual\\file.md': '## Markdown header', 'path\\to\\manual\\file.html': '
html file content
' }; @@ -381,43 +361,72 @@ describe( 'compileHtmlFiles', () => { sourceFiles: [ 'path\\to\\manual\\file.js' ] } ); - expect( stubs.commonmark.parse ).to.be.calledWithExactly( '## Markdown header' ); - expect( stubs.fs.ensureDirSync ).to.be.calledWithExactly( 'buildDir' ); - - /* eslint-disable max-len */ - expect( stubs.fs.outputFileSync ).to.be.calledWithExactly( + expect( vi.mocked( fs ).outputFileSync ).toHaveBeenCalledExactlyOnceWith( 'buildDir\\path\\to\\manual\\file.html', [ '
template html content
', '

Markdown header

', '', '
' + - '' + + '' + '', '
html file content
', '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + '' ].join( '\n' ) ); - /* eslint-enable max-len */ + } ); - expect( stubs.chokidar.watch ).to.be.calledWithExactly( + it( 'should listen to changes in source files', () => { + patternFiles = { + 'path/to/manual/**/*.!(js|html|md)': [ 'static-file.png' ] + }; + + files = { + '\\template.html': '
template html content
', + 'path\\to\\manual\\file.md': '## Markdown header', + 'path\\to\\manual\\file.html': '
html file content
' + }; + + compileHtmlFiles( { + buildDir: 'buildDir', + sourceFiles: [ 'path\\to\\manual\\file.js' ] + } ); + + expect( vi.mocked( chokidar ).watch ).toHaveBeenCalledTimes( 2 ); + expect( vi.mocked( chokidar ).watch ).toHaveBeenCalledWith( 'path\\to\\manual\\file.md', { ignoreInitial: true } ); - expect( stubs.chokidar.watch ).to.be.calledWithExactly( + expect( vi.mocked( chokidar ).watch ).toHaveBeenCalledWith( 'path\\to\\manual\\file.html', { ignoreInitial: true } ); - expect( stubs.fs.copySync ).to.be.calledWithExactly( - 'static-file.png', 'buildDir\\static-file.png' - ); + } ); + + it( 'should copy static resources', () => { + patternFiles = { + 'path/to/manual/**/*.!(js|html|md)': [ 'static-file.png' ] + }; + + files = { + '\\template.html': '
template html content
', + 'path\\to\\manual\\file.md': '## Markdown header', + 'path\\to\\manual\\file.html': '
html file content
' + }; + + compileHtmlFiles( { + buildDir: 'buildDir', + sourceFiles: [ 'path\\to\\manual\\file.js' ] + } ); + + expect( vi.mocked( fs ).copySync ).toHaveBeenCalledExactlyOnceWith( 'static-file.png', 'buildDir\\static-file.png' ); } ); } ); } ); diff --git a/packages/ckeditor5-dev-tests/tests/utils/manual-tests/compilescripts.js b/packages/ckeditor5-dev-tests/tests/utils/manual-tests/compilescripts.js index f3323a1cd..5659c7417 100644 --- a/packages/ckeditor5-dev-tests/tests/utils/manual-tests/compilescripts.js +++ b/packages/ckeditor5-dev-tests/tests/utils/manual-tests/compilescripts.js @@ -3,51 +3,40 @@ * For licensing, see LICENSE.md. */ -'use strict'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import webpack from 'webpack'; +import getRelativeFilePath from '../../../lib/utils/getrelativefilepath.js'; +import requireDll from '../../../lib/utils/requiredll.js'; +import getWebpackConfigForManualTests from '../../../lib/utils/manual-tests/getwebpackconfig.js'; +import compileManualTestScripts from '../../../lib/utils/manual-tests/compilescripts.js'; -const mockery = require( 'mockery' ); -const { expect } = require( 'chai' ); -const sinon = require( 'sinon' ); +vi.mock( 'webpack' ); +vi.mock( '../../../lib/utils/getrelativefilepath.js' ); +vi.mock( '../../../lib/utils/requiredll.js' ); +vi.mock( '../../../lib/utils/manual-tests/getwebpackconfig.js' ); -describe( 'compileManualTestScripts', () => { - let sandbox, stubs, webpackError, compileManualTestScripts; +describe( 'compileManualTestScripts()', () => { + let webpackError; beforeEach( () => { webpackError = null; - mockery.enable( { - useCleanCache: true, - warnOnReplace: false, - warnOnUnregistered: false + vi.mocked( webpack ).mockImplementation( ( config, callback ) => { + callback( webpackError ); } ); - sandbox = sinon.createSandbox(); - - stubs = { - webpack: sandbox.spy( ( config, callback ) => { - callback( webpackError ); - } ), - getWebpackConfig: sandbox.spy( ( { entries, buildDir } ) => ( { - entries, - buildDir - } ) ), - getRelativeFilePath: sandbox.spy( x => x ), - onTestCompilationStatus: sinon.stub() - }; - - mockery.registerMock( './getwebpackconfig', stubs.getWebpackConfig ); - mockery.registerMock( '../getrelativefilepath', stubs.getRelativeFilePath ); - mockery.registerMock( 'webpack', stubs.webpack ); - - compileManualTestScripts = require( '../../../lib/utils/manual-tests/compilescripts' ); - } ); + vi.mocked( getWebpackConfigForManualTests ).mockImplementation( ( { entries, buildDir } ) => ( { + entries, + buildDir + } ) ); - afterEach( () => { - sandbox.restore(); - mockery.disable(); + vi.mocked( getRelativeFilePath ).mockImplementation( input => input ); } ); it( 'should compile manual test scripts (DLL only)', async () => { + vi.mocked( requireDll ).mockReturnValue( true ); + const onTestCompilationStatus = vi.fn(); + await compileManualTestScripts( { cwd: 'workspace', buildDir: 'buildDir', @@ -57,43 +46,44 @@ describe( 'compileManualTestScripts', () => { ], themePath: 'path/to/theme', language: 'en', - onTestCompilationStatus: stubs.onTestCompilationStatus, + onTestCompilationStatus, additionalLanguages: [ 'pl', 'ar' ], debug: [ 'CK_DEBUG' ], disableWatch: false } ); - expect( stubs.getWebpackConfig.calledOnce ).to.equal( true ); - - sinon.assert.calledWith( stubs.getWebpackConfig.firstCall, { + expect( vi.mocked( getWebpackConfigForManualTests ) ).toHaveBeenCalledExactlyOnceWith( expect.objectContaining( { cwd: 'workspace', requireDll: true, buildDir: 'buildDir', themePath: 'path/to/theme', language: 'en', - onTestCompilationStatus: stubs.onTestCompilationStatus, + onTestCompilationStatus, additionalLanguages: [ 'pl', 'ar' ], entries: { 'ckeditor5-foo/manual/file1-dll': 'ckeditor5-foo/manual/file1-dll.js', 'ckeditor5-foo/manual/file2-dll': 'ckeditor5-foo/manual/file2-dll.js' }, debug: [ 'CK_DEBUG' ], - disableWatch: false, - identityFile: undefined, - tsconfig: undefined - } ); - - expect( stubs.webpack.calledOnce ).to.equal( true ); - expect( stubs.webpack.firstCall.args[ 0 ] ).to.deep.equal( { - buildDir: 'buildDir', - entries: { - 'ckeditor5-foo/manual/file1-dll': 'ckeditor5-foo/manual/file1-dll.js', - 'ckeditor5-foo/manual/file2-dll': 'ckeditor5-foo/manual/file2-dll.js' - } - } ); + disableWatch: false + } ) ); + + expect( vi.mocked( webpack ) ).toHaveBeenCalledExactlyOnceWith( + { + buildDir: 'buildDir', + entries: { + 'ckeditor5-foo/manual/file1-dll': 'ckeditor5-foo/manual/file1-dll.js', + 'ckeditor5-foo/manual/file2-dll': 'ckeditor5-foo/manual/file2-dll.js' + } + }, + expect.any( Function ) + ); } ); it( 'should compile manual test scripts (non-DLL only)', async () => { + vi.mocked( requireDll ).mockReturnValue( false ); + const onTestCompilationStatus = vi.fn(); + await compileManualTestScripts( { cwd: 'workspace', buildDir: 'buildDir', @@ -103,43 +93,43 @@ describe( 'compileManualTestScripts', () => { ], themePath: 'path/to/theme', language: 'en', - onTestCompilationStatus: stubs.onTestCompilationStatus, + onTestCompilationStatus, additionalLanguages: [ 'pl', 'ar' ], debug: [ 'CK_DEBUG' ], disableWatch: false } ); - expect( stubs.getWebpackConfig.calledOnce ).to.equal( true ); - - sinon.assert.calledWith( stubs.getWebpackConfig.firstCall, { + expect( vi.mocked( getWebpackConfigForManualTests ) ).toHaveBeenCalledExactlyOnceWith( expect.objectContaining( { cwd: 'workspace', requireDll: false, buildDir: 'buildDir', themePath: 'path/to/theme', language: 'en', - onTestCompilationStatus: stubs.onTestCompilationStatus, + onTestCompilationStatus, additionalLanguages: [ 'pl', 'ar' ], entries: { 'ckeditor5-foo/manual/file1': 'ckeditor5-foo/manual/file1.js', 'ckeditor5-foo/manual/file2': 'ckeditor5-foo/manual/file2.js' }, debug: [ 'CK_DEBUG' ], - disableWatch: false, - identityFile: undefined, - tsconfig: undefined - } ); - - expect( stubs.webpack.calledOnce ).to.equal( true ); - expect( stubs.webpack.firstCall.args[ 0 ] ).to.deep.equal( { - buildDir: 'buildDir', - entries: { - 'ckeditor5-foo/manual/file1': 'ckeditor5-foo/manual/file1.js', - 'ckeditor5-foo/manual/file2': 'ckeditor5-foo/manual/file2.js' - } - } ); + disableWatch: false + } ) ); + + expect( vi.mocked( webpack ) ).toHaveBeenCalledExactlyOnceWith( + { + buildDir: 'buildDir', + entries: { + 'ckeditor5-foo/manual/file1': 'ckeditor5-foo/manual/file1.js', + 'ckeditor5-foo/manual/file2': 'ckeditor5-foo/manual/file2.js' + } + }, + expect.any( Function ) + ); } ); it( 'should compile manual test scripts (DLL and non-DLL)', async () => { + vi.mocked( requireDll ).mockImplementation( input => input.includes( 'dll' ) ); + await compileManualTestScripts( { cwd: 'workspace', buildDir: 'buildDir', @@ -149,66 +139,19 @@ describe( 'compileManualTestScripts', () => { ], themePath: 'path/to/theme', language: 'en', - onTestCompilationStatus: stubs.onTestCompilationStatus, + onTestCompilationStatus: vi.fn(), additionalLanguages: [ 'pl', 'ar' ], debug: [ 'CK_DEBUG' ], disableWatch: false } ); - expect( stubs.getWebpackConfig.calledTwice ).to.equal( true ); - - sinon.assert.calledWith( stubs.getWebpackConfig.firstCall, { - cwd: 'workspace', - requireDll: true, - buildDir: 'buildDir', - themePath: 'path/to/theme', - language: 'en', - onTestCompilationStatus: stubs.onTestCompilationStatus, - additionalLanguages: [ 'pl', 'ar' ], - entries: { - 'ckeditor5-foo/manual/file2-dll': 'ckeditor5-foo/manual/file2-dll.js' - }, - debug: [ 'CK_DEBUG' ], - disableWatch: false, - identityFile: undefined, - tsconfig: undefined - } ); - - sinon.assert.calledWith( stubs.getWebpackConfig.secondCall, { - cwd: 'workspace', - requireDll: false, - buildDir: 'buildDir', - themePath: 'path/to/theme', - language: 'en', - onTestCompilationStatus: stubs.onTestCompilationStatus, - additionalLanguages: [ 'pl', 'ar' ], - entries: { - 'ckeditor5-foo/manual/file1': 'ckeditor5-foo/manual/file1.js' - }, - debug: [ 'CK_DEBUG' ], - disableWatch: false, - identityFile: undefined, - tsconfig: undefined - } ); - - expect( stubs.webpack.calledTwice ).to.equal( true ); - - expect( stubs.webpack.firstCall.args[ 0 ] ).to.deep.equal( { - buildDir: 'buildDir', - entries: { - 'ckeditor5-foo/manual/file2-dll': 'ckeditor5-foo/manual/file2-dll.js' - } - } ); - - expect( stubs.webpack.secondCall.args[ 0 ] ).to.deep.equal( { - buildDir: 'buildDir', - entries: { - 'ckeditor5-foo/manual/file1': 'ckeditor5-foo/manual/file1.js' - } - } ); + expect( vi.mocked( getWebpackConfigForManualTests ) ).toHaveBeenCalledTimes( 2 ); + expect( vi.mocked( webpack ) ).toHaveBeenCalledTimes( 2 ); } ); it( 'should compile multiple manual test scripts', async () => { + vi.mocked( requireDll ).mockReturnValue( false ); + await compileManualTestScripts( { cwd: 'workspace', buildDir: 'buildDir', @@ -219,26 +162,25 @@ describe( 'compileManualTestScripts', () => { ], themePath: 'path/to/theme', language: null, - onTestCompilationStatus: stubs.onTestCompilationStatus, + onTestCompilationStatus: vi.fn(), additionalLanguages: null, tsconfig: undefined } ); - expect( stubs.getWebpackConfig.calledOnce ).to.equal( true ); - - expect( stubs.getRelativeFilePath.calledThrice ).to.equal( true ); - expect( stubs.getRelativeFilePath.firstCall.args[ 0 ] ) - .to.equal( 'ckeditor5-build-classic/tests/manual/ckeditor.js' ); - expect( stubs.getRelativeFilePath.secondCall.args[ 0 ] ) - .to.equal( 'ckeditor5-build-classic/tests/manual/ckeditor.compcat.js' ); - expect( stubs.getRelativeFilePath.thirdCall.args[ 0 ] ) - .to.equal( 'ckeditor5-editor-classic/tests/manual/classic.js' ); + expect( vi.mocked( getWebpackConfigForManualTests ) ).toHaveBeenCalledOnce(); + expect( vi.mocked( getRelativeFilePath ) ).toHaveBeenCalledTimes( 3 ); + expect( vi.mocked( getRelativeFilePath ) ).toHaveBeenCalledWith( 'ckeditor5-build-classic/tests/manual/ckeditor.js' ); + expect( vi.mocked( getRelativeFilePath ) ).toHaveBeenCalledWith( 'ckeditor5-build-classic/tests/manual/ckeditor.compcat.js' ); + expect( vi.mocked( getRelativeFilePath ) ).toHaveBeenCalledWith( 'ckeditor5-editor-classic/tests/manual/classic.js' ); } ); - it( 'rejects if webpack threw an error', () => { - webpackError = new Error( 'Unexpected error.' ); + it( 'rejects if webpack threw an error', async () => { + vi.mocked( webpack ).mockImplementation( () => { + throw new Error( 'Unexpected error' ); + } ); + vi.mocked( requireDll ).mockReturnValue( false ); - return compileManualTestScripts( { + await expect( compileManualTestScripts( { buildDir: 'buildDir', sourceFiles: [ 'ckeditor5-foo/manual/file1.js', @@ -246,16 +188,9 @@ describe( 'compileManualTestScripts', () => { ], themePath: 'path/to/theme', language: null, - onTestCompilationStatus: stubs.onTestCompilationStatus, + onTestCompilationStatus: vi.fn(), additionalLanguages: null - } ).then( - () => { - throw new Error( 'Expected to be rejected.' ); - }, - err => { - expect( err ).to.equal( webpackError ); - } - ); + } ) ).rejects.toThrow( 'Unexpected error' ); } ); it( 'works on Windows environments', async () => { @@ -266,12 +201,11 @@ describe( 'compileManualTestScripts', () => { ], themePath: 'path/to/theme', language: null, - onTestCompilationStatus: stubs.onTestCompilationStatus, + onTestCompilationStatus: vi.fn(), additionalLanguages: null } ); - expect( stubs.getRelativeFilePath.calledOnce ).to.equal( true ); - expect( stubs.getRelativeFilePath.firstCall.args[ 0 ] ).to.equal( 'ckeditor5-build-classic\\tests\\manual\\ckeditor.js' ); + expect( vi.mocked( getRelativeFilePath ) ).toHaveBeenCalledExactlyOnceWith( 'ckeditor5-build-classic\\tests\\manual\\ckeditor.js' ); } ); it( 'should pass identity file to webpack configuration factory', async () => { @@ -286,41 +220,16 @@ describe( 'compileManualTestScripts', () => { ], themePath: 'path/to/theme', language: 'en', - onTestCompilationStatus: stubs.onTestCompilationStatus, + onTestCompilationStatus: vi.fn(), additionalLanguages: [ 'pl', 'ar' ], debug: [ 'CK_DEBUG' ], identityFile, disableWatch: false } ); - expect( stubs.getWebpackConfig.calledOnce ).to.equal( true ); - - sinon.assert.calledWith( stubs.getWebpackConfig.firstCall, { - cwd: 'workspace', - requireDll: false, - buildDir: 'buildDir', - themePath: 'path/to/theme', - language: 'en', - onTestCompilationStatus: stubs.onTestCompilationStatus, - additionalLanguages: [ 'pl', 'ar' ], - entries: { - 'ckeditor5-foo/manual/file1': 'ckeditor5-foo/manual/file1.js', - 'ckeditor5-foo/manual/file2': 'ckeditor5-foo/manual/file2.js' - }, - debug: [ 'CK_DEBUG' ], - identityFile, - disableWatch: false, - tsconfig: undefined - } ); - - expect( stubs.webpack.calledOnce ).to.equal( true ); - expect( stubs.webpack.firstCall.args[ 0 ] ).to.deep.equal( { - buildDir: 'buildDir', - entries: { - 'ckeditor5-foo/manual/file1': 'ckeditor5-foo/manual/file1.js', - 'ckeditor5-foo/manual/file2': 'ckeditor5-foo/manual/file2.js' - } - } ); + expect( vi.mocked( getWebpackConfigForManualTests ) ).toHaveBeenCalledExactlyOnceWith( expect.objectContaining( { + identityFile + } ) ); } ); it( 'should pass the "disableWatch" option to webpack configuration factory', async () => { @@ -332,38 +241,15 @@ describe( 'compileManualTestScripts', () => { ], themePath: 'path/to/theme', language: 'en', - onTestCompilationStatus: stubs.onTestCompilationStatus, + onTestCompilationStatus: vi.fn(), additionalLanguages: [ 'pl', 'ar' ], debug: [ 'CK_DEBUG' ], disableWatch: true } ); - expect( stubs.getWebpackConfig.calledOnce ).to.equal( true ); - - sinon.assert.calledWith( stubs.getWebpackConfig.firstCall, { - cwd: 'workspace', - requireDll: false, - buildDir: 'buildDir', - themePath: 'path/to/theme', - language: 'en', - onTestCompilationStatus: stubs.onTestCompilationStatus, - additionalLanguages: [ 'pl', 'ar' ], - entries: { - 'ckeditor5-foo/manual/file1': 'ckeditor5-foo/manual/file1.js' - }, - debug: [ 'CK_DEBUG' ], - identityFile: undefined, - disableWatch: true, - tsconfig: undefined - } ); - - expect( stubs.webpack.calledOnce ).to.equal( true ); - expect( stubs.webpack.firstCall.args[ 0 ] ).to.deep.equal( { - buildDir: 'buildDir', - entries: { - 'ckeditor5-foo/manual/file1': 'ckeditor5-foo/manual/file1.js' - } - } ); + expect( vi.mocked( getWebpackConfigForManualTests ) ).toHaveBeenCalledExactlyOnceWith( expect.objectContaining( { + disableWatch: true + } ) ); } ); it( 'should pass correct entries object to the webpack for both JS and TS files', async () => { @@ -381,21 +267,18 @@ describe( 'compileManualTestScripts', () => { disableWatch: false } ); - expect( stubs.getWebpackConfig.calledOnce ).to.equal( true ); - expect( stubs.getWebpackConfig.firstCall.args[ 0 ] ).to.deep.include( { + expect( vi.mocked( getWebpackConfigForManualTests ) ).toHaveBeenCalledExactlyOnceWith( expect.objectContaining( { entries: { 'ckeditor5-foo\\manual\\file1': 'ckeditor5-foo\\manual\\file1.js', 'ckeditor5-foo\\manual\\file2': 'ckeditor5-foo\\manual\\file2.ts' } - } ); - - expect( stubs.webpack.calledOnce ).to.equal( true ); - expect( stubs.webpack.firstCall.args[ 0 ] ).to.deep.include( { + } ) ); + expect( vi.mocked( webpack ) ).toHaveBeenCalledExactlyOnceWith( expect.objectContaining( { entries: { 'ckeditor5-foo\\manual\\file1': 'ckeditor5-foo\\manual\\file1.js', 'ckeditor5-foo\\manual\\file2': 'ckeditor5-foo\\manual\\file2.ts' } - } ); + } ), expect.any( Function ) ); } ); it( 'should pass the "tsconfig" option to webpack configuration factory', async () => { @@ -407,37 +290,14 @@ describe( 'compileManualTestScripts', () => { ], themePath: 'path/to/theme', language: 'en', - onTestCompilationStatus: stubs.onTestCompilationStatus, + onTestCompilationStatus: vi.fn(), additionalLanguages: [ 'pl', 'ar' ], debug: [ 'CK_DEBUG' ], tsconfig: '/absolute/path/to/tsconfig.json' } ); - expect( stubs.getWebpackConfig.calledOnce ).to.equal( true ); - - sinon.assert.calledWith( stubs.getWebpackConfig.firstCall, { - cwd: 'workspace', - requireDll: false, - buildDir: 'buildDir', - themePath: 'path/to/theme', - language: 'en', - onTestCompilationStatus: stubs.onTestCompilationStatus, - additionalLanguages: [ 'pl', 'ar' ], - entries: { - 'ckeditor5-foo/manual/file1': 'ckeditor5-foo/manual/file1.js' - }, - debug: [ 'CK_DEBUG' ], - identityFile: undefined, - disableWatch: undefined, + expect( vi.mocked( getWebpackConfigForManualTests ) ).toHaveBeenCalledExactlyOnceWith( expect.objectContaining( { tsconfig: '/absolute/path/to/tsconfig.json' - } ); - - expect( stubs.webpack.calledOnce ).to.equal( true ); - expect( stubs.webpack.firstCall.args[ 0 ] ).to.deep.equal( { - buildDir: 'buildDir', - entries: { - 'ckeditor5-foo/manual/file1': 'ckeditor5-foo/manual/file1.js' - } - } ); + } ) ); } ); } ); diff --git a/packages/ckeditor5-dev-tests/tests/utils/manual-tests/createserver.js b/packages/ckeditor5-dev-tests/tests/utils/manual-tests/createserver.js index 44676ef51..5192181ed 100644 --- a/packages/ckeditor5-dev-tests/tests/utils/manual-tests/createserver.js +++ b/packages/ckeditor5-dev-tests/tests/utils/manual-tests/createserver.js @@ -3,51 +3,36 @@ * For licensing, see LICENSE.md. */ -'use strict'; +import http from 'http'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { logger } from '@ckeditor/ckeditor5-dev-utils'; +import createManualTestServer from '../../../lib/utils/manual-tests/createserver.js'; -const http = require( 'http' ); -const { use, expect } = require( 'chai' ); -const sinon = require( 'sinon' ); -const sinonChai = require( 'sinon-chai' ); -const mockery = require( 'mockery' ); +vi.mock( '@ckeditor/ckeditor5-dev-utils' ); -use( sinonChai ); +describe( 'createManualTestServer()', () => { + let loggerStub, server; -describe( 'createManualTestServer', () => { - let sandbox, httpCreateServerStub, createManualTestServer, server, loggerStub; + beforeEach( async () => { + const { createServer } = http; - beforeEach( () => { - sandbox = sinon.createSandbox(); + loggerStub = vi.fn(); - mockery.enable( { - useCleanCache: true, - warnOnReplace: false, - warnOnUnregistered: false + vi.mocked( logger ).mockReturnValue( { + info: loggerStub } ); - loggerStub = sinon.stub(); + vi.spyOn( http, 'createServer' ).mockImplementation( ( ...theArgs ) => { + server = createServer( ...theArgs ); - httpCreateServerStub = sandbox.stub( http, 'createServer' ).callsFake( function stubbedCreateServer( ...theArgs ) { - server = httpCreateServerStub.wrappedMethod( ...theArgs ); - sandbox.spy( server, 'listen' ); + vi.spyOn( server, 'listen' ); return server; } ); - - mockery.registerMock( '@ckeditor/ckeditor5-dev-utils', { - logger() { - return { - info: loggerStub - }; - } - } ); - - createManualTestServer = require( '../../../lib/utils/manual-tests/createserver' ); } ); afterEach( () => { server.close(); - sandbox.restore(); // To avoid false positives and encourage better testing practices, Mocha will no longer automatically // kill itself via `process.exit()` when it thinks it should be done running. Hence, we must close the stream @@ -55,40 +40,41 @@ describe( 'createManualTestServer', () => { if ( server._readline ) { server._readline.close(); } - - mockery.disable(); } ); it( 'should start http server', () => { createManualTestServer( 'workspace/build/.manual-tests' ); - expect( httpCreateServerStub ).to.be.calledOnce; + expect( vi.mocked( http ).createServer ).toHaveBeenCalledOnce(); } ); it( 'should listen on given port', () => { createManualTestServer( 'workspace/build/.manual-tests', 8888 ); - expect( httpCreateServerStub.returnValues[ 0 ].listen ).to.be.calledOnceWith( 8888 ); + expect( server ).toEqual( expect.objectContaining( { + listen: expect.any( Function ) + } ) ); - expect( loggerStub.calledOnce ).to.equal( true ); - expect( loggerStub.firstCall.firstArg ).to.equal( '[Server] Server running at http://localhost:8888/' ); + expect( server.listen ).toHaveBeenCalledExactlyOnceWith( 8888 ); + expect( loggerStub ).toHaveBeenCalledExactlyOnceWith( '[Server] Server running at http://localhost:8888/' ); } ); it( 'should listen on 8125 port if no specific port was given', () => { createManualTestServer( 'workspace/build/.manual-tests' ); - expect( httpCreateServerStub.returnValues[ 0 ].listen ).to.be.calledOnceWith( 8125 ); + expect( server ).toEqual( expect.objectContaining( { + listen: expect.any( Function ) + } ) ); - expect( loggerStub.calledOnce ).to.equal( true ); - expect( loggerStub.firstCall.firstArg ).to.equal( '[Server] Server running at http://localhost:8125/' ); + expect( server.listen ).toHaveBeenCalledExactlyOnceWith( 8125 ); + expect( loggerStub ).toHaveBeenCalledExactlyOnceWith( '[Server] Server running at http://localhost:8125/' ); } ); - it( 'should call the specificed callback when the server is running (e.g. to allow running web sockets)', () => { - const spy = sinon.spy(); + it( 'should call the specified callback when the server is running (e.g. to allow running web sockets)', () => { + const spy = vi.fn(); createManualTestServer( 'workspace/build/.manual-tests', 1234, spy ); - sinon.assert.calledOnce( spy ); - sinon.assert.calledWithExactly( spy, server ); + expect( spy ).toHaveBeenCalledExactlyOnceWith( server ); } ); } ); diff --git a/packages/ckeditor5-dev-tests/tests/utils/manual-tests/getwebpackconfig.js b/packages/ckeditor5-dev-tests/tests/utils/manual-tests/getwebpackconfig.js index cf1f40ddb..01ed4c270 100644 --- a/packages/ckeditor5-dev-tests/tests/utils/manual-tests/getwebpackconfig.js +++ b/packages/ckeditor5-dev-tests/tests/utils/manual-tests/getwebpackconfig.js @@ -3,72 +3,46 @@ * For licensing, see LICENSE.md. */ -'use strict'; - -const mockery = require( 'mockery' ); -const sinon = require( 'sinon' ); -const { expect } = require( 'chai' ); +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { loaders } from '@ckeditor/ckeditor5-dev-utils'; +import getDefinitionsFromFile from '../../../lib/utils/getdefinitionsfromfile.js'; +import getWebpackConfigForManualTests from '../../../lib/utils/manual-tests/getwebpackconfig.js'; + +const stubs = vi.hoisted( () => ( { + translations: { + plugin: { + constructor: vi.fn() + } + } +} ) ); + +vi.mock( 'webpack' ); +vi.mock( '@ckeditor/ckeditor5-dev-utils' ); +vi.mock( '@ckeditor/ckeditor5-dev-translations', () => ( { + CKEditorTranslationsPlugin: class CKEditorTranslationsPlugin { + constructor( ...args ) { + stubs.translations.plugin.constructor( ...args ); + } + } +} ) ); +vi.mock( '../../../lib/utils/getdefinitionsfromfile.js' ); describe( 'getWebpackConfigForManualTests()', () => { - let getWebpackConfigForManualTests, stubs; - beforeEach( () => { - mockery.enable( { - useCleanCache: true, - warnOnReplace: false, - warnOnUnregistered: false - } ); - - stubs = { - getDefinitionsFromFile: sinon.stub().returns( {} ), - loaders: { - getIconsLoader: sinon.stub().returns( {} ), - getStylesLoader: sinon.stub().returns( {} ), - getTypeScriptLoader: sinon.stub().returns( {} ), - getFormattedTextLoader: sinon.stub().returns( {} ), - getCoverageLoader: sinon.stub().returns( {} ), - getJavaScriptLoader: sinon.stub().returns( {} ) - }, - logger: {}, - webpack: { - DefinePlugin: sinon.stub(), - ProvidePlugin: sinon.stub(), - SourceMapDevToolPlugin: sinon.stub() - }, - devTranslations: { - CKEditorTranslationsPlugin: class { - constructor( args ) { - this.args = args; - } - } - } - }; - - mockery.registerMock( 'webpack', stubs.webpack ); - - mockery.registerMock( '@ckeditor/ckeditor5-dev-translations', stubs.devTranslations ); - - mockery.registerMock( '@ckeditor/ckeditor5-dev-utils', { - loaders: stubs.loaders, - logger: () => stubs.logger - } ); - - getWebpackConfigForManualTests = require( '../../../lib/utils/manual-tests/getwebpackconfig' ); - } ); - - afterEach( () => { - sinon.restore(); - mockery.disable(); - mockery.deregisterAll(); + vi.mocked( getDefinitionsFromFile ).mockReturnValue( {} ); + vi.mocked( loaders ).getIconsLoader.mockReturnValue( {} ); + vi.mocked( loaders ).getStylesLoader.mockReturnValue( {} ); + vi.mocked( loaders ).getTypeScriptLoader.mockReturnValue( {} ); + vi.mocked( loaders ).getFormattedTextLoader.mockReturnValue( {} ); + vi.mocked( loaders ).getCoverageLoader.mockReturnValue( {} ); + vi.mocked( loaders ).getJavaScriptLoader.mockReturnValue( {} ); } ); it( 'should return webpack configuration object', () => { const entries = { 'ckeditor5/tests/manual/all-features': '/home/ckeditor/ckeditor5/tests/manual/all-features.js' }; - const buildDir = '/home/ckeditor/ckeditor5/build/.manual-tests'; - const debug = []; const webpackConfig = getWebpackConfigForManualTests( { @@ -79,37 +53,42 @@ describe( 'getWebpackConfigForManualTests()', () => { tsconfig: '/tsconfig/path' } ); - expect( stubs.loaders.getIconsLoader.calledOnce ).to.equal( true ); - expect( stubs.loaders.getIconsLoader.firstCall.args[ 0 ] ).to.have.property( 'matchExtensionOnly', true ); - - expect( stubs.loaders.getStylesLoader.calledOnce ).to.equal( true ); - expect( stubs.loaders.getStylesLoader.firstCall.args[ 0 ] ).to.have.property( 'themePath', '/theme/path' ); - expect( stubs.loaders.getStylesLoader.firstCall.args[ 0 ] ).to.have.property( 'sourceMap', true ); + expect( vi.mocked( loaders ).getIconsLoader ).toHaveBeenCalledExactlyOnceWith( { + matchExtensionOnly: true + } ); + expect( vi.mocked( loaders ).getStylesLoader ).toHaveBeenCalledExactlyOnceWith( { + themePath: '/theme/path', + sourceMap: true + } ); - expect( stubs.loaders.getTypeScriptLoader.calledOnce ).to.equal( true ); + expect( vi.mocked( loaders ).getTypeScriptLoader ).toHaveBeenCalledExactlyOnceWith( { + debugFlags: debug, + configFile: '/tsconfig/path', + includeDebugLoader: true + } ); - expect( stubs.loaders.getTypeScriptLoader.firstCall.args[ 0 ] ).to.have.property( 'debugFlags', debug ); - expect( stubs.loaders.getTypeScriptLoader.firstCall.args[ 0 ] ).to.have.property( 'configFile', '/tsconfig/path' ); - expect( stubs.loaders.getTypeScriptLoader.firstCall.args[ 0 ] ).to.have.property( 'includeDebugLoader', true ); + expect( vi.mocked( loaders ).getFormattedTextLoader ).toHaveBeenCalledOnce(); - expect( stubs.loaders.getFormattedTextLoader.calledOnce ).to.equal( true ); + expect( vi.mocked( loaders ).getJavaScriptLoader ).toHaveBeenCalledExactlyOnceWith( { + debugFlags: debug + } ); - expect( stubs.loaders.getJavaScriptLoader.calledOnce ).to.equal( true ); - expect( stubs.loaders.getJavaScriptLoader.firstCall.args[ 0 ] ).to.have.property( 'debugFlags', debug ); + expect( vi.mocked( loaders ).getCoverageLoader ).not.toHaveBeenCalledOnce(); - expect( stubs.loaders.getCoverageLoader.called ).to.equal( false ); + expect( webpackConfig ).toEqual( expect.objectContaining( { + // To avoid "eval()" in files. + mode: 'none', + entry: entries, + output: { + path: buildDir + }, + plugins: expect.any( Array ), + watch: true, + resolve: expect.any( Object ) + } ) ); - expect( webpackConfig ).to.be.an( 'object' ); expect( webpackConfig.resolve.fallback.timers ).to.equal( false ); - // To avoid "eval()" in files. - expect( webpackConfig ).to.have.property( 'mode', 'none' ); - expect( webpackConfig ).to.have.property( 'entry', entries ); - expect( webpackConfig ).to.have.property( 'output' ); - expect( webpackConfig.output ).to.deep.equal( { path: buildDir } ); - expect( webpackConfig ).to.have.property( 'plugins' ); - expect( webpackConfig ).to.have.property( 'watch', true ); - // The `devtool` property has been replaced by the `SourceMapDevToolPlugin()`. expect( webpackConfig ).to.not.have.property( 'devtool' ); } ); @@ -125,12 +104,15 @@ describe( 'getWebpackConfigForManualTests()', () => { it( 'pattern passed to CKEditorTranslationsPlugin should match paths to ckeditor5 packages', () => { const webpackConfig = getWebpackConfigForManualTests( { disableWatch: true } ); - expect( webpackConfig ).to.have.property( 'plugins' ); - expect( webpackConfig.plugins ).to.be.an( 'Array' ); + expect( stubs.translations.plugin.constructor ).toHaveBeenCalledOnce(); const CKEditorTranslationsPlugin = webpackConfig.plugins.find( plugin => plugin.constructor.name === 'CKEditorTranslationsPlugin' ); - const pattern = CKEditorTranslationsPlugin.args.packageNamesPattern; + expect( CKEditorTranslationsPlugin ).toBeTruthy(); + + const [ firstCall ] = stubs.translations.plugin.constructor.mock.calls; + const [ firstArg ] = firstCall; + const { packageNamesPattern: pattern } = firstArg; expect( 'packages/ckeditor5-foo/bar'.match( pattern )[ 0 ] ).to.equal( 'packages/ckeditor5-foo/' ); } ); @@ -138,12 +120,15 @@ describe( 'getWebpackConfigForManualTests()', () => { it( 'pattern passed to CKEditorTranslationsPlugin should match paths to external repositories named like ckeditor5 package', () => { const webpackConfig = getWebpackConfigForManualTests( { disableWatch: true } ); - expect( webpackConfig ).to.have.property( 'plugins' ); - expect( webpackConfig.plugins ).to.be.an( 'Array' ); + expect( stubs.translations.plugin.constructor ).toHaveBeenCalledOnce(); const CKEditorTranslationsPlugin = webpackConfig.plugins.find( plugin => plugin.constructor.name === 'CKEditorTranslationsPlugin' ); - const pattern = CKEditorTranslationsPlugin.args.packageNamesPattern; + expect( CKEditorTranslationsPlugin ).toBeTruthy(); + + const [ firstCall ] = stubs.translations.plugin.constructor.mock.calls; + const [ firstArg ] = firstCall; + const { packageNamesPattern: pattern } = firstArg; expect( 'external/ckeditor5-foo/packages/ckeditor5-bar/baz'.match( pattern )[ 0 ] ).to.equal( 'packages/ckeditor5-bar/' ); } ); diff --git a/packages/ckeditor5-dev-tests/tests/utils/manual-tests/removedir.js b/packages/ckeditor5-dev-tests/tests/utils/manual-tests/removedir.js index 1ea41d208..86758d0d3 100644 --- a/packages/ckeditor5-dev-tests/tests/utils/manual-tests/removedir.js +++ b/packages/ckeditor5-dev-tests/tests/utils/manual-tests/removedir.js @@ -3,76 +3,46 @@ * For licensing, see LICENSE.md. */ -'use strict'; - -const mockery = require( 'mockery' ); -const { expect } = require( 'chai' ); -const sinon = require( 'sinon' ); - -describe( 'removeDir', () => { - let sandbox, removeDir; - const logMessages = []; - const deletedPaths = []; - - before( () => { - mockery.enable( { - useCleanCache: true, - warnOnReplace: false, - warnOnUnregistered: false - } ); - - mockery.registerMock( '@ckeditor/ckeditor5-dev-utils', { - logger: () => ( { - info( message ) { - logMessages.push( message ); - } - } ) - } ); - - mockery.registerMock( 'del', path => { - return Promise.resolve().then( () => { - deletedPaths.push( path ); - } ); +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import del from 'del'; +import { logger } from '@ckeditor/ckeditor5-dev-utils'; +import chalk from 'chalk'; +import removeDir from '../../../lib/utils/manual-tests/removedir.js'; + +vi.mock( '@ckeditor/ckeditor5-dev-utils' ); +vi.mock( 'del' ); +vi.mock( 'chalk', () => ( { + default: { + cyan: vi.fn() + } +} ) ); + +describe( 'removeDir()', () => { + let logInfo; + beforeEach( () => { + logInfo = vi.fn(); + + vi.mocked( del ).mockResolvedValue(); + vi.mocked( chalk ).cyan.mockImplementation( input => input ); + vi.mocked( logger ).mockReturnValue( { + info: logInfo } ); - - mockery.registerMock( 'chalk', { - cyan: message => `\u001b[36m${ message }\u001b[39m` - } ); - - removeDir = require( '../../../lib/utils/manual-tests/removedir' ); - sandbox = sinon.createSandbox(); } ); - after( () => { - mockery.disable(); - mockery.deregisterAll(); - } ); + it( 'should remove directory and log it', async () => { + await removeDir( 'workspace/directory' ); - afterEach( () => { - sandbox.restore(); - logMessages.length = 0; - deletedPaths.length = 0; + expect( vi.mocked( chalk ).cyan ).toHaveBeenCalledOnce(); + expect( vi.mocked( del ) ).toHaveBeenCalledExactlyOnceWith( 'workspace/directory' ); + expect( logInfo ).toHaveBeenCalledExactlyOnceWith( 'Removed directory \'workspace/directory\'' ); } ); - it( 'should remove directory and log it', () => { - return removeDir( 'workspace/directory' ).then( () => { - expect( logMessages ).to.deep.equal( [ - 'Removed directory \'\u001b[36mworkspace/directory\u001b[39m\'' - ] ); + it( 'should remove directory and does not inform about it', async () => { + await removeDir( 'workspace/directory', { silent: true } ); - expect( deletedPaths ).to.deep.equal( [ - 'workspace/directory' - ] ); - } ); - } ); - - it( 'should remove directory and does not inform about it', () => { - return removeDir( 'workspace/directory', { silent: true } ).then( () => { - expect( logMessages ).to.deep.equal( [] ); + expect( vi.mocked( del ) ).toHaveBeenCalledExactlyOnceWith( 'workspace/directory' ); - expect( deletedPaths ).to.deep.equal( [ - 'workspace/directory' - ] ); - } ); + expect( vi.mocked( chalk ).cyan ).not.toHaveBeenCalled(); + expect( logInfo ).not.toHaveBeenCalled(); } ); } ); From edd1a7021feb4055086dbbe764264ceb8286ac6c Mon Sep 17 00:00:00 2001 From: Kamil Piechaczek Date: Wed, 18 Sep 2024 13:48:44 +0200 Subject: [PATCH 13/15] 100% tests to Vitest. --- .../utils/automated-tests/parsearguments.js | 7 +- .../utils/automated-tests/parsearguments.js | 246 +++++++++--------- 2 files changed, 119 insertions(+), 134 deletions(-) diff --git a/packages/ckeditor5-dev-tests/lib/utils/automated-tests/parsearguments.js b/packages/ckeditor5-dev-tests/lib/utils/automated-tests/parsearguments.js index fe654960b..d201b89ae 100644 --- a/packages/ckeditor5-dev-tests/lib/utils/automated-tests/parsearguments.js +++ b/packages/ckeditor5-dev-tests/lib/utils/automated-tests/parsearguments.js @@ -3,14 +3,11 @@ * For licensing, see LICENSE.md. */ -import fs from 'fs'; +import fs from 'fs-extra'; import path from 'path'; -import { createRequire } from 'module'; import minimist from 'minimist'; import { tools, logger } from '@ckeditor/ckeditor5-dev-utils'; -const require = createRequire( import.meta.url ); - /** * @param {Array.} args * @returns {Object} @@ -173,7 +170,7 @@ export default function parseArguments( args ) { const files = new Set( options.files ); for ( const repositoryName of options.repositories ) { - const cwdPackageJson = require( path.join( cwd, 'package.json' ) ); + const cwdPackageJson = fs.readJsonSync( path.join( cwd, 'package.json' ) ); // Check the main repository. if ( repositoryName === cwdPackageJson.name ) { diff --git a/packages/ckeditor5-dev-tests/tests/utils/automated-tests/parsearguments.js b/packages/ckeditor5-dev-tests/tests/utils/automated-tests/parsearguments.js index 75c66178a..6c8937632 100644 --- a/packages/ckeditor5-dev-tests/tests/utils/automated-tests/parsearguments.js +++ b/packages/ckeditor5-dev-tests/tests/utils/automated-tests/parsearguments.js @@ -3,64 +3,35 @@ * For licensing, see LICENSE.md. */ -import { describe, expect, it, vi } from 'vitest'; - -const originalPosixJoin = path.posix.join; - -vi.mock( 'fs' ); +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import fs from 'fs-extra'; +import { tools, logger } from '@ckeditor/ckeditor5-dev-utils'; +import parseArguments from '../../../lib/utils/automated-tests/parsearguments.js'; + +vi.mock( 'path', () => ( { + default: { + join: vi.fn( ( ...chunks ) => chunks.join( '/' ) ), + dirname: vi.fn() + } +} ) ); +vi.mock( 'fs-extra' ); vi.mock( '@ckeditor/ckeditor5-dev-utils' ); describe( 'parseArguments()', () => { - let parseArguments, sandbox, stubs, packageName; + let logWarningStub; beforeEach( () => { - vi.spyOn( process, 'cwd' ).mockReturnValue( '/' ); - - stubs = { - cwd: sandbox.stub( process, 'cwd' ).callsFake( () => '/' ), - existsSync: sandbox.stub( fs, 'existsSync' ), - tools: { - getDirectories: sandbox.stub() - }, - logger: { - warning: sandbox.stub() - }, - fs: { - statSync: sandbox.stub() - }, - // To force unix paths in tests. - pathJoin: sandbox.stub( path, 'join' ).callsFake( ( ...chunks ) => originalPosixJoin( ...chunks ) ) - }; - - stubs.cwd.returns( '/home/project' ); - - packageName = 'ckeditor5'; - - mockery.registerMock( '/home/project/package.json', { - get name() { - return packageName; - } - } ); + logWarningStub = vi.fn(); - // mockery.registerMock( 'fs', stubs.fs ); - - parseArguments = proxyquire( '../../../lib/utils/automated-tests/parsearguments', { - '@ckeditor/ckeditor5-dev-utils': { - logger() { - return stubs.logger; - }, - tools: stubs.tools - }, - 'fs': stubs.fs + vi.spyOn( process, 'cwd' ).mockReturnValue( '/home/project' ); + vi.mocked( logger ).mockReturnValue( { + warning: logWarningStub } ); } ); - afterEach( () => { - sandbox.restore(); - mockery.disable(); - } ); - it( 'replaces kebab-case strings with camelCase values', () => { + vi.mocked( fs ).readJsonSync.mockReturnValue( {} ); + const options = parseArguments( [ '--source-map', 'true', @@ -87,6 +58,8 @@ describe( 'parseArguments()', () => { } ); it( 'deletes all aliases keys from returned object', () => { + vi.mocked( fs ).readJsonSync.mockReturnValue( {} ); + const options = parseArguments( [ '-b', 'Chrome,Firefox', @@ -180,63 +153,67 @@ describe( 'parseArguments()', () => { expect( options.repositories ).to.deep.equal( [] ); } ); - it( - 'returns an array of packages to tests when `--repositories` is specified ' + - '(root directory check)', - () => { - packageName = 'ckeditor5'; - - stubs.fs.statSync.withArgs( '/home/project/external' ).throws( 'ENOENT: no such file or directory' ); + it( 'returns an array of packages to tests when `--repositories` is specified (root directory check)', () => { + vi.mocked( fs ).readJsonSync.mockReturnValue( { name: 'ckeditor5' } ); + vi.mocked( fs ).statSync.mockImplementation( input => { + if ( input === '/home/project/external' ) { + throw new Error( 'ENOENT: no such file or directory' ); + } + } ); + vi.mocked( tools ).getDirectories.mockImplementation( input => { + if ( input === '/home/project/packages' ) { + return [ + 'ckeditor5-core', + 'ckeditor5-engine' + ]; + } + } ); - stubs.tools.getDirectories.withArgs( '/home/project/packages' ).returns( [ - 'ckeditor5-core', - 'ckeditor5-engine' - ] ); - - const options = parseArguments( [ - '--repositories', - 'ckeditor5' - ] ); - - expect( options.files ).to.deep.equal( [ 'core', 'engine' ] ); + const options = parseArguments( [ + '--repositories', + 'ckeditor5' + ] ); - expect( stubs.logger.warning.callCount ).to.equal( 1 ); - expect( stubs.logger.warning.firstCall.args[ 0 ] ).to.equal( - 'The `external/` directory does not exist. Only the root repository will be checked.' - ); - } - ); + expect( options.files ).to.deep.equal( [ 'core', 'engine' ] ); - it( - 'returns an array of packages to tests when `--repositories` is specified ' + - '(external directory check)', - () => { - packageName = 'foo'; + expect( logWarningStub ).toHaveBeenCalledExactlyOnceWith( + 'The `external/` directory does not exist. Only the root repository will be checked.' + ); + } ); - stubs.fs.statSync.returns( { isDirectory: () => true } ); - stubs.tools.getDirectories.withArgs( '/home/project/external/ckeditor5/packages' ).returns( [ - 'ckeditor5-core', - 'ckeditor5-engine' - ] ); + it( 'returns an array of packages to tests when `--repositories` is specified (external directory check)', () => { + vi.mocked( fs ).readJsonSync.mockReturnValue( { name: 'foo' } ); + vi.mocked( fs ).statSync.mockReturnValue( { isDirectory: () => true } ); + vi.mocked( tools ).getDirectories.mockImplementation( input => { + if ( input === '/home/project/external/ckeditor5/packages' ) { + return [ + 'ckeditor5-core', + 'ckeditor5-engine' + ]; + } + } ); - const options = parseArguments( [ - '--repositories', - 'ckeditor5' - ] ); + const options = parseArguments( [ + '--repositories', + 'ckeditor5' + ] ); - expect( options.files ).to.deep.equal( [ 'core', 'engine' ] ); - expect( stubs.logger.warning.callCount ).to.equal( 0 ); - } - ); + expect( options.files ).to.deep.equal( [ 'core', 'engine' ] ); + expect( logWarningStub ).not.toHaveBeenCalled(); + } ); it( 'returns an array of packages to tests when `--repositories` is specified ' + '(external directory check, specified repository does not exist)', () => { - packageName = 'foo'; + vi.mocked( fs ).readJsonSync.mockReturnValue( { name: 'foo' } ); + vi.mocked( fs ).statSync.mockImplementation( input => { + if ( input === '/home/project/external' ) { + return { isDirectory: () => true }; + } - stubs.fs.statSync.withArgs( '/home/project/external' ).returns( { isDirectory: () => true } ); - stubs.fs.statSync.withArgs( '/home/project/external/ckeditor5' ).throws( 'ENOENT: no such file or directory' ); + throw new Error( 'ENOENT: no such file or directory' ); + } ); const options = parseArguments( [ '--repositories', @@ -244,9 +221,7 @@ describe( 'parseArguments()', () => { ] ); expect( options.files ).to.deep.equal( [] ); - - expect( stubs.logger.warning.callCount ).to.equal( 1 ); - expect( stubs.logger.warning.firstCall.args[ 0 ] ).to.equal( + expect( logWarningStub ).toHaveBeenCalledExactlyOnceWith( 'Did not find the repository "ckeditor5" in the root repository or the "external/" directory.' ); } @@ -256,14 +231,23 @@ describe( 'parseArguments()', () => { 'returns an array of packages (unique list) to tests when `--repositories` is specified ' + '(root directory check + `--files` specified)', () => { - packageName = 'ckeditor5'; - - stubs.fs.statSync.withArgs( '/home/project/external' ).returns( { isDirectory: () => true } ); - stubs.tools.getDirectories.withArgs( '/home/project/packages' ).returns( [ - 'ckeditor5-core', - 'ckeditor5-engine', - 'ckeditor5-utils' - ] ); + vi.mocked( fs ).readJsonSync.mockReturnValue( { name: 'ckeditor5' } ); + vi.mocked( fs ).statSync.mockImplementation( input => { + if ( input === '/home/project/external' ) { + return { isDirectory: () => true }; + } + + throw new Error( 'ENOENT: no such file or directory' ); + } ); + vi.mocked( tools ).getDirectories.mockImplementation( input => { + if ( input === '/home/project/packages' ) { + return [ + 'ckeditor5-core', + 'ckeditor5-engine', + 'ckeditor5-utils' + ]; + } + } ); const options = parseArguments( [ '--repositories', @@ -280,23 +264,28 @@ describe( 'parseArguments()', () => { 'returns an array of packages to tests when `--repositories` is specified ' + '(root and external directories check)', () => { - packageName = 'ckeditor5'; - - stubs.fs.statSync.withArgs( '/home/project/external' ).returns( { isDirectory: () => true } ); - stubs.fs.statSync.withArgs( '/home/project/external/foo' ).returns( { isDirectory: () => true } ); - stubs.fs.statSync.withArgs( '/home/project/external/bar' ).returns( { isDirectory: () => true } ); - stubs.tools.getDirectories.withArgs( '/home/project/packages' ).returns( [ - 'ckeditor5-core', - 'ckeditor5-engine' - ] ); - stubs.tools.getDirectories.withArgs( '/home/project/external/foo/packages' ).returns( [ - 'ckeditor5-foo-1', - 'ckeditor5-foo-2' - ] ); - stubs.tools.getDirectories.withArgs( '/home/project/external/bar/packages' ).returns( [ - 'ckeditor5-bar-1', - 'ckeditor5-bar-2' - ] ); + vi.mocked( fs ).readJsonSync.mockReturnValue( { name: 'ckeditor5' } ); + vi.mocked( fs ).statSync.mockReturnValue( { isDirectory: () => true } ); + vi.mocked( tools ).getDirectories.mockImplementation( input => { + if ( input === '/home/project/packages' ) { + return [ + 'ckeditor5-core', + 'ckeditor5-engine' + ]; + } + if ( input === '/home/project/external/foo/packages' ) { + return [ + 'ckeditor5-foo-1', + 'ckeditor5-foo-2' + ]; + } + if ( input === '/home/project/external/bar/packages' ) { + return [ + 'ckeditor5-bar-1', + 'ckeditor5-bar-2' + ]; + } + } ); const options = parseArguments( [ '--repositories', @@ -330,7 +319,7 @@ describe( 'parseArguments()', () => { describe( 'tsconfig', () => { it( 'should be null by default, if `tsconfig.test.json` does not exist', () => { - stubs.existsSync.returns( false ); + vi.mocked( fs ).existsSync.mockReturnValue( false ); const options = parseArguments( [] ); @@ -338,8 +327,7 @@ describe( 'parseArguments()', () => { } ); it( 'should use `tsconfig.test.json` from `cwd` if it is available by default', () => { - stubs.cwd.returns( '/home/project' ); - stubs.existsSync.returns( true ); + vi.mocked( fs ).existsSync.mockReturnValue( true ); const options = parseArguments( [] ); @@ -347,16 +335,16 @@ describe( 'parseArguments()', () => { } ); it( 'should parse `--tsconfig` to absolute path if it is set and it exists', () => { - stubs.cwd.returns( '/home/project' ); - stubs.existsSync.returns( true ); - const options = parseArguments( [ '--tsconfig', './configs/tsconfig.json' ] ); + vi.mocked( fs ).existsSync.mockReturnValue( true ); + + const options = parseArguments( [ '--tsconfig', 'configs/tsconfig.json' ] ); expect( options.tsconfig ).to.be.equal( '/home/project/configs/tsconfig.json' ); } ); it( 'should be null if `--tsconfig` points to non-existing file', () => { - stubs.cwd.returns( '/home/project' ); - stubs.existsSync.returns( false ); + vi.mocked( fs ).existsSync.mockReturnValue( false ); + const options = parseArguments( [ '--tsconfig', './configs/tsconfig.json' ] ); expect( options.tsconfig ).to.equal( null ); From f7137388d9a07d69c11a0947370b0c9752320e48 Mon Sep 17 00:00:00 2001 From: Kamil Piechaczek Date: Wed, 18 Sep 2024 13:54:17 +0200 Subject: [PATCH 14/15] ESM for a script that updates licenses. --- scripts/bump-year.js | 45 ++++++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/scripts/bump-year.js b/scripts/bump-year.js index 993315aaa..6465720b5 100644 --- a/scripts/bump-year.js +++ b/scripts/bump-year.js @@ -17,26 +17,27 @@ git commit -am "Internal: Bumped the year." && git push */ -require( '@ckeditor/ckeditor5-dev-bump-year' ) - .bumpYear( { - cwd: process.cwd(), - globPatterns: [ - { // LICENSE.md, .eslintrc.js, etc. - pattern: '*', - options: { - dot: true - } - }, - { - pattern: '.husky/*' - }, - { - pattern: '!(coverage|.nyc_output)/**', - options: { - ignore: [ - '**/typedoc-plugins/tests/module-fixer/fixtures/emptyfile.ts' - ] - } +import { bumpYear } from '@ckeditor/ckeditor5-dev-bump-year'; + +bumpYear( { + cwd: process.cwd(), + globPatterns: [ + { // LICENSE.md, .eslintrc.js, etc. + pattern: '*', + options: { + dot: true + } + }, + { + pattern: '.husky/*' + }, + { + pattern: '!(coverage|.nyc_output)/**', + options: { + ignore: [ + '**/typedoc-plugins/tests/module-fixer/fixtures/emptyfile.ts' + ] } - ] - } ); + } + ] +} ); From 0aa7ab0cd83c14303e4987176b1de06c18e9672b Mon Sep 17 00:00:00 2001 From: Kamil Piechaczek Date: Thu, 19 Sep 2024 08:50:00 +0200 Subject: [PATCH 15/15] Replaced require with import (readline) for Windows. --- .../lib/utils/manual-tests/createserver.js | 12 ++++------ .../tests/utils/manual-tests/createserver.js | 23 +++++++++++++------ 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/packages/ckeditor5-dev-tests/lib/utils/manual-tests/createserver.js b/packages/ckeditor5-dev-tests/lib/utils/manual-tests/createserver.js index bafe7dfc4..1dcd59108 100644 --- a/packages/ckeditor5-dev-tests/lib/utils/manual-tests/createserver.js +++ b/packages/ckeditor5-dev-tests/lib/utils/manual-tests/createserver.js @@ -4,12 +4,13 @@ */ import http from 'http'; +import fs from 'fs'; import path from 'path'; +import readline from 'readline'; +import { fileURLToPath } from 'url'; import { globSync } from 'glob'; -import fs from 'fs'; import combine from 'dom-combiner'; import { logger } from '@ckeditor/ckeditor5-dev-utils'; -import { fileURLToPath } from 'url'; const __filename = fileURLToPath( import.meta.url ); const __dirname = path.dirname( __filename ); @@ -30,15 +31,12 @@ export default function createManualTestServer( sourcePath, port = 8125, onCreat // SIGINT isn't caught on Windows in process. However, `CTRL+C` can be caught // by `readline` module. After that we can emit SIGINT to the process manually. if ( process.platform === 'win32' ) { - const readline = require( 'readline' ).createInterface( { + const readlineInterface = readline.createInterface( { input: process.stdin, output: process.stdout } ); - // Save the reference of the stream to be able to close it in tests. - server._readline = readline; - - readline.on( 'SIGINT', () => { + readlineInterface.on( 'SIGINT', () => { process.emit( 'SIGINT' ); } ); } diff --git a/packages/ckeditor5-dev-tests/tests/utils/manual-tests/createserver.js b/packages/ckeditor5-dev-tests/tests/utils/manual-tests/createserver.js index 5192181ed..4abfaf93c 100644 --- a/packages/ckeditor5-dev-tests/tests/utils/manual-tests/createserver.js +++ b/packages/ckeditor5-dev-tests/tests/utils/manual-tests/createserver.js @@ -4,11 +4,13 @@ */ import http from 'http'; +import readline from 'readline'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { logger } from '@ckeditor/ckeditor5-dev-utils'; import createManualTestServer from '../../../lib/utils/manual-tests/createserver.js'; vi.mock( '@ckeditor/ckeditor5-dev-utils' ); +vi.mock( 'readline' ); describe( 'createManualTestServer()', () => { let loggerStub, server; @@ -33,13 +35,6 @@ describe( 'createManualTestServer()', () => { afterEach( () => { server.close(); - - // To avoid false positives and encourage better testing practices, Mocha will no longer automatically - // kill itself via `process.exit()` when it thinks it should be done running. Hence, we must close the stream - // before leaving the test. See: https://stackoverflow.com/a/52143003. - if ( server._readline ) { - server._readline.close(); - } } ); it( 'should start http server', () => { @@ -77,4 +72,18 @@ describe( 'createManualTestServer()', () => { expect( spy ).toHaveBeenCalledExactlyOnceWith( server ); } ); + + it( 'should use "readline" to listen to the SIGINT event on Windows', () => { + const readlineInterface = { + on: vi.fn() + }; + + vi.mocked( readline ).createInterface.mockReturnValue( readlineInterface ); + vi.spyOn( process, 'platform', 'get' ).mockReturnValue( 'win32' ); + + createManualTestServer( 'workspace/build/.manual-tests' ); + + expect( vi.mocked( readline ).createInterface ).toHaveBeenCalledOnce(); + expect( readlineInterface.on ).toHaveBeenCalledExactlyOnceWith( 'SIGINT', expect.any( Function ) ); + } ); } );