Skip to content

Commit 7154c62

Browse files
rubennortefacebook-github-bot
authored andcommitted
Improve formatting of table in console.table (#48591)
Summary: Pull Request resolved: #48591 Changelog: [General][Changed] Improved formatting of values logged via `console.table` (including Markdown format). This provides several improvements over the format of tables logged via `console.table`: * Markdown format for easy integration in existing documents. * Increased alignment with the spec and Chrome/Firefox implementations: * Added index columns. * Logged all available columns. * Format for all types of values (including objects, functions, etc.). Reviewed By: javache Differential Revision: D67794858 fbshipit-source-id: 464c938ed51f28a8e071bc46f0f5b0d970005873
1 parent caa77fb commit 7154c62

File tree

2 files changed

+161
-36
lines changed

2 files changed

+161
-36
lines changed

packages/polyfills/__tests__/console-itest.js

+117-27
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ const LOG_LEVELS = {
1818

1919
describe('console', () => {
2020
describe('.table(data, rows)', () => {
21-
it('should print the passed array as a table', () => {
21+
it('should print the passed array as a Markdown table', () => {
2222
const originalNativeLoggingHook = global.nativeLoggingHook;
2323
const logFn = (global.nativeLoggingHook = jest.fn());
2424

@@ -34,20 +34,20 @@ describe('console', () => {
3434
expect(logFn).toHaveBeenCalledTimes(1);
3535
expect(logFn.mock.lastCall).toEqual([
3636
`
37-
name | value
38-
-------|------
39-
First | 500 \u0020
40-
Second | 600 \u0020
41-
Third | 700 \u0020
42-
Fourth | 800 `,
37+
| (index) | name | value | extraValue |
38+
| ------- | -------- | ----- | ---------- |
39+
| 0 | 'First' | 500 | |
40+
| 1 | 'Second' | 600 | |
41+
| 2 | 'Third' | 700 | |
42+
| 3 | 'Fourth' | 800 | true |`,
4343
LOG_LEVELS.info,
4444
]);
4545
} finally {
4646
global.nativeLoggingHook = originalNativeLoggingHook;
4747
}
4848
});
4949

50-
it('should print the passed dictionary as a table', () => {
50+
it('should print the passed dictionary as a Markdown table', () => {
5151
const originalNativeLoggingHook = global.nativeLoggingHook;
5252
const logFn = (global.nativeLoggingHook = jest.fn());
5353

@@ -63,12 +63,102 @@ Fourth | 800 `,
6363
expect(logFn).toHaveBeenCalledTimes(1);
6464
expect(logFn.mock.lastCall).toEqual([
6565
`
66-
(index) | name | value
67-
--------|--------|------
68-
first | First | 500 \u0020
69-
second | Second | 600 \u0020
70-
third | Third | 700 \u0020
71-
fourth | Fourth | 800 `,
66+
| (index) | name | value | extraValue |
67+
| ------- | -------- | ----- | ---------- |
68+
| first | 'First' | 500 | |
69+
| second | 'Second' | 600 | |
70+
| third | 'Third' | 700 | |
71+
| fourth | 'Fourth' | 800 | true |`,
72+
LOG_LEVELS.info,
73+
]);
74+
} finally {
75+
global.nativeLoggingHook = originalNativeLoggingHook;
76+
}
77+
});
78+
79+
it('should work with different types of values', () => {
80+
const originalNativeLoggingHook = global.nativeLoggingHook;
81+
const logFn = (global.nativeLoggingHook = jest.fn());
82+
83+
// TODO: replace with `beforeEach` when supported.
84+
try {
85+
console.table([
86+
{
87+
string: '',
88+
number: 0,
89+
boolean: true,
90+
function: () => {},
91+
object: {a: 1, b: 2},
92+
null: null,
93+
undefined: undefined,
94+
},
95+
{
96+
string: 'a',
97+
number: 1,
98+
boolean: true,
99+
function: () => {},
100+
object: {a: 1, b: 2},
101+
null: null,
102+
undefined: undefined,
103+
},
104+
{
105+
string: 'aa',
106+
number: 2,
107+
boolean: false,
108+
function: () => {},
109+
object: {a: 1, b: 2},
110+
null: null,
111+
undefined: undefined,
112+
},
113+
{
114+
string: 'aaa',
115+
number: 3,
116+
boolean: false,
117+
function: () => {},
118+
object: {a: 1, b: 2},
119+
null: null,
120+
undefined: undefined,
121+
},
122+
]);
123+
124+
expect(logFn).toHaveBeenCalledTimes(1);
125+
expect(logFn.mock.lastCall).toEqual([
126+
`
127+
| (index) | string | number | boolean | function | object | null | undefined |
128+
| ------- | ------ | ------ | ------- | -------- | ------ | ---- | --------- |
129+
| 0 | '' | 0 | true | ƒ | {…} | null | undefined |
130+
| 1 | 'a' | 1 | true | ƒ | {…} | null | undefined |
131+
| 2 | 'aa' | 2 | false | ƒ | {…} | null | undefined |
132+
| 3 | 'aaa' | 3 | false | ƒ | {…} | null | undefined |`,
133+
LOG_LEVELS.info,
134+
]);
135+
} finally {
136+
global.nativeLoggingHook = originalNativeLoggingHook;
137+
}
138+
});
139+
140+
it('should print the keys in all the objects', () => {
141+
const originalNativeLoggingHook = global.nativeLoggingHook;
142+
const logFn = (global.nativeLoggingHook = jest.fn());
143+
144+
// TODO: replace with `beforeEach` when supported.
145+
try {
146+
console.table([
147+
{name: 'foo'},
148+
{name: 'bar', value: 1},
149+
{value: 2, surname: 'baz'},
150+
{address: 'other'},
151+
]);
152+
153+
expect(logFn).toHaveBeenCalledTimes(1);
154+
expect(logFn.mock.lastCall).toEqual([
155+
`
156+
| (index) | name | value | surname | address |
157+
| ------- | ----- | ----- | ------- | ------- |
158+
| 0 | 'foo' | | | |
159+
| 1 | 'bar' | 1 | | |
160+
| 2 | | 2 | 'baz' | |
161+
| 3 | | | | 'other' |`,
72162
LOG_LEVELS.info,
73163
]);
74164
} finally {
@@ -107,7 +197,7 @@ fourth | Fourth | 800 `,
107197
});
108198

109199
// This test is currently failing
110-
it.skip('should print an indices table for an array of empty objects', () => {
200+
it('should print an indices table for an array of empty objects', () => {
111201
const originalNativeLoggingHook = global.nativeLoggingHook;
112202
const logFn = (global.nativeLoggingHook = jest.fn());
113203

@@ -118,12 +208,12 @@ fourth | Fourth | 800 `,
118208
expect(logFn).toHaveBeenCalledTimes(1);
119209
expect(logFn.mock.lastCall).toEqual([
120210
`
121-
(index)
122-
-------
123-
0 \u0020
124-
1 \u0020
125-
2 \u0020
126-
3 `,
211+
| (index) |
212+
| ------- |
213+
| 0 |
214+
| 1 |
215+
| 2 |
216+
| 3 |`,
127217
LOG_LEVELS.info,
128218
]);
129219
} finally {
@@ -147,12 +237,12 @@ fourth | Fourth | 800 `,
147237
expect(logFn).toHaveBeenCalledTimes(1);
148238
expect(logFn.mock.lastCall).toEqual([
149239
`
150-
(index)
151-
-------
152-
first \u0020
153-
second\u0020
154-
third \u0020
155-
fourth `,
240+
| (index) |
241+
| ------- |
242+
| first |
243+
| second |
244+
| third |
245+
| fourth |`,
156246
LOG_LEVELS.info,
157247
]);
158248
} finally {

packages/polyfills/console.js

+44-9
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,7 @@ const inspect = (function () {
380380
return inspect;
381381
})();
382382

383-
const OBJECT_COLUMN_NAME = '(index)';
383+
const INDEX_COLUMN_NAME = '(index)';
384384
const LOG_LEVELS = {
385385
trace: 0,
386386
info: 1,
@@ -433,16 +433,46 @@ function repeat(element, n) {
433433
});
434434
}
435435

436+
function formatCellValue(cell, key) {
437+
if (key === INDEX_COLUMN_NAME) {
438+
return cell[key];
439+
}
440+
441+
if (cell.hasOwnProperty(key)) {
442+
var cellValue = cell[key];
443+
444+
switch (typeof cellValue) {
445+
case 'function':
446+
return 'ƒ';
447+
case 'string':
448+
return "'" + cellValue + "'";
449+
case 'object':
450+
return cellValue == null ? 'null' : '{…}';
451+
}
452+
453+
return String(cellValue);
454+
}
455+
return '';
456+
}
457+
436458
function consoleTablePolyfill(rows) {
437459
// convert object -> array
438-
if (!Array.isArray(rows)) {
460+
if (Array.isArray(rows)) {
461+
rows = rows.map((row, index) => {
462+
var processedRow = {};
463+
processedRow[INDEX_COLUMN_NAME] = String(index);
464+
Object.assign(processedRow, row);
465+
return processedRow;
466+
});
467+
} else {
439468
var data = rows;
440469
rows = [];
441470
for (var key in data) {
442471
if (data.hasOwnProperty(key)) {
443-
var row = Object.assign({}, data[key]);
444-
row[OBJECT_COLUMN_NAME] = key;
445-
rows.push(row);
472+
var processedRow = {};
473+
processedRow[INDEX_COLUMN_NAME] = key;
474+
Object.assign(processedRow, data[key]);
475+
rows.push(processedRow);
446476
}
447477
}
448478
}
@@ -451,7 +481,12 @@ function consoleTablePolyfill(rows) {
451481
return;
452482
}
453483

454-
var columns = Object.keys(rows[0]).sort();
484+
var columns = Array.from(
485+
rows.reduce((columnSet, row) => {
486+
Object.keys(row).forEach(key => columnSet.add(key));
487+
return columnSet;
488+
}, new Set()),
489+
);
455490
var stringRows = [];
456491
var columnWidths = [];
457492

@@ -460,7 +495,7 @@ function consoleTablePolyfill(rows) {
460495
columns.forEach(function (k, i) {
461496
columnWidths[i] = k.length;
462497
for (var j = 0; j < rows.length; j++) {
463-
var cellStr = (rows[j][k] || '?').toString();
498+
var cellStr = formatCellValue(rows[j], k);
464499
stringRows[j] = stringRows[j] || [];
465500
stringRows[j][i] = cellStr;
466501
columnWidths[i] = Math.max(columnWidths[i], cellStr.length);
@@ -475,13 +510,13 @@ function consoleTablePolyfill(rows) {
475510
return cell + extraSpaces;
476511
});
477512
space = space || ' ';
478-
return cells.join(space + '|' + space);
513+
return '| ' + cells.join(space + '|' + space) + ' |';
479514
}
480515

481516
var separators = columnWidths.map(function (columnWidth) {
482517
return repeat('-', columnWidth).join('');
483518
});
484-
var separatorRow = joinRow(separators, '-');
519+
var separatorRow = joinRow(separators);
485520
var header = joinRow(columns);
486521
var table = [header, separatorRow];
487522

0 commit comments

Comments
 (0)