Skip to content
This repository has been archived by the owner on Dec 4, 2023. It is now read-only.

Commit

Permalink
Growl: Web notifications & Desktop prerequisite software check (mocha…
Browse files Browse the repository at this point in the history
…js#3542)

Added (minorly brittle) means to verify prerequisite software
is installed. Migrated Growl support code to its own file. This
implementation's checks are required to enable Growl; failure
will write notice to `stderr`. Other modifications based on
discussion from Chad Rickman's PR mochajs#3311. This also checks for
errors from Growl callback.

Provided browser notification support as well for modern browsers
by replacing the existing noop stub with an implementation for web
notifications via the Mocha `growl` pseudo-reporter (when run in browser).

Updated user guide and wiki for desktop notification support.

Fixes mochajs#3111

Signed-off-by: Paul Roebuck <plroebuck@users.noreply.github.com>
  • Loading branch information
plroebuck committed Dec 23, 2018
1 parent 41cfdeb commit b36234a
Show file tree
Hide file tree
Showing 9 changed files with 492 additions and 63 deletions.
Binary file added assets/mocha-logo-96.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
101 changes: 90 additions & 11 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Mocha is a feature-rich JavaScript test framework running on [Node.js](https://n
- [async test timeout support](#delayed-root-suite)
- [test retry support](#retry-tests)
- [test-specific timeouts](#test-level)
- [growl notification support](#mochaopts)
- [Growl support](#desktop-notification-support)
- [reports test durations](#test-duration)
- [highlights slow tests](#dot-matrix)
- [file watcher support](#min)
Expand Down Expand Up @@ -70,6 +70,7 @@ Mocha is a feature-rich JavaScript test framework running on [Node.js](https://n
- [Interfaces](#interfaces)
- [Reporters](#reporters)
- [Running Mocha in the Browser](#running-mocha-in-the-browser)
- [Desktop Notification Support](#desktop-notification-support)
- [Configuring Mocha (Node.js)](#configuring-mocha-nodejs)
- [`mocha.opts`](#mochaopts)
- [The `test/` Directory](#the-test-directory)
Expand Down Expand Up @@ -748,7 +749,7 @@ To tweak what's considered "slow", you can use the `slow()` method:

```js
describe('something slow', function() {
this.slow(10000);
this.slow(300000); // five minutes

it('should take long enough for me to go make a sandwich', function() {
// ...
Expand Down Expand Up @@ -1487,20 +1488,22 @@ A typical setup might look something like the following, where we call `mocha.se
<head>
<meta charset="utf-8">
<title>Mocha Tests</title>
<link href="https://unpkg.com/mocha@5.2.0/mocha.css" rel="stylesheet" />
<link rel="stylesheet" href="https://unpkg.com/mocha/mocha.css" />
</head>
<body>
<div id="mocha"></div>

<script src="https://unpkg.com/chai/chai.js"></script>
<script src="https://unpkg.com/mocha@5.2.0/mocha.js"></script>
<script src="https://unpkg.com/mocha/mocha.js"></script>

<script>mocha.setup('bdd')</script>
<script class="mocha-init">
mocha.setup('bdd');
mocha.checkLeaks();
</script>
<script src="test.array.js"></script>
<script src="test.object.js"></script>
<script src="test.xhr.js"></script>
<script>
mocha.checkLeaks();
<script class="mocha-exec">
mocha.run();
</script>
</body>
Expand Down Expand Up @@ -1547,6 +1550,69 @@ The "HTML" reporter is what you see when running Mocha in the browser. It looks

[Mochawesome](https://www.npmjs.com/package/mochawesome) is a great alternative to the default HTML reporter.

## Desktop Notification Support

Desktop notifications allow asynchronous communication of events without
forcing you to react to a notification immediately. Their appearance
and specific functionality vary across platforms. They typically disappear
automatically after a short delay, but their content is often stored in some
manner that allows you to access past notifications.

[Growl][] was an early notification system implementation for OS X and Windows,
hence, the name of Mocha's `--growl` option.

Once enabled, when your root suite completes test execution, a desktop
notification should appear informing you whether your tests passed or failed.

### Node-based notifications

In order to use desktop notifications with the command-line interface (CLI),
you **must** first install some platform-specific prerequisite software.
Instructions for doing so can be found [here][Growl-install].

Enable Mocha's desktop notifications as follows:

```sh
$ mocha --growl
```

### Browser-based notifications

Web notification support is being made available for current versions of
modern browsers. Ensure your browser version supports both
[promises](https://caniuse.com/#feat=promises) and
[web notifications](https://caniuse.com/#feat=notifications). As the
Notification API evolved over time, **do not expect** the minimum possible
browser version to necessarily work.

Enable Mocha's web notifications with a slight modification to your
client-side mocha HTML. Add a call to `mocha.growl()` prior to running your
tests as shown below:

```html
<!DOCTYPE html>
<html>
<head>
<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="https://unpkg.com/mocha/mocha.css" />
</head>
<body>
<div id="mocha"></div>
<script src="https://unpkg.com/mocha/mocha.js"></script>
<script class="mocha-init">
mocha.setup('bdd');
mocha.growl(); // <-- Enables web notifications
</script>
<script src="tests.js"></script>
<script class="mocha-exec">
mocha.run();
</script>
</body>
</html>
```

## Configuring Mocha (Node.js)

> *New in v6.0.0*
Expand Down Expand Up @@ -1631,11 +1697,11 @@ For example, suppose you have the following `mocha.opts` file:

The settings above will default the reporter to `dot`, require the `should`
library, and use `bdd` as the interface. With this, you may then invoke `mocha`
with additional arguments, here enabling [Growl](http://growl.info/) support,
and changing the reporter to `list`:
with additional arguments, here changing the reporter to `list` and setting the
slow threshold to half a second:

```sh
$ mocha --reporter list --growl
$ mocha --reporter list --slow 500
```

To ignore your `mocha.opts`, use the `--no-opts` option.
Expand Down Expand Up @@ -1727,7 +1793,20 @@ $ REPORTER=nyan npm test

## More Information

In addition to chatting with us on [Gitter], for additional information such as using spies, mocking, and shared behaviours be sure to check out the [Mocha Wiki](https://github.com/mochajs/mocha/wiki) on GitHub. For discussions join the [Google Group](https://groups.google.com/group/mochajs). For a running example of Mocha, view [example/tests.html](example/tests.html). For the JavaScript API, view the [API documentation](api/) or the [source](https://github.com/mochajs/mocha/blob/master/lib/mocha.js#L51).
In addition to chatting with us on [Gitter][Gitter-mocha], for additional information such as using
spies, mocking, and shared behaviours be sure to check out the [Mocha Wiki][Mocha-wiki] on GitHub.
For discussions join the [Google Group][Google-mocha]. For a running example of Mocha, view
[example/tests.html](example/tests.html). For the JavaScript API, view the [API documentation](api/)
or the [source](https://github.com/mochajs/mocha/blob/master/lib/mocha.js).

[//]: # (Cross reference section)

[Gitter-mocha]: https://gitter.im/mochajs/mocha
[Google-mocha]: https://groups.google.com/group/mochajs
[Growl]: http://growl.info/
[Growl-install]: https://github.com/mochajs/mocha/wiki/Growl-Notifications
[Mocha-website]: https://mochajs.org/
[Mocha-wiki]: https://github.com/mochajs/mocha/wiki

<!-- AUTO-GENERATED-CONTENT:START (manifest:template=[Gitter]: ${gitter}) -->
[Gitter]: https://gitter.im/mochajs/mocha
Expand Down
166 changes: 164 additions & 2 deletions lib/browser/growl.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,167 @@
'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;

/**
* 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('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 ' + runner.total + ' 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);
}

0 comments on commit b36234a

Please sign in to comment.