Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: mochajs/mocha
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 5bd33a0ba201d227159759e8ced86756595b0c54
Choose a base ref
...
head repository: mochajs/mocha
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: eb781e25a0c212aca8c39a0c7c82d426442def68
Choose a head ref

Commits on Apr 8, 2018

  1. Tap reporter: report thrown error messages (#3317)

    original work by @chrmod; continuation of PR #2908. 
    
    BREAKING CHANGES; SEMVER MAJOR
    
    Signed-off-by: Christopher Hiller <boneskull@boneskull.com>
    boneskull authored Apr 8, 2018
    Copy the full SHA
    1acea30 View commit details

Commits on Apr 12, 2018

  1. Implement API documentation using npm:documantation.

    Fixes #3138
    Replaces and closes #3239
    
    basically working
    
    Suite and utils work
    
    Runner and Suite datatypes work
    
    Hook extends Runnable - at least link in description
    
    hook is child of mocha and has error
    
    first solid pass
    
    Remove attempted module link from Hook to Runnable because of documentationjs/documentation#820
    
    Switch API documenation to html output, link to them, include in site build
    
    Update TOC
    
    Bring lock file in sync with package.json
    
    Linting
    
    Update package-lock.json
    dfberry authored and Munter committed Apr 12, 2018
    Copy the full SHA
    2f48a23 View commit details
  2. update CHANGELOG for v5.1.0 [ci skip]

    Signed-off-by: Christopher Hiller <boneskull@boneskull.com>
    boneskull committed Apr 12, 2018
    Copy the full SHA
    56e8452 View commit details
  3. Release v5.1.0

    Signed-off-by: Christopher Hiller <boneskull@boneskull.com>
    boneskull committed Apr 12, 2018
    Copy the full SHA
    3ac8e55 View commit details
  4. update package-lock.json [ci skip]

    Signed-off-by: Christopher Hiller <boneskull@boneskull.com>
    boneskull committed Apr 12, 2018
    Copy the full SHA
    58efe58 View commit details

Commits on Apr 18, 2018

  1. fix ESLint problems and consolidate configuration

    - we weren't being strict enough about disallowing ES6.  even though we
      had `es6: false` in the config, the `parserOptions` were set to use
      `ecmaVersion: 8`.  oops
    - linted a couple files
    - deleted all `.eslintrc.yml` files except for the root one; use `overrides`
      instead
    
    Signed-off-by: Christopher Hiller <boneskull@boneskull.com>
    boneskull committed Apr 18, 2018
    Copy the full SHA
    2d1b49c View commit details
  2. update outdated info in docs/README.md [ci skip]

    Signed-off-by: Christopher Hiller <boneskull@boneskull.com>
    boneskull committed Apr 18, 2018
    Copy the full SHA
    07e1ed2 View commit details
  3. update CHANGELOG.md for v5.1.1 [ci skip]

    Signed-off-by: Christopher Hiller <boneskull@boneskull.com>
    boneskull committed Apr 18, 2018
    Copy the full SHA
    d2f6a25 View commit details
  4. Release v5.1.1

    Signed-off-by: Christopher Hiller <boneskull@boneskull.com>
    boneskull committed Apr 18, 2018
    Copy the full SHA
    471ab31 View commit details
  5. Copy the full SHA
    a6115ac View commit details
  6. remove Node.js v7 from AppVeyor build; add Node.js v9

    Signed-off-by: Christopher Hiller <boneskull@boneskull.com>
    boneskull committed Apr 18, 2018
    Copy the full SHA
    e288dbb View commit details
  7. fixed redundant images folders

    loged in commit for CLA (+1 squashed commit)
    Squashed commits:
    [0f603e3] fixed redundant images folders
    
    fixed path in bin/_mocha
    DavNej authored and boneskull committed Apr 18, 2018
    2
    Copy the full SHA
    ad54763 View commit details
  8. update contributors [ci skip]

    Signed-off-by: Christopher Hiller <boneskull@boneskull.com>
    boneskull committed Apr 18, 2018
    Copy the full SHA
    d3d7799 View commit details

Commits on Apr 21, 2018

  1. fix growl asset paths

    Signed-off-by: Christopher Hiller <boneskull@boneskull.com>
    boneskull committed Apr 21, 2018
    Copy the full SHA
    8a909b5 View commit details
  2. remove old svg logo

    Signed-off-by: Christopher Hiller <boneskull@boneskull.com>
    boneskull committed Apr 21, 2018
    Copy the full SHA
    3a65a64 View commit details
  3. Copy the full SHA
    9270018 View commit details
  4. Fix missing LICENSE

    Munter committed Apr 21, 2018
    Copy the full SHA
    3368a7e View commit details
  5. Copy the full SHA
    36e2086 View commit details
  6. Copy the full SHA
    82ced6c View commit details
  7. use renamed @mocha/docdash

    Signed-off-by: Christopher Hiller <boneskull@boneskull.com>
    boneskull authored and Munter committed Apr 21, 2018
    Copy the full SHA
    7f8fa42 View commit details
  8. use custom docs/API.md instead of broken README.md

    Signed-off-by: Christopher Hiller <boneskull@boneskull.com>
    boneskull authored and Munter committed Apr 21, 2018
    Copy the full SHA
    efff26f View commit details

Commits on Apr 22, 2018

  1. add Prettier, ESLint and githook integrations

    Signed-off-by: Christopher Hiller <boneskull@boneskull.com>
    boneskull committed Apr 22, 2018
    Copy the full SHA
    58731d3 View commit details
  2. reformat everything with Prettier

    Signed-off-by: Christopher Hiller <boneskull@boneskull.com>
    boneskull committed Apr 22, 2018
    Copy the full SHA
    5fc5845 View commit details

Commits on Apr 23, 2018

  1. fix(bin/_mocha): Make --watch-extensions default to 'js'

    The change in c580294 to `--watch-extensions` failed to set a default value. This change specifies
    that default value.
    
    Fixes #3336
    plroebuck authored and boneskull committed Apr 23, 2018
    Copy the full SHA
    9d407de View commit details

Commits on Apr 24, 2018

  1. update package-lock.json for npm@6 [ci skip]

    Signed-off-by: Christopher Hiller <boneskull@boneskull.com>
    boneskull committed Apr 24, 2018
    Copy the full SHA
    a7b1139 View commit details

Commits on Apr 27, 2018

  1. add Node.js v10 to build; fix win32 issues (#3350)

    * fix npm version to 5.x
    * AppVeyor: add Node.js v10; use npm ci for speed
    * revert package-lock.json to what npm@5 uses
    * add .gitattributes
    * remove linebreak-style from eslintrc
    * avoid all symlink tests on win32
    
    Signed-off-by: Christopher Hiller <boneskull@boneskull.com>
    boneskull authored Apr 27, 2018
    1
    Copy the full SHA
    1224a48 View commit details
  2. Copy the full SHA
    99c2f8a View commit details

Commits on Apr 30, 2018

  1. fix(ocd): re-order Node.js tests in .travis.yml (descending)

    Signed-off-by: Christopher Hiller <boneskull@boneskull.com>
    boneskull committed Apr 30, 2018
    1
    Copy the full SHA
    08dbaa3 View commit details
  2. remove dead code in bin/_mocha

    Signed-off-by: Christopher Hiller <boneskull@boneskull.com>
    boneskull committed Apr 30, 2018
    Copy the full SHA
    1606f35 View commit details

Commits on May 1, 2018

  1. Annotate when exceptions are caught but ignored; closes #3354 (#3356)

    * style(*various*): Annotate when exceptions are caught but ignored
    
    There's an existing convertion to name intentionally ignored errors in catch block as `ignore`.
    Modified codebase to follow it.
    
    Fixes #3354
    
    * style(bin/options.js): Turn off prettier for single line
    
    Prettier reformatting is just wrong here. fs.readFileSync() should **not** be split across multiple
    lines.
    
    * Revert "style(bin/options.js): Turn off prettier for single line"
    
    This reverts commit eb034dd.
    
    * style(bin/options.js): Revert un-Prettier comment per Boneskull's request
    plroebuck authored and boneskull committed May 1, 2018
    Copy the full SHA
    8368722 View commit details

Commits on May 4, 2018

  1. Copy the full SHA
    7613521 View commit details

Commits on May 8, 2018

  1. feat(bin/options.js): Add support for comment lines in "mocha.opts"

    Modified code to strip comment lines, those beginning with a hash character ('#'), from "mocha.opts"
    prior to processing its contents.
    
    Fixes #3370
    plroebuck authored and Bamieh committed May 8, 2018
    Copy the full SHA
    cd3e1b3 View commit details
  2. docs(docs/index.md): Update "mocha.opts" documentation

    Added information on file content, noting support for comment/blank lines.
    plroebuck authored and Bamieh committed May 8, 2018
    Copy the full SHA
    aef1b0f View commit details
  3. migrate Mocha's tests to Unexpected assertion library (#3343)

    * convert tests to use Unexpected as assertion library
    
    Squashed commits:
    [fc418f8] remove extra call to done() in test/unit/runnable.spec.js
    [f1bc24f] lint test/browser-specific/setup.js
    [48af58c] fix test flake in ESM test
    [346ab5c] convert bundle and browser-specific tests to unexpected
    [c76b2f4] fix typo in test/README.md [ci skip]
    [783f16a] add test/README.md [ci skip]
    [a5949bb] convert all "only" tests to unexpected
    [15c921e] lint test/integration/diffs.spec.js
    [0d7635e] convert all individual reporter tests to unexpectedalso don't eat `STDOUT`--this means the tests have more output--could be
    useful for debugging.
    [190f5ed] convert compiler tests to unexpected
    [822b115] fix test/integration/glob.spec.js
    [db4853d] fix jsapi test
    [6f9831d] remove debugs from test/integration/diffs.spec.js
    [4cfbe4f] remove useless manual browser tests that will never get used properly
    [4e1d97b] convert test/reporters/base.spec.js to unexpected- add `chai` as dependency for `chai`-specific test, since the old one
    was based on what we thought it should be, not what it actually is.
    - improve test output: stop eating `STDOUT` and use color where possible
    [a8378f9] convert test/node-unit/color.spec.js to unexpected
    [92f50de] convert test/integration/reporters.spec.js to unexpected
    [652a331] remove global assert
    [ec857ef] convert test/integration/diffs.spec.js to unexpectedthis required a bit of massaging, since I had to change the
    fixture to use `assert`, which produces slightly different output.also move `getDiffs()` out of `test/integration/helpers.js`,
    since it's only used in `test/integration/diffs.spec.js`.
    [436e85d] remove userland assert moduleit's safe to use the built-in assert module.
    [dd7aec7] remove expect.js & karma-expect; add karma-unexpected
    [01dff70] lint test/setup.js
    [77203bf] convert test/integration/regression.spec.js to unexpected
    [e9eb05e] add more assertions; fix others
    [241159e] convert test/require/require.spec.js to unexpected
    [eb6bcff] convert test/integration/options.spec.js to unexpected
    [ada86aa] add custom assertions
    [5368ebe] convert test/integration/no-diff.spec.js to unexpected
    [043cfe3] convert test/integration/glob.spec.js to unexpected
    [df748a3] convert test/integration/compiler-globbing.spec.js to unexpected
    [5c0a8d0] convert test/interfaces/tdd.spec.js to unexpected
    [c6622a6] convert test/interfaces/qunit.spec.js to unexpected
    [1ecd62f] convert test/interfaces/exports.spec.js to unexpected
    [cabda05] convert test/interfaces/bdd.spec.js to unexpected
    [454018b] convert test/node-unit/stack-trace-filter.spec.js to unexpected
    [be6c332] fix unhelpful error thrown from lookupFiles; convert test/node-unit/file-utils.spec.js to unexpected
    [419d3ed] convert test/unit/grep.spec.js to unexpected
    [4837a6a] convert test/unit/hook-sync-nested.spec.js to unexpected
    [6d6b632] convert test/unit/hook-async.spec.js to unexpected
    [6ec01eb] convert test/unit/hook-sync.spec.js to unexpected
    [d4f6515] convert test/unit/ms.spec.js to unexpected
    [c43c096] convert test/unit/mocha.spec.js to unexpected
    [7aa9b82] convert test/unit/root.spec.js to unexpected
    [13a9e24] convert test/unit/runnable.spec.js to unexpected
    [cf9b376] convert test/unit/runner.spec.js to unexpected
    [efe37f9] simplify exception tests in test/unit/suite.spec.js
    [c08ba9e] convert test/unit/suite.spec.js to unexpected
    [41a85d9] convert test/unit/test.spec.js to unexpected
    [19f2d44] convert test/unit/throw.spec.js to unexpected
    [70ec725] convert test/unit/context.spec.js to unexpected
    [8c6cefd] convert test/unit/utils.spec.js to unexpected
    [a626278] add unexpected
    * fix missing assertion in an "options" integration test
    * convert new test in options.spec.js to unexpected
    * combine Chai test into a single assertion; increase timeout for reporter tests
    
    Signed-off-by: Christopher Hiller <boneskull@boneskull.com>
    boneskull authored May 8, 2018
    Copy the full SHA
    668b541 View commit details

Commits on May 17, 2018

  1. update all dependencies

    boneskull committed May 17, 2018
    Copy the full SHA
    42005f3 View commit details

Commits on May 18, 2018

  1. Copy the full SHA
    adea377 View commit details
  2. Copy the full SHA
    3d05aeb View commit details
  3. Release v5.2.0

    boneskull committed May 18, 2018
    Copy the full SHA
    2735e35 View commit details
  4. Copy the full SHA
    c4c421d View commit details
  5. Copy the full SHA
    df69279 View commit details

Commits on May 20, 2018

  1. fix markdown weirdness (#3386)

    Signed-off-by: Outsider <outsideris@gmail.com>
    outsideris authored May 20, 2018
    Copy the full SHA
    0d95e3f View commit details

Commits on May 31, 2018

  1. Copy the full SHA
    41cce5f View commit details

Commits on Jun 6, 2018

  1. Remove bower install instructions

    Remove installing mocha via bower instruction because mocha no longer supports bower installs.
    goteamtim authored and boneskull committed Jun 6, 2018
    Copy the full SHA
    d737ca2 View commit details
  2. Copy the full SHA
    eeccd05 View commit details

Commits on Jun 20, 2018

  1. Update _mocha

    Bamieh committed Jun 20, 2018
    Copy the full SHA
    e34e9f1 View commit details
  2. fix indentation

    Bamieh committed Jun 20, 2018
    Copy the full SHA
    babd958 View commit details

Commits on Jun 23, 2018

  1. add blocking mocha and test

    craigtaub authored and Bamieh committed Jun 23, 2018
    Copy the full SHA
    a5b5efc View commit details
  2. renamed to hanging

    craigtaub authored and Bamieh committed Jun 23, 2018
    Copy the full SHA
    e7ab24d View commit details
  3. test exit code

    craigtaub authored and Bamieh committed Jun 23, 2018
    Copy the full SHA
    63e7c28 View commit details
  4. rename file

    craigtaub authored and Bamieh committed Jun 23, 2018
    Copy the full SHA
    e67d1cf View commit details
Showing 355 changed files with 36,210 additions and 25,273 deletions.
44 changes: 44 additions & 0 deletions .eleventy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
'use strict';

module.exports = function(eleventyConfig) {
eleventyConfig.addPassthroughCopy('docs/css');
eleventyConfig.addPassthroughCopy('docs/js');
eleventyConfig.addPassthroughCopy('docs/images');
eleventyConfig.addPassthroughCopy('docs/CNAME');
eleventyConfig.addPassthroughCopy('docs/_headers');
eleventyConfig.addPassthroughCopy('docs/favicon.ico');

eleventyConfig.addPassthroughCopy('docs/example');

/* Markdown Plugins */
const markdown = require('markdown-it')({
html: true,
linkify: true
});

markdown.use(require('markdown-it-anchor'), {
slugify: require('uslug'),
permalink: true,
permalinkBefore: true,
permalinkClass: 'direct-link',
permalinkSymbol: '#'
});

markdown.use(require('markdown-it-attrs'), {
leftDelimiter: '{:',
rightDelimiter: '}'
});

markdown.use(require('markdown-it-prism'));

eleventyConfig.setLibrary('md', markdown);

return {
passthroughFileCopy: true,
dir: {
input: 'docs',
includes: '_includes',
output: 'docs/_site'
}
};
};
3 changes: 3 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -2,4 +2,7 @@ coverage/
mocha.js
*.fixture.js
docs/
out/
!lib/mocha.js
test/integration/fixtures
!.*.js
85 changes: 80 additions & 5 deletions .eslintrc.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,90 @@
root: true
extends: semistandard
extends:
- semistandard
- plugin:prettier/recommended
env:
node: yes
browser: yes
es6: no
parserOptions:
ecmaVersion: 5
ecmaFeatures:
globalReturn: no
jsx: no
sourceType: script
rules:
strict:
- error
- safe
linebreak-style:
- error
- unix
overrides:
- files:
- scripts/**/*.js
- package-scripts.js
- karma.conf.js
- .wallaby.js
- .eleventy.js
- bin/*
- lib/cli/**/*.js
- test/node-unit/**/*.js
- test/integration/options/watch.spec.js
- test/integration/helpers.js
- lib/growl.js
parserOptions:
ecmaVersion: 6
env:
browser: no

- files:
- test/**/*.{js,mjs}
env:
mocha: yes
globals:
expect: no
- files:
- bin/*
- lib/**/*.js
rules:
no-restricted-globals:
- error
- name: setTimeout
message: &GH-237 See https://github.com/mochajs/mocha/issues/237
- name: clearTimeout
message: *GH-237
- name: setInterval
message: *GH-237
- name: clearInterval
message: *GH-237
- name: setImmediate
message: *GH-237
- name: clearImmediate
message: *GH-237
- name: Date
message: *GH-237
no-restricted-modules:
- error
- timers
no-restricted-syntax:
- error
# disallow `global.setTimeout()`, `global.setInterval()`, etc.
- selector: 'CallExpression[callee.object.name=global][callee.property.name=/(set|clear)(Timeout|Immediate|Interval)/]'
message: *GH-237
# disallow `new global.Date()`
- selector: 'NewExpression[callee.object.name=global][callee.property.name=Date]'
message: *GH-237
# disallow property access of `global.<timer>.*`
- selector: '*[object.object.name=global][object.property.name=/(Date|(set|clear)(Timeout|Immediate|Interval))/]:expression'
message: *GH-237

- files:
- test/**/*.mjs
parserOptions:
ecmaVersion: 6
sourceType: module

- files:
- lib/reporters/*.js
rules:
no-restricted-syntax:
- error
# disallow Reporters using `console.log()`
- selector: 'CallExpression[callee.object.name=console][callee.property.name=log]'
message: &GH-3604 See https://github.com/mochajs/mocha/issues/3604
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* text=auto eol=lf
54 changes: 45 additions & 9 deletions .github/CODE_OF_CONDUCT.md
Original file line number Diff line number Diff line change
@@ -33,14 +33,14 @@ community should be respectful when dealing with other members as well as with p
We are a community of professionals, and we conduct ourselves professionally. Be kind to others. Do not insult or put
down other participants. Harassment and other exclusionary behavior aren’t acceptable. This includes, but is not limited to:

* Violent threats or language directed against another person.
* Discriminatory jokes and language.
* Posting sexually explicit or violent material.
* Posting (or threatening to post) other people’s personally identifying information (“doxing”).
* Personal insults, especially those using racist or sexist terms.
* Unwelcome sexual attention.
* Advocating for, or encouraging, any of the above behavior.
* Repeated harassment of others. In general, if someone asks you to stop, then stop.
- Violent threats or language directed against another person.
- Discriminatory jokes and language.
- Posting sexually explicit or violent material.
- Posting (or threatening to post) other people’s personally identifying information (“doxing”).
- Personal insults, especially those using racist or sexist terms.
- Unwelcome sexual attention.
- Advocating for, or encouraging, any of the above behavior.
- Repeated harassment of others. In general, if someone asks you to stop, then stop.

## When we disagree, try to understand why

@@ -55,4 +55,40 @@ Original text courtesy of the Speak Up! project and Django Project.

## QUESTIONS?

If you have questions, please see the FAQ. If that doesn’t answer your questions, feel free to email conduct@js.foundation.
If you have questions, please see the FAQ. If that doesn’t answer your questions, feel free to email report@lists.openjsf.org.

# OpenJS Foundation Code of Conduct

The OpenJS Foundation and its member projects use the Contributor
Covenant v1.4.1 as its Code of Conduct. Refer to the following
for the full text:

- [english](https://www.contributor-covenant.org/version/1/4/code-of-conduct)
- [translations](https://www.contributor-covenant.org/translations)

Refer to the section on reporting and escalation in this document for the specific emails that can be used to report and escalate issues.

## Reporting

### Project Spaces

For reporting issues in spaces related to a member project please use the email provided by the project for reporting. Projects handle CoC issues related to the spaces that they maintain. Projects maintainers commit to:

- maintain the confidentiality with regard to the reporter of an incident
- to participate in the path for escalation as outlined in
the section on Escalation when required.

### Foundation Spaces

For reporting issues in spaces managed by the OpenJS Foundation, for example, repositories within the OpenJS organization, use the email `report@lists.openjsf.org`. The Cross Project Council (CPC) is responsible for managing these reports and commits to:

- maintain the confidentiality with regard to the reporter of an incident
- to participate in the path for escalation as outlined in
the section on Escalation when required.

## Escalation

The OpenJS Foundation maintains a Code of Conduct Panel (CoCP). This is a foundation-wide team established to manage escalation when a reporter believes that a report to a member project or the CPC has not been properly handled. In order to escalate to the CoCP send an email to `"coc-escalation@lists.openjsf.org`.

For more information, refer to the full
[Code of Conduct governance document](https://github.com/openjs-foundation/bootstrap/blob/master/proposals/stage-1/CODE_OF_CONDUCT/FOUNDATION_CODE_OF_CONDUCT_REQUIREMENTS.md).
66 changes: 39 additions & 27 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -28,7 +28,7 @@ It's also important to understand some overarching goals of Mocha, detailed belo

### :soccer: About Project Goals

Mocha is a test framework. Developers use it against anything from legacy spaghetti in IE7 to stage-0 TC39 features in Electron. While still staying current, Mocha will only drop support for old platforms as a last resort. If and only if Mocha cannot move forward as a project, support will be dropped. If workarounds exist, they are preferred.
Mocha is a test framework. Developers use it against anything from legacy spaghetti in IE11 to stage-0 TC39 features in Electron. Mocha is committed to providing support for maintained (LTS) versions of Node.js and popular browsers (of which IE11 is still one, as of December 2018).

Mocha adheres strictly to [semantic versioning](https://semver.org). We are *extremely cautious* with changes that have the potential to break; given the size of Mocha's user base, it's *highly unlikely* a breaking change will slide by.

@@ -40,46 +40,58 @@ We ask you please keep these goals in mind when making or proposing changes.

Follow these steps to get going. If you are having trouble, don't be afraid to [ask for help](#got-a-question).

> PRO TIP: Run `npm start` to see a list of commands which can be run with `npm start <command>`
> PRO TIP: After `npm install`, run `npm start` to see a list of commands which can be run with `npm start <command>` (powered by [nps](https://npm.im/nps)).
1. [Install Node.js 4.x or newer](https://nodejs.org/download).
1. [Install Node.js 6.x or newer](https://nodejs.org/en/download/).
- If you're new to installing Node, a tool like [nvm](https://github.com/creationix/nvm#install-script) can help you manage multiple version installations.
- You will need [Google Chrome](https://www.google.com/chrome/) to run browser-based tests locally.
1. Follow [Github's documentation](https://help.github.com/articles/fork-a-repo/) on setting up Git, forking and cloning.
1. Create a new branch in your working copy. Give your branch a descriptive name, such as `issue/12345`: `git checkout -b issue/12345`.
1. Execute `npm install` to install the development dependencies.
- Do not use `yarn install`.
- Some optional dependencies may fail; you can safely ignore these unless you are trying to build the documentation.
- If you're sick of seeing the failures, run `npm install --ignore-scripts`.
1. Make your changes and add them via `git add`.
- Your changes will likely be somewhere in `lib/`, `bin/` or `browser-entry.js` (if your changes are browser-specific).
- Unit and/or integration **tests are required** for any code change. These live in `test/`.
- **Do not modify** the root `mocha.js` file directly; it is automatically generated.
- Your changes will likely be somewhere in `lib/`, `bin/` or `browser-entry.js` if your changes are browser-specific.
- Please add unit and/or integration tests (depending on the nature of your changes).
- Keep your PR focused. Don't fix two things at once, or make formatting changes alongside bug fixes.
1. Before committing, run `npm test`.
- This will run unit tests, Node.js and browser integration tests, and lint the source code.
- The "browser" tests use Mocha to test itself; it will rebuild the root `mocha.js` file with your changes.
- **Please avoid committing changes to `mocha.js`**.
- Keep your PR focused. Don't fix two things at once; don't upgrade dependencies unless necessary.
1. Before committing, run `npm start test`.
- This will run both Node.js-based and browser-based tests.
- Ultimately, your pull request will be built on our continuous integration servers ([Travis CI](https://travis-ci.org/mochajs/mocha) and [AppVeyor](https://ci.appveyor.com/project/boneskull/mocha)). The first step to ensuring these checks pass is to test on your own machine.
- A coverage check will be sent to [Coveralls](https://coveralls.io/github/mochajs/mocha). **A drop in code coverage % is considered a failed check**.
1. Commit your changes.
- Use a brief message on the first line, referencing a relevant issue (e.g. `#12345`).
- Use a brief message on the first line, referencing a relevant issue (e.g. `closes #12345`).
- Add detail in subsequent lines.
1. Push your changes to your fork.
1. Navigate to the source repository. You should see a notification about your recent changes in your fork's branch, with a button to create a pull request. Click it.
1. Describe your changes in detail here. Once you're satisfied, submit the form.
- *PRO TIP*: If you've used a multi-line commit message, Github will pre-fill the PR's description with it.
1. If you have not signed our Contributor License Agreement, a friendly robot will prompt you to do so. A [CLA](https://cla.js.foundation/mochajs/mocha) (electronic) signature is **required** for all contributions of code to Mocha.
1. CI will run against your changes.
- If the changes fail the checks, you will need to address those before merging.
- You don't need to make a new PR to make changes. Instead, commit on top of your changes, and push these to your fork's branch. The PR will be updated, and CI will re-run.
- Github will indicate if there's a conflict. If this happens, you will need to [rebase](https://help.github.com/articles/about-git-rebase/) your branch onto the `master` branch of the source repository. *Don't merge.*
- It's no longer necessary to "squash" your changes.
1. Be patient while your PR is reviewed. This can take awhile ([why?](https://github.com/orgs/mochajs/projects/4)). We may request changes; don't be afraid to question them.
- A pre-commit hook will run which automatically formats your staged changes (and fixes any problems it can) with ESLint and Prettier. If ESLint fails to fix an issue, your commit will fail and you will need to manually correct the problem.
1. <a name="up-to-date"/> (Optional) Ensure you are up-to-date with Mocha's `master` branch:
- You can add an "upstream" remote repo using `git remote add upstream https://github.com/mochajs/mocha.git && git fetch upstream`.
- Navigate to your `master` branch using `git checkout master`.
- Pull changes from `upstream` using `git pull upstream master`.
- If any changes were pulled in, rebase your branch onto `master` by switching back to your branch (`git checkout <your-branch>`) then rebasing using `git rebase master`.
1. Push your changes to your fork; `git push origin`.
1. In your browser, navigate to [mochajs/mocha](https://github.com/mochajs/mocha). You should see a notification about your recent changes in your fork's branch, with a (green?) button to create a pull request. Click it.
1. Describe your changes in detail here, following the template. Once you're satisfied, submit the form.
1. If you have not signed our [Contributor License Agreement](https://js.foundation/cla), a friendly robot will prompt you to do so. A [CLA](https://cla.js.foundation/mochajs/mocha) (electronic) signature is **required** for all contributions of code to Mocha.
1. Continuous integration checks will run against your changes. The result of these checks will be displayed on your PR.
- If the checks fail, you must address those before the PR is accepted.
- GitHub will indicate if there's a conflict. If this happens, you will need to [rebase](https://help.github.com/articles/about-git-rebase/) your branch onto the `master` branch of the source repository. **Do not `git merge`**.
- (Optional) [Squash](https://help.github.com/articles/about-pull-request-merges/#squash-and-merge-your-pull-request-commits) your changesets. If you have multiple changesets in your PR, they will be squashed upon PR acceptance by the Mocha team.
1. Be patient while your PR is reviewed. This can take a while. We may request changes, but don't be afraid to question them.
1. Your PR might become conflicted with the code in `master`. If this is the case, you will need to [update your PR](#up-to-date) and resolve your conflicts.
1. You don't need to make a new PR to any needed changes. Instead, commit on top of your changes, and push these to your fork's branch. The PR will be updated, and CI will re-run.

Join us in the [contributors' chat](https://gitter.im/mochajs/contributors)!

## :angel: I Just Want To Help

*Excellent.* Here's how:

- **Handy with JavaScript?** Please check out the issues labeled [`help-wanted`](https://github.com/mochajs/mocha/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+label%3Ahelp-wanted).
- **Can you write good (and do other stuff good too)?** Help with the documentation. See the [issues for our site](https://github.com/mochajs/mocha/issues?q=is%3Aopen+is%3Aissue+label%3Adocumentation).
- **Design your thing?** [Our site](https://github.com/mochajs/mocha/tree/master/docs) needs your magic touch.
- **Know Mocha's codebase?** We could use your help triaging issues and/or reviewing pull requests. Please contact an [org member](https://github.com/orgs/mochajs/people), and we'll chat.
- **Handy with JavaScript?** Please check out the issues labeled [`help wanted`](https://github.com/mochajs/mocha/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22) or [`good-first-issue`](https://github.com/mochajs/mocha/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3Agood-first-issue). Try `npx good-first-issue mocha`!
- **Can you write ~~good~~ well?** The [documentation](https://mochajs.org) almost always needs some love. See the [doc-related issues](https://github.com/mochajs/mocha/issues?q=is%3Aopen+is%3Aissue+label%3Adocumentation).
- **Design your thing?** [Our site](https://mochajs.org) needs your magic touch.
- **Familiar with Mocha's codebase?** We could use your help triaging issues and/or reviewing pull requests. Please contact an [org member](https://github.com/orgs/mochajs/people), and we'll chat.
- **Want to build our community?** Mocha has a *lot* of users. We could use your help bringing everyone together in peace and harmony. Please contact an [org member](https://github.com/mochajs/people).
- **You can sell dirt to worms?** Let's raise Mocha's profile in the JavaScript and OSS communities. Please contact an [org member](https://github.com/orgs/mochajs/people)!
- **Wait--you write unit tests for *fun*?** A PR which increases coverage is unlikely to be turned down.
- **Are you experienced?** If you're a seasoned Mocha user, why not help answer some questions in the [chat room](https://gitter.im/mochajs/mocha)?
- **Are you experienced?** :guitar: If you're a seasoned Mocha user, why not help answer some questions in the [main chat room](https://gitter.im/mochajs/mocha)?
26 changes: 21 additions & 5 deletions .github/ISSUE_TEMPLATE.md → .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
---
name: Bug report
about: To report a part of mocha not working as expected
title: ''
labels: 'unconfirmed-bug'
---

<!--
Have you read Mocha's Code of Conduct? By filing an Issue, you are expected to comply with it, including treating everyone with respect: https://github.com/mochajs/mocha/blob/master/.github/CODE_OF_CONDUCT.md
For more, check out the Mocha Gitter chat room: https://gitter.im/mochajs/mocha
Detail the steps necessary to reproduce the problem. To get the fastest support, create an MCVE and upload it to GitHub.
create an [MCVE](https://stackoverflow.com/help/mcve) and upload it to GitHub.
-->

### Prerequisites
@@ -12,7 +22,7 @@ Place an `x` between the square brackets on the lines below for every satisfied
- [ ] Checked that your issue hasn't already been filed by cross-referencing [issues with the `faq` label](https://github.com/mochajs/mocha/issues?utf8=%E2%9C%93&q=is%3Aissue%20label%3Afaq%20)
- [ ] Checked next-gen ES issues and syntax problems by using the same environment and/or transpiler configuration without Mocha to ensure it isn't just a feature that actually isn't supported in the environment in question or a bug in your code.
- [ ] 'Smoke tested' the code to be tested by running it outside the real test suite to get a better sense of whether the problem is in the code under test, your usage of Mocha, or Mocha itself
- [ ] Ensured that there is no discrepancy between the locally and globally installed versions of Mocha. You can find them with: `node node_modules/.bin/mocha --version`(Local) and `mocha --version`(Global). We recommend avoiding the use of globally installed Mocha.
- [ ] Ensured that there is no discrepancy between the locally and globally installed versions of Mocha. You can find them with: `node node_modules/.bin/mocha --version`(Local) and `mocha --version`(Global). We recommend that you _not_ install Mocha globally.

### Description

@@ -30,6 +40,10 @@ on how to create a minimal, complete, and verifiable example.
**Expected behavior:** [What you expect to happen]

**Actual behavior:** [What actually happens]
<!--
Please include any output, especially error messages (including stacktrace). Remember, we can't see your screen.
Scrub if needed so as not to reveal passwords, etc.
-->

**Reproduces how often:** [What percentage of the time does it reproduce?]

@@ -39,11 +53,13 @@ on how to create a minimal, complete, and verifiable example.

- The output of `mocha --version` and `node node_modules/.bin/mocha --version`:
- The output of `node --version`:
- The version and architecture of your operating system:
- Your shell (bash, zsh, PowerShell, cmd, etc.):
- Your operating system
- name and version:
- architecture (32 or 64-bit):
- Your shell (e.g., bash, zsh, PowerShell, cmd):
- Your browser and version (if running browser tests):
- Any other third party Mocha related modules (with versions):
- The code transpiler being used:
- Any third-party Mocha-related modules (and their versions):
- Any code transpiler (e.g., TypeScript, CoffeeScript, Babel) being used (and its version):

### Additional Information

18 changes: 18 additions & 0 deletions .github/ISSUE_TEMPLATE/feature_request.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
name: Feature request
about: Suggest an idea for Mocha
title: ''
labels: 'feature'
---

**Is your feature request related to a problem or a nice-to-have?? Please describe.**
A clear and concise description of what the problem is. E.g. I'm always frustrated when [...]

**Describe the solution you'd like**
A clear and concise description of what you want to happen.

**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.

**Additional context**
Add any other context or screenshots about the feature request here.
15 changes: 15 additions & 0 deletions .github/ISSUE_TEMPLATE/support-question.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
name: Support Question
about: If you have a question, please check out our Gitter or StackOverflow!
title: ''
labels: 'question'
---

<!--
We primarily use GitHub as an issue tracker; for usage and support questions, please check out these resources below. Thanks!.
* Website: https://mochajs.org/
* Chat room: http://gitter.im/mochajs/mocha
* StackOverflow: https://stackoverflow.com/questions/tagged/mocha using the tag `mocha`
* API documentation: https://mochajs.org/api/
-->
152 changes: 135 additions & 17 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,25 +1,143 @@
.DS_Store
node_modules
*.sock
*.sw*
.idea
*.iml
*.patch
*.diff
npm-debug.log*
.envrc
# Mocha-specific
docs/_site
docs/_dist
mocha.js
.karma/
!lib/mocha.js

#########################################
# NON-MOCHA STUFF GOES BELOW THIS THING #
#########################################

# Git mergetool
# Use `git config mergetool.keepBackup false` to stop generating these files
*.orig
.nyc_output/
coverage/

# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# nyc/istanbul
coverage
.nyc_output

# Dependencies
node_modules/

# npm
.npm
*.tgz

# Optional eslint cache
.eslintcache

# Optional REPL history
.node_repl_history

# dotenv environment variables file
.env
.env.test

# Yarn
yarn.lock
/mocha.js
# artifacts from various diff tools
.yarn-integrity

# Various temporary files
*~

# Emacs
\#*\#
/.emacs.desktop
/.emacs.desktop.lock
*.elc
auto-save-list
tramp
.\#*

# Vim
[._]*.s[a-v][a-z]
[._]*.sw[a-p]
[._]s[a-rt-v][a-z]
[._]ss[a-gi-z]
[._]sw[a-p]
Session.vim
[._]*.un~

# Diff
*.patch
*.diff

# VSCode
.vscode/

# JetBrains' IDEs
.idea/
*.iws
out/
.idea_modules/
atlassian-ide-plugin.xml

# SourceTree
*_BACKUP_*
*_BASE_*
*_LOCAL_*
*_REMOTE_*
docs/_site
docs/_dist
.vscode/

# SublimeText
*.tmlanguage.cache
*.tmPreferences.cache
*.stTheme.cache
*.sublime-workspace
*.sublime-project
sftp-config.json
Package Control.last-run
Package Control.ca-list
Package Control.ca-bundle
Package Control.system-ca-bundle
Package Control.cache/
Package Control.ca-certs/
Package Control.merged-ca-bundle
Package Control.user-ca-bundle
oscrypto-ca-bundle.crt
bh_unicode_properties.cache
GitHub.sublime-settings

# direnv
.envrc

# Linux
.fuse_hidden*
.directory
.Trash-*
.nfs*

# Windows
Thumbs.db
ehthumbs.db
ehthumbs_vista.db
*.stackdump
[Dd]esktop.ini
$RECYCLE.BIN/
*.cab
*.msi
*.msix
*.msm
*.msp
*.lnk

# macOS
.DS_Store
.AppleDouble
.LSOverride
._*
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk

# SauceConnect
*.sock
10 changes: 10 additions & 0 deletions .lintstagedrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"linters": {
"(bin/*|*.js|**/*.js)": ["eslint --fix", "git add"],
"(*.{json,yml,md,html}|**/*.{json,yml,md,html})": [
"prettier --write",
"git add"
]
},
"ignore": ["docs/**/*.js", "test/**/*.fixture.js", "package*.json"]
}
7 changes: 7 additions & 0 deletions .mocharc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
require: test/setup
ui: bdd
global:
- okGlobalA,okGlobalB
- okGlobalC
- callback*
timeout: 300
29 changes: 20 additions & 9 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
###
### .travis.yml
###

# these are executed in order. each must pass for the next to be run
stages:
- smoke # this ensures a "user" install works properly
@@ -7,7 +11,12 @@ stages:

# defaults
language: node_js
node_js: '9'
node_js: '12'
addons:
apt:
packages:
# Growl
- libnotify-bin
# `nvm install` happens before the cache is restored, which means
# we must install our own npm elsewhere (`~/npm`)
before_install: |
@@ -32,15 +41,17 @@ jobs:

- &node
script: npm start test.node
node_js: '8'
node_js: '10'

- <<: *node
node_js: '6'
node_js: '8'

- <<: *node
node_js: '4'
node_js: '6'

- script: npm start test.bundle test.browser
# XXX: update when canvas supplies a prebuilt binary for Node.js v12.x
node_js: 10
install: npm ci # we need the native modules here
addons:
artifacts:
@@ -59,21 +70,21 @@ jobs:
env: null
before_install: true
install: npm install --production
# `--opts /dev/null` means "ignore test/mocha.opts"
script: ./bin/mocha --opts /dev/null --reporter spec test/sanity/sanity.spec.js

script: ./bin/mocha --no-config --reporter spec test/sanity/sanity.spec.js
cache:
directories:
- ~/.npm
- node_modules # npm install, unlike npm ci, doesn't wipe node_modules

- <<: *smoke
node_js: '8'
node_js: '10'

- <<: *smoke
node_js: '6'
node_js: '8'

- <<: *smoke
node_js: '4'
node_js: '6'

- stage: precache
script: true
36 changes: 26 additions & 10 deletions .wallaby.js
Original file line number Diff line number Diff line change
@@ -3,37 +3,53 @@
module.exports = () => {
return {
files: [
'index.js', 'lib/**/*.js', 'test/setup.js',
'index.js',
'lib/**/*.{js,json}',
'test/setup.js',
'test/assertions.js',
{
pattern: 'test/node-unit/**/*.fixture.js',
instrument: false
}, {
},
{
pattern: 'test/unit/**/*.fixture.js',
instrument: false
}
},
'package.json',
'test/opts/mocha.opts',
'mocharc.yml'
],
filesWithNoCoverageCalculated: ['test/**/*.fixture.js'],
tests: [
'test/unit/**/*.spec.js', 'test/node-unit/**/*.spec.js'
filesWithNoCoverageCalculated: [
'test/**/*.fixture.js',
'test/setup.js',
'test/assertions.js',
'lib/browser/**/*.js'
],
tests: ['test/unit/**/*.spec.js', 'test/node-unit/**/*.spec.js'],
env: {
type: 'node',
runner: 'node'
},
workers: {recycle: true},
testFramework: {type: 'mocha', path: __dirname},
setup (wallaby) {
setup(wallaby) {
// running mocha instance is not the same as mocha under test,
// running mocha is the project's source code mocha, mocha under test is instrumented version of the source code
const runningMocha = wallaby.testFramework;
runningMocha.timeout(200);
// to expose it/describe etc. on the mocha under test
const mochaUnderTest = new (require('./'))();
mochaUnderTest.suite.emit('pre-require', global, '', mochaUnderTest);
const MochaUnderTest = require('./');
const mochaUnderTest = new MochaUnderTest();
mochaUnderTest.suite.emit(
MochaUnderTest.Suite.constants.EVENT_FILE_PRE_REQUIRE,
global,
'',
mochaUnderTest
);
// to make test/node-unit/color.spec.js pass, we need to run mocha in the project's folder context
const childProcess = require('child_process');
const execFile = childProcess.execFile;
childProcess.execFile = function () {
childProcess.execFile = function() {
let opts = arguments[2];
if (typeof opts === 'function') {
opts = {};
1,890 changes: 910 additions & 980 deletions CHANGELOG.md

Large diffs are not rendered by default.

3 changes: 0 additions & 3 deletions Gemfile

This file was deleted.

51 changes: 0 additions & 51 deletions Gemfile.lock

This file was deleted.

158 changes: 111 additions & 47 deletions MAINTAINERS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,44 @@
# Mocha Maintainer's Handbook

<!-- toc -->

- [Introduction](#introduction)
- [Terminology](#terminology)
- [User](#user)
- [Contributor](#contributor)
- [A Note About Donations](#a-note-about-donations)
- [Maintainer](#maintainer)
- [The Responsibilities of a Maintainer](#the-responsibilities-of-a-maintainer)
- [The Rights of a Maintainer](#the-rights-of-a-maintainer)
- [About "Owners"](#about-owners)
- [Mocha's Decision-Making Process](#mochas-decision-making-process)
- [Communication](#communication)
- [Working with Issues & Pull Requests](#working-with-issues--pull-requests)
- [Semantic Versioning](#semantic-versioning)
- [Questions](#questions)
- [Bugs](#bugs)
- [Features](#features)
- [Subsystems, Environments, Etc.](#subsystems-environments-etc)
- [Feedback & Follow-ups](#feedback--follow-ups)
- [Meta](#meta)
- [Closing Issues](#closing-issues)
- [Commenting on Issues and Reviewing Pull Requests](#commenting-on-issues-and-reviewing-pull-requests)
- [Reviewing Code](#reviewing-code)
- [The Part About Jerks](#the-part-about-jerks)
- [Rude or Entitled People](#rude-or-entitled-people)
- [Code of Conduct Violations](#code-of-conduct-violations)
- [Branches](#branches)
- [Merging PRs](#merging-prs)
- [Using Milestones](#using-milestones)
- [Mocha's Release Process](#mochas-release-process)
- [Projects](#projects)
- [About The JS Foundation](#about-the-js-foundation)
- [About OpenCollective](#about-opencollective)

<!-- tocstop -->

## Introduction

Hi stranger! We've written this document for:

1. Active maintainers of Mocha
@@ -18,7 +57,7 @@ Anyone involved with Mocha will fall into one of these buckets: **user**, **cont

A "user" for the purpose of this document is any *individual developer* who consumes Mocha to write and/or execute tests. A user interacts with contributors. A user interacts with the software, web site, documentation, etc., which these contributors provide.

As a user, you're expected to follow the [code of conduct](https://github.com/mochajs/mocha/blob/master/CHANGELOG.md) when interacting in Mocha's "official" social spaces. This includes:
As a user, you're expected to follow the [code of conduct](https://github.com/mochajs/mocha/blob/master/.github/CODE_OF_CONDUCT.md) when interacting in Mocha's "official" social spaces. This includes:

- Any chatroom under the `mochajs` organization on Gitter
- Any project under the `mochajs` organization on GitHub
@@ -45,7 +84,7 @@ A "contributor" is any individual who has *given back* in some way to the projec
1. Recruiting more contributors! Don't spam.
1. Researching the user base, getting feedback, etc. Don't spam.

A contributor is *usually* a user as well, but this isn't a hard-and-fast rule. A contributor is also expected to adhere to the [code of conduct](https://github.com/mochajs/mocha/blob/master/CHANGELOG.md) as a user would.
A contributor is *usually* a user as well, but this isn't a hard-and-fast rule. A contributor is also expected to adhere to the [code of conduct](https://github.com/mochajs/mocha/blob/master/.github/CODE_OF_CONDUCT.md) as a user would.

As you can see, it's wide open! Think of it another way: if you are *adding value to Mocha*, then you are a contributor.

@@ -133,23 +172,44 @@ All new issues will need to be triaged, and pull requests must be examined. Mai

> If you see an issue or PR that could use some labels, please add them!
The following list is incomplete, but it's better than nothing:

### Semantic Versioning

*All* issues which will be resolved by commit(s) should have one of these three labels:
The TL;DR of Semantic Versioning is:

- MAJOR version when you make incompatible API changes,
- MINOR version when you add functionality in a backwards-compatible manner, and
- PATCH version when you make backwards-compatible bug fixes.

Pull requests *must* have one of these three (3) labels:

- `semver-patch` for backwards-compatible bug fixes, documentation, or anything which does not affect a "production" (`npm install mocha`) installation of Mocha
- `semver-minor` for backwards-compatible new features or usability/interface enhancements
- `semver-major` for backwards-incompatible ("breaking") changes to the API

A PR which introduces a breaking change is considered to be `semver-major`, *regardless* of whether it's a bug fix, feature, or whatever.

- `semver-patch` for bug fixes, documentation, development environment changes, CI and tests
- `semver-minor` for enhancements ("features")
- `semver-major` for backwards-incompatible ("breaking") changes to the *output*, *API*, or environment support
For the purposes of the above definitions, Mocha has some unique considerations, and includes the following in its definition of "API":

To be clear:
1. Mocha's *documented*, programmatic interface which *is not explicitly tagged with `@private`*
1. Mocha's machine-readable reporter output
1. Mocha's default settings
1. Mocha's command-line options
1. The environments which Mocha supports; this includes:
1. Browser versions
1. Node.js versions
1. Compatibility with popular module loaders (e.g., AMD)

- Features, bugs, and updated tests are examples of issues which will be resolved by commit(s).
- Support questions, unconfirmed bugs and [bikeshedding](https://en.wikipedia.org/wiki/Law_of_triviality) are examples of issues which do *not* need `semver-*` labels, since they won't necessarily result in any changes to the codebase.
- Pull requests *do not* need `semver-*` labels, *unless there is no associated issue* (PRO TIP: make an issue!)
- An issue or PR which will introduce a breaking change will be `semver-major`, *regardless* of any other label.
- **Breaking changes to private APIs will be `semver-major`, if and only if they are known to be consumed by actively developed project(s).**
**Err on the side of the user; breaking changes to private APIs will be `semver-major`, if and only if they are known to be consumed by actively developed project(s).**

Examples of a breaking changes might be:

- Throwing an `Error` where one wasn't thrown before
- Removing a command-line option or alias
- Removing an environment from the CI configuration
- Changing the default reporter!
- Changing defaults in a way which would cause tests which were previously successful to start failing, or a failing test to start passing
- The exception is fixing likely false-positives
- A good example would be changing the default `timeout` value

### Questions

@@ -179,7 +239,7 @@ If it's *not* a Mocha problem (people tend not to believe this), you may want to
- `documentation`: Issues around incorrect or missing docs
- `qa`: Issues around Mocha's own test suite
- `chore`: Refactors, CI tweaks, dependencies, etc.
- `developer experience`: Issues which will make it easier to contribute and maintain Mocha
- `developer-experience`: Issues which will make it easier to contribute and maintain Mocha

### Feedback & Follow-ups

@@ -191,7 +251,8 @@ Issues or PRs which require action or feedback from a *specific* maintainer, sho
### Meta

- `stale`: The "stalebot" marks things as stale and will close issues if they need feedback but haven't received any. Comment on an issue to prevent this from happening.
- `help-wanted`: If it's an issue that is not a high priority for the maintenance team, use this label to solicit contributions.
- `help wanted`: If it's an issue that is not a high priority for the maintenance team, use this label to solicit contributions. Note lack of `-` in this label's name.
- `good-first-issue`: Typically combined with `help wanted`; an issue that new contributors to Mocha may find straightforward.

### Closing Issues

@@ -203,18 +264,6 @@ If the issue is a support question, and you believe it has been answered, close

If the issue is not Mocha-related, and/or a bug cannot be confirmed, label it `invalid` and close.

It's easy to reopen issues. If you're not sure, just close it!

## Milestones

A major release following SemVer is "just a number". Yet, given the vast amount of projects which consume Mocha, we should avoid *frequent* breaking changes, as this becomes disruptive.

In the manner of "ripping off a band-aid", sometimes we will want to group features or breaking changes together.

If so, we can add those issues and/or PRs to a new GitHub "milestone", to keep track of what needs to go in before we release.

> Historically, milestones have not been useful for anything other than grouping breaking changes together.
## Commenting on Issues and Reviewing Pull Requests

**All maintainers should be courteous and kind.** Thank the external contributor for the pull request, even if it is not merged. If the pull request has been opened (and subsequently closed) without discussion in a corresponding issue, let them know that by creating an issue first, they could have saved wasted effort. *Clearly and objectively* explain the reasoning for rejecting any PR.
@@ -255,9 +304,12 @@ Here are some suggestions:

## Branches

`master` is the only maintained branch in `mochajs/mocha` or any of the other repos.
`master` is the only maintained branch in `mochajs/mocha` or any of the other repos. **`master` is the only branch to which force-pushing is disallowed.**

Maintainers may push new branches to a repo, as long as they remove them when finished (merging a PR will prompt to do so).

Please *please* ***please*** delete old or unused branches.

## Merging PRs

GitHub has several options for how to merge a PR. Here's what we do:
@@ -266,35 +318,47 @@ GitHub has several options for how to merge a PR. Here's what we do:
1. *If a PR has a single commit*, "Rebase".
1. Don't "Merge".

### Merging `semver-major` PRs
**Upon acceptance of a PR, you must assign it a milestone.**

### Using Milestones

If you know that the PR is breaking, assign it to a new or existing milestone correlating with the next major release. For example, if Mocha's current version is v6.5.2, then this milestone would be named `v7.0.0`.

Once a `semver-major` PR has landed, this means the next release will be a major release. This is because we only maintain a single branch, `master`. So, this opens the floodgates to cram more breaking changes in. See the section about "milestones".
Likewise, if the PR is `semver-minor`, create or use a new milestone correlating to the next *minor* release, e.g., `v6.6.0`.

This is not necessarily ideal, and we should consider another method of using branches *if* it has little potential for confusion!
If it's unclear what the next milestone will be, use or create a milestone named `next`. This milestone will be renamed to the new version at release time.

By using milestones, we can cherry-pick non-breaking changes into minor or patch releases, and keep `master` as the latest version.

**This is subject to change, hopefully.**

## Mocha's Release Process

*It's easier to release often.*

1. Decide whether this is a `patch`, `minor`, or `major` release by the PRs which have been merged since the last release.
1. Decide whether this is a `patch`, `minor`, or `major` release.
1. Checkout `master` in your working copy & pull.
1. Modify `CHANGELOG.md`; follow the existing conventions in that file. Commit this file only; add `[ci skip]` to the commit message to avoid a build.
1. Use `npm version` to bump the version; see `npm version --help` for more info. (Hint--use `-m`: e.g. `npm version patch -m 'Release v%s'`)
1. Push `master` to origin with your new tag; e.g. `git push origin master --tags`
1. Copy & paste the added lines to a new GitHub "release". Be sure to add any missing link references (use "preview" button). Save release as draft.
1. Meanwhile, you can check [the build](https://travis-ci.org/mochajs/mocha) on Travis-CI.
1. Once it's green and you're satisfied with the release notes, open your draft release on GitHub, then click "publish"
1. Back in your working copy, run `npm publish`.
1. Announce the update on Twitter or just tell your dog or something.
1. Modify `CHANGELOG.md`; follow the existing conventions in that file. Use the "pull request" number, unless there isn't one. *You do not need to add Markdown links; this is done automatically.*
1. You can omit stuff from `CHANGELOG.md` that was done by a maintainer, but would have no interest to consumers of Mocha.
1. If the changes aren't of interest to consumers but *were not* made by a maintainer, reference them anyway. It's cool to give attribution!
1. Use `npm version` (use `npm@6+`) to bump the version; see `npm version --help` for more info. (Hint--use `-m`: e.g., `npm version patch -m 'Release v%s'`)
1. This command will update the list of contributors (from the Git history) in `package.json`, and add GitHub links to `CHANGELOG.md`.
1. These changes are then added to the Git "stage" and will be added to the commit.
1. Push `master` to `origin` with your new tag; e.g. `git push origin master --tags`
1. Copy & paste the `CHANGELOG.md` lines to a new GitHub "release". Save release as draft.
1. Meanwhile, you can check [the build](https://travis-ci.org/mochajs/mocha) on Travis-CI and [AppVeyor](https://ci.appveyor.com/project/boneskull/mocha).
1. Once the build is green, and you're satisfied with the release notes, open your draft release on GitHub, then click "publish."
1. Back in your working copy, run `npm publish`. *If you're doing a prerelease, ensure that you use `--tag=next`.*
1. Announce the update on Twitter or just tell your dog or something. New releases will be automatically tweeted by [@b0neskull](https://twitter.com/b0neskull) via a feed subscription to Mocha's "releases" page on GitHub.

In addition to above, you'll need to ensure the docs at [https://mochajs.org](https://mochajs.org) are updated:

1. *If you're doing a prerelease*, fast-forward the `next` branch to `master`, and push it. This updates [https://next.mochajs.org](https://next.mochajs.org). That's all.
1. *If this is NOT a prerelease*, fast-forward the `mochajs.org` branch to `master` and push it. This updates [https://mochajs.org](https://mochajs.org).
1. *If this is a "final" release* (the first release of a major *after* one or more prereleases) then remove the `next` tag from npm via `npm dist-tag rm next`.

*Note: there are too many steps above.*

> As of this writing, `npm version` (using npm@5) is not working well, and you may have to tag manually. Commit the change to the version in `package.json` with a message of the format `Release vX.Y.Z`, then tag the changeset using `vX.Y.Z`.
## Projects

There are [Projects](https://github.com/mochajs/mocha/projects), but we haven't yet settled on how to use them.

## About The JS Foundation

The [JS Foundation](https://js.foundation) retains copyright of all projects underneath the [mochajs org](https://github.com/mochajs). The Foundation does not influence technical decisions nor the project roadmap. It is, however, charged with ensuring the continued vitality and sustainability of projects under its banner.
43 changes: 22 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
@@ -11,9 +11,10 @@

## Links

- **[Documentation](https://mochajs.org)**
- **[Documentation](https://mochajs.org/)**
- **[Release Notes / History / Changes](https://github.com/mochajs/mocha/blob/master/CHANGELOG.md)**
- [Code of Conduct](https://github.com/mochajs/mocha/blob/master/.github/CODE_OF_CONDUCT.md)
- [Contributing](https://github.com/mochajs/mocha/blob/master/.github/CONTRIBUTING.md)
- [Gitter Chatroom](https://gitter.im/mochajs/mocha) (ask questions here!)
- [Google Group](https://groups.google.com/group/mochajs)
- [Issue Tracker](https://github.com/mochajs/mocha/issues)
@@ -57,26 +58,26 @@

Does your company use Mocha? Ask your manager or marketing team if your company would be interested in supporting our project. Support will allow the maintainers to dedicate more time for maintenance and new features for everyone. Also, your company's logo will show [on GitHub](https://github.com/mochajs/mocha#readme) and on [our site](https://mochajs.org) - who doesn't want a little extra exposure? [Here's the info](https://opencollective.com/mochajs#sponsor).

[![MochaJS Backer](https://opencollective.com/mochajs/sponsor/0/avatar)](https://opencollective.com/mochajs/sponsor/0/website)
[![MochaJS Backer](https://opencollective.com/mochajs/sponsor/1/avatar)](https://opencollective.com/mochajs/sponsor/1/website)
[![MochaJS Backer](https://opencollective.com/mochajs/sponsor/2/avatar)](https://opencollective.com/mochajs/sponsor/2/website)
[![MochaJS Backer](https://opencollective.com/mochajs/sponsor/3/avatar)](https://opencollective.com/mochajs/sponsor/3/website)
[![MochaJS Backer](https://opencollective.com/mochajs/sponsor/4/avatar)](https://opencollective.com/mochajs/sponsor/4/website)
[![MochaJS Backer](https://opencollective.com/mochajs/sponsor/5/avatar)](https://opencollective.com/mochajs/sponsor/5/website)
[![MochaJS Backer](https://opencollective.com/mochajs/sponsor/6/avatar)](https://opencollective.com/mochajs/sponsor/6/website)
[![MochaJS Backer](https://opencollective.com/mochajs/sponsor/7/avatar)](https://opencollective.com/mochajs/sponsor/7/website)
[![MochaJS Backer](https://opencollective.com/mochajs/sponsor/8/avatar)](https://opencollective.com/mochajs/sponsor/8/website)
[![MochaJS Backer](https://opencollective.com/mochajs/sponsor/9/avatar)](https://opencollective.com/mochajs/sponsor/9/website)
[![MochaJS Backer](https://opencollective.com/mochajs/sponsor/10/avatar)](https://opencollective.com/mochajs/sponsor/10/website)
[![MochaJS Backer](https://opencollective.com/mochajs/sponsor/11/avatar)](https://opencollective.com/mochajs/sponsor/11/website)
[![MochaJS Backer](https://opencollective.com/mochajs/sponsor/12/avatar)](https://opencollective.com/mochajs/sponsor/12/website)
[![MochaJS Backer](https://opencollective.com/mochajs/sponsor/13/avatar)](https://opencollective.com/mochajs/sponsor/13/website)
[![MochaJS Backer](https://opencollective.com/mochajs/sponsor/14/avatar)](https://opencollective.com/mochajs/sponsor/14/website)
[![MochaJS Backer](https://opencollective.com/mochajs/sponsor/15/avatar)](https://opencollective.com/mochajs/sponsor/15/website)
[![MochaJS Backer](https://opencollective.com/mochajs/sponsor/16/avatar)](https://opencollective.com/mochajs/sponsor/16/website)
[![MochaJS Backer](https://opencollective.com/mochajs/sponsor/17/avatar)](https://opencollective.com/mochajs/sponsor/17/website)
[![MochaJS Backer](https://opencollective.com/mochajs/sponsor/18/avatar)](https://opencollective.com/mochajs/sponsor/18/website)
[![MochaJS Backer](https://opencollective.com/mochajs/sponsor/19/avatar)](https://opencollective.com/mochajs/sponsor/19/website)
[![MochaJS Sponsor](https://opencollective.com/mochajs/sponsor/0/avatar)](https://opencollective.com/mochajs/sponsor/0/website)
[![MochaJS Sponsor](https://opencollective.com/mochajs/sponsor/1/avatar)](https://opencollective.com/mochajs/sponsor/1/website)
[![MochaJS Sponsor](https://opencollective.com/mochajs/sponsor/2/avatar)](https://opencollective.com/mochajs/sponsor/2/website)
[![MochaJS Sponsor](https://opencollective.com/mochajs/sponsor/3/avatar)](https://opencollective.com/mochajs/sponsor/3/website)
[![MochaJS Sponsor](https://opencollective.com/mochajs/sponsor/4/avatar)](https://opencollective.com/mochajs/sponsor/4/website)
[![MochaJS Sponsor](https://opencollective.com/mochajs/sponsor/5/avatar)](https://opencollective.com/mochajs/sponsor/5/website)
[![MochaJS Sponsor](https://opencollective.com/mochajs/sponsor/6/avatar)](https://opencollective.com/mochajs/sponsor/6/website)
[![MochaJS Sponsor](https://opencollective.com/mochajs/sponsor/7/avatar)](https://opencollective.com/mochajs/sponsor/7/website)
[![MochaJS Sponsor](https://opencollective.com/mochajs/sponsor/8/avatar)](https://opencollective.com/mochajs/sponsor/8/website)
[![MochaJS Sponsor](https://opencollective.com/mochajs/sponsor/9/avatar)](https://opencollective.com/mochajs/sponsor/9/website)
[![MochaJS Sponsor](https://opencollective.com/mochajs/sponsor/10/avatar)](https://opencollective.com/mochajs/sponsor/10/website)
[![MochaJS Sponsor](https://opencollective.com/mochajs/sponsor/11/avatar)](https://opencollective.com/mochajs/sponsor/11/website)
[![MochaJS Sponsor](https://opencollective.com/mochajs/sponsor/12/avatar)](https://opencollective.com/mochajs/sponsor/12/website)
[![MochaJS Sponsor](https://opencollective.com/mochajs/sponsor/13/avatar)](https://opencollective.com/mochajs/sponsor/13/website)
[![MochaJS Sponsor](https://opencollective.com/mochajs/sponsor/14/avatar)](https://opencollective.com/mochajs/sponsor/14/website)
[![MochaJS Sponsor](https://opencollective.com/mochajs/sponsor/15/avatar)](https://opencollective.com/mochajs/sponsor/15/website)
[![MochaJS Sponsor](https://opencollective.com/mochajs/sponsor/16/avatar)](https://opencollective.com/mochajs/sponsor/16/website)
[![MochaJS Sponsor](https://opencollective.com/mochajs/sponsor/17/avatar)](https://opencollective.com/mochajs/sponsor/17/website)
[![MochaJS Sponsor](https://opencollective.com/mochajs/sponsor/18/avatar)](https://opencollective.com/mochajs/sponsor/18/website)
[![MochaJS Sponsor](https://opencollective.com/mochajs/sponsor/19/avatar)](https://opencollective.com/mochajs/sponsor/19/website)

## Development

82 changes: 66 additions & 16 deletions appveyor.yml
Original file line number Diff line number Diff line change
@@ -1,27 +1,77 @@
platform:
- x64
###
### appveyor.yml
###

## General configuration
version: '{build}'
skip_commits:
message: /\[ci\s+skip\]/

## Environment configuration
shallow_clone: true
clone_depth: 1
environment:
matrix:
- nodejs_version: '12'
- nodejs_version: '10'
- nodejs_version: '8'
- nodejs_version: '7'
- nodejs_version: '6'
- nodejs_version: '4'
install:
- ps: Install-Product node $env:nodejs_version x64
- set CI=true
- set PATH=%APPDATA%\npm;c:\MinGW\bin;%PATH%
- npm install -g npm
- npm install
matrix:
fast_finish: true
build: off
version: '{build}'
shallow_clone: true
clone_depth: 1
install:
## Manual Growl install
- ps: Add-AppveyorMessage "Installing Growl..."
- ps: $seaURL = "https://github.com/briandunnington/growl-for-windows/releases/download/final/GrowlInstaller.exe"
- ps: $seaPath = "$($env:USERPROFILE)\GrowlInstaller.exe"
- ps: $webclient = New-Object Net.WebClient
- ps: $webclient.DownloadFile($seaURL, $seaPath)
- ps: mkdir C:\GrowlInstaller | out-null
- ps: 7z x $seaPath -oC:\GrowlInstaller | out-null
- ps: cmd /c start /wait msiexec /i C:\GrowlInstaller\Growl_v2.0.msi /quiet
- ps: $env:path = "C:\Program Files (x86)\Growl for Windows;$env:path"
## Growl requires some time before it's ready to handle notifications
- ps: Add-AppveyorMessage "Starting Growl service..."
- ps: Start-Process -NoNewWindow Growl
## Node-related installs
- ps: Add-AppveyorMessage "Installing Node..."
- set PATH=%APPDATA%\npm;C:\MinGW\bin;%PATH%
## Prefer pre-installed Node versions, with fallback to manual update
- ps: |
try {
Install-Product node $env:nodejs_version $env:platform
} catch {
Add-AppveyorMessage " install failed - attempting manual update..."
Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version) $env:platform
}
- ps: Add-AppveyorMessage "Installing npm..."
- npm install -g npm
## Mocha-related package installs
- ps: Add-AppveyorMessage "Installing Mocha dependencies..."
- npm ci --ignore-scripts

## Build configuration
platform:
- x64
build: script
build_script:
- ps: Add-AppveyorMessage "Verify Growl responding..."
- ps: growlnotify test

## Test configuration
before_test:
- set CI=true
test_script:
- ps: Add-AppveyorMessage "Displaying version information"
- ps: (Get-ComputerInfo -Property OsName,OsVersion,OsArchitecture | Format-Table -HideTableHeaders -AutoSize | Out-String).Trim()
- node --version
- npm --version
- ps: Add-AppveyorMessage "Running tests..."
- npm start test.node
skip_commits:
message: /\[ci\s+skip\]/
- ps: Add-AppveyorMessage "Done"

## Notifications
notifications:
- provider: Email
on_build_success: false
on_build_failure: false
on_build_status_changed: false
File renamed without changes
File renamed without changes
Binary file added assets/mocha-logo-96.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 0 additions & 3 deletions bin/.eslintrc.yml

This file was deleted.

599 changes: 4 additions & 595 deletions bin/_mocha

Large diffs are not rendered by default.

228 changes: 151 additions & 77 deletions bin/mocha
Original file line number Diff line number Diff line change
@@ -3,87 +3,161 @@
'use strict';

/**
* This tiny wrapper file checks for known node flags and appends them
* when found, before invoking the "real" _mocha(1) executable.
* This wrapper executable checks for known node flags and appends them when found,
* before invoking the "real" executable (`lib/cli/cli.js`)
*
* @module bin/mocha
* @private
*/

const spawn = require('child_process').spawn;
const path = require('path');
const getOptions = require('./options');
const args = [path.join(__dirname, '_mocha')];

// Load mocha.opts into process.argv
// Must be loaded here to handle node-specific options
getOptions();

process.argv.slice(2).forEach(arg => {
const flag = arg.split('=')[0];

switch (flag) {
case '-d':
args.unshift('--debug');
args.push('--no-timeouts');
break;
case 'debug':
case '--debug':
case '--debug-brk':
case '--inspect':
case '--inspect-brk':
args.unshift(arg);
args.push('--no-timeouts');
break;
case '-gc':
case '--expose-gc':
args.unshift('--expose-gc');
break;
case '--gc-global':
case '--es_staging':
case '--no-deprecation':
case '--no-warnings':
case '--prof':
case '--log-timer-events':
case '--throw-deprecation':
case '--trace-deprecation':
case '--trace-warnings':
case '--use_strict':
case '--allow-natives-syntax':
case '--perf-basic-prof':
case '--napi-modules':
args.unshift(arg);
break;
default:
if (arg.indexOf('--harmony') === 0) {
args.unshift(arg);
} else if (arg.indexOf('--trace') === 0) {
args.unshift(arg);
} else if (arg.indexOf('--icu-data-dir') === 0) {
args.unshift(arg);
} else if (arg.indexOf('--max-old-space-size') === 0) {
args.unshift(arg);
} else if (arg.indexOf('--preserve-symlinks') === 0) {
args.unshift(arg);
} else {
args.push(arg);
}
break;
const {deprecate, warn} = require('../lib/utils');
const {loadOptions} = require('../lib/cli/options');
const {
unparseNodeFlags,
isNodeFlag,
impliesNoTimeouts
} = require('../lib/cli/node-flags');
const unparse = require('yargs-unparser');
const debug = require('debug')('mocha:cli:mocha');
const {aliases} = require('../lib/cli/run-option-metadata');
const nodeEnv = require('node-environment-flags');

const mochaArgs = {};
const nodeArgs = {};

const opts = loadOptions(process.argv.slice(2));
debug('loaded opts', opts);

/**
* Given option/command `value`, disable timeouts if applicable
* @param {string} [value] - Value to check
* @ignore
*/
const disableTimeouts = value => {
if (impliesNoTimeouts(value)) {
debug(`option "${value}" disabled timeouts`);
mochaArgs.timeout = 0;
delete mochaArgs.timeouts;
delete mochaArgs.t;
}
});
};

const proc = spawn(process.execPath, args, {
stdio: 'inherit'
/**
* If `value` begins with `v8-` and is not explicitly `v8-options`, remove prefix
* @param {string} [value] - Value to check
* @returns {string} `value` with prefix (maybe) removed
* @ignore
*/
const trimV8Option = value =>
value !== 'v8-options' && /^v8-/.test(value) ? value.slice(3) : value;

// sort options into "node" and "mocha" buckets
Object.keys(opts).forEach(opt => {
if (isNodeFlag(opt)) {
nodeArgs[trimV8Option(opt)] = opts[opt];
disableTimeouts(opt);
} else {
mochaArgs[opt] = opts[opt];
}
});
proc.on('exit', (code, signal) => {
process.on('exit', () => {
if (signal) {
process.kill(process.pid, signal);
} else {
process.exit(code);
}

// Native debugger handling
// see https://nodejs.org/api/debugger.html#debugger_debugger
// look for 'debug' or 'inspect' that would launch this debugger,
// remove it from Mocha's opts and prepend it to Node's opts.
// also coerce depending on Node.js version.
// A deprecation warning will be printed by node, if applicable.
// (mochaArgs._ are "positional" arguments, not prefixed with - or --)
if (/^(debug|inspect)$/.test(mochaArgs._[0])) {
const command = mochaArgs._.shift();
disableTimeouts(command);
// don't conflict with inspector
['debug', 'inspect', 'debug-brk', 'inspect-brk']
.filter(opt => opt in nodeArgs || opt in mochaArgs)
.forEach(opt => {
warn(`command "${command}" provided; --${opt} ignored`);
delete nodeArgs[opt];
delete mochaArgs[opt];
});
nodeArgs._ = [
parseInt(
process.version
.slice(1)
.split('.')
.shift(),
10
) >= 8
? 'inspect'
: 'debug'
];
}

// allow --debug to invoke --inspect on Node.js v8 or newer.
['debug', 'debug-brk']
.filter(opt => opt in nodeArgs && !nodeEnv.has(opt))
.forEach(opt => {
const newOpt = opt === 'debug' ? 'inspect' : 'inspect-brk';
warn(
`"--${opt}" is not available in Node.js ${process.version}; use "--${newOpt}" instead.`
);
nodeArgs[newOpt] = nodeArgs[opt];
mochaArgs.timeout = false;
debug(`--${opt} -> ${newOpt}`);
delete nodeArgs[opt];
});
});

// terminate children.
process.on('SIGINT', () => {
proc.kill('SIGINT'); // calls runner.abort()
proc.kill('SIGTERM'); // if that didn't work, we're probably in an infinite loop, so make it die.
});
// historical
if (nodeArgs.gc) {
deprecate(
'"-gc" is deprecated and will be removed from a future version of Mocha. Use "--gc-global" instead.'
);
nodeArgs['gc-global'] = nodeArgs.gc;
delete nodeArgs.gc;
}

// --require/-r is treated as Mocha flag except when 'esm' is preloaded
if (mochaArgs.require && mochaArgs.require.includes('esm')) {
nodeArgs.require = ['esm'];
mochaArgs.require = mochaArgs.require.filter(mod => mod !== 'esm');
if (!mochaArgs.require.length) {
delete mochaArgs.require;
}
delete mochaArgs.r;
}

if (Object.keys(nodeArgs).length) {
const {spawn} = require('child_process');
const mochaPath = require.resolve('../lib/cli/cli.js');

debug('final node args', nodeArgs);

const args = [].concat(
unparseNodeFlags(nodeArgs),
mochaPath,
unparse(mochaArgs, {alias: aliases})
);

debug(`exec ${process.execPath} w/ args:`, args);

const proc = spawn(process.execPath, args, {
stdio: 'inherit'
});

proc.on('exit', (code, signal) => {
process.on('exit', () => {
if (signal) {
process.kill(process.pid, signal);
} else {
process.exit(code);
}
});
});

// terminate children.
process.on('SIGINT', () => {
proc.kill('SIGINT'); // calls runner.abort()
proc.kill('SIGTERM'); // if that didn't work, we're probably in an infinite loop, so make it die.
});
} else {
require('../lib/cli/cli').main(unparse(mochaArgs, {alias: aliases}));
}
45 changes: 6 additions & 39 deletions bin/options.js
Original file line number Diff line number Diff line change
@@ -1,43 +1,10 @@
'use strict';

/**
* Dependencies.
/*
* This module is deprecated and will be removed in a future version of Mocha.
* @deprecated Deprecated in v6.0.0; source moved into {@link module:lib/cli/options lib/cli/options module}.
* @module
* @exports module:lib/cli/options
*/

const fs = require('fs');

/**
* Export `getOptions`.
*/

module.exports = getOptions;

/**
* Get options.
*/

function getOptions () {
if (process.argv.length === 3 && (process.argv[2] === '-h' || process.argv[2] === '--help')) {
return;
}

const optsPath = process.argv.indexOf('--opts') === -1
? 'test/mocha.opts'
: process.argv[process.argv.indexOf('--opts') + 1];

try {
const opts = fs.readFileSync(optsPath, 'utf8')
.replace(/\\\s/g, '%20')
.split(/\s/)
.filter(Boolean)
.map(value => value.replace(/%20/g, ' '));

process.argv = process.argv
.slice(0, 2)
.concat(opts.concat(process.argv.slice(2)));
} catch (err) {
// ignore
}

process.env.LOADED_MOCHA_OPTS = true;
}
module.exports = require('../lib/cli/options');
38 changes: 21 additions & 17 deletions browser-entry.js
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@
* Shim process.stdout.
*/

process.stdout = require('browser-stdout')({level: false});
process.stdout = require('browser-stdout')({label: false});

var Mocha = require('./lib/mocha');

@@ -17,7 +17,7 @@ var Mocha = require('./lib/mocha');
* @return {undefined}
*/

var mocha = new Mocha({ reporter: 'html' });
var mocha = new Mocha({reporter: 'html'});

/**
* Save timer references to avoid Sinon interfering (see GH-237).
@@ -38,12 +38,12 @@ var originalOnerrorHandler = global.onerror;
* Revert to original onerror handler if previously defined.
*/

process.removeListener = function (e, fn) {
process.removeListener = function(e, fn) {
if (e === 'uncaughtException') {
if (originalOnerrorHandler) {
global.onerror = originalOnerrorHandler;
} else {
global.onerror = function () {};
global.onerror = function() {};
}
var i = uncaughtExceptionHandlers.indexOf(fn);
if (i !== -1) {
@@ -56,9 +56,9 @@ process.removeListener = function (e, fn) {
* Implements uncaughtException listener.
*/

process.on = function (e, fn) {
process.on = function(e, fn) {
if (e === 'uncaughtException') {
global.onerror = function (err, url, line) {
global.onerror = function(err, url, line) {
fn(new Error(err + ' (' + url + ':' + line + ')'));
return !mocha.allowUncaught;
};
@@ -74,9 +74,9 @@ mocha.suite.removeAllListeners('pre-require');
var immediateQueue = [];
var immediateTimeout;

function timeslice () {
function timeslice() {
var immediateStart = new Date().getTime();
while (immediateQueue.length && (new Date().getTime() - immediateStart) < 100) {
while (immediateQueue.length && new Date().getTime() - immediateStart < 100) {
immediateQueue.shift()();
}
if (immediateQueue.length) {
@@ -90,7 +90,7 @@ function timeslice () {
* High-performance override of Runner.immediately.
*/

Mocha.Runner.immediately = function (callback) {
Mocha.Runner.immediately = function(callback) {
immediateQueue.push(callback);
if (!immediateTimeout) {
immediateTimeout = setTimeout(timeslice, 0);
@@ -102,8 +102,8 @@ Mocha.Runner.immediately = function (callback) {
* This is useful when running tests in a browser because window.onerror will
* only receive the 'message' attribute of the Error.
*/
mocha.throwError = function (err) {
uncaughtExceptionHandlers.forEach(function (fn) {
mocha.throwError = function(err) {
uncaughtExceptionHandlers.forEach(function(fn) {
fn(err);
});
throw err;
@@ -114,7 +114,7 @@ mocha.throwError = function (err) {
* Normally this would happen in Mocha.prototype.loadFiles.
*/

mocha.ui = function (ui) {
mocha.ui = function(ui) {
Mocha.prototype.ui.call(this, ui);
this.suite.emit('pre-require', global, null, this);
return this;
@@ -124,9 +124,9 @@ mocha.ui = function (ui) {
* Setup mocha with the given setting options.
*/

mocha.setup = function (opts) {
mocha.setup = function(opts) {
if (typeof opts === 'string') {
opts = { ui: opts };
opts = {ui: opts};
}
for (var opt in opts) {
if (opts.hasOwnProperty(opt)) {
@@ -140,7 +140,7 @@ mocha.setup = function (opts) {
* Run mocha, returning the Runner.
*/

mocha.run = function (fn) {
mocha.run = function(fn) {
var options = mocha.options;
mocha.globals('location');

@@ -155,10 +155,14 @@ mocha.run = function (fn) {
mocha.invert();
}

return Mocha.prototype.run.call(mocha, function (err) {
return Mocha.prototype.run.call(mocha, function(err) {
// The DOM Document is not available in Web Workers.
var document = global.document;
if (document && document.getElementById('mocha') && options.noHighlighting !== true) {
if (
document &&
document.getElementById('mocha') &&
options.noHighlighting !== true
) {
Mocha.utils.highlightTags('code');
}
if (fn) {
4 changes: 4 additions & 0 deletions docs/.browserslistrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
## Browsers support for autoprefixing CSS for the website
last 2 major versions
not dead
Firefox ESR
6 changes: 6 additions & 0 deletions docs/.eleventyignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
README.md
API.md
LICENSE*
.*
_dist/
example/
3 changes: 0 additions & 3 deletions docs/.eslintrc.yml

This file was deleted.

20 changes: 20 additions & 0 deletions docs/API.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Mocha's API Documentation

* * *

Congratulations! You've found Mocha's API documentation. These docs are for developers who wish to:

- Create an extension for Mocha, or
- Develop Mocha itself, or
- Do something else fancy with Mocha

Otherwise, **you probably want the [main documentation](https://mochajs.org)**.

## Other Links

- **[Main Documentation](https://mochajs.org)**
- **[Release Notes / History / Changes](https://github.com/mochajs/mocha/blob/master/CHANGELOG.md)**
- [Code of Conduct](https://github.com/mochajs/mocha/blob/master/.github/CODE_OF_CONDUCT.md)
- [Gitter Chatroom](https://gitter.im/mochajs/mocha) (ask questions here!)
- [Google Group](https://groups.google.com/group/mochajs)
- [Issue Tracker](https://github.com/mochajs/mocha/issues)
23 changes: 10 additions & 13 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -1,32 +1,29 @@
# mochajs.org

*So you wanna build the site?*
_So you wanna build the site?_

[mochajs.org](https://mochajs.org) is built using [Jekyll](http://jekyllrb.com), the popular static site generator.
[mochajs.org](https://mochajs.org) is built using [Eleventy](https://www.11ty.io/), a simple static site generator.

## Prerequisites

- Ruby
- RubyGems
- Bundler (`gem install bundler`)
- Node.js v4.0.0 or greater
- Node.js v6.x or greater

## Development

1. Run `npm install` to get Node.js deps.
1. Run `bundle install` to install Jekyll and its dependencies. This may or may not require elevated privileges, depending on your system.
1. To serve the site and rebuild as changes are made, execute `npm run serveDocs`.
1. To rebuild the site *once*, execute `npm start buildDocs`.
1. Run `npm install` from working copy root to get Node.js deps.
1. To serve the site and rebuild as changes are made, execute `npm start docs.watch`.
1. To rebuild the site _once_, execute `npm start docs`.

### Notes

- The content lives in `docs/index.md`; everything else is markup, scripts, assets, etc.
- `docs/index.md` may be mutated upon build. If you update the table of contents, **you must commit `index.md`**; GitHub won't do it for you.
- `docs/_site/` is where the generated static site lives (and is what you see at [mochajs.org](https://mochajs.org)). It is *not* under version control.
- This file (`docs/README.md`) should _not_ be included in the build.
- `docs/_dist` is where the deployed site lives. `docs/_site` is essentially a build step. These directories are _not_ under version control.
- See `package-scripts.js` for details on what the builds are actually doing; especially see [markdown-magic](https://npm.im/markdown-magic) for how we're dynamically inserting information into `docs/index.md`.

## License

:copyright: 2016-2017 [JS Foundation](https://js.foundation) and contributors.
:copyright: 2016-2018 [JS Foundation](https://js.foundation) and contributors.

Content licensed [CC-BY-4.0](https://raw.githubusercontent.com/mochajs/mocha/master/docs/LICENSE-CC-BY-4.0).

8 changes: 0 additions & 8 deletions docs/_config.yml

This file was deleted.

3 changes: 2 additions & 1 deletion docs/_headers
Original file line number Diff line number Diff line change
@@ -2,7 +2,8 @@
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Strict-Transport-Security: max-age=31536000, includeSubDomains
Strict-Transport-Security: max-age=31536000; includeSubDomains
Referrer-Policy: strict-origin-when-cross-origin

## Far future expires for hashed file names
/static/*
80 changes: 80 additions & 0 deletions docs/_includes/default.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<title>{{ title }}</title>
<link
rel="preload"
href="https://opencollective.com/static/images/user.svg"
as="image"
crossorigin="anonymous"
/>
<link rel="stylesheet" href="css/normalize.css" />
<link rel="stylesheet" href="css/style.css" />
<link rel="stylesheet" href="css/prism.css" />
<link rel="icon" href="favicon.ico" />

<!--[if lt IE 9]> <script src="js/html5shiv.min.js"></script> <![endif]-->
</head>

<body>
<header id="_header">
<h1>
<a href="/">
<img id="mocha-logo" src="/images/mocha-logo.svg" alt="Mocha logo" />
</a>
</h1>
<p id="tag"><em>simple</em>, <em>flexible</em>, <em>fun</em></p>
</header>

<main id="content">{{ content }}</main>

<aside class="sponsorship">
<a href="https://matomo.org/" rel="external noopener" target="_blank">
<img
id="matomoLogo"
src="images/matomo-logo.png?trim"
alt="Matomo logo"
/>
</a>
<a
title="Mocha is an OpenJS Foundation Project"
href="https://openjsf.org"
rel="external noopener"
target="_blank"
>
<img
src="/images/openjsf-logo.svg"
width="300"
height="94"
alt="OpenJS Foundation Logo"
/>
</a>
</aside>

<footer>
<div>
<a rel="home" href="https://mochajs.org/">mochajs.org</a>
is licensed under a
<a
rel="license external noopener"
href="https://creativecommons.org/licenses/by/4.0/"
>
Creative Commons Attribution 4.0 International License</a
>.
<dl class="dl-inline last-modified">
<dt>Last updated</dt>
<dd>
<time
itemprop="lastModified"
datetime="{{ 'now' | date: '%Y-%m-%dT%H:%M:%SZ' }}"
>
{{ 'now' | date: '%a %b %d %H:%M:%S %Y' }}
</time>
</dd>
</dl>
</div>
</footer>
</body>
</html>
16 changes: 0 additions & 16 deletions docs/_includes/footer.html

This file was deleted.

16 changes: 0 additions & 16 deletions docs/_includes/head.html

This file was deleted.

11 changes: 0 additions & 11 deletions docs/_includes/header.html

This file was deleted.

17 changes: 0 additions & 17 deletions docs/_layouts/default.html

This file was deleted.

113 changes: 113 additions & 0 deletions docs/api-tutorials/custom-reporter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
Mocha allows you to define and use custom reporters install via `npm`.

For example, if `mocha-foo-reporter` was published to the npm registry, you could install it via `npm install mocha-foo-reporter --save-dev`, then use it via `mocha --reporter mocha-foo-reporter`.

## Example Custom Reporter

If you're looking to get started quickly, here's an example of a custom reporter:

<!-- AUTO-GENERATED-CONTENT:START (file:src=../../test/integration/fixtures/simple-reporter.js&header=// my-reporter.js)' -->

```js
// my-reporter.js
'use strict';

const Mocha = require('mocha');
const {
EVENT_RUN_BEGIN,
EVENT_RUN_END,
EVENT_TEST_FAIL,
EVENT_TEST_PASS,
EVENT_SUITE_BEGIN,
EVENT_SUITE_END
} = Mocha.Runner.constants;

// this reporter outputs test results, indenting two spaces per suite
class MyReporter {
constructor(runner) {
this._indents = 0;
const stats = runner.stats;

runner
.once(EVENT_RUN_BEGIN, () => {
console.log('start');
})
.on(EVENT_SUITE_BEGIN, () => {
this.increaseIndent();
})
.on(EVENT_SUITE_END, () => {
this.decreaseIndent();
})
.on(EVENT_TEST_PASS, test => {
// Test#fullTitle() returns the suite name(s)
// prepended to the test title
console.log(`${this.indent()}pass: ${test.fullTitle()}`);
})
.on(EVENT_TEST_FAIL, (test, err) => {
console.log(
`${this.indent()}fail: ${test.fullTitle()} - error: ${err.message}`
);
})
.once(EVENT_RUN_END, () => {
console.log(`end: ${stats.passes}/${stats.passes + stats.failures} ok`);
});
}

indent() {
return Array(this._indents).join(' ');
}

increaseIndent() {
this._indents++;
}

decreaseIndent() {
this._indents--;
}
}

module.exports = MyReporter;
```

<!-- AUTO-GENERATED-CONTENT:END -->

To use this reporter, execute `mocha --reporter /path/to/my-reporter.js`.

For further examples, the built-in reporter implementations are the [best place to look](https://github.com/mochajs/mocha/tree/master/lib/reporters). The [integration tests](https://github.com/mochajs/mocha/tree/master/test/reporters) may also be helpful.

## The `Base` Reporter Class

[Base]{@link Mocha.reporters.Base} is an "abstract" class. It contains console-specific settings and utilities, commonly used by built-in reporters. Browsing the built-in reporter implementations, you may often see static properties of [Base]{@link Mocha.reporters.Base} referenced.

It's often useful--but not necessary--for a custom reporter to extend [Base]{@link Mocha.reporters.Base}.

## Statistics

Mocha adds a `stats` property of type {@link StatsCollector} to the reporter's `Runner` instance (passed as first argument to constructor).

## Events

A reporter should listen for events emitted from the `runner` (a singleton instance of {@link Runner}).

The event names are exported from the `constants` property of `Mocha.Runner`:

| Constant | Event Name | Event Arguments | Description |
| -------------------- | ----------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `EVENT_RUN_BEGIN` | `start` | _(n/a)_ | Execution will begin. |
| `EVENT_RUN_END` | `end` | _(n/a)_ | All [Suites]{@link Suite}, [Tests]{@link Test} and [Hooks]{@link Hook} have completed execution. |
| `EVENT_DELAY_BEGIN` | `waiting` | _(n/a)_ | Waiting for `global.run()` to be called; only emitted when [delay](/#delayed-root-suite) option is `true`. |
| `EVENT_DELAY_END` | `ready` | _(n/a)_ | User called `global.run()` and the root suite is ready to execute. |
| `EVENT_SUITE_BEGIN` | `suite` | `Suite` | The [Hooks]{@link Hook} and [Tests]{@link Test} within a {@link Suite} will be executed, including any nested [Suites]{@link Suite}. |
| `EVENT_SUITE_END` | `suite end` | `Suite` | The [Hooks]{@link Hook}, [Tests]{@link Test}, and nested [Suites]{@link Suite} within a {@link Suite} have completed execution. |
| `EVENT_HOOK_BEGIN` | `hook` | `Hook` | A {@link Hook} will be executed. |
| `EVENT_HOOK_END` | `hook end` | `Hook` | A {@link Hook} has completed execution. |
| `EVENT_TEST_BEGIN` | `test` | `Test` | A {@link Test} will be executed. |
| `EVENT_TEST_END` | `test end` | `Test` | A {@link Test} has completed execution. |
| `EVENT_TEST_FAIL` | `fail` | `Test`, `Error` | A {@link Test} has failed or thrown an exception. |
| `EVENT_TEST_PASS` | `pass` | `Test` | A {@link Test} has passed. |
| `EVENT_TEST_PENDING` | `pending` | `Test` | A {@link Test} was skipped. |
| `EVENT_TEST_RETRY` | `retry` | `Test`, `Error` | A {@link Test} failed, but is about to be retried; only emitted if the `retry` option is nonzero. |

**Please use these constants** instead of the event names in your own reporter! This will ensure compatibility with future versions of Mocha.

> It's important to understand that all `Suite` callbacks will be run _before_ the {@link Runner} emits `EVENT_RUN_BEGIN`. Hooks and tests, however, won't run until _after_ the {@link Runner} emits `EVENT_RUN_BEGIN`.
5 changes: 5 additions & 0 deletions docs/api-tutorials/jsdoc.tutorials.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"custom-reporter": {
"title": "Create a Custom Reporter"
}
}
148 changes: 148 additions & 0 deletions docs/css/prism.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/* PrismJS 1.15.0
https://prismjs.com/download.html#themes=prism&languages=clike+javascript+bash+json */


/**
* prism.js default theme for JavaScript, CSS and HTML
* Based on dabblet (http://dabblet.com)
* @author Lea Verou
*/

code[class*="language-"],
pre[class*="language-"] {
color: black;
/* background: none; */
text-shadow: 0 1px white;
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}

pre[class*="language-"]::-moz-selection,
pre[class*="language-"] ::-moz-selection,
code[class*="language-"]::-moz-selection,
code[class*="language-"] ::-moz-selection {
text-shadow: none;
background: #b3d4fc;
}

pre[class*="language-"]::selection,
pre[class*="language-"] ::selection,
code[class*="language-"]::selection,
code[class*="language-"] ::selection {
text-shadow: none;
background: #b3d4fc;
}

@media print {
code[class*="language-"],
pre[class*="language-"] {
text-shadow: none;
}
}


/* Code blocks */

pre[class*="language-"] {
padding: 1em;
margin: .5em 0;
overflow: auto;
}

:not(pre)>code[class*="language-"],
pre[class*="language-"] {
/* background: #f5f2f0; */
}


/* Inline code */

:not(pre)>code[class*="language-"] {
padding: .1em;
border-radius: .3em;
white-space: normal;
}

.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: slategray;
}

.token.punctuation {
color: #999;
}

.namespace {
opacity: .7;
}

.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.constant,
.token.symbol,
.token.deleted {
color: #905;
}

.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
color: #690;
}

.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
color: #9a6e3a;
}

.token.atrule,
.token.attr-value,
.token.keyword {
color: #07a;
}

.token.function,
.token.class-name {
color: #DD4A68;
}

.token.regex,
.token.important,
.token.variable {
color: #e90;
}

.token.important,
.token.bold {
font-weight: bold;
}

.token.italic {
font-style: italic;
}

.token.entity {
cursor: help;
}
59 changes: 0 additions & 59 deletions docs/css/pygments.css

This file was deleted.

130 changes: 95 additions & 35 deletions docs/css/style.css
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
html {
font: 16px/1.6 "Helvetica Neue", Helvetica, Arial, sans-serif;
font: 16px/1.6 'Helvetica Neue', Helvetica, Arial, sans-serif;
}

body {
color: #2c2c2c;
border-top: 2px solid #ddd;
color: #2c2c2c;
}

@keyframes fadein {
@@ -27,9 +27,10 @@ body {
}
}

header, #content {
max-width: 920px;
header,
#content {
margin: 0 auto;
max-width: 920px;
padding-left: 30px;
padding-right: 30px;
}
@@ -38,31 +39,35 @@ header {
padding-top: 20px;
}

#mocha-logo {
height: 192px;
width: 192px;
}

#content {
padding-bottom: 60px;
}

#_header h1 {
animation: fadein 1s forwards;
margin-top: 0;
margin-left: -19px;
opacity: 0;
animation: fadein 1s forwards;
}

#tag {
animation: fadein 1s forwards, slideright 1s forwards;
color: #c29d7f;
font-weight: 100;
font-size: 30px;
font-weight: 100;
letter-spacing: 2px;
margin-top: -158px;
margin-left: 185px;
margin-bottom: 125px;
letter-spacing: 2px;

animation: fadein 1s forwards, slideright 1s forwards;
}

#tag em {
font-style: normal
font-style: normal;
}

nav.badges a + a {
@@ -85,37 +90,37 @@ nav.badges a + a {

.faded-images img {
opacity: 0;
transition: opacity .3s;
transition: opacity 0.3s;
}

.faded-images.is-loaded img {
opacity: 1;
}

#_backers a img {
width: 64px;
background: url(/images/backer-background.svg?inline) center center no-repeat;
width: 64px;
}

h2 {
margin-top: 80px;
border-bottom: 1px solid #ddd;
font-weight: 400;
letter-spacing: 1px;
border-bottom: 1px solid #ddd;
margin-top: 80px;
text-transform: uppercase;
}

h3 {
border-bottom: 1px solid #eee;
font-weight: 200;
letter-spacing: 1px;
border-bottom: 1px solid #eee;
margin-top: 40px;
text-transform: uppercase;
}

h3 > code {
text-transform: none;
font-size: 14px;
text-transform: none;
}

#content > p:first-child {
@@ -125,65 +130,121 @@ h3 > code {
}

a {
color: #8D6748;
color: #8d6748;
}

a:hover {
color: #717171;
}

a.direct-link {
background: url(../images/link-icon.svg) center center no-repeat;
background-size: auto 60%;
opacity: 0;
overflow: hidden;
position: absolute;
text-decoration: none;
text-indent: -60px;
transform: translateX(-100%);
width: 30px;
}

:hover > a.direct-link {
opacity: 1;
}

ul {
box-sizing: content-box;
column-count: 2;
column-gap: 30px;
margin-top: 20px;
padding: 0 15px;
column-gap: 30px;
column-count: 2;
box-sizing: content-box;
}

ul li {
margin-top: 5px;
list-style: none;
border-bottom: 1px solid #eee;
padding: 5px 0;
break-inside: avoid;
list-style: none;
margin-top: 5px;
padding: 5px 0;
}

code {
font: 14px monaco, monospace;
line-height: 1.8;
}

:not(pre) > code {
background-color: #f5f2f0;
border-radius: 3px;
padding: 0.2em 0.4em;
}

pre {
margin: 20px 0;
padding: 20px;
background-color: #f3f3f3;
border: 1px solid #ddd;
border-bottom-color: #ccc;
background-color: #f3f3f3;
border-radius: 3px;
box-shadow: inset 0 0 10px #ddd;
margin: 20px 0;
overflow-x: auto;
padding: 20px;
}

img.screenshot {
display: block;
margin: 30px auto;
border-radius: 3px;
box-shadow: 0 3px 10px #dedede, 0 1px 5px #888;
display: block;
margin: 30px auto;
max-width: 100%;
}

#matomoLogo {
display: block;
height: 176px;
}

.sponsorship {
display: flex;
justify-content: center;
margin-bottom: 60px;
align-items: center;
}

.sponsorship a {
padding: 0 40px;
}

footer {
background-color: #eee;
border-top: 1px solid #ddd;
padding: 50px 0;
text-align: right;
border-top: 1px solid #ddd;
}

footer span {
display: block;
margin-right: 30px;
footer div {
color: #888;
font-size: 0.8em;
margin-right: 30px;
}

.last-modified {
font-style: italic;
}

.dl-inline dt,
.dl-inline dd {
display: inline;
margin: 0;
}

.dl-inline dt:after {
content: ': ';
}

.dl-inline dd + dt:before {
content: '';
display: block;
}

@media all and (max-width: 600px) {
@@ -198,13 +259,12 @@ footer span {
}

pre {
padding: 10px;
margin: 20px -11px;
padding: 10px;
}

}

blockquote {
padding: 10px;
border-left: 1px solid #eee;
padding: 10px;
}
7 changes: 4 additions & 3 deletions docs/example/Array.js
Original file line number Diff line number Diff line change
@@ -13,7 +13,7 @@ describe('Array', function(){
var arr = [];
var n = arr.push('foo');
expect(n).to.equal(1);
var n = arr.push('bar');
n = arr.push('bar');
expect(n).to.equal(2);
})

@@ -39,7 +39,7 @@ describe('Array', function(){
var arr = [];
var n = arr.unshift('foo');
expect(n).to.equal(1);
var n = arr.unshift('bar');
n = arr.unshift('bar');
expect(n).to.equal(2);
})

@@ -70,4 +70,5 @@ describe('Array', function(){
expect(arr).to.have.length(1);
})
})
})
})

3,403 changes: 0 additions & 3,403 deletions docs/example/chai.js

This file was deleted.

6 changes: 3 additions & 3 deletions docs/example/tests.html
Original file line number Diff line number Diff line change
@@ -3,13 +3,13 @@
<head>
<meta charset="utf-8">
<title>Mocha</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/mocha/4.0.1/mocha.min.css">
<link rel="stylesheet" href="https://unpkg.com/mocha/mocha.css">
<link rel="shortcut icon" href="../favicon.ico">
</head>
<body>
<div id="mocha"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mocha/4.0.1/mocha.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/chai/4.1.2/chai.min.js"></script>
<script src="https://unpkg.com/mocha/mocha.js"></script>
<script src="https://unpkg.com/chai/chai.js"></script>
<script>mocha.setup('bdd')</script>
<script>expect = chai.expect</script>
<script src="Array.js"></script>
13 changes: 13 additions & 0 deletions docs/images/link-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/matomo-logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
68 changes: 68 additions & 0 deletions docs/images/openjsf-logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/test-duration-range.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1,234 changes: 909 additions & 325 deletions docs/index.md

Large diffs are not rendered by default.

338 changes: 0 additions & 338 deletions docs/js/anchor.js

This file was deleted.

7 changes: 0 additions & 7 deletions docs/js/ga.js

This file was deleted.

16 changes: 16 additions & 0 deletions example/config/.mocharc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
'use strict';

// Here's a JavaScript-based config file.
// If you need conditional logic, you might want to use this type of config.
// Otherwise, JSON or YAML is recommended.

module.exports = {
diff: true,
extension: ['js'],
opts: './test/mocha.opts',
package: './package.json',
reporter: 'spec',
slow: 75,
timeout: 2000,
ui: 'bdd'
};
14 changes: 14 additions & 0 deletions example/config/.mocharc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// This config file contains Mocha's defaults.
// As you can see, comments are allowed.
// This same configuration could be provided in the `mocha` property of your
// project's `package.json`.
{
"diff": true,
"extension": ["js"],
"opts": "./test/mocha.opts",
"package": "./package.json",
"reporter": "spec",
"slow": 75,
"timeout": 2000,
"ui": "bdd"
}
14 changes: 14 additions & 0 deletions example/config/.mocharc.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// This config file contains Mocha's defaults.
// As you can see, comments are allowed.
// This same configuration could be provided in the `mocha` property of your
// project's `package.json`.
{
"diff": true,
"extension": ["js"],
"opts": "./test/mocha.opts",
"package": /* 📦 */ "./package.json",
"reporter": /* 📋 */ "spec",
"slow": 75,
"timeout": 2000,
"ui": "bdd"
}
46 changes: 46 additions & 0 deletions example/config/.mocharc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# This is an example Mocha config containing every Mocha option plus others
allow-uncaught: false
async-only: false
bail: false
check-leaks: false
color: true
delay: false
diff: true
exit: false # could be expressed as "no-exit: true"
extension:
- js
# fgrep and grep are mutually exclusive
# fgrep: something
file:
- /path/to/some/file
- /path/to/some/other/file
forbid-only: false
forbid-pending: false
full-trace: false
global:
- jQuery
- $
# fgrep and grep are mutually exclusive
# grep: something
growl: false
ignore:
- /path/to/some/ignored/file
inline-diffs: false
# needs to be used with grep or fgrep
# invert: false
opts: './test/mocha.opts'
recursive: false
reporter: spec
reporter-option:
- foo=bar
- baz=quux
require: '@babel/register'
retries: 1
slow: 75
sort: false
spec: test/**/*.spec.js # the positional arguments!
timeout: false # same as "no-timeout: true" or "timeout: 0"
trace-warnings: true # node flags ok
ui: bdd
v8-stack-trace-limit: 100 # V8 flags are prepended with "v8-"
watch: false
7 changes: 7 additions & 0 deletions example/config/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Mocha Configuration Examples

In this directory, you'll find example Mocha configurations for Node.js.

As of this writing, **these examples are intended to illustrate the available options; they are not intended to serve as boilerplates.**

[Read more about configuration here](https://mochajs.org/#configuring-mocha-nodejs).
33 changes: 33 additions & 0 deletions jsdoc.conf.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"markdown": {
"hardwrap": true,
"parser": "gfm"
},
"mocha-docdash": {
"sort": true,
"static": false
},
"opts": {
"destination": "docs/_site/api",
"encoding": "utf8",
"recurse": true,
"template": "node_modules/@mocha/docdash",
"tutorials": "docs/api-tutorials",
"verbose": true
},
"plugins": ["plugins/markdown"],
"source": {
"include": ["lib/", "./docs/API.md", "bin"]
},
"tags": {
"allowUnknownTags": true
},
"templates": {
"cleverLinks": false,
"default": {
"includeDate": false,
"outputSourceFiles": true
},
"monospaceLinks": false
}
}
56 changes: 32 additions & 24 deletions karma.conf.js
Original file line number Diff line number Diff line change
@@ -3,36 +3,34 @@
const fs = require('fs');
const path = require('path');
const mkdirp = require('mkdirp');
const os = require('os');
const baseBundleDirpath = path.join(__dirname, '.karma');

const hostname = os.hostname();

const browserPlatformPairs = {
'chrome@latest': 'Windows 8',
'chrome@latest': 'Windows 10',
'MicrosoftEdge@latest': 'Windows 10',
'internet explorer@11.0': 'Windows 8.1',
'internet explorer@latest': 'Windows 10',
'firefox@latest': 'Windows 10',
'safari@latest': 'OS X 10.12'
'safari@latest': 'macOS 10.13'
};

module.exports = config => {
let bundleDirpath;
const cfg = {
frameworks: [
'browserify',
'expect',
'mocha'
],
frameworks: ['browserify', 'mocha'],
files: [
// we use the BDD interface for all of the tests that
// aren't interface-specific.
'test/browser-fixtures/bdd.fixture.js',
'test/unit/*.spec.js'
],
preprocessors: {
'test/**/*.js': ['browserify']
},
browserify: {
debug: true,
configure: function configure (b) {
configure: function configure(b) {
b.ignore('glob')
.ignore('fs')
.ignore('path')
@@ -43,8 +41,10 @@ module.exports = config => {
}
if (bundleDirpath) {
// write bundle to directory for debugging
fs.writeFileSync(path.join(bundleDirpath,
`mocha.${Date.now()}.js`), content);
fs.writeFileSync(
path.join(bundleDirpath, `mocha.${Date.now()}.js`),
content
);
}
});
}
@@ -55,8 +55,7 @@ module.exports = config => {
logLevel: config.LOG_INFO,
client: {
mocha: {
reporter: 'html',
timeout: 500
opts: require.resolve('./test/browser-specific/mocha.opts')
}
},
mochaReporter: {
@@ -94,11 +93,11 @@ module.exports = config => {
} else if (env.APPVEYOR) {
throw new Error('no browser tests should run on AppVeyor!');
} else {
console.error('Local/unknown environment detected');
bundleDirpath = path.join(baseBundleDirpath, 'local');
console.error(`Local environment (${hostname}) detected`);
bundleDirpath = path.join(baseBundleDirpath, hostname);
// don't need to run sauce from appveyor b/c travis does it.
if (env.SAUCE_USERNAME || env.SAUCE_ACCESS_KEY) {
const id = `${require('os').hostname()} (${Date.now()})`;
const id = `${hostname} (${Date.now()})`;
sauceConfig = {
build: id,
tunnelIdentifier: id,
@@ -131,19 +130,20 @@ module.exports = config => {
if (cfg.sauceLabs) {
cfg.sauceLabs.testName = `Interface "${MOCHA_TEST}" Integration Tests`;
}
cfg.files = [
`test/browser-fixtures/${MOCHA_TEST}.fixture.js`,
`test/interfaces/${MOCHA_TEST}.spec.js`
];
cfg.files = [`test/interfaces/${MOCHA_TEST}.spec.js`];
cfg.client.mocha.ui = MOCHA_TEST;
break;

case 'esm':
// just run against ChromeHeadless, since other browsers may not
// support
cfg.browsers = ['ChromeHeadless'];
cfg.files = [
'test/browser-fixtures/esm.fixture.html',
'test/browser-specific/esm.spec.js'
{
pattern: 'test/browser-specific/fixtures/esm.fixture.mjs',
type: 'module'
},
{pattern: 'test/browser-specific/esm.spec.mjs', type: 'module'}
];
break;
default:
@@ -152,10 +152,18 @@ module.exports = config => {
}
}

cfg.files.unshift(
require.resolve('unexpected/unexpected'),
{pattern: require.resolve('unexpected/unexpected.js.map'), included: false},
require.resolve('unexpected-sinon'),
require.resolve('unexpected-eventemitter/dist/unexpected-eventemitter.js'),
require.resolve('./test/browser-specific/setup')
);

config.set(cfg);
};

function addSauceTests (cfg) {
function addSauceTests(cfg) {
cfg.reporters.push('saucelabs');
const browsers = Object.keys(browserPlatformPairs);
cfg.browsers = cfg.browsers.concat(browsers);
4 changes: 0 additions & 4 deletions lib/browser/.eslintrc.yml

This file was deleted.

167 changes: 165 additions & 2 deletions lib/browser/growl.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,168 @@
'use strict';

// just stub out growl
/**
* Web Notifications module.
* @module Growl
*/

module.exports = require('../utils').noop;
/**
* Save timer references to avoid Sinon interfering (see GH-237).
*/
var Date = global.Date;
var setTimeout = global.setTimeout;
var EVENT_RUN_END = require('../runner').constants.EVENT_RUN_END;

/**
* Checks if browser notification support exists.
*
* @public
* @see {@link https://caniuse.com/#feat=notifications|Browser support (notifications)}
* @see {@link https://caniuse.com/#feat=promises|Browser support (promises)}
* @see {@link Mocha#growl}
* @see {@link Mocha#isGrowlCapable}
* @return {boolean} whether browser notification support exists
*/
exports.isCapable = function() {
var hasNotificationSupport = 'Notification' in window;
var hasPromiseSupport = typeof Promise === 'function';
return process.browser && hasNotificationSupport && hasPromiseSupport;
};

/**
* Implements browser notifications as a pseudo-reporter.
*
* @public
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/notification|Notification API}
* @see {@link https://developers.google.com/web/fundamentals/push-notifications/display-a-notification|Displaying a Notification}
* @see {@link Growl#isPermitted}
* @see {@link Mocha#_growl}
* @param {Runner} runner - Runner instance.
*/
exports.notify = function(runner) {
var promise = isPermitted();

/**
* Attempt notification.
*/
var sendNotification = function() {
// If user hasn't responded yet... "No notification for you!" (Seinfeld)
Promise.race([promise, Promise.resolve(undefined)])
.then(canNotify)
.then(function() {
display(runner);
})
.catch(notPermitted);
};

runner.once(EVENT_RUN_END, sendNotification);
};

/**
* Checks if browser notification is permitted by user.
*
* @private
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Notification/permission|Notification.permission}
* @see {@link Mocha#growl}
* @see {@link Mocha#isGrowlPermitted}
* @returns {Promise<boolean>} promise determining if browser notification
* permissible when fulfilled.
*/
function isPermitted() {
var permitted = {
granted: function allow() {
return Promise.resolve(true);
},
denied: function deny() {
return Promise.resolve(false);
},
default: function ask() {
return Notification.requestPermission().then(function(permission) {
return permission === 'granted';
});
}
};

return permitted[Notification.permission]();
}

/**
* @summary
* Determines if notification should proceed.
*
* @description
* Notification shall <strong>not</strong> proceed unless `value` is true.
*
* `value` will equal one of:
* <ul>
* <li><code>true</code> (from `isPermitted`)</li>
* <li><code>false</code> (from `isPermitted`)</li>
* <li><code>undefined</code> (from `Promise.race`)</li>
* </ul>
*
* @private
* @param {boolean|undefined} value - Determines if notification permissible.
* @returns {Promise<undefined>} Notification can proceed
*/
function canNotify(value) {
if (!value) {
var why = value === false ? 'blocked' : 'unacknowledged';
var reason = 'not permitted by user (' + why + ')';
return Promise.reject(new Error(reason));
}
return Promise.resolve();
}

/**
* Displays the notification.
*
* @private
* @param {Runner} runner - Runner instance.
*/
function display(runner) {
var stats = runner.stats;
var symbol = {
cross: '\u274C',
tick: '\u2705'
};
var logo = require('../../package').notifyLogo;
var _message;
var message;
var title;

if (stats.failures) {
_message = stats.failures + ' of ' + stats.tests + ' tests failed';
message = symbol.cross + ' ' + _message;
title = 'Failed';
} else {
_message = stats.passes + ' tests passed in ' + stats.duration + 'ms';
message = symbol.tick + ' ' + _message;
title = 'Passed';
}

// Send notification
var options = {
badge: logo,
body: message,
dir: 'ltr',
icon: logo,
lang: 'en-US',
name: 'mocha',
requireInteraction: false,
timestamp: Date.now()
};
var notification = new Notification(title, options);

// Autoclose after brief delay (makes various browsers act same)
var FORCE_DURATION = 4000;
setTimeout(notification.close.bind(notification), FORCE_DURATION);
}

/**
* As notifications are tangential to our purpose, just log the error.
*
* @private
* @param {Error} err - Why notification didn't happen.
*/
function notPermitted(err) {
console.error('notification error:', err.message);
}
22 changes: 11 additions & 11 deletions lib/browser/progress.js
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@ module.exports = Progress;
/**
* Initialize a new `Progress` indicator.
*/
function Progress () {
function Progress() {
this.percent = 0;
this.size(0);
this.fontSize(11);
@@ -19,35 +19,35 @@ function Progress () {
/**
* Set progress size to `size`.
*
* @api public
* @public
* @param {number} size
* @return {Progress} Progress instance.
*/
Progress.prototype.size = function (size) {
Progress.prototype.size = function(size) {
this._size = size;
return this;
};

/**
* Set text to `text`.
*
* @api public
* @public
* @param {string} text
* @return {Progress} Progress instance.
*/
Progress.prototype.text = function (text) {
Progress.prototype.text = function(text) {
this._text = text;
return this;
};

/**
* Set font size to `size`.
*
* @api public
* @public
* @param {number} size
* @return {Progress} Progress instance.
*/
Progress.prototype.fontSize = function (size) {
Progress.prototype.fontSize = function(size) {
this._fontSize = size;
return this;
};
@@ -58,7 +58,7 @@ Progress.prototype.fontSize = function (size) {
* @param {string} family
* @return {Progress} Progress instance.
*/
Progress.prototype.font = function (family) {
Progress.prototype.font = function(family) {
this._font = family;
return this;
};
@@ -69,7 +69,7 @@ Progress.prototype.font = function (family) {
* @param {number} n
* @return {Progress} Progress instance.
*/
Progress.prototype.update = function (n) {
Progress.prototype.update = function(n) {
this.percent = n;
return this;
};
@@ -80,7 +80,7 @@ Progress.prototype.update = function (n) {
* @param {CanvasRenderingContext2d} ctx
* @return {Progress} Progress instance.
*/
Progress.prototype.draw = function (ctx) {
Progress.prototype.draw = function(ctx) {
try {
var percent = Math.min(this.percent, 100);
var size = this._size;
@@ -112,7 +112,7 @@ Progress.prototype.draw = function (ctx) {
var w = ctx.measureText(text).width;

ctx.fillText(text, x - w / 2 + 1, y + fontSize / 2 - 1);
} catch (err) {
} catch (ignore) {
// don't fail if we can't render progress
}
return this;
6 changes: 3 additions & 3 deletions lib/template.html → lib/browser/template.html
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<!DOCTYPE html>
<html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Mocha</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="mocha.css" />
<link rel="stylesheet" href="mocha.css">
</head>
<body>
<div id="mocha"></div>
4 changes: 2 additions & 2 deletions lib/browser/tty.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
'use strict';

exports.isatty = function isatty () {
exports.isatty = function isatty() {
return true;
};

exports.getWindowSize = function getWindowSize () {
exports.getWindowSize = function getWindowSize() {
if ('innerHeight' in global) {
return [global.innerHeight, global.innerWidth];
}
74 changes: 74 additions & 0 deletions lib/cli/cli.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#!/usr/bin/env node

'use strict';

/**
* This is where we finally parse and handle arguments passed to the `mocha` executable.
* Option parsing is handled by {@link https://npm.im/yargs yargs}.
* If executed via `node`, this module will run {@linkcode module:lib/cli/cli.main main()}.
*
* @private
* @module
*/

const debug = require('debug')('mocha:cli:cli');
const symbols = require('log-symbols');
const yargs = require('yargs/yargs');
const path = require('path');
const {loadOptions, YARGS_PARSER_CONFIG} = require('./options');
const commands = require('./commands');
const ansi = require('ansi-colors');
const {repository, homepage, version, gitter} = require('../../package.json');

/**
* - Accepts an `Array` of arguments
* - Modifies {@link https://nodejs.org/api/modules.html#modules_module_paths Node.js' search path} for easy loading of consumer modules
* - Sets {@linkcode https://nodejs.org/api/errors.html#errors_error_stacktracelimit Error.stackTraceLimit} to `Infinity`
* @summary Mocha's main entry point from the command-line.
* @param {string[]} argv - Array of arguments to parse, or by default the lovely `process.argv.slice(2)`
*/
exports.main = (argv = process.argv.slice(2)) => {
debug('entered main with raw args', argv);
// ensure we can require() from current working directory
module.paths.push(process.cwd(), path.resolve('node_modules'));

Error.stackTraceLimit = Infinity; // configurable via --stack-trace-limit?

var args = loadOptions(argv);

yargs()
.scriptName('mocha')
.command(commands.run)
.command(commands.init)
.updateStrings({
'Positionals:': 'Positional Arguments',
'Options:': 'Other Options',
'Commands:': 'Commands'
})
.fail((msg, err, yargs) => {
debug(err);
yargs.showHelp();
console.error(`\n${symbols.error} ${ansi.red('ERROR:')} ${msg}`);
process.exit(1);
})
.help('help', 'Show usage information & exit')
.alias('help', 'h')
.version('version', 'Show version number & exit', version)
.alias('version', 'V')
.wrap(process.stdout.columns ? Math.min(process.stdout.columns, 80) : 80)
.epilog(
`Mocha Resources
Chat: ${ansi.magenta(gitter)}
GitHub: ${ansi.blue(repository.url)}
Docs: ${ansi.yellow(homepage)}
`
)
.parserConfiguration(YARGS_PARSER_CONFIG)
.config(args)
.parse(args._);
};

// allow direct execution
if (require.main === module) {
exports.main();
}
85 changes: 85 additions & 0 deletions lib/cli/collect-files.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
'use strict';

const path = require('path');
const ansi = require('ansi-colors');
const debug = require('debug')('mocha:cli:run:helpers');
const minimatch = require('minimatch');
const utils = require('../utils');

/**
* Exports a function that collects test files from CLI parameters.
* @see module:lib/cli/run-helpers
* @see module:lib/cli/watch-run
* @module
* @private
*/

/**
* Smash together an array of test files in the correct order
* @param {Object} opts - Options
* @param {string[]} opts.extension - File extensions to use
* @param {string[]} opts.spec - Files, dirs, globs to run
* @param {string[]} opts.ignore - Files, dirs, globs to ignore
* @param {string[]} opts.file - List of additional files to include
* @param {boolean} opts.recursive - Find files recursively
* @param {boolean} opts.sort - Sort test files
* @returns {string[]} List of files to test
* @private
*/
module.exports = ({ignore, extension, file, recursive, sort, spec} = {}) => {
let files = [];
const unmatched = [];
spec.forEach(arg => {
let newFiles;
try {
newFiles = utils.lookupFiles(arg, extension, recursive);
} catch (err) {
if (err.code === 'ERR_MOCHA_NO_FILES_MATCH_PATTERN') {
unmatched.push({message: err.message, pattern: err.pattern});
return;
}

throw err;
}

if (typeof newFiles !== 'undefined') {
if (typeof newFiles === 'string') {
newFiles = [newFiles];
}
newFiles = newFiles.filter(fileName =>
ignore.every(pattern => !minimatch(fileName, pattern))
);
}

files = files.concat(newFiles);
});

const fileArgs = file.map(filepath => path.resolve(filepath));
files = files.map(filepath => path.resolve(filepath));

// ensure we don't sort the stuff from fileArgs; order is important!
if (sort) {
files.sort();
}

// add files given through --file to be ran first
files = fileArgs.concat(files);
debug('files (in order): ', files);

if (!files.length) {
// give full message details when only 1 file is missing
const noneFoundMsg =
unmatched.length === 1
? `Error: No test files found: ${JSON.stringify(unmatched[0].pattern)}` // stringify to print escaped characters raw
: 'Error: No test files found';
console.error(ansi.red(noneFoundMsg));
process.exit(1);
} else {
// print messages as an warning
unmatched.forEach(warning => {
console.warn(ansi.yellow(`Warning: ${warning.message}`));
});
}

return files;
};
13 changes: 13 additions & 0 deletions lib/cli/commands.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
'use strict';

/**
* Exports Yargs commands
* @see https://git.io/fpJ0G
* @private
* @module
*/

exports.init = require('./init');

// default command
exports.run = require('./run');
101 changes: 101 additions & 0 deletions lib/cli/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
'use strict';

/**
* Responsible for loading / finding Mocha's "rc" files.
* This doesn't have anything to do with `mocha.opts`.
*
* @private
* @module
*/

const fs = require('fs');
const path = require('path');
const debug = require('debug')('mocha:cli:config');
const findUp = require('find-up');

/**
* These are the valid config files, in order of precedence;
* e.g., if `.mocharc.js` is present, then `.mocharc.yaml` and the rest
* will be ignored.
* The user should still be able to explicitly specify a file.
* @private
*/
exports.CONFIG_FILES = [
'.mocharc.js',
'.mocharc.yaml',
'.mocharc.yml',
'.mocharc.jsonc',
'.mocharc.json'
];

const isModuleNotFoundError = err =>
err.code !== 'MODULE_NOT_FOUND' ||
err.message.indexOf('Cannot find module') !== -1;

/**
* Parsers for various config filetypes. Each accepts a filepath and
* returns an object (but could throw)
*/
const parsers = (exports.parsers = {
yaml: filepath =>
require('js-yaml').safeLoad(fs.readFileSync(filepath, 'utf8')),
js: filepath => {
const cwdFilepath = path.resolve(filepath);
try {
debug(`parsers: load using cwd-relative path: "${cwdFilepath}"`);
return require(cwdFilepath);
} catch (err) {
if (isModuleNotFoundError(err)) {
debug(`parsers: retry load as module-relative path: "${filepath}"`);
return require(filepath);
} else {
throw err; // rethrow
}
}
},
json: filepath =>
JSON.parse(
require('strip-json-comments')(fs.readFileSync(filepath, 'utf8'))
)
});

/**
* Loads and parses, based on file extension, a config file.
* "JSON" files may have comments.
*
* @private
* @param {string} filepath - Config file path to load
* @returns {Object} Parsed config object
*/
exports.loadConfig = filepath => {
let config = {};
debug(`loadConfig: "${filepath}"`);

const ext = path.extname(filepath);
try {
if (ext === '.yml' || ext === '.yaml') {
config = parsers.yaml(filepath);
} else if (ext === '.js') {
config = parsers.js(filepath);
} else {
config = parsers.json(filepath);
}
} catch (err) {
throw new Error(`failed to parse config "${filepath}": ${err}`);
}
return config;
};

/**
* Find ("find up") config file starting at `cwd`
*
* @param {string} [cwd] - Current working directory
* @returns {string|null} Filepath to config, if found
*/
exports.findConfig = (cwd = process.cwd()) => {
const filepath = findUp.sync(exports.CONFIG_FILES, {cwd});
if (filepath) {
debug(`findConfig: found "${filepath}"`);
}
return filepath;
};
9 changes: 9 additions & 0 deletions lib/cli/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
'use strict';

/**
* Just exports {@link module:lib/cli/cli} for convenience.
* @private
* @module lib/cli
* @exports module:lib/cli/cli
*/
module.exports = require('./cli');
37 changes: 37 additions & 0 deletions lib/cli/init.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
'use strict';

/**
* Command module for "init" command
*
* @private
* @module
*/

const fs = require('fs');
const path = require('path');
const mkdirp = require('mkdirp');

exports.command = 'init <path>';

exports.description = 'create a client-side Mocha setup at <path>';

exports.builder = yargs =>
yargs.positional('path', {
type: 'string',
normalize: true
});

exports.handler = argv => {
const destdir = argv.path;
const srcdir = path.join(__dirname, '..', '..');
mkdirp.sync(destdir);
const css = fs.readFileSync(path.join(srcdir, 'mocha.css'));
const js = fs.readFileSync(path.join(srcdir, 'mocha.js'));
const tmpl = fs.readFileSync(
path.join(srcdir, 'lib', 'browser', 'template.html')
);
fs.writeFileSync(path.join(destdir, 'mocha.css'), css);
fs.writeFileSync(path.join(destdir, 'mocha.js'), js);
fs.writeFileSync(path.join(destdir, 'tests.spec.js'), '');
fs.writeFileSync(path.join(destdir, 'index.html'), tmpl);
};
89 changes: 89 additions & 0 deletions lib/cli/node-flags.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
'use strict';

/**
* Some settings and code related to Mocha's handling of Node.js/V8 flags.
* @private
* @module
*/

const nodeFlags = require('node-environment-flags');
const unparse = require('yargs-unparser');

/**
* These flags are considered "debug" flags.
* @see {@link impliesNoTimeouts}
* @private
*/
const debugFlags = new Set(['debug', 'debug-brk', 'inspect', 'inspect-brk']);

/**
* Mocha has historical support for various `node` and V8 flags which might not
* appear in `process.allowedNodeEnvironmentFlags`.
* These include:
* - `--preserve-symlinks`
* - `--harmony-*`
* - `--gc-global`
* - `--trace-*`
* - `--es-staging`
* - `--use-strict`
* - `--v8-*` (but *not* `--v8-options`)
* @summary Whether or not to pass a flag along to the `node` executable.
* @param {string} flag - Flag to test
* @param {boolean} [bareword=true] - If `false`, we expect `flag` to have one or two leading dashes.
* @returns {boolean} If the flag is considered a "Node" flag.
* @private
*/
exports.isNodeFlag = (flag, bareword = true) => {
if (!bareword) {
// check if the flag begins with dashes; if not, not a node flag.
if (!/^--?/.test(flag)) {
return false;
}
// strip the leading dashes to match against subsequent checks
flag = flag.replace(/^--?/, '');
}
return (
// treat --require/-r as Mocha flag even though it's also a node flag
!(flag === 'require' || flag === 'r') &&
// check actual node flags from `process.allowedNodeEnvironmentFlags`,
// then historical support for various V8 and non-`NODE_OPTIONS` flags
// and also any V8 flags with `--v8-` prefix
(nodeFlags.has(flag) ||
debugFlags.has(flag) ||
/(?:preserve-symlinks(?:-main)?|harmony(?:[_-]|$)|(?:trace[_-].+$)|gc(?:[_-]global)?$|es[_-]staging$|use[_-]strict$|v8[_-](?!options).+?$)/.test(
flag
))
);
};

/**
* Returns `true` if the flag is a "debug-like" flag. These require timeouts
* to be suppressed, or pausing the debugger on breakpoints will cause test failures.
* @param {string} flag - Flag to test
* @returns {boolean}
* @private
*/
exports.impliesNoTimeouts = flag => debugFlags.has(flag);

/**
* All non-strictly-boolean arguments to node--those with values--must specify those values using `=`, e.g., `--inspect=0.0.0.0`.
* Unparse these arguments using `yargs-unparser` (which would result in `--inspect 0.0.0.0`), then supply `=` where we have values.
* Apparently --require in Node.js v8 does NOT want `=`.
* There's probably an easier or more robust way to do this; fixes welcome
* @param {Object} opts - Arguments object
* @returns {string[]} Unparsed arguments using `=` to specify values
* @private
*/
exports.unparseNodeFlags = opts => {
var args = unparse(opts);
return args.length
? args
.join(' ')
.split(/\b/)
.map((arg, index, args) =>
arg === ' ' && args[index - 1] !== 'require' ? '=' : arg
)
.join('')
.split(' ')
: [];
};
70 changes: 70 additions & 0 deletions lib/cli/one-and-dones.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
'use strict';

/**
* Contains "command" code for "one-and-dones"--options passed
* to Mocha which cause it to just dump some info and exit.
* See {@link module:lib/cli/one-and-dones.ONE_AND_DONE_ARGS ONE_AND_DONE_ARGS} for more info.
* @module
* @private
*/

const align = require('wide-align');
const Mocha = require('../mocha');

/**
* Dumps a sorted list of the enumerable, lower-case keys of some object
* to `STDOUT`.
* @param {Object} obj - Object, ostensibly having some enumerable keys
* @ignore
* @private
*/
const showKeys = obj => {
console.log();
const keys = Object.keys(obj);
const maxKeyLength = keys.reduce((max, key) => Math.max(max, key.length), 0);
keys
.filter(
key => /^[a-z]/.test(key) && !obj[key].browserOnly && !obj[key].abstract
)
.sort()
.forEach(key => {
const description = obj[key].description;
console.log(
` ${align.left(key, maxKeyLength + 1)}${
description ? `- ${description}` : ''
}`
);
});
console.log();
};

/**
* Handlers for one-and-done options
* @namespace
* @private
*/
exports.ONE_AND_DONES = {
/**
* Dump list of built-in interfaces
* @private
*/
interfaces: () => {
showKeys(Mocha.interfaces);
},
/**
* Dump list of built-in reporters
* @private
*/
reporters: () => {
showKeys(Mocha.reporters);
}
};

/**
* A Set of all one-and-done options
* @type Set<string>
* @private
*/
exports.ONE_AND_DONE_ARGS = new Set(
['help', 'h', 'version', 'V'].concat(Object.keys(exports.ONE_AND_DONES))
);
362 changes: 362 additions & 0 deletions lib/cli/options.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,362 @@
'use strict';

/**
* Main entry point for handling filesystem-based configuration,
* whether that's `mocha.opts` or a config file or `package.json` or whatever.
* @module
*/

const fs = require('fs');
const ansi = require('ansi-colors');
const yargsParser = require('yargs-parser');
const {types, aliases} = require('./run-option-metadata');
const {ONE_AND_DONE_ARGS} = require('./one-and-dones');
const mocharc = require('../mocharc.json');
const {list} = require('./run-helpers');
const {loadConfig, findConfig} = require('./config');
const findUp = require('find-up');
const {deprecate} = require('../utils');
const debug = require('debug')('mocha:cli:options');
const {isNodeFlag} = require('./node-flags');

/**
* The `yargs-parser` namespace
* @external yargsParser
* @see {@link https://npm.im/yargs-parser}
*/

/**
* An object returned by a configured `yargs-parser` representing arguments
* @memberof external:yargsParser
* @interface Arguments
*/

/**
* Base yargs parser configuration
* @private
*/
const YARGS_PARSER_CONFIG = {
'combine-arrays': true,
'short-option-groups': false,
'dot-notation': false
};

/**
* This is the config pulled from the `yargs` property of Mocha's
* `package.json`, but it also disables camel case expansion as to
* avoid outputting non-canonical keynames, as we need to do some
* lookups.
* @private
* @ignore
*/
const configuration = Object.assign({}, YARGS_PARSER_CONFIG, {
'camel-case-expansion': false
});

/**
* This is a really fancy way to:
* - ensure unique values for `array`-type options
* - use its array's last element for `boolean`/`number`/`string`- options given multiple times
* This is passed as the `coerce` option to `yargs-parser`
* @private
* @ignore
*/
const coerceOpts = Object.assign(
types.array.reduce(
(acc, arg) =>
Object.assign(acc, {[arg]: v => Array.from(new Set(list(v)))}),
{}
),
types.boolean
.concat(types.string, types.number)
.reduce(
(acc, arg) =>
Object.assign(acc, {[arg]: v => (Array.isArray(v) ? v.pop() : v)}),
{}
)
);

/**
* We do not have a case when multiple arguments are ever allowed after a flag
* (e.g., `--foo bar baz quux`), so we fix the number of arguments to 1 across
* the board of non-boolean options.
* This is passed as the `narg` option to `yargs-parser`
* @private
* @ignore
*/
const nargOpts = types.array
.concat(types.string, types.number)
.reduce((acc, arg) => Object.assign(acc, {[arg]: 1}), {});

/**
* Wrapper around `yargs-parser` which applies our settings
* @param {string|string[]} args - Arguments to parse
* @param {Object} defaultValues - Default values of mocharc.json
* @param {...Object} configObjects - `configObjects` for yargs-parser
* @private
* @ignore
*/
const parse = (args = [], defaultValues = {}, ...configObjects) => {
// save node-specific args for special handling.
// 1. when these args have a "=" they should be considered to have values
// 2. if they don't, they just boolean flags
// 3. to avoid explicitly defining the set of them, we tell yargs-parser they
// are ALL boolean flags.
// 4. we can then reapply the values after yargs-parser is done.
const nodeArgs = (Array.isArray(args) ? args : args.split(' ')).reduce(
(acc, arg) => {
const pair = arg.split('=');
let flag = pair[0];
if (isNodeFlag(flag, false)) {
flag = flag.replace(/^--?/, '');
return arg.includes('=')
? acc.concat([[flag, pair[1]]])
: acc.concat([[flag, true]]);
}
return acc;
},
[]
);

const result = yargsParser.detailed(args, {
configuration,
configObjects,
default: defaultValues,
coerce: coerceOpts,
narg: nargOpts,
alias: aliases,
string: types.string,
array: types.array,
number: types.number,
boolean: types.boolean.concat(nodeArgs.map(pair => pair[0]))
});
if (result.error) {
console.error(ansi.red(`Error: ${result.error.message}`));
process.exit(1);
}

// reapply "=" arg values from above
nodeArgs.forEach(([key, value]) => {
result.argv[key] = value;
});

return result.argv;
};

/**
* - Replaces comments with empty strings
* - Replaces escaped spaces (e.g., 'xxx\ yyy') with HTML space
* - Splits on whitespace, creating array of substrings
* - Filters empty string elements from array
* - Replaces any HTML space with space
* @summary Parses options read from run-control file.
* @private
* @param {string} content - Content read from run-control file.
* @returns {string[]} cmdline options (and associated arguments)
* @ignore
*/
const parseMochaOpts = content =>
content
.replace(/^#.*$/gm, '')
.replace(/\\\s/g, '%20')
.split(/\s/)
.filter(Boolean)
.map(value => value.replace(/%20/g, ' '));

/**
* Prepends options from run-control file to the command line arguments.
*
* @deprecated Deprecated in v6.0.0; This function is no longer used internally and will be removed in a future version.
* @public
* @alias module:lib/cli/options
* @see {@link https://mochajs.org/#mochaopts|mocha.opts}
*/
module.exports = function getOptions() {
deprecate(
'getOptions() is DEPRECATED and will be removed from a future version of Mocha. Use loadOptions() instead'
);
if (process.argv.length === 3 && ONE_AND_DONE_ARGS.has(process.argv[2])) {
return;
}

const optsPath =
process.argv.indexOf('--opts') === -1
? mocharc.opts
: process.argv[process.argv.indexOf('--opts') + 1];

try {
const options = parseMochaOpts(fs.readFileSync(optsPath, 'utf8'));

process.argv = process.argv
.slice(0, 2)
.concat(options.concat(process.argv.slice(2)));
} catch (ignore) {
// NOTE: should console.error() and throw the error
}

process.env.LOADED_MOCHA_OPTS = true;
};

/**
* Given filepath in `args.opts`, attempt to load and parse a `mocha.opts` file.
* @param {Object} [args] - Arguments object
* @param {string|boolean} [args.opts] - Filepath to mocha.opts; defaults to whatever's in `mocharc.opts`, or `false` to skip
* @returns {external:yargsParser.Arguments|void} If read, object containing parsed arguments
* @memberof module:lib/cli/options
* @see {@link /#mochaopts|mocha.opts}
* @public
*/
const loadMochaOpts = (args = {}) => {
let result;
let filepath = args.opts;
// /dev/null is backwards compat
if (filepath === false || filepath === '/dev/null') {
return result;
}
filepath = filepath || mocharc.opts;
result = {};
let mochaOpts;
try {
mochaOpts = fs.readFileSync(filepath, 'utf8');
debug(`read ${filepath}`);
} catch (err) {
if (args.opts) {
throw new Error(`Unable to read ${filepath}: ${err}`);
}
// ignore otherwise. we tried
debug(`No mocha.opts found at ${filepath}`);
}

// real args should override `mocha.opts` which should override defaults.
// if there's an exception to catch here, I'm not sure what it is.
// by attaching the `no-opts` arg, we avoid re-parsing of `mocha.opts`.
if (mochaOpts) {
result = parse(parseMochaOpts(mochaOpts));
debug(`${filepath} parsed succesfully`);
}
return result;
};

module.exports.loadMochaOpts = loadMochaOpts;

/**
* Given path to config file in `args.config`, attempt to load & parse config file.
* @param {Object} [args] - Arguments object
* @param {string|boolean} [args.config] - Path to config file or `false` to skip
* @public
* @memberof module:lib/cli/options
* @returns {external:yargsParser.Arguments|void} Parsed config, or nothing if `args.config` is `false`
*/
const loadRc = (args = {}) => {
if (args.config !== false) {
const config = args.config || findConfig();
return config ? loadConfig(config) : {};
}
};

module.exports.loadRc = loadRc;

/**
* Given path to `package.json` in `args.package`, attempt to load config from `mocha` prop.
* @param {Object} [args] - Arguments object
* @param {string|boolean} [args.config] - Path to `package.json` or `false` to skip
* @public
* @memberof module:lib/cli/options
* @returns {external:yargsParser.Arguments|void} Parsed config, or nothing if `args.package` is `false`
*/
const loadPkgRc = (args = {}) => {
let result;
if (args.package === false) {
return result;
}
result = {};
const filepath = args.package || findUp.sync(mocharc.package);
if (filepath) {
try {
const pkg = JSON.parse(fs.readFileSync(filepath, 'utf8'));
if (pkg.mocha) {
debug(`'mocha' prop of package.json parsed:`, pkg.mocha);
result = pkg.mocha;
} else {
debug(`no config found in ${filepath}`);
}
} catch (err) {
if (args.package) {
throw new Error(`Unable to read/parse ${filepath}: ${err}`);
}
debug(`failed to read default package.json at ${filepath}; ignoring`);
}
}
return result;
};

module.exports.loadPkgRc = loadPkgRc;

/**
* Priority list:
*
* 1. Command-line args
* 2. RC file (`.mocharc.js`, `.mocharc.ya?ml`, `mocharc.json`)
* 3. `mocha` prop of `package.json`
* 4. `mocha.opts`
* 5. default configuration (`lib/mocharc.json`)
*
* If a {@link module:lib/cli/one-and-dones.ONE_AND_DONE_ARGS "one-and-done" option} is present in the `argv` array, no external config files will be read.
* @summary Parses options read from `mocha.opts`, `.mocharc.*` and `package.json`.
* @param {string|string[]} [argv] - Arguments to parse
* @public
* @memberof module:lib/cli/options
* @returns {external:yargsParser.Arguments} Parsed args from everything
*/
const loadOptions = (argv = []) => {
let args = parse(argv);
// short-circuit: look for a flag that would abort loading of mocha.opts
if (
Array.from(ONE_AND_DONE_ARGS).reduce(
(acc, arg) => acc || arg in args,
false
)
) {
return args;
}

const rcConfig = loadRc(args);
const pkgConfig = loadPkgRc(args);
const optsConfig = loadMochaOpts(args);

if (rcConfig) {
args.config = false;
args._ = args._.concat(rcConfig._ || []);
}
if (pkgConfig) {
args.package = false;
args._ = args._.concat(pkgConfig._ || []);
}
if (optsConfig) {
args.opts = false;
args._ = args._.concat(optsConfig._ || []);
}

args = parse(
args._,
mocharc,
args,
rcConfig || {},
pkgConfig || {},
optsConfig || {}
);

// recombine positional arguments and "spec"
if (args.spec) {
args._ = args._.concat(args.spec);
delete args.spec;
}

// make unique
args._ = Array.from(new Set(args._));

return args;
};

module.exports.loadOptions = loadOptions;
module.exports.YARGS_PARSER_CONFIG = YARGS_PARSER_CONFIG;
179 changes: 179 additions & 0 deletions lib/cli/run-helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
'use strict';

/**
* Helper scripts for the `run` command
* @see module:lib/cli/run
* @module
* @private
*/

const fs = require('fs');
const path = require('path');
const debug = require('debug')('mocha:cli:run:helpers');
const watchRun = require('./watch-run');
const collectFiles = require('./collect-files');

const cwd = (exports.cwd = process.cwd());

exports.watchRun = watchRun;

/**
* Exits Mocha when tests + code under test has finished execution (default)
* @param {number} code - Exit code; typically # of failures
* @ignore
* @private
*/
const exitMochaLater = code => {
process.on('exit', () => {
process.exitCode = Math.min(code, 255);
});
};

/**
* Exits Mocha when Mocha itself has finished execution, regardless of
* what the tests or code under test is doing.
* @param {number} code - Exit code; typically # of failures
* @ignore
* @private
*/
const exitMocha = code => {
const clampedCode = Math.min(code, 255);
let draining = 0;

// Eagerly set the process's exit code in case stream.write doesn't
// execute its callback before the process terminates.
process.exitCode = clampedCode;

// flush output for Node.js Windows pipe bug
// https://github.com/joyent/node/issues/6247 is just one bug example
// https://github.com/visionmedia/mocha/issues/333 has a good discussion
const done = () => {
if (!draining--) {
process.exit(clampedCode);
}
};

const streams = [process.stdout, process.stderr];

streams.forEach(stream => {
// submit empty write request and wait for completion
draining += 1;
stream.write('', done);
});

done();
};

/**
* Coerce a comma-delimited string (or array thereof) into a flattened array of
* strings
* @param {string|string[]} str - Value to coerce
* @returns {string[]} Array of strings
* @private
*/
exports.list = str =>
Array.isArray(str) ? exports.list(str.join(',')) : str.split(/ *, */);

/**
* `require()` the modules as required by `--require <require>`
* @param {string[]} requires - Modules to require
* @private
*/
exports.handleRequires = (requires = []) => {
requires.forEach(mod => {
let modpath = mod;
if (fs.existsSync(mod, {cwd}) || fs.existsSync(`${mod}.js`, {cwd})) {
modpath = path.resolve(mod);
debug(`resolved ${mod} to ${modpath}`);
}
require(modpath);
debug(`loaded require "${mod}"`);
});
};

/**
* Collect test files and run mocha instance.
* @param {Mocha} mocha - Mocha instance
* @param {Options} [opts] - Command line options
* @param {boolean} [opts.exit] - Whether or not to force-exit after tests are complete
* @param {Object} fileCollectParams - Parameters that control test
* file collection. See `lib/cli/collect-files.js`.
* @returns {Runner}
* @private
*/
exports.singleRun = (mocha, {exit}, fileCollectParams) => {
const files = collectFiles(fileCollectParams);
debug('running tests with files', files);
mocha.files = files;
return mocha.run(exit ? exitMocha : exitMochaLater);
};

/**
* Actually run tests
* @param {Mocha} mocha - Mocha instance
* @param {Object} opts - Command line options
* @private
*/
exports.runMocha = (mocha, options) => {
const {
watch = false,
extension = [],
ui = 'bdd',
exit = false,
ignore = [],
file = [],
recursive = false,
sort = false,
spec = []
} = options;

const fileCollectParams = {
ignore,
extension,
file,
recursive,
sort,
spec
};

if (watch) {
watchRun(mocha, {ui}, fileCollectParams);
} else {
exports.singleRun(mocha, {exit}, fileCollectParams);
}
};

/**
* Used for `--reporter` and `--ui`. Ensures there's only one, and asserts
* that it actually exists.
* @todo XXX This must get run after requires are processed, as it'll prevent
* interfaces from loading.
* @param {Object} opts - Options object
* @param {string} key - Resolvable module name or path
* @param {Object} [map] - An object perhaps having key `key`
* @private
*/
exports.validatePlugin = (opts, key, map = {}) => {
if (Array.isArray(opts[key])) {
throw new TypeError(`"--${key} <${key}>" can only be specified once`);
}

const unknownError = () => new Error(`Unknown "${key}": ${opts[key]}`);

if (!map[opts[key]]) {
try {
opts[key] = require(opts[key]);
} catch (err) {
if (err.code === 'MODULE_NOT_FOUND') {
// Try to load reporters from a path (absolute or relative)
try {
opts[key] = require(path.resolve(process.cwd(), opts[key]));
} catch (err) {
throw unknownError();
}
} else {
throw unknownError();
}
}
}
};
87 changes: 87 additions & 0 deletions lib/cli/run-option-metadata.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
'use strict';

/**
* Metadata about various options of the `run` command
* @see module:lib/cli/run
* @module
* @private
*/

/**
* Dictionary of yargs option types to list of options having said type
* @type {{string:string[]}}
* @private
*/
exports.types = {
array: [
'extension',
'file',
'global',
'ignore',
'require',
'reporter-option',
'spec'
],
boolean: [
'allow-uncaught',
'async-only',
'bail',
'check-leaks',
'color',
'delay',
'diff',
'exit',
'forbid-only',
'forbid-pending',
'full-trace',
'growl',
'inline-diffs',
'interfaces',
'invert',
'no-colors',
'recursive',
'reporters',
'sort',
'watch'
],
number: ['retries'],
string: [
'config',
'fgrep',
'grep',
'opts',
'package',
'reporter',
'ui',
'slow',
'timeout'
]
};

/**
* Option aliases keyed by canonical option name.
* Arrays used to reduce
* @type {{string:string[]}}
* @private
*/
exports.aliases = {
'async-only': ['A'],
bail: ['b'],
color: ['c', 'colors'],
extension: ['watch-extensions'],
fgrep: ['f'],
global: ['globals'],
grep: ['g'],
growl: ['G'],
ignore: ['exclude'],
invert: ['i'],
'no-colors': ['C'],
reporter: ['R'],
'reporter-option': ['reporter-options', 'O'],
require: ['r'],
slow: ['s'],
sort: ['S'],
timeout: ['t', 'timeouts'],
ui: ['u'],
watch: ['w']
};
293 changes: 293 additions & 0 deletions lib/cli/run.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,293 @@
'use strict';

/**
* Definition for Mocha's default ("run tests") command
*
* @module
* @private
*/

const Mocha = require('../mocha');
const {
createUnsupportedError,
createInvalidArgumentValueError,
createMissingArgumentError
} = require('../errors');

const {
list,
handleRequires,
validatePlugin,
runMocha
} = require('./run-helpers');
const {ONE_AND_DONES, ONE_AND_DONE_ARGS} = require('./one-and-dones');
const debug = require('debug')('mocha:cli:run');
const defaults = require('../mocharc');
const {types, aliases} = require('./run-option-metadata');

/**
* Logical option groups
* @constant
*/
const GROUPS = {
FILES: 'File Handling',
FILTERS: 'Test Filters',
NODEJS: 'Node.js & V8',
OUTPUT: 'Reporting & Output',
RULES: 'Rules & Behavior',
CONFIG: 'Configuration'
};

exports.command = ['$0 [spec..]', 'debug [spec..]'];

exports.describe = 'Run tests with Mocha';

exports.builder = yargs =>
yargs
.options({
'allow-uncaught': {
description: 'Allow uncaught errors to propagate',
group: GROUPS.RULES
},
'async-only': {
description:
'Require all tests to use a callback (async) or return a Promise',
group: GROUPS.RULES
},
bail: {
description: 'Abort ("bail") after first test failure',
group: GROUPS.RULES
},
'check-leaks': {
description: 'Check for global variable leaks',
group: GROUPS.RULES
},
color: {
description: 'Force-enable color output',
group: GROUPS.OUTPUT
},
config: {
config: true,
defaultDescription: '(nearest rc file)',
description: 'Path to config file',
group: GROUPS.CONFIG
},
delay: {
description: 'Delay initial execution of root suite',
group: GROUPS.RULES
},
diff: {
default: true,
description: 'Show diff on failure',
group: GROUPS.OUTPUT
},
exit: {
description: 'Force Mocha to quit after tests complete',
group: GROUPS.RULES
},
extension: {
default: defaults.extension,
defaultDescription: 'js',
description: 'File extension(s) to load and/or watch',
group: GROUPS.FILES,
requiresArg: true,
coerce: list
},
fgrep: {
conflicts: 'grep',
description: 'Only run tests containing this string',
group: GROUPS.FILTERS,
requiresArg: true
},
file: {
defaultDescription: '(none)',
description:
'Specify file(s) to be loaded prior to root suite execution',
group: GROUPS.FILES,
normalize: true,
requiresArg: true
},
'forbid-only': {
description: 'Fail if exclusive test(s) encountered',
group: GROUPS.RULES
},
'forbid-pending': {
description: 'Fail if pending test(s) encountered',
group: GROUPS.RULES
},
'full-trace': {
description: 'Display full stack traces',
group: GROUPS.OUTPUT
},
global: {
coerce: list,
description: 'List of allowed global variables',
group: GROUPS.RULES,
requiresArg: true
},
grep: {
coerce: value => (!value ? null : value),
conflicts: 'fgrep',
description: 'Only run tests matching this string or regexp',
group: GROUPS.FILTERS,
requiresArg: true
},
growl: {
description: 'Enable Growl notifications',
group: GROUPS.OUTPUT
},
ignore: {
defaultDescription: '(none)',
description: 'Ignore file(s) or glob pattern(s)',
group: GROUPS.FILES,
requiresArg: true
},
'inline-diffs': {
description:
'Display actual/expected differences inline within each string',
group: GROUPS.OUTPUT
},
interfaces: {
conflicts: Array.from(ONE_AND_DONE_ARGS),
description: 'List built-in user interfaces & exit'
},
invert: {
description: 'Inverts --grep and --fgrep matches',
group: GROUPS.FILTERS
},
'no-colors': {
description: 'Force-disable color output',
group: GROUPS.OUTPUT,
hidden: true
},
opts: {
default: defaults.opts,
description: 'Path to `mocha.opts`',
group: GROUPS.CONFIG,
normalize: true,
requiresArg: true
},
package: {
description: 'Path to package.json for config',
group: GROUPS.CONFIG,
normalize: true,
requiresArg: true
},
recursive: {
description: 'Look for tests in subdirectories',
group: GROUPS.FILES
},
reporter: {
default: defaults.reporter,
description: 'Specify reporter to use',
group: GROUPS.OUTPUT,
requiresArg: true
},
reporters: {
conflicts: Array.from(ONE_AND_DONE_ARGS),
description: 'List built-in reporters & exit'
},
'reporter-option': {
coerce: opts =>
list(opts).reduce((acc, opt) => {
const pair = opt.split('=');

if (pair.length > 2 || !pair.length) {
throw createInvalidArgumentValueError(
`invalid reporter option '${opt}'`,
'--reporter-option',
opt,
'expected "key=value" format'
);
}

acc[pair[0]] = pair.length === 2 ? pair[1] : true;
return acc;
}, {}),
description: 'Reporter-specific options (<k=v,[k1=v1,..]>)',
group: GROUPS.OUTPUT,
requiresArg: true
},
require: {
defaultDescription: '(none)',
description: 'Require module',
group: GROUPS.FILES,
requiresArg: true
},
retries: {
description: 'Retry failed tests this many times',
group: GROUPS.RULES
},
slow: {
default: defaults.slow,
description: 'Specify "slow" test threshold (in milliseconds)',
group: GROUPS.RULES
},
sort: {
description: 'Sort test files',
group: GROUPS.FILES
},
timeout: {
default: defaults.timeout,
description: 'Specify test timeout threshold (in milliseconds)',
group: GROUPS.RULES
},
ui: {
default: defaults.ui,
description: 'Specify user interface',
group: GROUPS.RULES,
requiresArg: true
},
watch: {
description: 'Watch files in the current working directory for changes',
group: GROUPS.FILES
}
})
.positional('spec', {
default: ['test'],
description: 'One or more files, directories, or globs to test',
type: 'array'
})
.check(argv => {
// "one-and-dones"; let yargs handle help and version
Object.keys(ONE_AND_DONES).forEach(opt => {
if (argv[opt]) {
ONE_AND_DONES[opt].call(null, yargs);
process.exit();
}
});

// yargs.implies() isn't flexible enough to handle this
if (argv.invert && !('fgrep' in argv || 'grep' in argv)) {
throw createMissingArgumentError(
'"--invert" requires one of "--fgrep <str>" or "--grep <regexp>"',
'--fgrep|--grep',
'string|regexp'
);
}

if (argv.compilers) {
throw createUnsupportedError(
`--compilers is DEPRECATED and no longer supported.
See https://git.io/vdcSr for migration information.`
);
}

// load requires first, because it can impact "plugin" validation
handleRequires(argv.require);
validatePlugin(argv, 'reporter', Mocha.reporters);
validatePlugin(argv, 'ui', Mocha.interfaces);

return true;
})
.array(types.array)
.boolean(types.boolean)
.string(types.string)
.number(types.number)
.alias(aliases);

exports.handler = argv => {
debug('post-yargs config', argv);
const mocha = new Mocha(argv);
runMocha(mocha, argv);
};
106 changes: 106 additions & 0 deletions lib/cli/watch-run.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
'use strict';

const utils = require('../utils');
const Context = require('../context');
const Mocha = require('../mocha');
const collectFiles = require('./collect-files');

/**
* Exports the `watchRun` function that runs mocha in "watch" mode.
* @see module:lib/cli/run-helpers
* @module
* @private
*/

/**
* Run Mocha in "watch" mode
* @param {Mocha} mocha - Mocha instance
* @param {Object} opts - Options
* @param {string} opts.ui - User interface
* @param {Object} fileCollectParams - Parameters that control test
* file collection. See `lib/cli/collect-files.js`.
* @param {string[]} fileCollectParams.extension - List of extensions to watch
* @private
*/
module.exports = (mocha, {ui}, fileCollectParams) => {
let runner;
const files = collectFiles(fileCollectParams);

console.log();
hideCursor();
process.on('SIGINT', () => {
showCursor();
console.log('\n');
// By UNIX/Posix convention this indicates that the process was
// killed by SIGINT which has portable number 2.
process.exit(128 + 2);
});

const watchFiles = utils.files(process.cwd(), fileCollectParams.extension);
let runAgain = false;

const loadAndRun = () => {
try {
mocha.files = files;
runAgain = false;
runner = mocha.run(() => {
runner = null;
if (runAgain) {
rerun();
}
});
} catch (e) {
console.log(e.stack);
}
};

const purge = () => {
watchFiles.forEach(Mocha.unloadFile);
};

loadAndRun();

const rerun = () => {
purge();
eraseLine();
mocha.suite = mocha.suite.clone();
mocha.suite.ctx = new Context();
mocha.ui(ui);
loadAndRun();
};

utils.watch(watchFiles, () => {
runAgain = true;
if (runner) {
runner.abort();
} else {
rerun();
}
});
};

/**
* Hide the cursor.
* @ignore
* @private
*/
const hideCursor = () => {
process.stdout.write('\u001b[?25l');
};

/**
* Show the cursor.
* @ignore
* @private
*/
const showCursor = () => {
process.stdout.write('\u001b[?25h');
};

/**
* Erases the line on stdout
* @private
*/
const eraseLine = () => {
process.stdout.write('\u001b[2K');
};
34 changes: 18 additions & 16 deletions lib/context.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
'use strict';

/**
* @module Context
*/
/**
* Expose `Context`.
*/
@@ -9,18 +11,18 @@ module.exports = Context;
/**
* Initialize a new `Context`.
*
* @api private
* @private
*/
function Context () {}
function Context() {}

/**
* Set or get the context `Runnable` to `runnable`.
*
* @api private
* @private
* @param {Runnable} runnable
* @return {Context}
* @return {Context} context
*/
Context.prototype.runnable = function (runnable) {
Context.prototype.runnable = function(runnable) {
if (!arguments.length) {
return this._runnable;
}
@@ -31,11 +33,11 @@ Context.prototype.runnable = function (runnable) {
/**
* Set or get test timeout `ms`.
*
* @api private
* @private
* @param {number} ms
* @return {Context} self
*/
Context.prototype.timeout = function (ms) {
Context.prototype.timeout = function(ms) {
if (!arguments.length) {
return this.runnable().timeout();
}
@@ -46,11 +48,11 @@ Context.prototype.timeout = function (ms) {
/**
* Set test timeout `enabled`.
*
* @api private
* @private
* @param {boolean} enabled
* @return {Context} self
*/
Context.prototype.enableTimeouts = function (enabled) {
Context.prototype.enableTimeouts = function(enabled) {
if (!arguments.length) {
return this.runnable().enableTimeouts();
}
@@ -61,11 +63,11 @@ Context.prototype.enableTimeouts = function (enabled) {
/**
* Set or get test slowness threshold `ms`.
*
* @api private
* @private
* @param {number} ms
* @return {Context} self
*/
Context.prototype.slow = function (ms) {
Context.prototype.slow = function(ms) {
if (!arguments.length) {
return this.runnable().slow();
}
@@ -76,21 +78,21 @@ Context.prototype.slow = function (ms) {
/**
* Mark a test as skipped.
*
* @api private
* @private
* @throws Pending
*/
Context.prototype.skip = function () {
Context.prototype.skip = function() {
this.runnable().skip();
};

/**
* Set or get a number of allowed retries on failed tests
*
* @api private
* @private
* @param {number} n
* @return {Context} self
*/
Context.prototype.retries = function (n) {
Context.prototype.retries = function(n) {
if (!arguments.length) {
return this.runnable().retries();
}
141 changes: 141 additions & 0 deletions lib/errors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
'use strict';
/**
* @module Errors
*/
/**
* Factory functions to create throwable error objects
*/

/**
* Creates an error object to be thrown when no files to be tested could be found using specified pattern.
*
* @public
* @param {string} message - Error message to be displayed.
* @param {string} pattern - User-specified argument value.
* @returns {Error} instance detailing the error condition
*/
function createNoFilesMatchPatternError(message, pattern) {
var err = new Error(message);
err.code = 'ERR_MOCHA_NO_FILES_MATCH_PATTERN';
err.pattern = pattern;
return err;
}

/**
* Creates an error object to be thrown when the reporter specified in the options was not found.
*
* @public
* @param {string} message - Error message to be displayed.
* @param {string} reporter - User-specified reporter value.
* @returns {Error} instance detailing the error condition
*/
function createInvalidReporterError(message, reporter) {
var err = new TypeError(message);
err.code = 'ERR_MOCHA_INVALID_REPORTER';
err.reporter = reporter;
return err;
}

/**
* Creates an error object to be thrown when the interface specified in the options was not found.
*
* @public
* @param {string} message - Error message to be displayed.
* @param {string} ui - User-specified interface value.
* @returns {Error} instance detailing the error condition
*/
function createInvalidInterfaceError(message, ui) {
var err = new Error(message);
err.code = 'ERR_MOCHA_INVALID_INTERFACE';
err.interface = ui;
return err;
}

/**
* Creates an error object to be thrown when a behavior, option, or parameter is unsupported.
*
* @public
* @param {string} message - Error message to be displayed.
* @returns {Error} instance detailing the error condition
*/
function createUnsupportedError(message) {
var err = new Error(message);
err.code = 'ERR_MOCHA_UNSUPPORTED';
return err;
}

/**
* Creates an error object to be thrown when an argument is missing.
*
* @public
* @param {string} message - Error message to be displayed.
* @param {string} argument - Argument name.
* @param {string} expected - Expected argument datatype.
* @returns {Error} instance detailing the error condition
*/
function createMissingArgumentError(message, argument, expected) {
return createInvalidArgumentTypeError(message, argument, expected);
}

/**
* Creates an error object to be thrown when an argument did not use the supported type
*
* @public
* @param {string} message - Error message to be displayed.
* @param {string} argument - Argument name.
* @param {string} expected - Expected argument datatype.
* @returns {Error} instance detailing the error condition
*/
function createInvalidArgumentTypeError(message, argument, expected) {
var err = new TypeError(message);
err.code = 'ERR_MOCHA_INVALID_ARG_TYPE';
err.argument = argument;
err.expected = expected;
err.actual = typeof argument;
return err;
}

/**
* Creates an error object to be thrown when an argument did not use the supported value
*
* @public
* @param {string} message - Error message to be displayed.
* @param {string} argument - Argument name.
* @param {string} value - Argument value.
* @param {string} [reason] - Why value is invalid.
* @returns {Error} instance detailing the error condition
*/
function createInvalidArgumentValueError(message, argument, value, reason) {
var err = new TypeError(message);
err.code = 'ERR_MOCHA_INVALID_ARG_VALUE';
err.argument = argument;
err.value = value;
err.reason = typeof reason !== 'undefined' ? reason : 'is invalid';
return err;
}

/**
* Creates an error object to be thrown when an exception was caught, but the `Error` is falsy or undefined.
*
* @public
* @param {string} message - Error message to be displayed.
* @returns {Error} instance detailing the error condition
*/
function createInvalidExceptionError(message, value) {
var err = new Error(message);
err.code = 'ERR_MOCHA_INVALID_EXCEPTION';
err.valueType = typeof value;
err.value = value;
return err;
}

module.exports = {
createInvalidArgumentTypeError: createInvalidArgumentTypeError,
createInvalidArgumentValueError: createInvalidArgumentValueError,
createInvalidExceptionError: createInvalidExceptionError,
createInvalidInterfaceError: createInvalidInterfaceError,
createInvalidReporterError: createInvalidReporterError,
createMissingArgumentError: createMissingArgumentError,
createNoFilesMatchPatternError: createNoFilesMatchPatternError,
createUnsupportedError: createUnsupportedError
};
136 changes: 136 additions & 0 deletions lib/growl.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
'use strict';

/**
* Desktop Notifications module.
* @module Growl
*/

const os = require('os');
const path = require('path');
const {sync: which} = require('which');
const {EVENT_RUN_END} = require('./runner').constants;

/**
* @summary
* Checks if Growl notification support seems likely.
*
* @description
* Glosses over the distinction between an unsupported platform
* and one that lacks prerequisite software installations.
*
* @public
* @see {@link https://github.com/tj/node-growl/blob/master/README.md|Prerequisite Installs}
* @see {@link Mocha#growl}
* @see {@link Mocha#isGrowlCapable}
* @return {boolean} whether Growl notification support can be expected
*/
exports.isCapable = () => {
if (!process.browser) {
return getSupportBinaries().reduce(
(acc, binary) => acc || Boolean(which(binary, {nothrow: true})),
false
);
}
return false;
};

/**
* Implements desktop notifications as a pseudo-reporter.
*
* @public
* @see {@link Mocha#_growl}
* @param {Runner} runner - Runner instance.
*/
exports.notify = runner => {
runner.once(EVENT_RUN_END, () => {
display(runner);
});
};

/**
* Displays the notification.
*
* @private
* @param {Runner} runner - Runner instance.
*/
const display = runner => {
const growl = require('growl');
const stats = runner.stats;
const symbol = {
cross: '\u274C',
tick: '\u2705'
};
let _message;
let message;
let title;

if (stats.failures) {
_message = `${stats.failures} of ${stats.tests} tests failed`;
message = `${symbol.cross} ${_message}`;
title = 'Failed';
} else {
_message = `${stats.passes} tests passed in ${stats.duration}ms`;
message = `${symbol.tick} ${_message}`;
title = 'Passed';
}

// Send notification
const options = {
image: logo(),
name: 'mocha',
title
};
growl(message, options, onCompletion);
};

/**
* @summary
* Callback for result of attempted Growl notification.
*
* @description
* Despite its appearance, this is <strong>not</strong> an Error-first
* callback -- all parameters are populated regardless of success.
*
* @private
* @callback Growl~growlCB
* @param {*} err - Error object, or <code>null</code> if successful.
*/
function onCompletion(err) {
if (err) {
// As notifications are tangential to our purpose, just log the error.
const message =
err.code === 'ENOENT' ? 'prerequisite software not found' : err.message;
console.error('notification error:', message);
}
}

/**
* Returns Mocha logo image path.
*
* @private
* @return {string} Pathname of Mocha logo
*/
const logo = () => {
return path.join(__dirname, '..', 'assets', 'mocha-logo-96.png');
};

/**
* @summary
* Gets platform-specific Growl support binaries.
*
* @description
* Somewhat brittle dependency on `growl` package implementation, but it
* rarely changes.
*
* @private
* @see {@link https://github.com/tj/node-growl/blob/master/lib/growl.js#L28-L126|setupCmd}
* @return {string[]} names of Growl support binaries
*/
const getSupportBinaries = () => {
const binaries = {
Darwin: ['terminal-notifier', 'growlnotify'],
Linux: ['notify-send', 'growl'],
Windows_NT: ['growlnotify.exe']
};
return binaries[os.type()] || [];
};
16 changes: 7 additions & 9 deletions lib/hook.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
'use strict';

/**
* Module dependencies.
*/

var Runnable = require('./runnable');
var inherits = require('./utils').inherits;

@@ -14,13 +10,14 @@ var inherits = require('./utils').inherits;
module.exports = Hook;

/**
* Initialize a new `Hook` with the given `title` and callback `fn`.
* Initialize a new `Hook` with the given `title` and callback `fn`
*
* @class
* @extends Runnable
* @param {String} title
* @param {Function} fn
* @api private
*/
function Hook (title, fn) {
function Hook(title, fn) {
Runnable.call(this, title, fn);
this.type = 'hook';
}
@@ -33,11 +30,12 @@ inherits(Hook, Runnable);
/**
* Get or set the test `err`.
*
* @memberof Hook
* @public
* @param {Error} err
* @return {Error}
* @api public
*/
Hook.prototype.error = function (err) {
Hook.prototype.error = function(err) {
if (!arguments.length) {
err = this._error;
this._error = null;
29 changes: 16 additions & 13 deletions lib/interfaces/bdd.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
'use strict';

/**
* Module dependencies.
*/

var Test = require('../test');
var EVENT_FILE_PRE_REQUIRE = require('../suite').constants
.EVENT_FILE_PRE_REQUIRE;

/**
* BDD-style interface:
@@ -23,10 +21,10 @@ var Test = require('../test');
*
* @param {Suite} suite Root suite.
*/
module.exports = function (suite) {
module.exports = function bddInterface(suite) {
var suites = [suite];

suite.on('pre-require', function (context, file, mocha) {
suite.on(EVENT_FILE_PRE_REQUIRE, function(context, file, mocha) {
var common = require('./common')(suites, context, mocha);

context.before = common.before;
@@ -40,7 +38,7 @@ module.exports = function (suite) {
* and/or tests.
*/

context.describe = context.context = function (title, fn) {
context.describe = context.context = function(title, fn) {
return common.suite.create({
title: title,
file: file,
@@ -52,7 +50,10 @@ module.exports = function (suite) {
* Pending describe.
*/

context.xdescribe = context.xcontext = context.describe.skip = function (title, fn) {
context.xdescribe = context.xcontext = context.describe.skip = function(
title,
fn
) {
return common.suite.skip({
title: title,
file: file,
@@ -64,7 +65,7 @@ module.exports = function (suite) {
* Exclusive suite.
*/

context.describe.only = function (title, fn) {
context.describe.only = function(title, fn) {
return common.suite.only({
title: title,
file: file,
@@ -78,7 +79,7 @@ module.exports = function (suite) {
* acting as a thunk.
*/

context.it = context.specify = function (title, fn) {
context.it = context.specify = function(title, fn) {
var suite = suites[0];
if (suite.isPending()) {
fn = null;
@@ -93,23 +94,25 @@ module.exports = function (suite) {
* Exclusive test-case.
*/

context.it.only = function (title, fn) {
context.it.only = function(title, fn) {
return common.test.only(mocha, context.it(title, fn));
};

/**
* Pending test case.
*/

context.xit = context.xspecify = context.it.skip = function (title) {
context.xit = context.xspecify = context.it.skip = function(title) {
return context.it(title);
};

/**
* Number of attempts to retry.
*/
context.it.retries = function (n) {
context.it.retries = function(n) {
context.retries(n);
};
});
};

module.exports.description = 'BDD or RSpec style [default]';
68 changes: 51 additions & 17 deletions lib/interfaces/common.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
'use strict';

var Suite = require('../suite');
var errors = require('../errors');
var createMissingArgumentError = errors.createMissingArgumentError;

/**
* Functions common to more than one interface.
@@ -10,7 +12,23 @@ var Suite = require('../suite');
* @param {Mocha} mocha
* @return {Object} An object containing common functions.
*/
module.exports = function (suites, context, mocha) {
module.exports = function(suites, context, mocha) {
/**
* Check if the suite should be tested.
*
* @private
* @param {Suite} suite - suite to check
* @returns {boolean}
*/
function shouldBeTested(suite) {
return (
!mocha.options.grep ||
(mocha.options.grep &&
mocha.options.grep.test(suite.fullTitle()) &&
!mocha.options.invert)
);
}

return {
/**
* This is only present if flag --delay is passed into Mocha. It triggers
@@ -19,8 +37,8 @@ module.exports = function (suites, context, mocha) {
* @param {Suite} suite The root suite.
* @return {Function} A function which runs the root suite
*/
runWithSuite: function runWithSuite (suite) {
return function run () {
runWithSuite: function runWithSuite(suite) {
return function run() {
suite.run();
};
},
@@ -31,7 +49,7 @@ module.exports = function (suites, context, mocha) {
* @param {string} name
* @param {Function} fn
*/
before: function (name, fn) {
before: function(name, fn) {
suites[0].beforeAll(name, fn);
},

@@ -41,7 +59,7 @@ module.exports = function (suites, context, mocha) {
* @param {string} name
* @param {Function} fn
*/
after: function (name, fn) {
after: function(name, fn) {
suites[0].afterAll(name, fn);
},

@@ -51,7 +69,7 @@ module.exports = function (suites, context, mocha) {
* @param {string} name
* @param {Function} fn
*/
beforeEach: function (name, fn) {
beforeEach: function(name, fn) {
suites[0].beforeEach(name, fn);
},

@@ -61,7 +79,7 @@ module.exports = function (suites, context, mocha) {
* @param {string} name
* @param {Function} fn
*/
afterEach: function (name, fn) {
afterEach: function(name, fn) {
suites[0].afterEach(name, fn);
},

@@ -73,7 +91,7 @@ module.exports = function (suites, context, mocha) {
* @param {Object} opts
* @returns {Suite}
*/
only: function only (opts) {
only: function only(opts) {
opts.isOnly = true;
return this.create(opts);
},
@@ -85,13 +103,14 @@ module.exports = function (suites, context, mocha) {
* @param {Object} opts
* @returns {Suite}
*/
skip: function skip (opts) {
skip: function skip(opts) {
opts.pending = true;
return this.create(opts);
},

/**
* Creates a suite.
*
* @param {Object} opts Options
* @param {string} opts.title Title of Suite
* @param {Function} [opts.fn] Suite Function (not always applicable)
@@ -100,19 +119,35 @@ module.exports = function (suites, context, mocha) {
* @param {boolean} [opts.isOnly] Is Suite exclusive?
* @returns {Suite}
*/
create: function create (opts) {
create: function create(opts) {
var suite = Suite.create(suites[0], opts.title);
suite.pending = Boolean(opts.pending);
suite.file = opts.file;
suites.unshift(suite);
if (opts.isOnly) {
suite.parent._onlySuites = suite.parent._onlySuites.concat(suite);
if (mocha.options.forbidOnly && shouldBeTested(suite)) {
throw new Error('`.only` forbidden');
}

suite.parent.appendOnlySuite(suite);
}
if (suite.pending) {
if (mocha.options.forbidPending && shouldBeTested(suite)) {
throw new Error('Pending test forbidden');
}
}
if (typeof opts.fn === 'function') {
opts.fn.call(suite);
suites.shift();
} else if (typeof opts.fn === 'undefined' && !suite.pending) {
throw new Error('Suite "' + suite.fullTitle() + '" was defined but no callback was supplied. Supply a callback or explicitly skip the suite.');
throw createMissingArgumentError(
'Suite "' +
suite.fullTitle() +
'" was defined but no callback was supplied. ' +
'Supply a callback or explicitly skip the suite.',
'callback',
'function'
);
} else if (!opts.fn && suite.pending) {
suites.shift();
}
@@ -122,16 +157,15 @@ module.exports = function (suites, context, mocha) {
},

test: {

/**
* Exclusive test-case.
*
* @param {Object} mocha
* @param {Function} test
* @returns {*}
*/
only: function (mocha, test) {
test.parent._onlyTests = test.parent._onlyTests.concat(test);
only: function(mocha, test) {
test.parent.appendOnlyTest(test);
return test;
},

@@ -140,7 +174,7 @@ module.exports = function (suites, context, mocha) {
*
* @param {string} title
*/
skip: function (title) {
skip: function(title) {
context.test(title);
},

@@ -149,7 +183,7 @@ module.exports = function (suites, context, mocha) {
*
* @param {number} n
*/
retries: function (n) {
retries: function(n) {
context.retries(n);
}
}
13 changes: 5 additions & 8 deletions lib/interfaces/exports.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
'use strict';

/**
* Module dependencies.
*/

var Suite = require('../suite');
var Test = require('../test');

@@ -24,12 +19,12 @@ var Test = require('../test');
*
* @param {Suite} suite Root suite.
*/
module.exports = function (suite) {
module.exports = function(suite) {
var suites = [suite];

suite.on('require', visit);
suite.on(Suite.constants.EVENT_FILE_REQUIRE, visit);

function visit (obj, file) {
function visit(obj, file) {
var suite;
for (var key in obj) {
if (typeof obj[key] === 'function') {
@@ -61,3 +56,5 @@ module.exports = function (suite) {
}
}
};

module.exports.description = 'Node.js module ("exports") style';
20 changes: 10 additions & 10 deletions lib/interfaces/qunit.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
'use strict';

/**
* Module dependencies.
*/

var Test = require('../test');
var EVENT_FILE_PRE_REQUIRE = require('../suite').constants
.EVENT_FILE_PRE_REQUIRE;

/**
* QUnit-style interface:
@@ -31,10 +29,10 @@ var Test = require('../test');
*
* @param {Suite} suite Root suite.
*/
module.exports = function (suite) {
module.exports = function qUnitInterface(suite) {
var suites = [suite];

suite.on('pre-require', function (context, file, mocha) {
suite.on(EVENT_FILE_PRE_REQUIRE, function(context, file, mocha) {
var common = require('./common')(suites, context, mocha);

context.before = common.before;
@@ -46,7 +44,7 @@ module.exports = function (suite) {
* Describe a "suite" with the given `title`.
*/

context.suite = function (title) {
context.suite = function(title) {
if (suites.length > 1) {
suites.shift();
}
@@ -61,7 +59,7 @@ module.exports = function (suite) {
* Exclusive Suite.
*/

context.suite.only = function (title) {
context.suite.only = function(title) {
if (suites.length > 1) {
suites.shift();
}
@@ -78,7 +76,7 @@ module.exports = function (suite) {
* acting as a thunk.
*/

context.test = function (title, fn) {
context.test = function(title, fn) {
var test = new Test(title, fn);
test.file = file;
suites[0].addTest(test);
@@ -89,11 +87,13 @@ module.exports = function (suite) {
* Exclusive test-case.
*/

context.test.only = function (title, fn) {
context.test.only = function(title, fn) {
return common.test.only(mocha, context.test(title, fn));
};

context.test.skip = common.test.skip;
context.test.retries = common.test.retries;
});
};

module.exports.description = 'QUnit style';
23 changes: 12 additions & 11 deletions lib/interfaces/tdd.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
'use strict';

/**
* Module dependencies.
*/

var Test = require('../test');
var EVENT_FILE_PRE_REQUIRE = require('../suite').constants
.EVENT_FILE_PRE_REQUIRE;

/**
* TDD-style interface:
@@ -31,10 +29,10 @@ var Test = require('../test');
*
* @param {Suite} suite Root suite.
*/
module.exports = function (suite) {
module.exports = function(suite) {
var suites = [suite];

suite.on('pre-require', function (context, file, mocha) {
suite.on(EVENT_FILE_PRE_REQUIRE, function(context, file, mocha) {
var common = require('./common')(suites, context, mocha);

context.setup = common.beforeEach;
@@ -47,7 +45,7 @@ module.exports = function (suite) {
* Describe a "suite" with the given `title` and callback `fn` containing
* nested suites and/or tests.
*/
context.suite = function (title, fn) {
context.suite = function(title, fn) {
return common.suite.create({
title: title,
file: file,
@@ -58,7 +56,7 @@ module.exports = function (suite) {
/**
* Pending suite.
*/
context.suite.skip = function (title, fn) {
context.suite.skip = function(title, fn) {
return common.suite.skip({
title: title,
file: file,
@@ -69,7 +67,7 @@ module.exports = function (suite) {
/**
* Exclusive test-case.
*/
context.suite.only = function (title, fn) {
context.suite.only = function(title, fn) {
return common.suite.only({
title: title,
file: file,
@@ -81,7 +79,7 @@ module.exports = function (suite) {
* Describe a specification or test-case with the given `title` and
* callback `fn` acting as a thunk.
*/
context.test = function (title, fn) {
context.test = function(title, fn) {
var suite = suites[0];
if (suite.isPending()) {
fn = null;
@@ -96,11 +94,14 @@ module.exports = function (suite) {
* Exclusive test-case.
*/

context.test.only = function (title, fn) {
context.test.only = function(title, fn) {
return common.test.only(mocha, context.test(title, fn));
};

context.test.skip = common.test.skip;
context.test.retries = common.test.retries;
});
};

module.exports.description =
'traditional "suite"/"test" instead of BDD\'s "describe"/"it"';
Loading