Skip to content

Commit 81c60f9

Browse files
authored
Growl: Web notifications & Desktop prerequisite software check (#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 #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 #3111 Signed-off-by: Paul Roebuck <[email protected]>
1 parent 0b9bc69 commit 81c60f9

File tree

9 files changed

+492
-63
lines changed

9 files changed

+492
-63
lines changed

assets/mocha-logo-96.png

4.82 KB
Loading

docs/index.md

Lines changed: 90 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Mocha is a feature-rich JavaScript test framework running on [Node.js](https://n
2626
- [async test timeout support](#delayed-root-suite)
2727
- [test retry support](#retry-tests)
2828
- [test-specific timeouts](#test-level)
29-
- [growl notification support](#mochaopts)
29+
- [Growl support](#desktop-notification-support)
3030
- [reports test durations](#test-duration)
3131
- [highlights slow tests](#dot-matrix)
3232
- [file watcher support](#min)
@@ -70,6 +70,7 @@ Mocha is a feature-rich JavaScript test framework running on [Node.js](https://n
7070
- [Interfaces](#interfaces)
7171
- [Reporters](#reporters)
7272
- [Running Mocha in the Browser](#running-mocha-in-the-browser)
73+
- [Desktop Notification Support](#desktop-notification-support)
7374
- [Configuring Mocha (Node.js)](#configuring-mocha-nodejs)
7475
- [`mocha.opts`](#mochaopts)
7576
- [The `test/` Directory](#the-test-directory)
@@ -748,7 +749,7 @@ To tweak what's considered "slow", you can use the `slow()` method:
748749

749750
```js
750751
describe('something slow', function() {
751-
this.slow(10000);
752+
this.slow(300000); // five minutes
752753

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

14951496
<script src="https://unpkg.com/chai/chai.js"></script>
1496-
<script src="https://unpkg.com/mocha@5.2.0/mocha.js"></script>
1497+
<script src="https://unpkg.com/mocha/mocha.js"></script>
14971498

1498-
<script>mocha.setup('bdd')</script>
1499+
<script class="mocha-init">
1500+
mocha.setup('bdd');
1501+
mocha.checkLeaks();
1502+
</script>
14991503
<script src="test.array.js"></script>
15001504
<script src="test.object.js"></script>
15011505
<script src="test.xhr.js"></script>
1502-
<script>
1503-
mocha.checkLeaks();
1506+
<script class="mocha-exec">
15041507
mocha.run();
15051508
</script>
15061509
</body>
@@ -1547,6 +1550,69 @@ The "HTML" reporter is what you see when running Mocha in the browser. It looks
15471550

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

1553+
## Desktop Notification Support
1554+
1555+
Desktop notifications allow asynchronous communication of events without
1556+
forcing you to react to a notification immediately. Their appearance
1557+
and specific functionality vary across platforms. They typically disappear
1558+
automatically after a short delay, but their content is often stored in some
1559+
manner that allows you to access past notifications.
1560+
1561+
[Growl][] was an early notification system implementation for OS X and Windows,
1562+
hence, the name of Mocha's `--growl` option.
1563+
1564+
Once enabled, when your root suite completes test execution, a desktop
1565+
notification should appear informing you whether your tests passed or failed.
1566+
1567+
### Node-based notifications
1568+
1569+
In order to use desktop notifications with the command-line interface (CLI),
1570+
you **must** first install some platform-specific prerequisite software.
1571+
Instructions for doing so can be found [here][Growl-install].
1572+
1573+
Enable Mocha's desktop notifications as follows:
1574+
1575+
```sh
1576+
$ mocha --growl
1577+
```
1578+
1579+
### Browser-based notifications
1580+
1581+
Web notification support is being made available for current versions of
1582+
modern browsers. Ensure your browser version supports both
1583+
[promises](https://caniuse.com/#feat=promises) and
1584+
[web notifications](https://caniuse.com/#feat=notifications). As the
1585+
Notification API evolved over time, **do not expect** the minimum possible
1586+
browser version to necessarily work.
1587+
1588+
Enable Mocha's web notifications with a slight modification to your
1589+
client-side mocha HTML. Add a call to `mocha.growl()` prior to running your
1590+
tests as shown below:
1591+
1592+
```html
1593+
<!DOCTYPE html>
1594+
<html>
1595+
<head>
1596+
<title>Mocha</title>
1597+
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
1598+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
1599+
<link rel="stylesheet" href="https://unpkg.com/mocha/mocha.css" />
1600+
</head>
1601+
<body>
1602+
<div id="mocha"></div>
1603+
<script src="https://unpkg.com/mocha/mocha.js"></script>
1604+
<script class="mocha-init">
1605+
mocha.setup('bdd');
1606+
mocha.growl(); // <-- Enables web notifications
1607+
</script>
1608+
<script src="tests.js"></script>
1609+
<script class="mocha-exec">
1610+
mocha.run();
1611+
</script>
1612+
</body>
1613+
</html>
1614+
```
1615+
15501616
## Configuring Mocha (Node.js)
15511617

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

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

16371703
```sh
1638-
$ mocha --reporter list --growl
1704+
$ mocha --reporter list --slow 500
16391705
```
16401706

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

17281794
## More Information
17291795

1730-
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).
1796+
In addition to chatting with us on [Gitter][Gitter-mocha], for additional information such as using
1797+
spies, mocking, and shared behaviours be sure to check out the [Mocha Wiki][Mocha-wiki] on GitHub.
1798+
For discussions join the [Google Group][Google-mocha]. For a running example of Mocha, view
1799+
[example/tests.html](example/tests.html). For the JavaScript API, view the [API documentation](api/)
1800+
or the [source](https://github.com/mochajs/mocha/blob/master/lib/mocha.js).
1801+
1802+
[//]: # (Cross reference section)
1803+
1804+
[Gitter-mocha]: https://gitter.im/mochajs/mocha
1805+
[Google-mocha]: https://groups.google.com/group/mochajs
1806+
[Growl]: http://growl.info/
1807+
[Growl-install]: https://github.com/mochajs/mocha/wiki/Growl-Notifications
1808+
[Mocha-website]: https://mochajs.org/
1809+
[Mocha-wiki]: https://github.com/mochajs/mocha/wiki
17311810

17321811
<!-- AUTO-GENERATED-CONTENT:START (manifest:template=[Gitter]: ${gitter}) -->
17331812
[Gitter]: https://gitter.im/mochajs/mocha

lib/browser/growl.js

Lines changed: 164 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,167 @@
11
'use strict';
22

3-
// just stub out growl
3+
/**
4+
* Web Notifications module.
5+
* @module Growl
6+
*/
47

5-
module.exports = require('../utils').noop;
8+
/**
9+
* Save timer references to avoid Sinon interfering (see GH-237).
10+
*/
11+
var Date = global.Date;
12+
var setTimeout = global.setTimeout;
13+
14+
/**
15+
* Checks if browser notification support exists.
16+
*
17+
* @public
18+
* @see {@link https://caniuse.com/#feat=notifications|Browser support (notifications)}
19+
* @see {@link https://caniuse.com/#feat=promises|Browser support (promises)}
20+
* @see {@link Mocha#growl}
21+
* @see {@link Mocha#isGrowlCapable}
22+
* @return {boolean} whether browser notification support exists
23+
*/
24+
exports.isCapable = function() {
25+
var hasNotificationSupport = 'Notification' in window;
26+
var hasPromiseSupport = typeof Promise === 'function';
27+
return process.browser && hasNotificationSupport && hasPromiseSupport;
28+
};
29+
30+
/**
31+
* Implements browser notifications as a pseudo-reporter.
32+
*
33+
* @public
34+
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/notification|Notification API}
35+
* @see {@link https://developers.google.com/web/fundamentals/push-notifications/display-a-notification|Displaying a Notification}
36+
* @see {@link Growl#isPermitted}
37+
* @see {@link Mocha#_growl}
38+
* @param {Runner} runner - Runner instance.
39+
*/
40+
exports.notify = function(runner) {
41+
var promise = isPermitted();
42+
43+
/**
44+
* Attempt notification.
45+
*/
46+
var sendNotification = function() {
47+
// If user hasn't responded yet... "No notification for you!" (Seinfeld)
48+
Promise.race([promise, Promise.resolve(undefined)])
49+
.then(canNotify)
50+
.then(function() {
51+
display(runner);
52+
})
53+
.catch(notPermitted);
54+
};
55+
56+
runner.once('end', sendNotification);
57+
};
58+
59+
/**
60+
* Checks if browser notification is permitted by user.
61+
*
62+
* @private
63+
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Notification/permission|Notification.permission}
64+
* @see {@link Mocha#growl}
65+
* @see {@link Mocha#isGrowlPermitted}
66+
* @returns {Promise<boolean>} promise determining if browser notification
67+
* permissible when fulfilled.
68+
*/
69+
function isPermitted() {
70+
var permitted = {
71+
granted: function allow() {
72+
return Promise.resolve(true);
73+
},
74+
denied: function deny() {
75+
return Promise.resolve(false);
76+
},
77+
default: function ask() {
78+
return Notification.requestPermission().then(function(permission) {
79+
return permission === 'granted';
80+
});
81+
}
82+
};
83+
84+
return permitted[Notification.permission]();
85+
}
86+
87+
/**
88+
* @summary
89+
* Determines if notification should proceed.
90+
*
91+
* @description
92+
* Notification shall <strong>not</strong> proceed unless `value` is true.
93+
*
94+
* `value` will equal one of:
95+
* <ul>
96+
* <li><code>true</code> (from `isPermitted`)</li>
97+
* <li><code>false</code> (from `isPermitted`)</li>
98+
* <li><code>undefined</code> (from `Promise.race`)</li>
99+
* </ul>
100+
*
101+
* @private
102+
* @param {boolean|undefined} value - Determines if notification permissible.
103+
* @returns {Promise<undefined>} Notification can proceed
104+
*/
105+
function canNotify(value) {
106+
if (!value) {
107+
var why = value === false ? 'blocked' : 'unacknowledged';
108+
var reason = 'not permitted by user (' + why + ')';
109+
return Promise.reject(new Error(reason));
110+
}
111+
return Promise.resolve();
112+
}
113+
114+
/**
115+
* Displays the notification.
116+
*
117+
* @private
118+
* @param {Runner} runner - Runner instance.
119+
*/
120+
function display(runner) {
121+
var stats = runner.stats;
122+
var symbol = {
123+
cross: '\u274C',
124+
tick: '\u2705'
125+
};
126+
var logo = require('../../package').notifyLogo;
127+
var _message;
128+
var message;
129+
var title;
130+
131+
if (stats.failures) {
132+
_message = stats.failures + ' of ' + runner.total + ' tests failed';
133+
message = symbol.cross + ' ' + _message;
134+
title = 'Failed';
135+
} else {
136+
_message = stats.passes + ' tests passed in ' + stats.duration + 'ms';
137+
message = symbol.tick + ' ' + _message;
138+
title = 'Passed';
139+
}
140+
141+
// Send notification
142+
var options = {
143+
badge: logo,
144+
body: message,
145+
dir: 'ltr',
146+
icon: logo,
147+
lang: 'en-US',
148+
name: 'mocha',
149+
requireInteraction: false,
150+
timestamp: Date.now()
151+
};
152+
var notification = new Notification(title, options);
153+
154+
// Autoclose after brief delay (makes various browsers act same)
155+
var FORCE_DURATION = 4000;
156+
setTimeout(notification.close.bind(notification), FORCE_DURATION);
157+
}
158+
159+
/**
160+
* As notifications are tangential to our purpose, just log the error.
161+
*
162+
* @private
163+
* @param {Error} err - Why notification didn't happen.
164+
*/
165+
function notPermitted(err) {
166+
console.error('notification error:', err.message);
167+
}

0 commit comments

Comments
 (0)