diff --git a/packages/jest-reporters/package.json b/packages/jest-reporters/package.json index d6f3cb2f3eb8..26e2478139ab 100644 --- a/packages/jest-reporters/package.json +++ b/packages/jest-reporters/package.json @@ -23,7 +23,6 @@ "jest-runtime": "^24.9.0", "jest-util": "^24.9.0", "jest-worker": "^24.6.0", - "node-notifier": "^5.4.3", "slash": "^3.0.0", "source-map": "^0.6.0", "string-length": "^3.1.0" @@ -39,6 +38,9 @@ "@types/node-notifier": "^5.4.0", "strip-ansi": "^5.0.0" }, + "optionalDependencies": { + "node-notifier": "^5.4.3" + }, "engines": { "node": ">= 8" }, diff --git a/packages/jest-reporters/src/__tests__/notify_impl.test.ts b/packages/jest-reporters/src/__tests__/notify_impl.test.ts new file mode 100644 index 000000000000..9c1be65f59e0 --- /dev/null +++ b/packages/jest-reporters/src/__tests__/notify_impl.test.ts @@ -0,0 +1,47 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +beforeEach(() => { + jest.resetModules(); +}); + +test('without node-notifier notify_impl uses mock function that throws an error', () => { + jest.doMock('node-notifier', () => { + const error: any = new Error("Cannot find module 'node-notifier'"); + error.code = 'MODULE_NOT_FOUND'; + throw error; + }); + const notify = require('../notify_impl').default; + const arg = jest.fn(); + expect(() => notify(arg)).toThrow( + 'notify reporter requires optional dependeny node-notifier but it was not found', + ); +}); + +test('notify_impl throws the error when require throws an unexpected error', () => { + const error = new Error('unexpected require error'); + jest.doMock('node-notifier', () => { + throw error; + }); + expect(() => require('../notify_impl')).toThrow(error); +}); + +test('notify_impl uses node-notifier when it is available', () => { + const mockNodeNotifier = {notify: jest.fn()}; + jest.doMock('node-notifier', () => mockNodeNotifier); + const notify = require('../notify_impl').default; + // notify would be object equal to mockNodeNotifier.notify except we had + // to bind it to mockNodeNotifier, which returns a new function. so + // instead of expect(notify).toBe(mockNodeNotifier.notify) we need to + // check that the arguments and return values are forwarded, and that + // `this` is set to the module. + const arg = jest.fn(); + const result = notify(arg); + expect(mockNodeNotifier.notify).toBeCalledTimes(1); + expect(mockNodeNotifier.notify).toBeCalledWith(arg); + expect(mockNodeNotifier.notify).toReturnWith(result); + expect(mockNodeNotifier.notify.mock.instances[0]).toEqual(mockNodeNotifier); +}); diff --git a/packages/jest-reporters/src/notify_impl.ts b/packages/jest-reporters/src/notify_impl.ts new file mode 100644 index 000000000000..7fc5a0428c16 --- /dev/null +++ b/packages/jest-reporters/src/notify_impl.ts @@ -0,0 +1,52 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +export default getNotifier(); + +/** + * Return a notifier implementation. + * + * node-notifier is an optional dependency so it may not be installed. + * If node-notifier is not found then a stub notifier is returned that throws + * an error whenever it is used. + */ +function getNotifier(): Notify { + try { + const notifier: typeof import('node-notifier') = require('node-notifier'); + return notifier.notify.bind(notifier); + } catch (ex) { + if (ex.code !== 'MODULE_NOT_FOUND') { + throw ex; + } + return () => { + throw Error( + 'notify reporter requires optional dependeny node-notifier but it was not found', + ); + }; + } +} + +export type Notify = ( + notification?: Notification, + callback?: NotificationCallback, +) => unknown; + +interface Notification { + title?: string; + message?: string; + icon?: string; + closeLabel?: string; + actions?: string | Array; + timeout?: number; +} + +interface NotificationCallback { + (err: Error | null, response: string, metadata?: NotificationMetadata): void; +} + +interface NotificationMetadata { + activationValue?: string; +} diff --git a/packages/jest-reporters/src/notify_reporter.ts b/packages/jest-reporters/src/notify_reporter.ts index 7735baaf2d81..b25edbd63e88 100644 --- a/packages/jest-reporters/src/notify_reporter.ts +++ b/packages/jest-reporters/src/notify_reporter.ts @@ -10,9 +10,9 @@ import * as util from 'util'; import exit = require('exit'); import {Config} from '@jest/types'; import {AggregatedResult} from '@jest/test-result'; -import {notify} from 'node-notifier'; import {Context, TestSchedulerContext} from './types'; import BaseReporter from './base_reporter'; +import notify from './notify_impl'; const isDarwin = process.platform === 'darwin';