File size: 3,310 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
'use strict';

/**
 * Desktop Notifications module.
 * @module Growl
 */

const os = require('os');
const path = require('path');
const {sync: which} = require('which');
const {EVENT_RUN_END} = require('../runner').constants;
const {isBrowser} = require('../utils');

/**
 * @summary
 * Checks if Growl notification support seems likely.
 *
 * @description
 * Glosses over the distinction between an unsupported platform
 * and one that lacks prerequisite software installations.
 *
 * @public
 * @see {@link https://github.com/tj/node-growl/blob/master/README.md|Prerequisite Installs}
 * @see {@link Mocha#growl}
 * @see {@link Mocha#isGrowlCapable}
 * @return {boolean} whether Growl notification support can be expected
 */
exports.isCapable = () => {
  if (!isBrowser()) {
    return getSupportBinaries().reduce(
      (acc, binary) => acc || Boolean(which(binary, {nothrow: true})),
      false
    );
  }
  return false;
};

/**
 * Implements desktop notifications as a pseudo-reporter.
 *
 * @public
 * @see {@link Mocha#_growl}
 * @param {Runner} runner - Runner instance.
 */
exports.notify = runner => {
  runner.once(EVENT_RUN_END, () => {
    display(runner);
  });
};

/**
 * Displays the notification.
 *
 * @private
 * @param {Runner} runner - Runner instance.
 */
const display = runner => {
  const growl = require('growl');
  const stats = runner.stats;
  const symbol = {
    cross: '\u274C',
    tick: '\u2705'
  };
  let _message;
  let message;
  let 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
  const options = {
    image: logo(),
    name: 'mocha',
    title
  };
  growl(message, options, onCompletion);
};

/**
 * @summary
 * Callback for result of attempted Growl notification.
 *
 * @description
 * Despite its appearance, this is <strong>not</strong> an Error-first
 * callback -- all parameters are populated regardless of success.
 *
 * @private
 * @callback Growl~growlCB
 * @param {*} err - Error object, or <code>null</code> if successful.
 */
function onCompletion(err) {
  if (err) {
    // As notifications are tangential to our purpose, just log the error.
    const message =
      err.code === 'ENOENT' ? 'prerequisite software not found' : err.message;
    console.error('notification error:', message);
  }
}

/**
 * Returns Mocha logo image path.
 *
 * @private
 * @return {string} Pathname of Mocha logo
 */
const logo = () => {
  return path.join(__dirname, '..', 'assets', 'mocha-logo-96.png');
};

/**
 * @summary
 * Gets platform-specific Growl support binaries.
 *
 * @description
 * Somewhat brittle dependency on `growl` package implementation, but it
 * rarely changes.
 *
 * @private
 * @see {@link https://github.com/tj/node-growl/blob/master/lib/growl.js#L28-L126|setupCmd}
 * @return {string[]} names of Growl support binaries
 */
const getSupportBinaries = () => {
  const binaries = {
    Darwin: ['terminal-notifier', 'growlnotify'],
    Linux: ['notify-send', 'growl'],
    Windows_NT: ['growlnotify.exe']
  };
  return binaries[os.type()] || [];
};