169 lines
4.5 KiB
JavaScript
169 lines
4.5 KiB
JavaScript
|
'use strict';
|
||
|
|
||
|
/**
|
||
|
* Web Notifications module.
|
||
|
* @module Growl
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* 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);
|
||
|
}
|