growl.js 4.5 KB

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