diff --git a/.circleci/config.yml b/.circleci/config.yml index aa372074..c5f91e47 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -26,22 +26,10 @@ executors: docker: - image: circleci/node:12 working_directory: ~/workspace - node12_0: - docker: - - image: circleci/node:12.0 - working_directory: ~/workspace node12_17: docker: - image: circleci/node:12.17 working_directory: ~/workspace - node12_16: - docker: - - image: circleci/node:12.16 - working_directory: ~/workspace - node10: - docker: - - image: circleci/node:10 - working_directory: ~/workspace jobs: test: @@ -75,9 +63,6 @@ workflows: - node14_7 - node12_latest - node12_17 # first with dynamic import() of commonjs modules - - node12_16 - - node12_0 - - node10 cron: <<: *push_workflow triggers: diff --git a/.eslintignore b/.eslintignore index cda15b27..52293c38 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,3 +1,5 @@ spec/fixtures/cjs-syntax-error/syntax_error.js spec/fixtures/esm-importing-commonjs-syntax-error/syntax_error.js spec/fixtures/js-loader-import/*.js +spec/fixtures/js-loader-default/*.js +spec/fixtures/esm-reporter-packagejson/customReporter.js diff --git a/Gruntfile.js b/Gruntfile.js index d800aeee..31c7245d 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -33,6 +33,4 @@ module.exports = function(grunt) { runCommands(commands, done); }); - - grunt.loadTasks('tasks'); }; diff --git a/README.md b/README.md index 16a4e92e..d0c6f5f2 100644 --- a/README.md +++ b/README.md @@ -14,90 +14,43 @@ This module allows you to run Jasmine specs for your Node.js code. The output wi ## Documentation -https://jasmine.github.io/edge/node.html +https://jasmine.github.io/setup/nodejs.html -## Installation -```sh -# Local installation: -npm install --save-dev jasmine - -# Global installation -npm install -g jasmine -``` - -## Initializing - -To initialize a project for Jasmine - -`jasmine init` - -To initialize a project for Jasmine when being installed locally - -`node_modules/.bin/jasmine init` - -or - -`npx jasmine init` - -To seed your project with some examples - -`jasmine examples` - -## Usage +## Quick Start -To run your test suite +Installation: -`jasmine` - -## Configuration - -Customize `spec/support/jasmine.json` to enumerate the source and spec files you would like the Jasmine runner to include. -You may use dir glob strings. -More information on the format of `jasmine.json` can be found in [the documentation](http://jasmine.github.io/edge/node.html#section-Configuration) - -Alternatively, you may specify the path to your `jasmine.json` by setting an environment variable or an option: - -```shell -jasmine JASMINE_CONFIG_PATH=relative/path/to/your/jasmine.json -jasmine --config=relative/path/to/your/jasmine.json +```sh +npm install --save-dev jasmine ``` -## Using ES modules - -If the name of a spec file or helper file ends in `.mjs`, Jasmine will load it -as an [ES module](https://nodejs.org/docs/latest-v13.x/api/esm.html) rather -than a CommonJS module. This allows the spec file or helper to import other -ES modules. No extra configuration is required. - -You can also use ES modules with names ending in `.js` by adding -`"jsLoader": "import"` to `jasmine.json`. This should work for CommonJS modules -as well as ES modules. We expect to make it the default in a future release. -Please [log an issue](https://github.com/jasmine/jasmine-npm/issues) if you have -code that doesn't load correctly with `"jsLoader": "import"`. +To initialize a project for Jasmine: +```sh +npx jasmine init +```` -# Filtering specs +To seed your project with some examples: -Execute only those specs which filename match given glob: +```sh +npx jasmine examples +```` -```shell -jasmine "spec/**/critical/*Spec.js" -``` +To run your test suite: -Or a single file: +```sh +npx jasmine +```` -```shell -jasmine spec/currentSpec.js -``` +## ES and CommonJS module compatibility -Or execute only those specs which name matches a particular regex: +Jasmine is compatible with both ES modules and CommonJS modules. See the +[setup guide](https://jasmine.github.io/setup/nodejs.html) for more information. -```shell -jasmine --filter "adapter21*" -``` -(where the *name* of a spec is the first parameter passed to `describe()`) +## Node version compatibility +Jasmine supports Node 12.x where x >=17, Node 14, and Node 16. ## Support diff --git a/lib/command.js b/lib/command.js index bba2950f..1e70762d 100644 --- a/lib/command.js +++ b/lib/command.js @@ -1,5 +1,6 @@ const path = require('path'); const fs = require('fs'); +const Loader = require('./loader'); exports = module.exports = Command; @@ -30,7 +31,7 @@ function Command(projectBaseDir, examplesDir, print) { const command = this; - this.run = function(jasmine, commands) { + this.run = async function(jasmine, commands) { setEnvironmentVariables(commands); let commandToRun; @@ -53,7 +54,7 @@ function Command(projectBaseDir, examplesDir, print) { print(''); help({print: print}); } else { - runJasmine(jasmine, env, print); + await runJasmine(jasmine, env, print); } } }; @@ -72,7 +73,6 @@ function parseOptions(argv) { reporter, configPath, filter, - stopOnFailure, failFast, random, seed; @@ -89,10 +89,8 @@ function parseOptions(argv) { helpers.push(arg.match("^--helper=(.*)")[1]); } else if (arg.match("^--require=")) { requires.push(arg.match("^--require=(.*)")[1]); - } else if (arg.match("^--stop-on-failure=")) { - stopOnFailure = arg.match("^--stop-on-failure=(.*)")[1] === 'true'; - } else if (arg.match("^--fail-fast=")) { - failFast = arg.match("^--fail-fast=(.*)")[1] === 'true'; + } else if (arg === '--fail-fast') { + failFast = true; } else if (arg.match("^--random=")) { random = arg.match("^--random=(.*)")[1] === 'true'; } else if (arg.match("^--seed=")) { @@ -113,7 +111,6 @@ function parseOptions(argv) { color: color, configPath: configPath, filter: filter, - stopOnFailure: stopOnFailure, failFast: failFast, helpers: helpers, requires: requires, @@ -125,14 +122,75 @@ function parseOptions(argv) { }; } -function runJasmine(jasmine, env, print) { - const loadConfig = require('./loadConfig'); - loadConfig(jasmine, env, print); - jasmine.execute(env.files, env.filter) - .catch(function(error) { - console.error(error); - process.exit(1); +async function runJasmine(jasmine, env, print) { + await jasmine.loadConfigFile(env.configPath || process.env.JASMINE_CONFIG_PATH); + + if (env.failFast !== undefined) { + jasmine.env.configure({ + stopSpecOnExpectationFailure: env.failFast, + stopOnSpecFailure: env.failFast }); + } + + if (env.seed !== undefined) { + jasmine.seed(env.seed); + } + + if (env.random !== undefined) { + jasmine.randomizeTests(env.random); + } + + if (env.helpers !== undefined && env.helpers.length) { + jasmine.addHelperFiles(env.helpers); + } + + if (env.requires !== undefined && env.requires.length) { + jasmine.addRequires(env.requires); + } + + if (env.reporter !== undefined) { + await registerReporter(env.reporter, jasmine); + } + + jasmine.showColors(env.color); + + try { + await jasmine.execute(env.files, env.filter); + } catch (error) { + console.error(error); + process.exit(1); + } +} + +async function registerReporter(reporterModuleName, jasmine) { + let Reporter; + + try { + Reporter = await new Loader().load(resolveReporter(reporterModuleName)); + } catch (e) { + throw new Error('Failed to load reporter module '+ reporterModuleName + + '\nUnderlying error: ' + e.stack + '\n(end underlying error)'); + } + + let reporter; + + try { + reporter = new Reporter(); + } catch (e) { + throw new Error('Failed to instantiate reporter from '+ reporterModuleName + + '\nUnderlying error: ' + e.stack + '\n(end underlying error)'); + + } + jasmine.clearReporters(); + jasmine.addReporter(reporter); +} + +function resolveReporter(nameOrPath) { + if (nameOrPath.startsWith('./') || nameOrPath.startsWith('../')) { + return path.resolve(nameOrPath); + } else { + return nameOrPath; + } } function initJasmine(options) { @@ -199,8 +257,7 @@ function help(options) { print('%s\tfilter specs to run only those that match the given string', lPad('--filter=', 18)); print('%s\tload helper files that match the given string', lPad('--helper=', 18)); print('%s\tload module that match the given string', lPad('--require=', 18)); - print('%s\t[true|false] stop spec execution on expectation failure', lPad('--stop-on-failure=', 18)); - print('%s\t[true|false] stop Jasmine execution on spec failure', lPad('--fail-fast=', 18)); + print('%s\tstop Jasmine execution on spec failure', lPad('--fail-fast', 18)); print('%s\tpath to your optional jasmine.json', lPad('--config=', 18)); print('%s\tpath to reporter to use instead of the default Jasmine reporter', lPad('--reporter=', 18)); print('%s\tmarker to signal the end of options meant for Jasmine', lPad('--', 18)); diff --git a/lib/examples/jasmine.json b/lib/examples/jasmine.json index 4f0fdb35..6afe6036 100644 --- a/lib/examples/jasmine.json +++ b/lib/examples/jasmine.json @@ -6,6 +6,8 @@ "helpers": [ "helpers/**/*.?(m)js" ], - "stopSpecOnExpectationFailure": false, - "random": true + "env": { + "stopSpecOnExpectationFailure": false, + "random": true + } } diff --git a/lib/exit_handler.js b/lib/exit_handler.js new file mode 100644 index 00000000..9bb62d78 --- /dev/null +++ b/lib/exit_handler.js @@ -0,0 +1,15 @@ +class ExitHandler { + constructor(onExit) { + this._onExit = onExit; + } + + install() { + process.on('exit', this._onExit); + } + + uninstall() { + process.removeListener('exit', this._onExit); + } +} + +module.exports = ExitHandler; diff --git a/lib/jasmine.js b/lib/jasmine.js index 55d65fb8..2d332c01 100644 --- a/lib/jasmine.js +++ b/lib/jasmine.js @@ -2,7 +2,7 @@ const path = require('path'); const util = require('util'); const glob = require('glob'); const Loader = require('./loader'); -const CompletionReporter = require('./reporters/completion_reporter'); +const ExitHandler = require('./exit_handler'); const ConsoleSpecFilter = require('./filters/console_spec_filter'); module.exports = Jasmine; @@ -20,6 +20,17 @@ module.exports.ConsoleReporter = require('./reporters/console_reporter'); * @name JasmineOptions#projectBaseDir * @type (string | undefined) */ +/** + * Whether to create the globals (describe, it, etc) that make up Jasmine's + * spec-writing interface. If it is set to false, the spec-writing interface + * can be accessed via jasmine-core's `noGlobals` method, e.g.: + * + * `const {describe, it, expect, jasmine} = require('jasmine-core').noGlobals();` + * + * @name JasmineOptions#globals + * @type (boolean | undefined) + * @default true + */ /** * @classdesc Configures, builds, and executes a Jasmine test suite @@ -34,8 +45,13 @@ function Jasmine(options) { options = options || {}; this.loader = options.loader || new Loader(); const jasmineCore = options.jasmineCore || require('jasmine-core'); - this.jasmineCorePath = path.join(jasmineCore.files.path, 'jasmine.js'); - this.jasmine = jasmineCore.boot(jasmineCore); + + if (options.globals === false) { + this.jasmine = jasmineCore.noGlobals().jasmine; + } else { + this.jasmine = jasmineCore.boot(jasmineCore); + } + this.projectBaseDir = options.projectBaseDir || path.resolve(); this.specDir = ''; this.specFiles = []; @@ -50,20 +66,12 @@ function Jasmine(options) { */ this.env = this.jasmine.getEnv({suppressLoadErrors: true}); this.reportersCount = 0; - this.completionReporter = new CompletionReporter(); - this.onCompleteCallbackAdded = false; this.exit = process.exit; this.showingColors = true; this.reporter = new module.exports.ConsoleReporter(); this.addReporter(this.reporter); this.defaultReporterConfigured = false; - const jasmineRunner = this; - this.completionReporter.onComplete(function(passed) { - jasmineRunner.exitCodeCompletion(passed); - }); - this.checkExit = checkExit(this); - /** * @function * @name Jasmine#coreVersion @@ -76,8 +84,6 @@ function Jasmine(options) { /** * Whether to cause the Node process to exit when the suite finishes executing. * - * _Note_: If {@link Jasmine#onComplete|onComplete} is called, Jasmine will not - * exit when the suite completes even if exitOnCompletion is set to true. * @name Jasmine#exitOnCompletion * @type {boolean} * @default true @@ -178,7 +184,6 @@ Jasmine.prototype.configureDefaultReporter = function(options) { process.stdout.write(util.format.apply(this, arguments)); }; options.showColors = options.hasOwnProperty('showColors') ? options.showColors : true; - options.jasmineCorePath = options.jasmineCorePath || this.jasmineCorePath; this.reporter.setOptions(options); this.defaultReporterConfigured = true; @@ -207,17 +212,12 @@ Jasmine.prototype.loadHelpers = async function() { Jasmine.prototype._loadFiles = async function(files) { for (const file of files) { - await this.loader.load(file, this._alwaysImport || false); + await this.loader.load(file); } - }; -Jasmine.prototype.loadRequires = function() { - // TODO: In 4.0, switch to calling _loadFiles - // (requires making this function async) - this.requires.forEach(function(r) { - require(r); - }); +Jasmine.prototype.loadRequires = async function() { + await this._loadFiles(this.requires); }; /** @@ -225,17 +225,30 @@ Jasmine.prototype.loadRequires = function() { * any JS file that's loadable via require and provides a Jasmine config * as its default export. * @param {string} [configFilePath=spec/support/jasmine.json] + * @return Promise */ -Jasmine.prototype.loadConfigFile = function(configFilePath) { - try { - const absoluteConfigFilePath = path.resolve(this.projectBaseDir, configFilePath || 'spec/support/jasmine.json'); - const config = require(absoluteConfigFilePath); - this.loadConfig(config); - } catch (e) { - if(configFilePath || e.code != 'MODULE_NOT_FOUND') { throw e; } +Jasmine.prototype.loadConfigFile = async function(configFilePath) { + if (configFilePath) { + await this.loadSpecificConfigFile_(configFilePath); + } else { + for (const ext of ['json', 'js']) { + try { + await this.loadSpecificConfigFile_(`spec/support/jasmine.${ext}`); + } catch (e) { + if (e.code !== 'MODULE_NOT_FOUND' && e.code !== 'ERR_MODULE_NOT_FOUND') { + throw e; + } + } + } } }; +Jasmine.prototype.loadSpecificConfigFile_ = async function(relativePath) { + const absolutePath = path.resolve(this.projectBaseDir, relativePath); + const config = await this.loader.load(absolutePath); + this.loadConfig(config); +}; + /** * Loads configuration from the specified object. * @param {Configuration} config @@ -294,6 +307,10 @@ Jasmine.prototype.loadConfig = function(config) { envConfig.random = config.random; } + if (config.verboseDeprecations !== undefined) { + envConfig.verboseDeprecations = config.verboseDeprecations; + } + /** * Specifies how to load files with names ending in .js. Valid values are @@ -303,11 +320,10 @@ Jasmine.prototype.loadConfig = function(config) { * @type string | undefined * @default "require" */ - if (config.jsLoader === 'import') { - checkForJsFileImportSupport(); - this._alwaysImport = true; - } else if (config.jsLoader === 'require' || config.jsLoader === undefined) { - this._alwaysImport = false; + if (config.jsLoader === 'import' || config.jsLoader === undefined) { + this.loader.alwaysImport = true; + } else if (config.jsLoader === 'require') { + this.loader.alwaysImport = false; } else { throw new Error(`"${config.jsLoader}" is not a valid value for the ` + 'jsLoader configuration property. Valid values are "import", ' + @@ -326,7 +342,7 @@ Jasmine.prototype.loadConfig = function(config) { * @type string[] | undefined */ if(config.helpers) { - this.addHelperFiles(config.helpers); + this.addMatchingHelperFiles(config.helpers); } /** @@ -346,7 +362,7 @@ Jasmine.prototype.loadConfig = function(config) { * @type string[] | undefined */ if(config.spec_files) { - this.addSpecFiles(config.spec_files); + this.addMatchingSpecFiles(config.spec_files); } }; @@ -369,30 +385,6 @@ Jasmine.prototype.addMatchingSpecFiles = addFiles('specFiles'); */ Jasmine.prototype.addMatchingHelperFiles = addFiles('helperFiles'); - -// Deprecated synonyms for the above. These are confusingly named (addSpecFiles -// doesn't just do N of what addSpecFile does) but they've been around a long -// time and there might be quite a bit of code that uses them. - -/** - * Synonym for {@link Jasmine#addMatchingSpecFiles} - * @function - * @name Jasmine#addSpecFiles - * @deprecated Use {@link Jasmine#addMatchingSpecFiles|addMatchingSpecFiles}, - * {@link Jasmine#loadConfig|loadConfig}, or {@link Jasmine#loadConfigFile|loadConfigFile} - * instead. - */ -Jasmine.prototype.addSpecFiles = Jasmine.prototype.addMatchingSpecFiles; -/** - * Synonym for {@link Jasmine#addMatchingHelperFiles} - * @name Jasmine#addHelperFiles - * @function - * @deprecated Use {@link Jasmine#addMatchingHelperFiles|addMatchingHelperFiles}, - * {@link Jasmine#loadConfig|loadConfig}, or {@link Jasmine#loadConfigFile|loadConfigFile} - * instead. - */ -Jasmine.prototype.addHelperFiles = Jasmine.prototype.addMatchingHelperFiles; - Jasmine.prototype.addRequires = function(requires) { const jasmineRunner = this; requires.forEach(function(r) { @@ -438,23 +430,6 @@ function addFiles(kind) { }; } -/** - * Registers a callback that will be called when execution finishes. - * - * _Note_: Only one callback can be registered. The callback will be called - * after the suite has completed and the results have been finalized, but not - * necessarily before all of Jasmine's cleanup has finished. Calling this - * function will also prevent Jasmine from exiting the Node process at the end - * of suite execution. - * - * @deprecated Set {@link Jasmine#exitOnCompletion|exitOnCompletion} to false - * and use the promise returned from {@link Jasmine#execute|execute} instead. - * @param {function} onCompleteCallback - */ -Jasmine.prototype.onComplete = function(onCompleteCallback) { - this.completionReporter.onComplete(onCompleteCallback); -}; - /** * Sets whether to cause specs to only have one expectation failure. * @function @@ -477,50 +452,19 @@ Jasmine.prototype.stopOnSpecFailure = function(value) { this.env.configure({stopOnSpecFailure: value}); }; -Jasmine.prototype.exitCodeCompletion = function(passed) { +Jasmine.prototype.flushOutput = async function() { // Ensure that all data has been written to stdout and stderr, // then exit with an appropriate status code. Otherwise, we // might exit before all previous writes have actually been // written when Jasmine is piped to another process that isn't // reading quickly enough. - const jasmineRunner = this; - const streams = [process.stdout, process.stderr]; - let writesToWait = streams.length; - streams.forEach(function(stream) { - stream.write('', null, exitIfAllStreamsCompleted); + var streams = [process.stdout, process.stderr]; + var promises = streams.map(stream => { + return new Promise(resolve => stream.write('', null, resolve)); }); - function exitIfAllStreamsCompleted() { - writesToWait--; - if (writesToWait === 0 && jasmineRunner.exitOnCompletion) { - if(passed) { - jasmineRunner.exit(0); - } - else { - jasmineRunner.exit(1); - } - } - } -}; - -const checkExit = function(jasmineRunner) { - return function() { - if (!jasmineRunner.completionReporter.isComplete()) { - process.exitCode = 4; - } - }; + return Promise.all(promises); }; -function checkForJsFileImportSupport() { - const v = process.versions.node - .split('.') - .map(el => parseInt(el, 10)); - - if (v[0] < 12 || (v[0] === 12 && v[1] < 17)) { - console.warn('Warning: jsLoader: "import" may not work reliably on Node ' + - 'versions before 12.17.'); - } -} - /** * Runs the test suite. * @@ -534,9 +478,7 @@ function checkForJsFileImportSupport() { * @return {Promise} Promise that is resolved when the suite completes. */ Jasmine.prototype.execute = async function(files, filterString) { - this.completionReporter.exitHandler = this.checkExit; - - this.loadRequires(); + await this.loadRequires(); await this.loadHelpers(); if (!this.defaultReporterConfigured) { this.configureDefaultReporter({ showColors: this.showingColors }); @@ -554,22 +496,34 @@ Jasmine.prototype.execute = async function(files, filterString) { if (files && files.length > 0) { this.specDir = ''; this.specFiles = []; - this.addSpecFiles(files); + this.addMatchingSpecFiles(files); } await this.loadSpecs(); - if (!this.completionReporterInstalled_) { - this.addReporter(this.completionReporter); - this.completionReporterInstalled_ = true; + const prematureExitHandler = new ExitHandler(() => this.exit(4)); + prematureExitHandler.install(); + const overallResult = await this.env.execute(); + await this.flushOutput(); + prematureExitHandler.uninstall(); + + if (this.exitOnCompletion) { + this.exit(exitCodeForStatus(overallResult.overallStatus)); } - - let overallResult; - this.addReporter({ - jasmineDone: r => overallResult = r - }); - await new Promise(resolve => { - this.env.execute(null, resolve); - }); + return overallResult; }; + +function exitCodeForStatus(status) { + switch (status) { + case 'passed': + return 0; + case 'incomplete': + return 2; + case 'failed': + return 3; + default: + console.error(`Unrecognized overall status: ${status}`); + return 1; + } +} diff --git a/lib/loadConfig.js b/lib/loadConfig.js deleted file mode 100644 index 78d32a31..00000000 --- a/lib/loadConfig.js +++ /dev/null @@ -1,34 +0,0 @@ -module.exports = exports = function(jasmine, env, print) { - jasmine.loadConfigFile(env.configPath || process.env.JASMINE_CONFIG_PATH); - if (env.stopOnFailure !== undefined) { - jasmine.stopSpecOnExpectationFailure(env.stopOnFailure); - } - if (env.failFast !== undefined) { - jasmine.stopOnSpecFailure(env.failFast); - } - if (env.seed !== undefined) { - jasmine.seed(env.seed); - } - if (env.random !== undefined) { - jasmine.randomizeTests(env.random); - } - if (env.helpers !== undefined && env.helpers.length) { - jasmine.addHelperFiles(env.helpers); - } - if (env.requires !== undefined && env.requires.length) { - jasmine.addRequires(env.requires); - } - if (env.reporter !== undefined) { - try { - const Report = require(env.reporter); - const reporter = new Report(); - jasmine.clearReporters(); - jasmine.addReporter(reporter); - } catch(e) { - print('failed to register reporter "' + env.reporter + '"'); - print(e.message); - print(e.stack); - } - } - jasmine.showColors(env.color); -}; diff --git a/lib/loader.js b/lib/loader.js index 4b8522d3..8655c32f 100644 --- a/lib/loader.js +++ b/lib/loader.js @@ -1,33 +1,39 @@ +const path = require('path'); module.exports = Loader; function Loader(options) { options = options || {}; this.require_ = options.requireShim || requireShim; this.import_ = options.importShim || importShim; + this.resolvePath_ = options.resolvePath || path.resolve.bind(path); + this.alwaysImport = true; } -Loader.prototype.load = function(path, alwaysImport) { - if (alwaysImport || path.endsWith('.mjs')) { +Loader.prototype.load = function(modulePath) { + if ((this.alwaysImport && !modulePath.endsWith('.json')) || modulePath.endsWith('.mjs')) { // The ES module spec requires import paths to be valid URLs. As of v14, - // Node enforces this on Windows but not on other OSes. - const url = `file://${path}`; - return this.import_(url).catch(function(e) { - return Promise.reject(fixupImportException(e, path)); - }); + // Node enforces this on Windows but not on other OSes. On OS X, import + // paths that are URLs must not contain parent directory references. + const url = `file://${this.resolvePath_(modulePath)}`; + return this.import_(url) + .then( + mod => mod.default, + e => Promise.reject(fixupImportException(e, modulePath)) + ); } else { return new Promise(resolve => { - this.require_(path); - resolve(); + const result = this.require_(modulePath); + resolve(result); }); } }; -function requireShim(path) { - require(path); +function requireShim(modulePath) { + return require(modulePath); } -function importShim(path) { - return import(path); +function importShim(modulePath) { + return import(modulePath); } @@ -93,7 +99,7 @@ function fixupImportException(e, importedPath) { // bottom of the stack traces in the examples for cases 3 and 4 above. To add // to the fun, file paths in errors on Windows can be either Windows style // paths (c:\path\to\file.js) or URLs (file:///c:/path/to/file.js). - + if (!(e instanceof SyntaxError)) { return e; } diff --git a/lib/reporters/completion_reporter.js b/lib/reporters/completion_reporter.js deleted file mode 100644 index e1fdee04..00000000 --- a/lib/reporters/completion_reporter.js +++ /dev/null @@ -1,29 +0,0 @@ -module.exports = function() { - let onCompleteCallback = function() {}; - let completed = false; - - this.onComplete = function(callback) { - onCompleteCallback = callback; - }; - - this.jasmineStarted = function() { - if (this.exitHandler) { - process.on('exit', this.exitHandler); - } - }; - - this.jasmineDone = function(result) { - completed = true; - if (this.exitHandler) { - process.removeListener('exit', this.exitHandler); - } - - onCompleteCallback(result.overallStatus === 'passed'); - }; - - this.isComplete = function() { - return completed; - }; - - this.exitHandler = null; -}; diff --git a/lib/reporters/console_reporter.js b/lib/reporters/console_reporter.js index 069ad253..f3020855 100644 --- a/lib/reporters/console_reporter.js +++ b/lib/reporters/console_reporter.js @@ -12,7 +12,6 @@ module.exports = exports = ConsoleReporter; function ConsoleReporter() { let print = function() {}, showColors = false, - jasmineCorePath = null, specCount, executableSpecCount, failureCount, @@ -25,7 +24,7 @@ function ConsoleReporter() { none: '\x1B[0m' }, failedSuites = [], - stackFilter = defaultStackFilter; + stackFilter = stack => stack; /** * Configures the reporter. @@ -48,9 +47,6 @@ function ConsoleReporter() { * @default false */ showColors = options.showColors || false; - if (options.jasmineCorePath) { - jasmineCorePath = options.jasmineCorePath; - } if (options.stackFilter) { stackFilter = options.stackFilter; } @@ -79,6 +75,10 @@ function ConsoleReporter() { }; this.jasmineDone = function(result) { + if (result.failedExpectations) { + failureCount += result.failedExpectations.length; + } + printNewline(); printNewline(); if (failedSpecs.length > 0) { @@ -99,7 +99,7 @@ function ConsoleReporter() { if (pendingSpecs.length > 0) { print("Pending:"); } - for(i = 0; i < pendingSpecs.length; i++) { + for (let i = 0; i < pendingSpecs.length; i++) { pendingSpecDetails(pendingSpecs[i], i + 1); } @@ -205,29 +205,18 @@ function ConsoleReporter() { return newArr.join('\n'); } - function defaultStackFilter(stack) { - if (!stack) { - return ''; - } - - const filteredStack = stack.split('\n').filter(function(stackLine) { - return stackLine.indexOf(jasmineCorePath) === -1; - }).join('\n'); - return filteredStack; - } - function specFailureDetails(result, failedSpecNumber) { printNewline(); print(failedSpecNumber + ') '); print(result.fullName); printFailedExpectations(result); - if (result.trace) { + if (result.debugLogs) { printNewline(); - print(indent('Trace:', 2)); + print(indent('Debug logs:', 2)); printNewline(); - for (const entry of result.trace) { + for (const entry of result.debugLogs) { print(indent(`${entry.timestamp}ms: ${entry.message}`, 4)); printNewline(); } diff --git a/package.json b/package.json index 5e6a937d..2f528dbf 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "bdd" ], "license": "MIT", - "version": "3.10.0", + "version": "4.0.0", "repository": { "type": "git", "url": "https://github.com/jasmine/jasmine-npm" @@ -19,6 +19,7 @@ "test": "node ./bin/jasmine.js", "posttest": "eslint \"bin/**/*.js\" \"lib/**/*.js\" \"spec/**/*.js\"" }, + "exports": "./lib/jasmine.js", "files": [ "bin", "lib", @@ -28,7 +29,7 @@ ], "dependencies": { "glob": "^7.1.6", - "jasmine-core": "~3.10.0" + "jasmine-core": "^4.0.0" }, "bin": "./bin/jasmine.js", "main": "./lib/jasmine.js", diff --git a/release_notes/3.10.0.md b/release_notes/3.10.0.md index 612434d3..536ff1fc 100644 --- a/release_notes/3.10.0.md +++ b/release_notes/3.10.0.md @@ -41,6 +41,12 @@ for more information. * Test suite improvements +## Supported environments + +The jasmine NPM package has been tested on Node 12, 14, and 16. See the +[jasmine-core release notes](https://github.com/jasmine/jasmine/blob/main/release_notes/3.10.0.md) +for supported browsers. + ------ _Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_ diff --git a/release_notes/3.99.0.md b/release_notes/3.99.0.md new file mode 100644 index 00000000..3182626a --- /dev/null +++ b/release_notes/3.99.0.md @@ -0,0 +1,10 @@ +# Jasmine NPM 3.99.0 Release Notes + +This release adds deprecation warnings for breaking changes that will be +introduced in Jasmine 4.0. Please see the +[migration guide](https://jasmine.github.io/tutorials/upgrading_to_Jasmine_4.0) +for more information. + +------ + +_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_ diff --git a/release_notes/4.0.0.md b/release_notes/4.0.0.md new file mode 100644 index 00000000..af83329a --- /dev/null +++ b/release_notes/4.0.0.md @@ -0,0 +1,108 @@ +# Jasmine NPM 4.0.0 Release Notes + +## Summary + +This is a major release. In addition to new features and bug fixes it contains +a variety of breaking changes that are intended to improve ES module support, +improve awkward or outdated interfaces, and make Jasmine easier to maintain +and contribute to. If you're upgrading from Jasmine 3.x, we recommend installing +3.99 and fixing any deprecation warnings that it emits before updating to 4.0. +See the [migration guide](https://jasmine.github.io/tutorials/upgrading_to_Jasmine_4.0) +for more information. Be sure to read the release notes for `jasmine-core` 4.0.0 +as well. + +## Highlights + +* The `jasmine-core` dependency has been updated to 4.0.0 +* ES module support is enabled by default. +* Node 10 and 12.0-12.16 are no longer supported. + +## Breaking changes + +* Node versions older than 12.17 are no longer supported because they have + insufficient support for interop between ES modules and CommonJS modules. + +* Beginning with this release, everything not documented in the + [API reference](https://jasmine.github.io/api/npm/4.0/Jasmine) is considered + a private API. + +* Config files can be ES modules. This is a breaking change because it requires + `Jasmine#loadConfigFile` to be async. + +* The `--fail-fast` CLI flag now causes Jasmine to stop spec execution on the + first expectation failure as well as stopping suite execution on the first + spec failure. + +* The ambiguously named `--stop-on-failure` CLI flag is no longer supported. + +* Failure to load or instantiate a reporter is a fatal error, not just a warning. + +* Relative reporter paths are resolved based on the working directory rather + than the location of the module inside Jasmine that calls `require()`. + +* The `jasmine` command now uses distinct exit codes for various types of non-success: + * 1 for anything unexpected, i.e. Jasmine didn't run to completion + * 2 for incomplete (focused specs/suites but no failures) + * 3 for failed (spec/suite failures) + * Other nonzero exit codes may be used for other purposes. Code that checks + the exit code of the `jasmine` command should not treat any value other than + 0 as success. + * Fixes [#154](https://github.com/jasmine/jasmine-npm/issues/154). + +* `Jasmine#onComplete` is no longer supported. To run code after execution + finishes, set the Jasmine instance's `exitOnCompletion` to false and use the + promise returned by `Jasmine#execute`. See the + [API reference for `execute`](https://jasmine.github.io/api/npm/4.0/Jasmine.html#execute) + for more information. + +## New features and bugfixes + +* Full support for ES modules in the default configuration: + * ES module support is enabled by default, but can still be disabled by + adding `jsLoader: "require"` to the configuration. `jsLoader: "import"` is + now a no-op. + + We think it's highly unlikely that this change will break anything. If it + does (i.e. your code works with `jsLoader: "require"` but not without it), + please [let us know](https://github.com/jasmine/jasmine-npm/issues/new). + This will help us understand whether the `jsLoader` config property is still + needed. + + * Files listed in the `requires` config property can be ES modules + + * Reporters specified with `--reporter=` can be ES modules. + +* Allow use without creating globals. + * See . + * Fixes [jasmine/jasmine#1235](https://github.com/jasmine/jasmine/issues/1235). + +* Autodiscover spec/support/jasmine.js as well as spec/support/jasmine.json. + +* Moved `stopSpecOnExpectationFailure` and `random` to `env` in the sample + config generated by `jasmine init`. + +* Top suite failures are included in the failure count displayed by the default + `ConsoleReporter`. + +* Added support for the debug logging feature introduced in `jasmine-core` 4.0.0. + +* Fixed handling of module paths containing `..` on OS X. + +## Internal improvements + +* Use the promise returned from `Env#execute` to determine when suite execution + is finished and obtain the overall result. + +* Removed unnecessary code to filter Jasmine's frames from stack traces. The same + filtering has been done in jasmine-core since 3.0. + +* Inlined loadConfig.js back into command.js to resolve naming conflicts. + +## Supported environments + +The jasmine NPM package has been tested on Node 12.17-12.22, 14, and 16. + + +------ + +_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_ diff --git a/spec/command_spec.js b/spec/command_spec.js index dc1b9ca2..95f4f9f7 100644 --- a/spec/command_spec.js +++ b/spec/command_spec.js @@ -21,11 +21,11 @@ function deleteDirectory(dir) { } } -function withValueForIsTTY(value, func) { +async function withValueForIsTTY(value, func) { const wasTTY = process.stdout.isTTY; try { process.stdout.isTTY = value; - func(); + await func(); } finally { process.stdout.isTTY = wasTTY; } @@ -52,8 +52,9 @@ describe('command', function() { this.command = new Command(projectBaseDir, examplesDir, this.out.print); - this.fakeJasmine = jasmine.createSpyObj('jasmine', ['loadConfigFile', 'addHelperFiles', 'addRequires', 'showColors', 'execute', 'stopSpecOnExpectationFailure', - 'stopOnSpecFailure', 'randomizeTests', 'seed', 'coreVersion', 'clearReporters', 'addReporter']); + this.fakeJasmine = jasmine.createSpyObj('jasmine', ['loadConfigFile', 'addHelperFiles', 'addRequires', 'showColors', 'execute', + 'randomizeTests', 'seed', 'coreVersion', 'clearReporters', 'addReporter']); + this.fakeJasmine.env = jasmine.createSpyObj('env', ['configure']); this.fakeJasmine.execute.and.returnValue(Promise.resolve()); }); @@ -141,9 +142,9 @@ describe('command', function() { }); describe('--', function() { - it('skips anything after it', function() { - withValueForIsTTY(true, function () { - this.command.run(this.fakeJasmine, ['node', 'bin/jasmine.js', '--', '--no-color']); + it('skips anything after it', async function() { + await withValueForIsTTY(true, async function () { + await this.command.run(this.fakeJasmine, ['node', 'bin/jasmine.js', '--', '--no-color']); expect(this.out.getOutput()).toBe(''); expect(this.fakeJasmine.showColors).toHaveBeenCalledWith(true); }.bind(this)); @@ -182,164 +183,204 @@ describe('command', function() { } }); - it('should load the default config file', function() { - this.command.run(this.fakeJasmine, ['node', 'bin/jasmine.js']); + it('should load the default config file', async function() { + await this.command.run(this.fakeJasmine, ['node', 'bin/jasmine.js']); expect(this.fakeJasmine.loadConfigFile).toHaveBeenCalledWith(undefined); }); - it('should load a custom config file specified by env variable', function() { - this.command.run(this.fakeJasmine, ['node', 'bin/jasmine.js', 'JASMINE_CONFIG_PATH=somewhere.json']); + it('should load a custom config file specified by env variable', async function() { + await this.command.run(this.fakeJasmine, ['node', 'bin/jasmine.js', 'JASMINE_CONFIG_PATH=somewhere.json']); expect(this.fakeJasmine.loadConfigFile).toHaveBeenCalledWith('somewhere.json'); }); - it('should load a custom config file specified by option', function() { - this.command.run(this.fakeJasmine, ['node', 'bin/jasmine.js', '--config=somewhere.json']); + it('should load a custom config file specified by option', async function() { + await this.command.run(this.fakeJasmine, ['node', 'bin/jasmine.js', '--config=somewhere.json']); expect(this.fakeJasmine.loadConfigFile).toHaveBeenCalledWith('somewhere.json'); }); - it('should show colors by default if stdout is a TTY', function() { - withValueForIsTTY(true, function () { - this.command.run(this.fakeJasmine, ['node', 'bin/jasmine.js']); + it('should show colors by default if stdout is a TTY', async function() { + await withValueForIsTTY(true, async function () { + await this.command.run(this.fakeJasmine, ['node', 'bin/jasmine.js']); expect(this.fakeJasmine.showColors).toHaveBeenCalledWith(true); }.bind(this)); }); - it('should not show colors by default if stdout is not a TTY', function() { - withValueForIsTTY(undefined, function () { - this.command.run(this.fakeJasmine, ['node', 'bin/jasmine.js']); + it('should not show colors by default if stdout is not a TTY', async function() { + await withValueForIsTTY(undefined, async function () { + await this.command.run(this.fakeJasmine, ['node', 'bin/jasmine.js']); expect(this.fakeJasmine.showColors).toHaveBeenCalledWith(false); }.bind(this)); }); - it('should allow colors to be turned off', function() { - this.command.run(this.fakeJasmine, ['node', 'bin/jasmine.js', '--no-color']); + it('should allow colors to be turned off', async function() { + await this.command.run(this.fakeJasmine, ['node', 'bin/jasmine.js', '--no-color']); expect(this.fakeJasmine.showColors).toHaveBeenCalledWith(false); }); - it('should be able to force colors to be turned on', function() { - withValueForIsTTY(undefined, function () { - this.command.run(this.fakeJasmine, ['node', 'bin/jasmine.js', '--color']); + it('should be able to force colors to be turned on', async function() { + await withValueForIsTTY(undefined, async function () { + await this.command.run(this.fakeJasmine, ['node', 'bin/jasmine.js', '--color']); expect(this.fakeJasmine.showColors).toHaveBeenCalledWith(true); }.bind(this)); }); - it('should execute the jasmine suite', function() { - this.command.run(this.fakeJasmine, ['node', 'bin/jasmine.js']); + it('should execute the jasmine suite', async function() { + await this.command.run(this.fakeJasmine, ['node', 'bin/jasmine.js']); expect(this.fakeJasmine.execute).toHaveBeenCalled(); }); - it('should be able to run only specified specs', function() { - this.command.run(this.fakeJasmine, ['spec/some/fileSpec.js', 'SOME_ENV=SOME_VALUE', '--no-color']); + it('should be able to run only specified specs', async function() { + await this.command.run(this.fakeJasmine, ['spec/some/fileSpec.js', 'SOME_ENV=SOME_VALUE', '--no-color']); expect(this.fakeJasmine.execute).toHaveBeenCalledWith(['spec/some/fileSpec.js'], undefined); }); - it('should be able filter by spec name', function() { - this.command.run(this.fakeJasmine, ['--filter=interesting spec']); + it('should be able filter by spec name', async function() { + await this.command.run(this.fakeJasmine, ['--filter=interesting spec']); expect(this.fakeJasmine.execute).toHaveBeenCalledWith(jasmine.any(Array), 'interesting spec'); }); - it('should be able to add one helper pattern', function() { - this.command.run(this.fakeJasmine, ['--helper=helpers/**/*.js']); + it('should be able to add one helper pattern', async function() { + await this.command.run(this.fakeJasmine, ['--helper=helpers/**/*.js']); expect(this.fakeJasmine.addHelperFiles).toHaveBeenCalledWith(['helpers/**/*.js']); }); - it('should be able to add many helper patterns', function() { - this.command.run(this.fakeJasmine, ['--helper=helpers/**/*.js', '--helper=other.js']); + it('should be able to add many helper patterns', async function() { + await this.command.run(this.fakeJasmine, ['--helper=helpers/**/*.js', '--helper=other.js']); expect(this.fakeJasmine.addHelperFiles).toHaveBeenCalledWith(['helpers/**/*.js', 'other.js']); }); - it('should not modify helper patterns if no argument given', function() { - this.command.run(this.fakeJasmine, []); + it('should not modify helper patterns if no argument given', async function() { + await this.command.run(this.fakeJasmine, []); expect(this.fakeJasmine.addHelperFiles).not.toHaveBeenCalled(); }); - it('should be able to add one require', function() { - this.command.run(this.fakeJasmine, ['--require=ts-node/require']); + it('should be able to add one require', async function() { + await this.command.run(this.fakeJasmine, ['--require=ts-node/require']); expect(this.fakeJasmine.addRequires).toHaveBeenCalledWith(['ts-node/require']); }); - it('should be able to add multiple requires', function() { - this.command.run(this.fakeJasmine, ['--require=ts-node/require', '--require=@babel/register']); + it('should be able to add multiple requires', async function() { + await this.command.run(this.fakeJasmine, ['--require=ts-node/require', '--require=@babel/register']); expect(this.fakeJasmine.addRequires).toHaveBeenCalledWith(['ts-node/require', '@babel/register']); }); - it('can specify a reporter', function() { + it('can specify a reporter', async function() { const reporterPath = path.resolve(path.join(__dirname, 'fixtures', 'customReporter.js')); const Reporter = require(reporterPath); - this.command.run(this.fakeJasmine, ['--reporter=' + reporterPath]); + await this.command.run(this.fakeJasmine, ['--reporter=' + reporterPath]); expect(this.fakeJasmine.clearReporters).toHaveBeenCalled(); expect(this.fakeJasmine.addReporter).toHaveBeenCalledWith(jasmine.any(Reporter)); }); - it('prints an error if the file does not export a reporter', function() { - const reporterPath = path.resolve(path.join(__dirname, 'fixtures', 'badReporter.js')); - this.command.run(this.fakeJasmine, ['--reporter=' + reporterPath]); - expect(this.fakeJasmine.clearReporters).not.toHaveBeenCalled(); - expect(this.fakeJasmine.addReporter).not.toHaveBeenCalled(); - expect(this.out.getOutput()).toContain('failed to register reporter'); + it('can specify a reporter that is an ES module', async function() { + await this.command.run(this.fakeJasmine, ['--reporter=./spec/fixtures/customReporter.mjs']); + expect(this.fakeJasmine.clearReporters).toHaveBeenCalled(); + expect(this.fakeJasmine.addReporter.calls.argsFor(0)[0].isCustomReporterDotMjs).toBe(true); }); - it('prints an error if the reporter file does not exist', function() { - const reporterPath = path.resolve(path.join(__dirname, 'fixtures', 'missingReporter.js')); - this.command.run(this.fakeJasmine, ['--reporter=' + reporterPath]); - expect(this.fakeJasmine.clearReporters).not.toHaveBeenCalled(); - expect(this.fakeJasmine.addReporter).not.toHaveBeenCalled(); - expect(this.out.getOutput()).toContain('failed to register reporter'); - }); + describe('When the reporter path is relative', function() { + beforeEach(function() { + this.originalWd = process.cwd(); + }); - it('should not configure stopping spec on expectation failure by default', function() { - this.command.run(this.fakeJasmine, []); - expect(this.fakeJasmine.stopSpecOnExpectationFailure).not.toHaveBeenCalled(); - }); + afterEach(function() { + process.chdir(this.originalWd); + }); - it('should be able to turn on stopping spec on expectation failure', function() { - this.command.run(this.fakeJasmine, ['--stop-on-failure=true']); - expect(this.fakeJasmine.stopSpecOnExpectationFailure).toHaveBeenCalledWith(true); - }); + it('evaluates the path based on the cwd', async function() { + const Reporter = require('./fixtures/customReporter.js'); + process.chdir('spec/fixtures'); + await this.command.run(this.fakeJasmine, ['--reporter=./customReporter.js']); + expect(this.fakeJasmine.clearReporters).toHaveBeenCalled(); + expect(this.fakeJasmine.addReporter).toHaveBeenCalledWith(jasmine.any(Reporter)); - it('should be able to turn off stopping spec on expectation failure', function() { - this.command.run(this.fakeJasmine, ['--stop-on-failure=false']); - expect(this.fakeJasmine.stopSpecOnExpectationFailure).toHaveBeenCalledWith(false); + this.fakeJasmine.clearReporters.calls.reset(); + this.fakeJasmine.addReporter.calls.reset(); + process.chdir('example'); + await this.command.run(this.fakeJasmine, ['--reporter=../customReporter.js']); + expect(this.fakeJasmine.clearReporters).toHaveBeenCalled(); + expect(this.fakeJasmine.addReporter).toHaveBeenCalledWith(jasmine.any(Reporter)); + }); }); - it('should not configure fail fast by default', function() { - this.command.run(this.fakeJasmine, []); - expect(this.fakeJasmine.stopOnSpecFailure).not.toHaveBeenCalled(); + it('throws with context if the file does not export a reporter', async function() { + const reporterPath = path.resolve(path.join(__dirname, 'fixtures', 'badReporter.js')); + await expectAsync( + this.command.run(this.fakeJasmine, ['--reporter=' + reporterPath]) + ).toBeRejectedWithError(new RegExp( + 'Failed to instantiate reporter from ' + + escapeStringForRegexp(reporterPath) + '\nUnderlying error: .' + + '*Reporter is not a constructor' + )); + expect(this.fakeJasmine.clearReporters).not.toHaveBeenCalled(); + expect(this.fakeJasmine.addReporter).not.toHaveBeenCalled(); }); - it('should be able to turn on fail fast', function() { - this.command.run(this.fakeJasmine, ['--fail-fast=true']); - expect(this.fakeJasmine.stopOnSpecFailure).toHaveBeenCalledWith(true); + it('throws with context if the reporter file does not exist', async function() { + const reporterPath = path.resolve(path.join(__dirname, 'fixtures', 'missingReporter.js')); + + await expectAsync( + this.command.run(this.fakeJasmine, ['--reporter=' + reporterPath]) + ).toBeRejectedWithError(new RegExp( + 'Failed to load reporter module ' + + escapeStringForRegexp(reporterPath) + '\nUnderlying error: ' + + '.*Cannot find module' + )); + + expect(this.fakeJasmine.clearReporters).not.toHaveBeenCalled(); + expect(this.fakeJasmine.addReporter).not.toHaveBeenCalled(); }); - it('should be able to turn off fail fast', function() { - this.command.run(this.fakeJasmine, ['--fail-fast=false']); - expect(this.fakeJasmine.stopOnSpecFailure).toHaveBeenCalledWith(false); + it('should not configure fail fast by default', async function() { + await this.command.run(this.fakeJasmine, []); + expect(this.fakeJasmine.env.configure).not.toHaveBeenCalledWith(jasmine.objectContaining({ + stopOnSpecFailure: jasmine.anything() + })); + expect(this.fakeJasmine.env.configure).not.toHaveBeenCalledWith(jasmine.objectContaining({ + stopSpecOnExpectationFailure: jasmine.anything() + })); }); - it('uses jasmine-core defaults if random is unspecified', function() { - this.command.run(this.fakeJasmine, []); + it('should be able to turn on fail fast', async function() { + await this.command.run(this.fakeJasmine, ['--fail-fast']); + expect(this.fakeJasmine.env.configure).toHaveBeenCalledWith({ + stopOnSpecFailure: true, + stopSpecOnExpectationFailure: true + }); + }); + + it('uses jasmine-core defaults if random is unspecified', async function() { + await this.command.run(this.fakeJasmine, []); expect(this.fakeJasmine.randomizeTests).not.toHaveBeenCalled(); }); - it('should be able to turn on random tests', function() { - this.command.run(this.fakeJasmine, ['--random=true']); + it('should be able to turn on random tests', async function() { + await this.command.run(this.fakeJasmine, ['--random=true']); expect(this.fakeJasmine.randomizeTests).toHaveBeenCalledWith(true); }); - it('should be able to turn off random tests', function() { - this.command.run(this.fakeJasmine, ['--random=false']); + it('should be able to turn off random tests', async function() { + await this.command.run(this.fakeJasmine, ['--random=false']); expect(this.fakeJasmine.randomizeTests).toHaveBeenCalledWith(false); }); - it('should not configure seed by default', function() { - this.command.run(this.fakeJasmine, []); + it('should not configure seed by default', async function() { + await this.command.run(this.fakeJasmine, []); expect(this.fakeJasmine.seed).not.toHaveBeenCalled(); }); - it('should be able to set a seed', function() { - this.command.run(this.fakeJasmine, ['--seed=12345']); + it('should be able to set a seed', async function() { + await this.command.run(this.fakeJasmine, ['--seed=12345']); expect(this.fakeJasmine.seed).toHaveBeenCalledWith('12345'); }); }); }); + +// Adapted from Sindre Sorhus's escape-string-regexp (MIT license) +function escapeStringForRegexp(string) { + // Escape characters with special meaning either inside or outside character sets. + // Use a simple backslash escape when it’s always valid, and a `\xnn` escape when the simpler form would be disallowed by Unicode patterns’ stricter grammar. + return string + .replace(/[|\\{}()[\]^$+*?.]/g, '\\$&') + .replace(/-/g, '\\x2d'); +} diff --git a/spec/fixtures/customReporter.mjs b/spec/fixtures/customReporter.mjs new file mode 100644 index 00000000..9f187eaf --- /dev/null +++ b/spec/fixtures/customReporter.mjs @@ -0,0 +1,7 @@ +export default function Reporter() {} + +Reporter.prototype.jasmineDone = function() { + console.log('customReporter.mjs jasmineDone'); +}; + +Reporter.prototype.isCustomReporterDotMjs = true; \ No newline at end of file diff --git a/spec/fixtures/esm-reporter-packagejson/customReporter.js b/spec/fixtures/esm-reporter-packagejson/customReporter.js new file mode 100644 index 00000000..83771da6 --- /dev/null +++ b/spec/fixtures/esm-reporter-packagejson/customReporter.js @@ -0,0 +1,5 @@ +export default function Reporter() {} + +Reporter.prototype.jasmineDone = function() { + console.log('customReporter.js jasmineDone'); +}; diff --git a/spec/fixtures/esm-reporter-packagejson/jasmine.json b/spec/fixtures/esm-reporter-packagejson/jasmine.json new file mode 100644 index 00000000..33b8d19f --- /dev/null +++ b/spec/fixtures/esm-reporter-packagejson/jasmine.json @@ -0,0 +1,6 @@ +{ + "spec_dir": ".", + "spec_files": [ + ], + "jsLoader": "import" +} diff --git a/spec/fixtures/esm-reporter-packagejson/package.json b/spec/fixtures/esm-reporter-packagejson/package.json new file mode 100644 index 00000000..3dbc1ca5 --- /dev/null +++ b/spec/fixtures/esm-reporter-packagejson/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} diff --git a/spec/fixtures/esm/jasmine.json b/spec/fixtures/esm/jasmine.mjs similarity index 84% rename from spec/fixtures/esm/jasmine.json rename to spec/fixtures/esm/jasmine.mjs index b6f1d265..d8e6408a 100644 --- a/spec/fixtures/esm/jasmine.json +++ b/spec/fixtures/esm/jasmine.mjs @@ -1,4 +1,4 @@ -{ +const config = { "spec_dir": ".", "spec_files": [ "commonjs_spec.js", @@ -11,4 +11,5 @@ ], "stopSpecOnExpectationFailure": false, "random": false -} +}; +export default config; diff --git a/spec/fixtures/js-loader-default/aSpec.js b/spec/fixtures/js-loader-default/aSpec.js deleted file mode 100644 index f219b1ab..00000000 --- a/spec/fixtures/js-loader-default/aSpec.js +++ /dev/null @@ -1,5 +0,0 @@ -describe('a file with js extension', function() { - it('was loaded as a CommonJS module', function() { - expect(module.parent).toBeTruthy(); - }); -}); diff --git a/spec/fixtures/js-loader-default/anEsModule.js b/spec/fixtures/js-loader-default/anEsModule.js new file mode 100644 index 00000000..5775bf01 --- /dev/null +++ b/spec/fixtures/js-loader-default/anEsModule.js @@ -0,0 +1,3 @@ +export function foo() { + return 42; +} diff --git a/spec/fixtures/js-loader-default/anEsModuleSpec.js b/spec/fixtures/js-loader-default/anEsModuleSpec.js new file mode 100644 index 00000000..25879b30 --- /dev/null +++ b/spec/fixtures/js-loader-default/anEsModuleSpec.js @@ -0,0 +1,7 @@ +import {foo} from './anEsModule.js'; + +describe('foo', function() { + it('returns 42', function() { + expect(foo()).toEqual(42); + }); +}); diff --git a/spec/fixtures/js-loader-default/anEsRequire.js b/spec/fixtures/js-loader-default/anEsRequire.js new file mode 100644 index 00000000..4eabc06e --- /dev/null +++ b/spec/fixtures/js-loader-default/anEsRequire.js @@ -0,0 +1 @@ +import './anEsModule.js'; diff --git a/spec/fixtures/js-loader-default/jasmine.json b/spec/fixtures/js-loader-default/jasmine.json index 4d8920dd..77dc1d6d 100644 --- a/spec/fixtures/js-loader-default/jasmine.json +++ b/spec/fixtures/js-loader-default/jasmine.json @@ -1,4 +1,5 @@ { "spec_dir": ".", - "spec_files": ["aSpec.js"] + "spec_files": ["anEsModuleSpec.js"], + "requires": ["../js-loader-default/anEsRequire.js"] } diff --git a/spec/fixtures/js-loader-default/package.json b/spec/fixtures/js-loader-default/package.json new file mode 100644 index 00000000..3dbc1ca5 --- /dev/null +++ b/spec/fixtures/js-loader-default/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} diff --git a/spec/fixtures/js-loader-import/anEsRequire.js b/spec/fixtures/js-loader-import/anEsRequire.js new file mode 100644 index 00000000..4eabc06e --- /dev/null +++ b/spec/fixtures/js-loader-import/anEsRequire.js @@ -0,0 +1 @@ +import './anEsModule.js'; diff --git a/spec/fixtures/js-loader-import/anotherEsModuleSpec.js b/spec/fixtures/js-loader-import/anotherEsModuleSpec.js new file mode 100644 index 00000000..25879b30 --- /dev/null +++ b/spec/fixtures/js-loader-import/anotherEsModuleSpec.js @@ -0,0 +1,7 @@ +import {foo} from './anEsModule.js'; + +describe('foo', function() { + it('returns 42', function() { + expect(foo()).toEqual(42); + }); +}); diff --git a/spec/fixtures/js-loader-import/jasmine.json b/spec/fixtures/js-loader-import/jasmine.json index 882f2974..13d141bf 100644 --- a/spec/fixtures/js-loader-import/jasmine.json +++ b/spec/fixtures/js-loader-import/jasmine.json @@ -1,5 +1,6 @@ { "spec_dir": ".", "spec_files": ["anEsModuleSpec.js"], + "requires": ["../js-loader-import/anEsRequire.js"], "jsLoader": "import" } diff --git a/spec/fixtures/js-loader-require/aRequire.js b/spec/fixtures/js-loader-require/aRequire.js new file mode 100644 index 00000000..4af3e913 --- /dev/null +++ b/spec/fixtures/js-loader-require/aRequire.js @@ -0,0 +1,3 @@ +if (!module.parent) { + throw new Error('aRequire.js was loaded as an ES module'); +} diff --git a/spec/fixtures/js-loader-require/jasmine.json b/spec/fixtures/js-loader-require/jasmine.json index 737dd792..b3b63b11 100644 --- a/spec/fixtures/js-loader-require/jasmine.json +++ b/spec/fixtures/js-loader-require/jasmine.json @@ -1,5 +1,6 @@ { "spec_dir": ".", "spec_files": ["aSpec.js"], + "requires": ["../spec/fixtures/js-loader-require/aRequire.js"], "jsLoader": "require" } diff --git a/spec/fixtures/no-globals/aSpec.js b/spec/fixtures/no-globals/aSpec.js new file mode 100644 index 00000000..f3f1b0f0 --- /dev/null +++ b/spec/fixtures/no-globals/aSpec.js @@ -0,0 +1,5 @@ +const {it, expect} = require('jasmine-core').noGlobals(); + +it('can use equality testers defined in a different file', function() { + expect(1).toEqual(2); +}); diff --git a/spec/fixtures/no-globals/runner.js b/spec/fixtures/no-globals/runner.js new file mode 100644 index 00000000..62176e96 --- /dev/null +++ b/spec/fixtures/no-globals/runner.js @@ -0,0 +1,23 @@ +const initialGlobals = Object.keys(global); +const Jasmine = require('../../../lib/jasmine.js'); +const {jasmine, beforeEach} = require('jasmine-core').noGlobals(); +beforeEach(function() { + jasmine.addCustomEqualityTester(function(a, b) { + if ((a === 1 && b === 2) || (a === 2 && b === 1)) { + return true; + } + }); +}); + +const runner = new Jasmine({globals: false}); +runner.addSpecFile('./aSpec.js'); +runner.exitOnCompletion = false; +runner.execute().then(function() { + const extraGlobals = Object.keys(global).filter(k => !initialGlobals.includes(k)); + + if (extraGlobals.length === 0) { + console.log('Globals OK'); + } else { + console.log('Extra globals:', extraGlobals); + } +}); diff --git a/spec/fixtures/prematureExit.js b/spec/fixtures/prematureExit.js new file mode 100644 index 00000000..d5c97630 --- /dev/null +++ b/spec/fixtures/prematureExit.js @@ -0,0 +1,8 @@ +const Jasmine = require('../..'); +const jasmine = new Jasmine(); + +it('a spec', function() { + process.exit(0); +}); + +jasmine.execute(); diff --git a/spec/fixtures/sample_project/spec/support/jasmine_alternate.cjs b/spec/fixtures/sample_project/spec/support/jasmine_alternate.cjs new file mode 100644 index 00000000..4da154c1 --- /dev/null +++ b/spec/fixtures/sample_project/spec/support/jasmine_alternate.cjs @@ -0,0 +1,13 @@ +module.exports = { + "spec_dir": "spec", + "spec_files": [ + "fixture_spec.js", + "**/*spec.js" + ], + "helpers": [ + "helper.js" + ], + "requires": [ + "ts-node/register" + ] +}; diff --git a/spec/fixtures/sample_project/spec/support/jasmine_alternate.mjs b/spec/fixtures/sample_project/spec/support/jasmine_alternate.mjs new file mode 100644 index 00000000..91bc0ec4 --- /dev/null +++ b/spec/fixtures/sample_project/spec/support/jasmine_alternate.mjs @@ -0,0 +1,15 @@ +const config = { + "spec_dir": "spec", + "spec_files": [ + "fixture_spec.js", + "**/*spec.js" + ], + "helpers": [ + "helper.js" + ], + "requires": [ + "ts-node/register" + ] +}; + +export default config; diff --git a/spec/integration_spec.js b/spec/integration_spec.js index 08d763db..144ca316 100644 --- a/spec/integration_spec.js +++ b/spec/integration_spec.js @@ -23,15 +23,9 @@ describe('Integration', function () { }); it('supports ES modules', async function () { - let {exitCode, output} = await runJasmine('spec/fixtures/esm', true); + const {exitCode, output} = await runJasmine('spec/fixtures/esm', 'jasmine.mjs'); expect(exitCode).toEqual(0); - // Node < 14 outputs a warning when ES modules are used, e.g.: - // (node:5258) ExperimentalWarning: The ESM module loader is experimental. - // The position of this warning in the output varies. Sometimes it - // occurs before the lines we're interested in but sometimes it's in - // the middle of them. - output = output.replace(/^.*ExperimentalWarning.*$\n/m, ''); - expect(output).toContain( + expect(stripExperimentalModulesWarning(output)).toContain( 'name_reporter\n' + 'commonjs_helper\n' + 'esm_helper\n' + @@ -41,35 +35,45 @@ describe('Integration', function () { ); }); - it('loads .js files using import when jsLoader is "import"', async function() { - await requireFunctioningJsImport(); - expect(await runJasmine('spec/fixtures/js-loader-import', false)).toBeSuccess(); + it('supports ES module reporters that end in .mjs', async function() { + let {output} = await runJasmine( + 'spec/fixtures/sample_project', + 'spec/support/jasmine.json', + ['--reporter=../customReporter.mjs'] + ); + expect(output).toContain('customReporter.mjs jasmineDone'); + }); + + it('supports ES module reporters that end in .js', async function() { + let {output} = await runJasmine( + 'spec/fixtures/esm-reporter-packagejson', + 'jasmine.json', + ['--reporter=./customReporter.js'] + ); + expect(output).toContain('customReporter.js jasmineDone'); }); - it('warns that jsLoader: "import" is not supported', async function() { - await requireBrokenJsImport(); - const {output} = await runJasmine('spec/fixtures/js-loader-import', false); - expect(output).toContain('Warning: jsLoader: "import" may not work ' + - 'reliably on Node versions before 12.17.'); + it('loads .js files using import when jsLoader is "import"', async function() { + expect(await runJasmine('spec/fixtures/js-loader-import')).toBeSuccess(); }); it('loads .js files using require when jsLoader is "require"', async function() { - expect(await runJasmine('spec/fixtures/js-loader-require', false)).toBeSuccess(); + expect(await runJasmine('spec/fixtures/js-loader-require')).toBeSuccess(); }); - it('loads .js files using require when jsLoader is undefined', async function() { - expect(await runJasmine('spec/fixtures/js-loader-default', false)).toBeSuccess(); + it('loads .js files using import when jsLoader is undefined', async function() { + expect(await runJasmine('spec/fixtures/js-loader-default')).toBeSuccess(); }); it('handles load-time exceptions from CommonJS specs properly', async function () { - const {exitCode, output} = await runJasmine('spec/fixtures/cjs-load-exception', false); + const {exitCode, output} = await runJasmine('spec/fixtures/cjs-load-exception'); expect(exitCode).toEqual(1); expect(output).toContain('Error: nope'); expect(output).toMatch(/at .*throws_on_load.js/); }); it('handles load-time exceptions from ESM specs properly', async function () { - const {exitCode, output} = await runJasmine('spec/fixtures/esm-load-exception', true); + const {exitCode, output} = await runJasmine('spec/fixtures/esm-load-exception'); expect(exitCode).toEqual(1); expect(output).toContain('Error: nope'); expect(output).toMatch(/at .*throws_on_load.mjs/); @@ -83,7 +87,7 @@ describe('Integration', function () { }); it('handles syntax errors in ESM specs properly', async function () { - const {exitCode, output} = await runJasmine('spec/fixtures/esm-syntax-error', true); + const {exitCode, output} = await runJasmine('spec/fixtures/esm-syntax-error'); expect(exitCode).toEqual(1); expect(output).toContain('SyntaxError'); expect(output).toContain('syntax_error.mjs'); @@ -102,14 +106,14 @@ describe('Integration', function () { } } - const {exitCode, output} = await runJasmine('spec/fixtures/esm-importing-commonjs-syntax-error', true); + const {exitCode, output} = await runJasmine('spec/fixtures/esm-importing-commonjs-syntax-error'); expect(exitCode).toEqual(1); expect(output).toContain('SyntaxError'); expect(output).toContain('syntax_error.js'); }); it('handles exceptions thrown from a module loaded from an ESM spec properly', async function() { - const {exitCode, output} = await runJasmine('spec/fixtures/esm-indirect-error', true); + const {exitCode, output} = await runJasmine('spec/fixtures/esm-indirect-error'); expect(exitCode).toEqual(1); expect(output).toContain('nope'); expect(output).toContain('throws.mjs'); @@ -118,13 +122,15 @@ describe('Integration', function () { it('can configure the env via the `env` config property', async function() { const {exitCode, output} = await runJasmine('spec/fixtures/env-config'); expect(exitCode).toEqual(0); - expect(output).toContain('in spec 1\n.in spec 2\n.in spec 3\n.in spec 4\n.in spec 5'); + expect(stripExperimentalModulesWarning(output)).toContain( + 'in spec 1\n.in spec 2\n.in spec 3\n.in spec 4\n.in spec 5' + ); }); describe('Programmatic usage', function() { it('exits on completion by default', async function() { const {exitCode, output} = await runCommand('node', ['spec/fixtures/defaultProgrammaticFail.js']); - expect(exitCode).toEqual(1); + expect(exitCode).toEqual(3); expect(output).toContain('1 spec, 1 failure'); }); @@ -152,15 +158,23 @@ describe('Integration', function () { expect(output).toContain('Promise incomplete!'); }); }); -}); -async function runJasmine(cwd, useExperimentalModulesFlag) { - const args = ['../../../bin/jasmine.js', '--config=jasmine.json']; + it('exits with status 4 when exit() is called before the suite finishes', async function() { + const {exitCode} = await runCommand('node', ['spec/fixtures/prematureExit.js']); + expect(exitCode).toEqual(4); + }); - if (useExperimentalModulesFlag) { - args.unshift('--experimental-modules'); - } + it('does not create globals when the globals option is false', async function() { + const {exitCode, output} = await runCommand('node', ['runner.js'], 'spec/fixtures/no-globals'); + expect(exitCode).toEqual(0); + expect(output).toContain('1 spec, 0 failures'); + expect(output).toContain('Globals OK'); + }); +}); + +async function runJasmine(cwd, config="jasmine.json", extraArgs = []) { + const args = ['../../../bin/jasmine.js', '--config=' + config].concat(extraArgs); return runCommand('node', args, cwd); } @@ -187,27 +201,11 @@ async function runCommand(cmd, args, cwd = '.') { }); } -async function requireFunctioningJsImport() { - if (!(await hasFunctioningJsImport())) { - pending("This Node version can't import .js files"); - } -} - -async function requireBrokenJsImport() { - if (await hasFunctioningJsImport()) { - pending("This Node version can import .js files"); - } -} - -async function hasFunctioningJsImport() { - try { - await import('./fixtures/js-loader-import/anEsModule.js'); - return true; - } catch (e) { - if (e.code === 'ERR_MODULE_NOT_FOUND') { - throw e; - } - - return false; - } +function stripExperimentalModulesWarning(jasmineOutput) { + // Node < 14 outputs a warning when ES modules are used, e.g.: + // (node:5258) ExperimentalWarning: The ESM module loader is experimental. + // The position of this warning in the output varies. Sometimes it + // occurs before the lines we're interested in but sometimes it's in + // the middle of them. + return jasmineOutput.replace(/^.*ExperimentalWarning.*$\n/m, ''); } diff --git a/spec/jasmine_spec.js b/spec/jasmine_spec.js index 6573b4b7..b3c2b3de 100644 --- a/spec/jasmine_spec.js +++ b/spec/jasmine_spec.js @@ -1,6 +1,7 @@ const path = require('path'); const slash = require('slash'); const Jasmine = require('../lib/jasmine'); +const Loader = require("../lib/loader"); describe('Jasmine', function() { beforeEach(function() { @@ -30,6 +31,27 @@ describe('Jasmine', function() { this.testJasmine.exit = function() { // Don't actually exit the node process }; + + this.execute = async function(options = {}) { + const overallStatus = options.overallStatus || 'passed'; + const executeArgs = options.executeArgs || []; + + let executePromise; + let resolveEnvExecutePromise; + const envExecutePromise = new Promise(resolve => { + resolveEnvExecutePromise = resolve; + }); + await new Promise(resolve => { + this.testJasmine.env.execute.and.callFake(function() { + resolve(); + return envExecutePromise; + }); + executePromise = this.testJasmine.execute.apply(this.testJasmine, executeArgs); + }); + + resolveEnvExecutePromise({overallStatus}); + return executePromise; + }; }); describe('constructor options', function() { @@ -55,23 +77,14 @@ describe('Jasmine', function() { }); describe('Methods that specify files via globs', function() { - describe('#addSpecFiles', function() { - hasCommonFileGlobBehavior('addSpecFiles', 'specFiles'); - }); - describe('#addMatchingSpecFiles', function() { hasCommonFileGlobBehavior('addMatchingSpecFiles', 'specFiles'); }); - describe('#addHelperFiles', function() { - hasCommonFileGlobBehavior('addHelperFiles', 'helperFiles'); - }); - describe('#addMatchingHelperFiles', function() { hasCommonFileGlobBehavior('addMatchingHelperFiles', 'helperFiles'); }); - function hasCommonFileGlobBehavior(method, destProp) { it('adds a file with an absolute path', function() { const aFile = path.join(this.testJasmine.projectBaseDir, this.testJasmine.specDir, 'spec/command_spec.js'); @@ -155,7 +168,6 @@ describe('Jasmine', function() { const reporterOptions = { print: 'printer', showColors: true, - jasmineCorePath: 'path', }; const expectedReporterOptions = Object.keys(reporterOptions).reduce(function(options, key) { @@ -176,7 +188,6 @@ describe('Jasmine', function() { const expectedReporterOptions = { print: jasmine.any(Function), showColors: true, - jasmineCorePath: path.normalize('fake/jasmine/path/jasmine.js') }; expect(this.testJasmine.reporter.setOptions).toHaveBeenCalledWith(expectedReporterOptions); @@ -190,16 +201,15 @@ describe('Jasmine', function() { describe('loading configurations', function() { beforeEach(function() { - this.loader = jasmine.createSpyObj('loader', ['load']); this.fixtureJasmine = new Jasmine({ jasmineCore: this.fakeJasmineCore, - loader: this.loader, projectBaseDir: 'spec/fixtures/sample_project' }); }); describe('from an object', function() { beforeEach(function() { + this.loader = this.fixtureJasmine.loader = jasmine.createSpyObj('loader', ['load']); this.configObject = { spec_dir: "spec", spec_files: [ @@ -297,6 +307,24 @@ describe('Jasmine', function() { }); }); + it('passes verboseDeprecations to jasmine-core when specified', function() { + this.configObject.verboseDeprecations = true; + this.fixtureJasmine.loadConfig(this.configObject); + + expect(this.fixtureJasmine.env.configure).toHaveBeenCalledWith( + jasmine.objectContaining({verboseDeprecations: true}) + ); + }); + + it('does not pass verboseDeprecations to jasmine-core when not specified', function() { + this.configObject.random = true; // or set any other property + this.fixtureJasmine.loadConfig(this.configObject); + + expect(this.fixtureJasmine.env.configure).toHaveBeenCalled(); + expect(this.fixtureJasmine.env.configure.calls.argsFor(0)[0].verboseDeprecations) + .toBeUndefined(); + }); + describe('with jsLoader: "require"', function () { it('tells the loader not to always import', async function() { this.configObject.jsLoader = 'require'; @@ -304,7 +332,8 @@ describe('Jasmine', function() { this.fixtureJasmine.loadConfig(this.configObject); await this.fixtureJasmine.loadSpecs(); - expect(this.loader.load).toHaveBeenCalledWith(jasmine.any(String), false); + expect(this.loader.load).toHaveBeenCalledWith(jasmine.any(String)); + expect(this.loader.alwaysImport).toBeFalse(); }); }); @@ -315,7 +344,8 @@ describe('Jasmine', function() { this.fixtureJasmine.loadConfig(this.configObject); await this.fixtureJasmine.loadSpecs(); - expect(this.loader.load).toHaveBeenCalledWith(jasmine.any(String), true); + expect(this.loader.load).toHaveBeenCalledWith(jasmine.any(String)); + expect(this.loader.alwaysImport).toBeTrue(); }); }); @@ -329,20 +359,21 @@ describe('Jasmine', function() { }); describe('with jsLoader undefined', function () { - it('tells the loader not to always import', async function() { + it('tells the loader to always import', async function() { this.configObject.jsLoader = undefined; - + this.fixtureJasmine.loadConfig(this.configObject); await this.fixtureJasmine.loadSpecs(); - expect(this.loader.load).toHaveBeenCalledWith(jasmine.any(String), false); + expect(this.loader.load).toHaveBeenCalledWith(jasmine.any(String)); + expect(this.loader.alwaysImport).toBeTrue(); }); }); }); describe('from a file', function() { - it('adds unique specs to the jasmine runner', function() { - this.fixtureJasmine.loadConfigFile('spec/support/jasmine_alternate.json'); + it('adds unique specs to the jasmine runner', async function() { + await this.fixtureJasmine.loadConfigFile('spec/support/jasmine_alternate.json'); expect(this.fixtureJasmine.helperFiles).toEqual(['spec/fixtures/sample_project/spec/helper.js']); expect(this.fixtureJasmine.requires).toEqual(['ts-node/register']); expect(this.fixtureJasmine.specFiles).toEqual([ @@ -351,9 +382,29 @@ describe('Jasmine', function() { ]); }); - it('loads the specified configuration file from an absolute path', function() { + it('can use an ES module', async function() { + await this.fixtureJasmine.loadConfigFile('spec/support/jasmine_alternate.mjs'); + expect(this.fixtureJasmine.helperFiles).toEqual(['spec/fixtures/sample_project/spec/helper.js']); + expect(this.fixtureJasmine.requires).toEqual(['ts-node/register']); + expect(this.fixtureJasmine.specFiles).toEqual([ + 'spec/fixtures/sample_project/spec/fixture_spec.js', + 'spec/fixtures/sample_project/spec/other_fixture_spec.js' + ]); + }); + + it('can use a CommonJS module', async function() { + await this.fixtureJasmine.loadConfigFile('spec/support/jasmine_alternate.cjs'); + expect(this.fixtureJasmine.helperFiles).toEqual(['spec/fixtures/sample_project/spec/helper.js']); + expect(this.fixtureJasmine.requires).toEqual(['ts-node/register']); + expect(this.fixtureJasmine.specFiles).toEqual([ + 'spec/fixtures/sample_project/spec/fixture_spec.js', + 'spec/fixtures/sample_project/spec/other_fixture_spec.js' + ]); + }); + + it('loads the specified configuration file from an absolute path', async function() { const absoluteConfigPath = path.join(__dirname, 'fixtures/sample_project/spec/support/jasmine_alternate.json'); - this.fixtureJasmine.loadConfigFile(absoluteConfigPath); + await this.fixtureJasmine.loadConfigFile(absoluteConfigPath); expect(this.fixtureJasmine.helperFiles).toEqual(['spec/fixtures/sample_project/spec/helper.js']); expect(this.fixtureJasmine.requires).toEqual(['ts-node/register']); expect(this.fixtureJasmine.specFiles).toEqual([ @@ -362,23 +413,38 @@ describe('Jasmine', function() { ]); }); - it('throw error if specified configuration file doesn\'t exist', function() { - const jasmine = this.fixtureJasmine; - function load() { jasmine.loadConfigFile('missing.json'); } - expect(load).toThrow(); + it("throws an error if the specified configuration file doesn't exist", async function() { + await expectAsync(this.fixtureJasmine.loadConfigFile('missing.json')).toBeRejected(); + }); + + it("does not throw if the default configuration files don't exist", async function() { + this.fixtureJasmine.projectBaseDir += '/missing'; + await expectAsync(this.fixtureJasmine.loadConfigFile()).toBeResolved(); }); - it('no error if default configuration file doesn\'t exist', function() { - const jasmine = this.fixtureJasmine; - function load() { - jasmine.projectBaseDir += '/missing'; - jasmine.loadConfigFile(); - } - expect(load).not.toThrow(); + it('loads the default .json configuration file', async function() { + await this.fixtureJasmine.loadConfigFile(); + expect(this.fixtureJasmine.specFiles).toEqual([ + jasmine.stringMatching('^spec[\\/]fixtures[\\/]sample_project[\\/]spec[\\/]fixture_spec.js$') + ]); }); - it('loads the default configuration file', function() { - this.fixtureJasmine.loadConfigFile(); + it('loads the default .js configuration file', async function() { + const config = require('./fixtures/sample_project/spec/support/jasmine.json'); + spyOn(Loader.prototype, 'load').and.callFake(function(path) { + if (path.endsWith('jasmine.js')) { + return Promise.resolve(config); + } else { + const e = new Error(`Module not found: ${path}`); + e.code = 'MODULE_NOT_FOUND'; + return Promise.reject(e); + } + }); + + await this.fixtureJasmine.loadConfigFile(); + expect(Loader.prototype.load).toHaveBeenCalledWith(jasmine.stringMatching( + 'jasmine\.js$' + )); expect(this.fixtureJasmine.specFiles).toEqual([ 'spec/fixtures/sample_project/spec/fixture_spec.js' ]); @@ -407,16 +473,6 @@ describe('Jasmine', function() { }); }); - describe('#onComplete', function() { - it('stores an onComplete function', function() { - const fakeOnCompleteCallback = function() {}; - spyOn(this.testJasmine.completionReporter, 'onComplete'); - - this.testJasmine.onComplete(fakeOnCompleteCallback); - expect(this.testJasmine.completionReporter.onComplete).toHaveBeenCalledWith(fakeOnCompleteCallback); - }); - }); - it("showing colors can be configured", function() { expect(this.testJasmine.showingColors).toBe(true); @@ -430,7 +486,7 @@ describe('Jasmine', function() { spyOn(this.testJasmine, 'configureDefaultReporter'); spyOn(this.testJasmine, 'loadSpecs'); - await this.testJasmine.execute(); + await this.execute(); expect(this.testJasmine.configureDefaultReporter).toHaveBeenCalledWith({showColors: true}); expect(this.testJasmine.loadSpecs).toHaveBeenCalled(); @@ -442,7 +498,7 @@ describe('Jasmine', function() { spyOn(this.testJasmine, 'loadSpecs'); this.testJasmine.showColors(false); - await this.testJasmine.execute(); + await this.execute(); expect(this.testJasmine.configureDefaultReporter).toHaveBeenCalledWith({showColors: false}); expect(this.testJasmine.loadSpecs).toHaveBeenCalled(); @@ -456,7 +512,7 @@ describe('Jasmine', function() { spyOn(this.testJasmine, 'configureDefaultReporter'); - await this.testJasmine.execute(); + await this.execute(); expect(this.testJasmine.configureDefaultReporter).not.toHaveBeenCalled(); expect(this.testJasmine.loadSpecs).toHaveBeenCalled(); @@ -470,18 +526,15 @@ describe('Jasmine', function() { }); spyOn(this.testJasmine, 'loadSpecs'); - await this.testJasmine.execute(); + await this.execute(); expect(this.testJasmine.configureDefaultReporter).toHaveBeenCalled(); }); it('can run only specified files', async function() { - spyOn(this.testJasmine, 'configureDefaultReporter'); - spyOn(this.testJasmine, 'loadSpecs'); - - this.testJasmine.loadConfigFile(); - - await this.testJasmine.execute(['spec/fixtures/sample_project/**/*spec.js']); + await this.execute({ + executeArgs: [['spec/fixtures/sample_project/**/*spec.js']] + }); const relativePaths = this.testJasmine.specFiles.map(function(filePath) { return slash(path.relative(__dirname, filePath)); @@ -491,167 +544,54 @@ describe('Jasmine', function() { }); it('should add spec filter if filterString is provided', async function() { - this.testJasmine.loadConfigFile(); - - await this.testJasmine.execute(['spec/fixtures/example/*spec.js'], 'interesting spec'); - expect(this.testJasmine.env.configure).toHaveBeenCalledWith({specFilter: jasmine.any(Function)}); - }); - - it('adds an exit code reporter the first time execute is called', async function() { - const completionReporterSpy = jasmine.createSpyObj('reporter', ['onComplete']); - this.testJasmine.completionReporter = completionReporterSpy; - spyOn(this.testJasmine, 'addReporter'); - - await this.testJasmine.execute(); - - expect(this.testJasmine.addReporter).toHaveBeenCalledWith(completionReporterSpy); - expect(this.testJasmine.completionReporter.exitHandler).toBe(this.testJasmine.checkExit); - this.testJasmine.addReporter.calls.reset(); - - await this.testJasmine.execute(); - expect(this.testJasmine.addReporter).not.toHaveBeenCalledWith( - completionReporterSpy - ); - }); - - describe('when exit is called prematurely', function() { - beforeEach(function() { - this.originalCode = process.exitCode; + await this.execute({ + executeArgs: [['spec/fixtures/example/*spec.js'], 'interesting spec'] }); - afterEach(function() { - process.exitCode = this.originalCode; - }); - - it('sets the exit code to failure', function() { - this.testJasmine.checkExit(); - expect(process.exitCode).toEqual(4); - }); - - it('leaves it if the suite has completed', function() { - const completionReporterSpy = jasmine.createSpyObj('reporter', ['isComplete']); - completionReporterSpy.isComplete.and.returnValue(true); - this.testJasmine.completionReporter = completionReporterSpy; - - this.testJasmine.checkExit(); - expect(process.exitCode).toBeUndefined(); - }); + expect(this.testJasmine.env.configure).toHaveBeenCalledWith({specFilter: jasmine.any(Function)}); }); describe('completion behavior', function() { beforeEach(function() { - this.runWithOverallStatus = async function(overallStatus) { - const reporters = []; - this.testJasmine.env = { - execute: jasmine.createSpy('env.execute'), - addReporter: reporter => { - reporters.push(reporter); - } - }; - spyOn(this.testJasmine, 'exit'); - - await new Promise(resolve => { - this.testJasmine.env.execute.and.callFake(resolve); - this.testJasmine.execute(); - }); - - for (const reporter of reporters) { - reporter.jasmineDone({overallStatus}); - } - - await sleep(10); - }; - - function sleep(ms) { - return new Promise(function (resolve) { - setTimeout(resolve, ms); - }); - } + spyOn(this.testJasmine, 'exit'); }); describe('default', function() { it('exits successfully when the whole suite is green', async function () { - await this.runWithOverallStatus('passed'); + await this.execute({overallStatus: 'passed'}); expect(this.testJasmine.exit).toHaveBeenCalledWith(0); }); - it('exits with a failure when anything in the suite is not green', async function () { - await this.runWithOverallStatus('failed'); - expect(this.testJasmine.exit).toHaveBeenCalledWith(1); + it('exits with a distinct status code when anything in the suite is not green', async function () { + await this.execute({overallStatus: 'failed'}); + expect(this.testJasmine.exit).toHaveBeenCalledWith(3); }); - }); - describe('When exitOnCompletion is set to false', function() { - it('does not exit', async function() { - this.testJasmine.exitOnCompletion = false; - await this.runWithOverallStatus('anything'); - expect(this.testJasmine.exit).not.toHaveBeenCalled(); + it('exits with a distinct status code when anything in the suite is focused', async function() { + await this.execute({overallStatus: 'incomplete'}); + expect(this.testJasmine.exit).toHaveBeenCalledWith(2); }); }); - describe('When #onComplete has been called', function() { - it('calls the supplied completion handler with true when the whole suite is green', async function() { - const completionHandler = jasmine.createSpy('completionHandler'); - this.testJasmine.onComplete(completionHandler); - await this.runWithOverallStatus('passed'); - expect(completionHandler).toHaveBeenCalledWith(true); - }); - - it('calls the supplied completion handler with false when anything in the suite is not green', async function() { - const completionHandler = jasmine.createSpy('completionHandler'); - this.testJasmine.onComplete(completionHandler); - await this.runWithOverallStatus('failed'); - expect(completionHandler).toHaveBeenCalledWith(false); - }); - + describe('When exitOnCompletion is set to false', function() { it('does not exit', async function() { - this.testJasmine.onComplete(function() {}); - await this.runWithOverallStatus('anything'); - expect(this.testJasmine.exit).not.toHaveBeenCalled(); - }); - - it('ignores exitOnCompletion', async function() { - this.testJasmine.onComplete(function() {}); - this.testJasmine.exitOnCompletion = true; - await this.runWithOverallStatus('anything'); + this.testJasmine.exitOnCompletion = false; + await this.execute(); expect(this.testJasmine.exit).not.toHaveBeenCalled(); }); }); }); describe('The returned promise', function() { - beforeEach(function() { - this.autocompletingFakeEnv = function(overallStatus) { - let reporters = []; - return { - execute: function(ignored, callback) { - for (const reporter of reporters) { - reporter.jasmineDone({overallStatus}); - } - callback(); - }, - addReporter: reporter => { - reporters.push(reporter); - }, - clearReporters: function() { - reporters = []; - } - }; - }; - }); - it('is resolved with the overall suite status', async function() { - this.testJasmine.env = this.autocompletingFakeEnv('failed'); - - await expectAsync(this.testJasmine.execute()) + await expectAsync(this.execute({overallStatus: 'failed'})) .toBeResolvedTo(jasmine.objectContaining({overallStatus: 'failed'})); }); it('is resolved with the overall suite status even if clearReporters was called', async function() { - this.testJasmine.env = this.autocompletingFakeEnv('incomplete'); this.testJasmine.clearReporters(); - await expectAsync(this.testJasmine.execute()) + await expectAsync(this.execute({overallStatus: 'incomplete'})) .toBeResolvedTo(jasmine.objectContaining({overallStatus: 'incomplete'})); }); }); diff --git a/spec/load_config_spec.js b/spec/load_config_spec.js deleted file mode 100644 index fa45184b..00000000 --- a/spec/load_config_spec.js +++ /dev/null @@ -1,52 +0,0 @@ -const path = require('path'); -const loadConfig = require('../lib/loadConfig'); - -describe('loadConfig', function() { - it('should configure the jasmine object based on env and call execute', function() { - const fakeJasmine = jasmine.createSpyObj('jasmine', ['loadConfigFile', 'addHelperFiles', 'addRequires', 'showColors', 'execute', 'stopSpecOnExpectationFailure', - 'stopOnSpecFailure', 'randomizeTests', 'seed', 'coreVersion', 'clearReporters', 'addReporter']), - env = { - configPath: 'somewhere.json', - stopOnFailure: true, - failFast: true, - seed: 12345, - random: true, - helpers: 'helpers/**/*.js', - requires: 'requires/**/*.js', - reporter: path.resolve(path.join(__dirname, 'fixtures', 'customReporter.js')), - color: true, - files: 'specs/**/*.spec.js', - filter: 'filter' - }; - loadConfig(fakeJasmine, env, console.log); - - expect(fakeJasmine.loadConfigFile).toHaveBeenCalledWith(env.configPath); - expect(fakeJasmine.stopSpecOnExpectationFailure).toHaveBeenCalledWith(env.stopOnFailure); - expect(fakeJasmine.stopOnSpecFailure).toHaveBeenCalledWith(env.failFast); - expect(fakeJasmine.seed).toHaveBeenCalledWith(env.seed); - expect(fakeJasmine.randomizeTests).toHaveBeenCalledWith(env.random); - expect(fakeJasmine.addHelperFiles).toHaveBeenCalledWith(env.helpers); - expect(fakeJasmine.addRequires).toHaveBeenCalledWith(env.requires); - expect(fakeJasmine.clearReporters).toHaveBeenCalled(); - expect(fakeJasmine.addReporter).toHaveBeenCalled(); - expect(fakeJasmine.showColors).toHaveBeenCalledWith(env.color); - }); - - it('should not configure the jasmine object when env is an empty object and call execute', function() { - const fakeJasmine = jasmine.createSpyObj('jasmine', ['loadConfigFile', 'addHelperFiles', 'addRequires', 'showColors', 'execute', 'stopSpecOnExpectationFailure', - 'stopOnSpecFailure', 'randomizeTests', 'seed', 'coreVersion', 'clearReporters', 'addReporter']), - env = {}; - loadConfig(fakeJasmine, env, console.log); - - expect(fakeJasmine.loadConfigFile).toHaveBeenCalled(); - expect(fakeJasmine.stopSpecOnExpectationFailure).not.toHaveBeenCalled(); - expect(fakeJasmine.stopOnSpecFailure).not.toHaveBeenCalled(); - expect(fakeJasmine.seed).not.toHaveBeenCalled(); - expect(fakeJasmine.randomizeTests).not.toHaveBeenCalled(); - expect(fakeJasmine.addHelperFiles).not.toHaveBeenCalled(); - expect(fakeJasmine.addRequires).not.toHaveBeenCalled(); - expect(fakeJasmine.clearReporters).not.toHaveBeenCalled(); - expect(fakeJasmine.addReporter).not.toHaveBeenCalled(); - expect(fakeJasmine.showColors).toHaveBeenCalled(); - }); -}); diff --git a/spec/loader_spec.js b/spec/loader_spec.js index f0bfcc92..db54af22 100644 --- a/spec/loader_spec.js +++ b/spec/loader_spec.js @@ -5,6 +5,10 @@ describe('loader', function() { delete global.require_tester_was_loaded; }); + it('sets alwaysImport to true by default', function() { + expect(new Loader().alwaysImport).toBeTrue(); + }); + describe('#load', function() { describe('With alwaysImport: true', function() { describe('When the path ends in .mjs', function () { @@ -14,6 +18,19 @@ describe('loader', function() { describe('When the path does not end in .mjs', function () { esModuleSharedExamples('js', true); }); + + it('uses require to load JSON files', async function() { + const requireShim = jasmine.createSpy('requireShim') + .and.returnValue(Promise.resolve()); + const importShim = jasmine.createSpy('importShim'); + const loader = new Loader({requireShim, importShim}); + loader.alwaysImport = true; + + await expectAsync(loader.load('./jasmine.json')).toBeResolved(); + + expect(requireShim).toHaveBeenCalledWith('./jasmine.json'); + expect(importShim).not.toHaveBeenCalled(); + }); }); describe('With alwaysImport: false', function() { @@ -21,14 +38,28 @@ describe('loader', function() { esModuleSharedExamples('mjs', false); }); + it('uses require to load JSON files', async function() { + const requireShim = jasmine.createSpy('requireShim') + .and.returnValue(Promise.resolve()); + const importShim = jasmine.createSpy('importShim'); + const loader = new Loader({requireShim, importShim}); + loader.alwaysImport = false; + + await expectAsync(loader.load('./jasmine.json')).toBeResolved(); + + expect(requireShim).toHaveBeenCalledWith('./jasmine.json'); + expect(importShim).not.toHaveBeenCalled(); + }); + describe('When the path does not end in .mjs', function () { it('loads the file as a commonjs module', async function () { const requireShim = jasmine.createSpy('requireShim') .and.returnValue(Promise.resolve()); const importShim = jasmine.createSpy('importShim'); const loader = new Loader({requireShim, importShim}); + loader.alwaysImport = false; - await expectAsync(loader.load('./foo/bar/baz', false)).toBeResolved(); + await expectAsync(loader.load('./foo/bar/baz')).toBeResolved(); expect(requireShim).toHaveBeenCalledWith('./foo/bar/baz'); expect(importShim).not.toHaveBeenCalled(); @@ -39,7 +70,8 @@ describe('loader', function() { const requireShim = jasmine.createSpy('requireShim') .and.throwError(underlyingError); const importShim = jasmine.createSpy('importShim'); - const loader = new Loader({requireShim, importShim}, false); + const loader = new Loader({requireShim, importShim}); + loader.alwaysImport = false; await expectAsync(loader.load('foo')).toBeRejectedWith(underlyingError); }); @@ -57,15 +89,19 @@ function esModuleSharedExamples(extension, alwaysImport) { }); const importShim = jasmine.createSpy('importShim') .and.returnValue(importPromise); - const loader = new Loader({requireShim, importShim}); + const resolvePath = jasmine.createSpy('resolvePath') + .and.returnValue('/the/path/to/the/module'); + const loader = new Loader({requireShim, importShim, resolvePath}); + loader.alwaysImport = alwaysImport; - const loaderPromise = loader.load(`./foo/bar/baz.${extension}`, alwaysImport); + const loaderPromise = loader.load(`./foo/bar/baz.${extension}`); expect(requireShim).not.toHaveBeenCalled(); - expect(importShim).toHaveBeenCalledWith(`file://./foo/bar/baz.${extension}`); + expect(resolvePath).toHaveBeenCalledWith(`./foo/bar/baz.${extension}`); + expect(importShim).toHaveBeenCalledWith('file:///the/path/to/the/module'); await expectAsync(loaderPromise).toBePending(); - resolve(); + resolve({}); await expectAsync(loaderPromise).toBeResolved(); }); diff --git a/spec/reporters/completion_reporter_spec.js b/spec/reporters/completion_reporter_spec.js deleted file mode 100644 index 97c3a386..00000000 --- a/spec/reporters/completion_reporter_spec.js +++ /dev/null @@ -1,42 +0,0 @@ -const CompletionReporter = require('../../lib/reporters/completion_reporter'); - -describe('CompletionReporter', function() { - beforeEach(function() { - this.reporter = new CompletionReporter(); - this.onComplete = jasmine.createSpy('onComplete'); - this.reporter.onComplete(this.onComplete); - this.processOn = spyOn(process, 'on').and.callThrough(); - this.processOff = spyOn(process, 'removeListener').and.callThrough(); - }); - - describe('When the overall status is "passed"', function() { - it('calls the completion callback with true', function() { - this.reporter.jasmineDone({overallStatus: 'passed'}); - expect(this.onComplete).toHaveBeenCalledWith(true); - }); - }); - - describe('When the overall status is anything else', function() { - it('calls the completion callback with false', function() { - this.reporter.jasmineDone({overallStatus: 'incomplete'}); - expect(this.onComplete).toHaveBeenCalledWith(false); - }); - }); - - describe('When jasmine is started and done', function() { - it('adds and removes the exit handler', function() { - this.reporter.exitHandler = function() {}; - this.reporter.jasmineStarted(); - this.reporter.jasmineDone({overallStatus: 'passed'}); - expect(this.processOn).toHaveBeenCalledWith('exit', this.reporter.exitHandler); - expect(this.processOff).toHaveBeenCalledWith('exit', this.reporter.exitHandler); - }); - - it('ignores the exit event if there is no exit handler', function() { - this.reporter.jasmineStarted(); - this.reporter.jasmineDone({overallStatus: 'passed'}); - expect(this.processOn).toHaveBeenCalledTimes(0); - expect(this.processOff).toHaveBeenCalledTimes(0); - }); - }); -}); diff --git a/spec/reporters/console_reporter_spec.js b/spec/reporters/console_reporter_spec.js index c42dcf3e..39eddb1d 100644 --- a/spec/reporters/console_reporter_spec.js +++ b/spec/reporters/console_reporter_spec.js @@ -1,12 +1,6 @@ const ConsoleReporter = require('../../lib/reporters/console_reporter'); -const jasmineCorePath = 'path/to/jasmine/core/jasmine.js'; describe("ConsoleReporter", function() { - const fakeStack = ['foo' + jasmineCorePath, - 'bar ' + jasmineCorePath, - 'line of useful stack trace', - 'baz ' + jasmineCorePath].join('\n'); - beforeEach(function() { this.out = (function() { let output = ""; @@ -145,7 +139,7 @@ describe("ConsoleReporter", function() { reporter.jasmineStarted(); this.out.clear(); - reporter.jasmineDone(); + reporter.jasmineDone({}); expect(this.out.getOutput()).toMatch(/No specs found/); }); @@ -222,7 +216,7 @@ describe("ConsoleReporter", function() { message: "Expected true to be false.", expected: false, actual: true, - stack: fakeStack + stack: '' } ], passedExpectations: [] @@ -236,6 +230,42 @@ describe("ConsoleReporter", function() { expect(this.out.getOutput()).toMatch("Finished in 0.1 seconds\n"); }); + it('counts failures that are reported in the jasmineDone event', function() { + const reporter = new ConsoleReporter(); + reporter.setOptions({ + print: this.out.print, + }); + + reporter.jasmineStarted(); + reporter.specDone({ + status: "failed", + description: "with a failing spec", + fullName: "with a failing spec", + failedExpectations: [ + { + message: "Expected true to be false.", + } + ], + passedExpectations: [] + }); + + this.out.clear(); + + reporter.jasmineDone({ + totalTime: 100, + failedExpectations: [ + { + message: "Expected true to be false.", + }, + { + message: "Expected true to be false.", + } + ], + }); + + expect(this.out.getOutput()).toMatch(/1 spec, 3 failures/); + }); + it("reports a summary when done that indicates the number of specs run (when it's less that the full number of specs)", function() { const reporter = new ConsoleReporter(); reporter.setOptions({ @@ -257,7 +287,6 @@ describe("ConsoleReporter", function() { const reporter = new ConsoleReporter(); reporter.setOptions({ print: this.out.print, - jasmineCorePath: jasmineCorePath }); reporter.jasmineStarted(); @@ -272,7 +301,7 @@ describe("ConsoleReporter", function() { message: "Expected true to be false.", expected: false, actual: true, - stack: fakeStack + stack: '' } ], passedExpectations: [] @@ -280,16 +309,15 @@ describe("ConsoleReporter", function() { this.out.clear(); - reporter.jasmineDone(); + reporter.jasmineDone({}); expect(this.out.getOutput()).toMatch(/1\) A suite with a failing spec/); }); - it("reports a summary when done that includes stack traces without jasmine internals for a failing suite", function() { + it("reports a summary when done that includes stack traces for a failing suite", function() { const reporter = new ConsoleReporter(); reporter.setOptions({ - print: this.out.print, - jasmineCorePath: jasmineCorePath + print: this.out.print }); reporter.jasmineStarted(); @@ -304,7 +332,7 @@ describe("ConsoleReporter", function() { message: "Expected true to be false.", expected: false, actual: true, - stack: fakeStack + stack: 'line 1\nline 2' } ], passedExpectations: [] @@ -312,18 +340,17 @@ describe("ConsoleReporter", function() { this.out.clear(); - reporter.jasmineDone(); + reporter.jasmineDone({}); expect(this.out.getOutput()).toMatch(/true to be false/); - expect(this.out.getOutput()).toMatch(/line of useful stack trace/); - expect(this.out.getOutput()).not.toMatch(jasmineCorePath); + expect(this.out.getOutput()).toMatch(/line 1/); + expect(this.out.getOutput()).toMatch(/line 2/); }); it("reports a summary when done in case that stack is somehow undefined", function() { const reporter = new ConsoleReporter(); reporter.setOptions({ print: this.out.print, - jasmineCorePath: jasmineCorePath }); reporter.jasmineStarted(); @@ -346,7 +373,7 @@ describe("ConsoleReporter", function() { this.out.clear(); - reporter.jasmineDone(); + reporter.jasmineDone({}); expect(this.out.getOutput()).toMatch(/true to be false/); }); @@ -374,7 +401,7 @@ describe("ConsoleReporter", function() { message: "Expected true to be false.", expected: false, actual: true, - stack: fakeStack + stack: 'the original stack trace' } ], passedExpectations: [] @@ -382,7 +409,7 @@ describe("ConsoleReporter", function() { this.out.clear(); - reporter.jasmineDone(); + reporter.jasmineDone({}); expect(this.out.getOutput()).toMatch(/true to be false/); expect(this.out.getOutput()).toMatch(stackLine); @@ -392,7 +419,6 @@ describe("ConsoleReporter", function() { const reporter = new ConsoleReporter(); reporter.setOptions({ print: this.out.print, - jasmineCorePath: jasmineCorePath }); reporter.jasmineStarted(); @@ -406,7 +432,7 @@ describe("ConsoleReporter", function() { this.out.clear(); - reporter.jasmineDone(); + reporter.jasmineDone({}); expect(this.out.getOutput()).toContain("A suite with a pending spec"); expect(this.out.getOutput()).toContain("It's not ready yet!"); @@ -416,7 +442,6 @@ describe("ConsoleReporter", function() { const reporter = new ConsoleReporter(); reporter.setOptions({ print: this.out.print, - jasmineCorePath: jasmineCorePath }); reporter.jasmineStarted(); @@ -435,7 +460,6 @@ describe("ConsoleReporter", function() { const reporter = new ConsoleReporter(); reporter.setOptions({ print: this.out.print, - jasmineCorePath: jasmineCorePath }); reporter.jasmineStarted(); @@ -450,7 +474,7 @@ describe("ConsoleReporter", function() { this.out.clear(); - reporter.jasmineDone(); + reporter.jasmineDone({}); expect(this.out.getOutput()).toContain("Spec has no expectations"); }); @@ -459,7 +483,6 @@ describe("ConsoleReporter", function() { const reporter = new ConsoleReporter(); reporter.setOptions({ print: this.out.print, - jasmineCorePath: jasmineCorePath }); reporter.jasmineStarted(); @@ -479,16 +502,15 @@ describe("ConsoleReporter", function() { this.out.clear(); - reporter.jasmineDone(); + reporter.jasmineDone({}); expect(this.out.getOutput()).not.toContain("Spec has no expectations"); }); - it('reports a summary with trace info for a failed spec with trace', function() { + it('reports a summary with debug log info for a failed spec with debug logs', function() { const reporter = new ConsoleReporter(); reporter.setOptions({ print: this.out.print, - jasmineCorePath: jasmineCorePath }); reporter.jasmineStarted(); @@ -498,21 +520,20 @@ describe("ConsoleReporter", function() { fullName: "A suite with a failing spec that has a trace", failedExpectations: [], passedExpectations: [], - trace: [ + debugLogs: [ {timestamp: 1, message: 'msg 1'}, {timestamp: 100, message: 'msg 2'}, ] }); - reporter.jasmineDone(); + reporter.jasmineDone({}); - expect(this.out.getOutput()).toContain(' Trace:\n 1ms: msg 1\n 100ms: msg 2'); + expect(this.out.getOutput()).toContain(' Debug logs:\n 1ms: msg 1\n 100ms: msg 2'); }); it('reports a summary without a "no expectations" message for a spec having passed expectations', function () { const reporter = new ConsoleReporter(); reporter.setOptions({ print: this.out.print, - jasmineCorePath: jasmineCorePath }); reporter.jasmineStarted(); @@ -542,7 +563,7 @@ describe("ConsoleReporter", function() { this.out.clear(); - reporter.jasmineDone(); + reporter.jasmineDone({}); expect(this.out.getOutput()).not.toContain("Spec has no expectations"); }); diff --git a/tasks/jasmine.js b/tasks/jasmine.js deleted file mode 100644 index b137c18e..00000000 --- a/tasks/jasmine.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict'; - -module.exports = function(grunt) { - var Jasmine = require('../lib/jasmine'); - - grunt.registerTask('specs', function() { - var jasmine = new Jasmine(); - var done = this.async(); - - jasmine.loadConfigFile(process.env.JASMINE_CONFIG_PATH || './spec/support/jasmine.json'); - jasmine.onComplete(done); - jasmine.execute(); - }); -};