File size: 4,618 Bytes
5c2ed06
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
'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;
var isBrowser = require('../utils').isBrowser;

/**
 * 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 isBrowser() && 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.json').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);
}