-
-
Notifications
You must be signed in to change notification settings - Fork 3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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 <plroebuck@users.noreply.github.com>
- Loading branch information
Showing
9 changed files
with
492 additions
and
63 deletions.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |
Oops, something went wrong.