UNPKG

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