From 965ceb10f281eae44756a5d08f5ee5e907373fba Mon Sep 17 00:00:00 2001 From: Ran Yitzhaki Date: Sat, 10 Feb 2018 05:15:45 +0200 Subject: [PATCH 1/4] add methods to custom and buffered consoles --- packages/jest-util/src/Console.js | 96 +++++++++++- .../src/__tests__/buffered_console.test.js | 134 +++++++++++++++++ .../jest-util/src/__tests__/console.test.js | 138 ++++++++++++++++++ packages/jest-util/src/buffered_console.js | 119 +++++++++++++-- types/Console.js | 16 +- 5 files changed, 482 insertions(+), 21 deletions(-) create mode 100644 packages/jest-util/src/__tests__/buffered_console.test.js create mode 100644 packages/jest-util/src/__tests__/console.test.js diff --git a/packages/jest-util/src/Console.js b/packages/jest-util/src/Console.js index 864be1520b14..37cd96540870 100644 --- a/packages/jest-util/src/Console.js +++ b/packages/jest-util/src/Console.js @@ -8,10 +8,11 @@ */ /* global stream$Writable */ -import type {LogType, LogMessage} from 'types/Console'; +import type {LogType, LogMessage, LogCounters, LogTimers} from 'types/Console'; import {format} from 'util'; import {Console} from 'console'; +import chalk from 'chalk'; import clearLine from './clear_line'; type Formatter = (type: LogType, message: LogMessage) => string; @@ -19,6 +20,9 @@ type Formatter = (type: LogType, message: LogMessage) => string; export default class CustomConsole extends Console { _stdout: stream$Writable; _formatBuffer: Formatter; + _counters: LogCounters; + _timers: LogTimers; + _groupDepth: number; constructor( stdout: stream$Writable, @@ -27,31 +31,107 @@ export default class CustomConsole extends Console { ) { super(stdout, stderr); this._formatBuffer = formatBuffer || ((type, message) => message); + this._counters = {}; + this._timers = {}; + this._groupDepth = 0; + } + + _logToParentConsole(message: string) { + super.log(message); } _log(type: LogType, message: string) { clearLine(this._stdout); - super.log(this._formatBuffer(type, message)); + this._logToParentConsole( + this._formatBuffer(type, ' '.repeat(this._groupDepth) + message), + ); + } + + assert(...args: Array) { + if (args[0]) { + this._log('assert', format(...args.slice(1))); + } + } + + count(label: string = 'default') { + if (!this._counters[label]) { + this._counters[label] = 0; + } + + this._log('count', format(`${label}: ${++this._counters[label]}`)); + } + + countReset(label: string = 'default') { + this._counters[label] = 0; } debug(...args: Array) { this._log('debug', format.apply(null, arguments)); } - log(...args: Array) { - this._log('log', format.apply(null, arguments)); + dir(...args: Array) { + this._log('dir', format.apply(null, arguments)); + } + + dirxml(...args: Array) { + this._log('dirxml', format.apply(null, arguments)); + } + + error(...args: Array) { + this._log('error', format.apply(null, arguments)); + } + + group(...args: Array) { + this._groupDepth++; + + if (args.length > 0) { + this._log('group', chalk.bold(format.apply(null, arguments))); + } + } + + groupCollapsed(...args: Array) { + this._groupDepth++; + + if (args.length > 0) { + this._log('groupCollapsed', chalk.bold(format.apply(null, arguments))); + } + } + + groupEnd() { + if (this._groupDepth > 0) { + this._groupDepth--; + } } info(...args: Array) { this._log('info', format.apply(null, arguments)); } - warn(...args: Array) { - this._log('warn', format.apply(null, arguments)); + log(...args: Array) { + this._log('log', format.apply(null, arguments)); } - error(...args: Array) { - this._log('error', format.apply(null, arguments)); + time(label: string = 'default') { + if (this._timers[label]) { + return; + } + + this._timers[label] = new Date(); + } + + timeEnd(label: string = 'default') { + const startTime = this._timers[label]; + + if (startTime) { + const endTime = new Date(); + const time = (endTime - startTime) / 1000; + this._log('time', format(`${label}: ${time}ms`)); + delete this._timers[label]; + } + } + + warn(...args: Array) { + this._log('warn', format.apply(null, arguments)); } getBuffer() { diff --git a/packages/jest-util/src/__tests__/buffered_console.test.js b/packages/jest-util/src/__tests__/buffered_console.test.js new file mode 100644 index 000000000000..bbae95c8c578 --- /dev/null +++ b/packages/jest-util/src/__tests__/buffered_console.test.js @@ -0,0 +1,134 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. 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. + * + * @flow + */ + +import chalk from 'chalk'; +import BufferedConsole from '../buffered_console'; + +describe('CustomConsole', () => { + let _console; + const stdout = () => + _console + .getBuffer() + .map(log => log.message) + .join('\n'); + + beforeEach(() => { + _console = new BufferedConsole(); + }); + + describe('assert', () => { + test('log when the assertion is truthy', () => { + _console.assert(true, 'ok'); + + expect(stdout()).toMatch('ok'); + }); + + test('do not log when the assertion is falsy', () => { + _console.assert(false, 'ok'); + + expect(stdout()).toEqual(''); + }); + }); + + describe('count', () => { + test('count using the default counter', () => { + _console.count(); + _console.count(); + _console.count(); + + expect(stdout()).toEqual('default: 1\ndefault: 2\ndefault: 3'); + }); + + test('count using the a labeled counter', () => { + _console.count('custom'); + _console.count('custom'); + _console.count('custom'); + + expect(stdout()).toEqual('custom: 1\ncustom: 2\ncustom: 3'); + }); + + test('countReset restarts default counter', () => { + _console.count(); + _console.count(); + _console.countReset(); + _console.count(); + expect(stdout()).toEqual('default: 1\ndefault: 2\ndefault: 1'); + }); + + test('countReset restarts custom counter', () => { + _console.count('custom'); + _console.count('custom'); + _console.countReset('custom'); + _console.count('custom'); + + expect(stdout()).toEqual('custom: 1\ncustom: 2\ncustom: 1'); + }); + }); + + describe('group', () => { + test('group without label', () => { + _console.group(); + _console.log('hey'); + _console.group(); + _console.log('there'); + + expect(stdout()).toEqual(' hey\n there'); + }); + + test('group with label', () => { + _console.group('first'); + _console.log('hey'); + _console.group('second'); + _console.log('there'); + + expect(stdout()).toEqual(` ${chalk.bold('first')} + hey + ${chalk.bold('second')} + there`); + }); + + test('groupEnd remove the indentation of the current group', () => { + _console.group(); + _console.log('hey'); + _console.groupEnd(); + _console.log('there'); + + expect(stdout()).toEqual(' hey\nthere'); + }); + + test('groupEnd can not remove the indentation below the starting point', () => { + _console.groupEnd(); + _console.groupEnd(); + _console.group(); + _console.log('hey'); + _console.groupEnd(); + _console.log('there'); + + expect(stdout()).toEqual(' hey\nthere'); + }); + }); + + describe('time', () => { + test('should return the time between time() and timeEnd() on default timer', () => { + _console.time(); + _console.timeEnd(); + + expect(stdout()).toMatch('default: '); + expect(stdout()).toMatch('ms'); + }); + + test('should return the time between time() and timeEnd() on custom timer', () => { + _console.time('custom'); + _console.timeEnd('custom'); + + expect(stdout()).toMatch('custom: '); + expect(stdout()).toMatch('ms'); + }); + }); +}); diff --git a/packages/jest-util/src/__tests__/console.test.js b/packages/jest-util/src/__tests__/console.test.js new file mode 100644 index 000000000000..d237b1e86d0f --- /dev/null +++ b/packages/jest-util/src/__tests__/console.test.js @@ -0,0 +1,138 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. 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. + * + * @flow + */ + +import chalk from 'chalk'; +import CustomConsole from '../Console'; + +describe('CustomConsole', () => { + let _console; + let _stdout = ''; + + beforeEach(() => { + _console = new CustomConsole(process.stdout, process.stderr); + // override the _logToParentConsole method to be able and assert on the stdout. + // $FlowFixMe + _console._logToParentConsole = message => { + _stdout += message + '\n'; + }; + + _stdout = ''; + }); + + describe('assert', () => { + test('log when the assertion is truthy', () => { + _console.assert(true, 'ok'); + + expect(_stdout).toMatch('ok'); + }); + + test('do not log when the assertion is falsy', () => { + _console.assert(false, 'ok'); + + expect(_stdout).toEqual(''); + }); + }); + + describe('count', () => { + test('count using the default counter', () => { + _console.count(); + _console.count(); + _console.count(); + + expect(_stdout).toEqual('default: 1\ndefault: 2\ndefault: 3\n'); + }); + + test('count using the a labeled counter', () => { + _console.count('custom'); + _console.count('custom'); + _console.count('custom'); + + expect(_stdout).toEqual('custom: 1\ncustom: 2\ncustom: 3\n'); + }); + + test('countReset restarts default counter', () => { + _console.count(); + _console.count(); + _console.countReset(); + _console.count(); + expect(_stdout).toEqual('default: 1\ndefault: 2\ndefault: 1\n'); + }); + + test('countReset restarts custom counter', () => { + _console.count('custom'); + _console.count('custom'); + _console.countReset('custom'); + _console.count('custom'); + + expect(_stdout).toEqual('custom: 1\ncustom: 2\ncustom: 1\n'); + }); + }); + + describe('group', () => { + test('group without label', () => { + _console.group(); + _console.log('hey'); + _console.group(); + _console.log('there'); + + expect(_stdout).toEqual(' hey\n there\n'); + }); + + test('group with label', () => { + _console.group('first'); + _console.log('hey'); + _console.group('second'); + _console.log('there'); + + expect(_stdout).toEqual(` ${chalk.bold('first')} + hey + ${chalk.bold('second')} + there +`); + }); + + test('groupEnd remove the indentation of the current group', () => { + _console.group(); + _console.log('hey'); + _console.groupEnd(); + _console.log('there'); + + expect(_stdout).toEqual(' hey\nthere\n'); + }); + + test('groupEnd can not remove the indentation below the starting point', () => { + _console.groupEnd(); + _console.groupEnd(); + _console.group(); + _console.log('hey'); + _console.groupEnd(); + _console.log('there'); + + expect(_stdout).toEqual(' hey\nthere\n'); + }); + }); + + describe('time', () => { + test('should return the time between time() and timeEnd() on default timer', () => { + _console.time(); + _console.timeEnd(); + + expect(_stdout).toMatch('default: '); + expect(_stdout).toMatch('ms'); + }); + + test('should return the time between time() and timeEnd() on custom timer', () => { + _console.time('custom'); + _console.timeEnd('custom'); + + expect(_stdout).toMatch('custom: '); + expect(_stdout).toMatch('ms'); + }); + }); +}); diff --git a/packages/jest-util/src/buffered_console.js b/packages/jest-util/src/buffered_console.js index 5581595c12d3..d04fa3faf7cb 100644 --- a/packages/jest-util/src/buffered_console.js +++ b/packages/jest-util/src/buffered_console.js @@ -7,19 +7,32 @@ * @flow */ -import type {ConsoleBuffer, LogMessage, LogType} from 'types/Console'; +import type { + ConsoleBuffer, + LogMessage, + LogType, + LogCounters, + LogTimers, +} from 'types/Console'; import {Console} from 'console'; import {format} from 'util'; +import chalk from 'chalk'; import callsites from 'callsites'; export default class BufferedConsole extends Console { _buffer: ConsoleBuffer; + _counters: LogCounters; + _timers: LogTimers; + _groupDepth: number; constructor() { const buffer = []; super({write: message => BufferedConsole.write(buffer, 'log', message)}); this._buffer = buffer; + this._counters = {}; + this._timers = {}; + this._groupDepth = 0; } static write( @@ -30,28 +43,110 @@ export default class BufferedConsole extends Console { ) { const call = callsites()[level != null ? level : 2]; const origin = call.getFileName() + ':' + call.getLineNumber(); - buffer.push({message, origin, type}); + + buffer.push({ + message, + origin, + type, + }); + return buffer; } - debug() { - BufferedConsole.write(this._buffer, 'debug', format.apply(null, arguments)); + _log(type: LogType, message: LogMessage) { + BufferedConsole.write( + this._buffer, + type, + ' '.repeat(this._groupDepth) + message, + 3, + ); + } + + assert(...args: Array) { + if (args[0]) { + this._log('assert', format(...args.slice(1))); + } } - log() { - BufferedConsole.write(this._buffer, 'log', format.apply(null, arguments)); + count(label: string = 'default') { + if (!this._counters[label]) { + this._counters[label] = 0; + } + + this._log('count', format(`${label}: ${++this._counters[label]}`)); + } + + countReset(label: string = 'default') { + this._counters[label] = 0; } - info() { - BufferedConsole.write(this._buffer, 'info', format.apply(null, arguments)); + debug(...args: Array) { + this._log('debug', format.apply(null, arguments)); } - warn() { - BufferedConsole.write(this._buffer, 'warn', format.apply(null, arguments)); + dir(...args: Array) { + this._log('dir', format.apply(null, arguments)); + } + + dirxml(...args: Array) { + this._log('dirxml', format.apply(null, arguments)); + } + + error(...args: Array) { + this._log('error', format.apply(null, arguments)); + } + + group(...args: Array) { + this._groupDepth++; + + if (args.length > 0) { + this._log('group', chalk.bold(format.apply(null, arguments))); + } + } + + groupCollapsed(...args: Array) { + this._groupDepth++; + + if (args.length > 0) { + this._log('groupCollapsed', chalk.bold(format.apply(null, arguments))); + } + } + + groupEnd() { + if (this._groupDepth > 0) { + this._groupDepth--; + } + } + + info(...args: Array) { + this._log('info', format.apply(null, arguments)); + } + + log(...args: Array) { + this._log('log', format.apply(null, arguments)); + } + + time(label: string = 'default') { + if (this._timers[label]) { + return; + } + + this._timers[label] = new Date(); + } + + timeEnd(label: string = 'default') { + const startTime = this._timers[label]; + + if (startTime) { + const endTime = new Date(); + const time = (endTime - startTime) / 1000; + this._log('time', format(`${label}: ${time}ms`)); + delete this._timers[label]; + } } - error() { - BufferedConsole.write(this._buffer, 'error', format.apply(null, arguments)); + warn(...args: Array) { + this._log('warn', format.apply(null, arguments)); } getBuffer() { diff --git a/types/Console.js b/types/Console.js index eed7c8e15eaf..bc50107e2d4f 100644 --- a/types/Console.js +++ b/types/Console.js @@ -13,5 +13,19 @@ export type LogEntry = {| origin: string, type: LogType, |}; -export type LogType = 'debug' | 'log' | 'info' | 'warn' | 'error'; +export type LogCounters = {[label: string]: number}; +export type LogTimers = {[label: string]: Date}; +export type LogType = + | 'assert' + | 'count' + | 'debug' + | 'dir' + | 'dirxml' + | 'error' + | 'group' + | 'groupCollapsed' + | 'info' + | 'log' + | 'time' + | 'warn'; export type ConsoleBuffer = Array; From c8dbfaf2d7f308c044e2086610be6a1cd4f39573 Mon Sep 17 00:00:00 2001 From: Ran Yitzhaki Date: Sat, 10 Feb 2018 13:22:20 +0200 Subject: [PATCH 2/4] update changelog with the console methods addition --- CHANGELOG.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f546890f9800..33376b8ff8a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ ## master +### Features + +* `[jest-util]` Add the following methods to the "console" implementations: + `assert`, `count`, `countReset`, `dir`, `dirxml`, `group`, `groupCollapsed`, + `groupEnd`, `time`, `timeEnd` + ([#5514](https://github.com/facebook/jest/pull/5514)) + ## jest 22.2.2 ### Fixes @@ -16,8 +23,9 @@ ([#5494](https://github.com/facebook/jest/pull/5494)) ### Chore & Maintenance -* `[filenames]` Standardize file names in root ([#5500](https://github.com/facebook/jest/pull/5500)) +* `[filenames]` Standardize file names in root + ([#5500](https://github.com/facebook/jest/pull/5500)) ## jest 22.2.1 From beb137efe95039d6d7f64fa8697a3a1e223e6842 Mon Sep 17 00:00:00 2001 From: Ran Yitzhaki Date: Sat, 10 Feb 2018 14:12:25 +0200 Subject: [PATCH 3/4] use spyOn console to mock the implementation --- packages/jest-util/src/__tests__/console.test.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/jest-util/src/__tests__/console.test.js b/packages/jest-util/src/__tests__/console.test.js index d237b1e86d0f..d42203e29271 100644 --- a/packages/jest-util/src/__tests__/console.test.js +++ b/packages/jest-util/src/__tests__/console.test.js @@ -16,11 +16,9 @@ describe('CustomConsole', () => { beforeEach(() => { _console = new CustomConsole(process.stdout, process.stderr); - // override the _logToParentConsole method to be able and assert on the stdout. - // $FlowFixMe - _console._logToParentConsole = message => { + jest.spyOn(_console, '_logToParentConsole').mockImplementation(message => { _stdout += message + '\n'; - }; + }); _stdout = ''; }); From 946b9cd581e2322d05fcc13eca7188845e1092c3 Mon Sep 17 00:00:00 2001 From: Ran Yitzhaki Date: Sat, 10 Feb 2018 14:50:36 +0200 Subject: [PATCH 4/4] refactor console's calls to use ...args instead of arguments --- packages/jest-util/src/Console.js | 36 +++++++++++----------- packages/jest-util/src/buffered_console.js | 36 +++++++++++----------- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/packages/jest-util/src/Console.js b/packages/jest-util/src/Console.js index 37cd96540870..3f7f109a7284 100644 --- a/packages/jest-util/src/Console.js +++ b/packages/jest-util/src/Console.js @@ -65,35 +65,35 @@ export default class CustomConsole extends Console { this._counters[label] = 0; } - debug(...args: Array) { - this._log('debug', format.apply(null, arguments)); + debug(...args: Array) { + this._log('debug', format(...args)); } - dir(...args: Array) { - this._log('dir', format.apply(null, arguments)); + dir(...args: Array) { + this._log('dir', format(...args)); } - dirxml(...args: Array) { - this._log('dirxml', format.apply(null, arguments)); + dirxml(...args: Array) { + this._log('dirxml', format(...args)); } - error(...args: Array) { - this._log('error', format.apply(null, arguments)); + error(...args: Array) { + this._log('error', format(...args)); } - group(...args: Array) { + group(...args: Array) { this._groupDepth++; if (args.length > 0) { - this._log('group', chalk.bold(format.apply(null, arguments))); + this._log('group', chalk.bold(format(...args))); } } - groupCollapsed(...args: Array) { + groupCollapsed(...args: Array) { this._groupDepth++; if (args.length > 0) { - this._log('groupCollapsed', chalk.bold(format.apply(null, arguments))); + this._log('groupCollapsed', chalk.bold(format(...args))); } } @@ -103,12 +103,12 @@ export default class CustomConsole extends Console { } } - info(...args: Array) { - this._log('info', format.apply(null, arguments)); + info(...args: Array) { + this._log('info', format(...args)); } - log(...args: Array) { - this._log('log', format.apply(null, arguments)); + log(...args: Array) { + this._log('log', format(...args)); } time(label: string = 'default') { @@ -130,8 +130,8 @@ export default class CustomConsole extends Console { } } - warn(...args: Array) { - this._log('warn', format.apply(null, arguments)); + warn(...args: Array) { + this._log('warn', format(...args)); } getBuffer() { diff --git a/packages/jest-util/src/buffered_console.js b/packages/jest-util/src/buffered_console.js index d04fa3faf7cb..1a563b500ca2 100644 --- a/packages/jest-util/src/buffered_console.js +++ b/packages/jest-util/src/buffered_console.js @@ -80,35 +80,35 @@ export default class BufferedConsole extends Console { this._counters[label] = 0; } - debug(...args: Array) { - this._log('debug', format.apply(null, arguments)); + debug(...args: Array) { + this._log('debug', format(...args)); } - dir(...args: Array) { - this._log('dir', format.apply(null, arguments)); + dir(...args: Array) { + this._log('dir', format(...args)); } - dirxml(...args: Array) { - this._log('dirxml', format.apply(null, arguments)); + dirxml(...args: Array) { + this._log('dirxml', format(...args)); } - error(...args: Array) { - this._log('error', format.apply(null, arguments)); + error(...args: Array) { + this._log('error', format(...args)); } - group(...args: Array) { + group(...args: Array) { this._groupDepth++; if (args.length > 0) { - this._log('group', chalk.bold(format.apply(null, arguments))); + this._log('group', chalk.bold(format(...args))); } } - groupCollapsed(...args: Array) { + groupCollapsed(...args: Array) { this._groupDepth++; if (args.length > 0) { - this._log('groupCollapsed', chalk.bold(format.apply(null, arguments))); + this._log('groupCollapsed', chalk.bold(format(...args))); } } @@ -118,12 +118,12 @@ export default class BufferedConsole extends Console { } } - info(...args: Array) { - this._log('info', format.apply(null, arguments)); + info(...args: Array) { + this._log('info', format(...args)); } - log(...args: Array) { - this._log('log', format.apply(null, arguments)); + log(...args: Array) { + this._log('log', format(...args)); } time(label: string = 'default') { @@ -145,8 +145,8 @@ export default class BufferedConsole extends Console { } } - warn(...args: Array) { - this._log('warn', format.apply(null, arguments)); + warn(...args: Array) { + this._log('warn', format(...args)); } getBuffer() {