From 69ccf09777135741d88428489a31bacb5d2e9709 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Tue, 14 Jun 2022 16:02:48 -0500 Subject: [PATCH 1/7] test: watchable ut --- .mocharc.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.mocharc.json b/.mocharc.json index a893d3523..377430530 100644 --- a/.mocharc.json +++ b/.mocharc.json @@ -9,5 +9,9 @@ "timeout": 60000, "watch-extensions": [ "ts" + ], + "watch-files": [ + "src", + "test" ] } From 466384d368d89374c54db49c95ba2eb71d30789c Mon Sep 17 00:00:00 2001 From: mshanemc Date: Tue, 14 Jun 2022 16:03:03 -0500 Subject: [PATCH 2/7] fix: giant tables don't exceed stack depth --- src/cli-ux/styled/table.ts | 51 ++++++++++++++++---------------- test/cli-ux/styled/table.test.ts | 30 +++++++++++++++++++ 2 files changed, 56 insertions(+), 25 deletions(-) diff --git a/src/cli-ux/styled/table.ts b/src/cli-ux/styled/table.ts index a9661f7f1..f56fc465c 100644 --- a/src/cli-ux/styled/table.ts +++ b/src/cli-ux/styled/table.ts @@ -18,10 +18,10 @@ class Table> { // assign columns this.columns = Object.keys(columns).map((key: string) => { const col = columns[key] - const extended = col.extended || false - const get = col.get || ((row: any) => row[key]) + const extended = col.extended ?? false + const get = col.get ?? ((row: any) => row[key]) const header = typeof col.header === 'string' ? col.header : capitalize(key.replace(/_/g, ' ')) - const minWidth = Math.max(col.minWidth || 0, sw(header) + 1) + const minWidth = Math.max(col.minWidth ?? 0, sw(header) + 1) return { extended, @@ -39,9 +39,9 @@ class Table> { output: csv ? 'csv' : output, extended, filter, - 'no-header': options['no-header'] || false, - 'no-truncate': options['no-truncate'] || false, - printLine: printLine || ((s: any) => process.stdout.write(s + '\n')), + 'no-header': options['no-header'] ?? false, + 'no-truncate': options['no-truncate'] ?? false, + printLine: printLine ?? ((s: any) => process.stdout.write(s + '\n')), rowStart: ' ', sort, title, @@ -144,7 +144,7 @@ class Table> { return columns.reduce((obj, col) => { return { ...obj, - [col.key]: d[col.key] || '', + [col.key]: d[col.key] ?? '', } }, {}) }) @@ -174,27 +174,18 @@ class Table> { private outputTable() { // tslint:disable-next-line:no-this-assignment - const {data, columns, options} = this - + const {data, options} = this // column truncation // // find max width for each column - for (const col of columns) { - // convert multi-line cell to single longest line - // for width calculations - const widthData = data.map((row: any) => { - const d = row[col.key] - const manyLines = d.split('\n') - if (manyLines.length > 1) { - return '*'.repeat(Math.max(...manyLines.map((r: string) => sw(r)))) - } - - return d - }) - const widths = ['.'.padEnd(col.minWidth! - 1), col.header, ...widthData.map((row: any) => row)].map(r => sw(r)) - col.maxWidth = Math.max(...widths) + 1 - col.width = col.maxWidth! - } + const columns = this.columns.map(c => { + const maxWidth = Math.max(sw('.'.padEnd(c.minWidth! - 1)), sw(c.header), getWidestColumnWith(data, c.key)) + 1 + return { + ...c, + maxWidth, + width: maxWidth, + } + }) // terminal width const maxWidth = stdtermwidth - 2 @@ -382,3 +373,13 @@ export namespace table { printLine?(s: any): any; } } + +const getWidestColumnWith = (data: any[], columnKey: string): number => { + return data.reduce((previous, current) => { + const d = current[columnKey] + // convert multi-line cell to single longest line + // for width calculations + const manyLines = (d as string).split('\n') + return Math.max(previous, manyLines.length > 1 ? Math.max(...manyLines.map((r: string) => sw(r))) : sw(d)) + }, 0) +} diff --git a/test/cli-ux/styled/table.test.ts b/test/cli-ux/styled/table.test.ts index ef4c0ac37..009d68ef1 100644 --- a/test/cli-ux/styled/table.test.ts +++ b/test/cli-ux/styled/table.test.ts @@ -318,4 +318,34 @@ describe('styled/table', () => { 123 supertable-test-1${ws}\n`) }) }) + + describe('scale tests', () => { + const bigRows = 150_000 + fancy + .stdout() + .end('very tall tables don\'t exceed stack depth', output => { + const data = Array.from({length: bigRows}).fill({id: '123', name: 'foo', value: 'bar'}) as Record[] + const tallColumns = { + id: {header: 'ID'}, + name: {}, + value: {header: 'TEST'}, + } + + CliUx.ux.table(data, tallColumns) + expect(output.stdout).to.include('ID') + }) + + fancy + .stdout() + .end('very tall, wide tables don\'t exceed stack depth', output => { + const columns = 100 + const row = Object.fromEntries(Array.from({length: columns}).map((_, i) => [`col${i}`, 'foo'])) + const data = Array.from({length: bigRows}).fill(row) as Record[] + const bigColumns = Object.fromEntries(Array.from({length: columns}).map((_, i) => [`col${i}`, {header: `col${i}`.toUpperCase()}])) + + CliUx.ux.table(data, bigColumns) + expect(output.stdout).to.include('COL1') + }) + }) }) + From 7d6cc4309880441dc764187c73c1c56236c0b884 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Wed, 15 Jun 2022 12:45:35 -0500 Subject: [PATCH 3/7] test: timeouts for windows --- test/cli-ux/styled/table.test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/cli-ux/styled/table.test.ts b/test/cli-ux/styled/table.test.ts index 009d68ef1..1e5de6977 100644 --- a/test/cli-ux/styled/table.test.ts +++ b/test/cli-ux/styled/table.test.ts @@ -323,6 +323,7 @@ describe('styled/table', () => { const bigRows = 150_000 fancy .stdout() + .timeout(600_000) .end('very tall tables don\'t exceed stack depth', output => { const data = Array.from({length: bigRows}).fill({id: '123', name: 'foo', value: 'bar'}) as Record[] const tallColumns = { @@ -337,6 +338,7 @@ describe('styled/table', () => { fancy .stdout() + .timeout(600_000) .end('very tall, wide tables don\'t exceed stack depth', output => { const columns = 100 const row = Object.fromEntries(Array.from({length: columns}).map((_, i) => [`col${i}`, 'foo'])) From 5423597d29466fe0c992fb8fe03930d739628f70 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Wed, 15 Jun 2022 13:13:45 -0500 Subject: [PATCH 4/7] test: move scale tests to e2e because slow --- test/cli-ux/styled/table.e2e.ts | 34 ++++++++++++++++++++++++++++++++ test/cli-ux/styled/table.test.ts | 31 ----------------------------- 2 files changed, 34 insertions(+), 31 deletions(-) create mode 100644 test/cli-ux/styled/table.e2e.ts diff --git a/test/cli-ux/styled/table.e2e.ts b/test/cli-ux/styled/table.e2e.ts new file mode 100644 index 000000000..0ecfd1b66 --- /dev/null +++ b/test/cli-ux/styled/table.e2e.ts @@ -0,0 +1,34 @@ +import {expect, fancy} from 'fancy-test' +import {CliUx} from '../../../src' + +describe('styled/table', () => { + describe('scale tests', () => { + const bigRows = 150_000 + fancy + .stdout() + .end('very tall tables don\'t exceed stack depth', output => { + const data = Array.from({length: bigRows}).fill({id: '123', name: 'foo', value: 'bar'}) as Record[] + const tallColumns = { + id: {header: 'ID'}, + name: {}, + value: {header: 'TEST'}, + } + + CliUx.ux.table(data, tallColumns) + expect(output.stdout).to.include('ID') + }) + + fancy + .stdout() + .end('very tall, wide tables don\'t exceed stack depth', output => { + const columns = 100 + const row = Object.fromEntries(Array.from({length: columns}).map((_, i) => [`col${i}`, 'foo'])) + const data = Array.from({length: bigRows}).fill(row) as Record[] + const bigColumns = Object.fromEntries(Array.from({length: columns}).map((_, i) => [`col${i}`, {header: `col${i}`.toUpperCase()}])) + + CliUx.ux.table(data, bigColumns) + expect(output.stdout).to.include('COL1') + }) + }) +}) + diff --git a/test/cli-ux/styled/table.test.ts b/test/cli-ux/styled/table.test.ts index 1e5de6977..d73344363 100644 --- a/test/cli-ux/styled/table.test.ts +++ b/test/cli-ux/styled/table.test.ts @@ -318,36 +318,5 @@ describe('styled/table', () => { 123 supertable-test-1${ws}\n`) }) }) - - describe('scale tests', () => { - const bigRows = 150_000 - fancy - .stdout() - .timeout(600_000) - .end('very tall tables don\'t exceed stack depth', output => { - const data = Array.from({length: bigRows}).fill({id: '123', name: 'foo', value: 'bar'}) as Record[] - const tallColumns = { - id: {header: 'ID'}, - name: {}, - value: {header: 'TEST'}, - } - - CliUx.ux.table(data, tallColumns) - expect(output.stdout).to.include('ID') - }) - - fancy - .stdout() - .timeout(600_000) - .end('very tall, wide tables don\'t exceed stack depth', output => { - const columns = 100 - const row = Object.fromEntries(Array.from({length: columns}).map((_, i) => [`col${i}`, 'foo'])) - const data = Array.from({length: bigRows}).fill(row) as Record[] - const bigColumns = Object.fromEntries(Array.from({length: columns}).map((_, i) => [`col${i}`, {header: `col${i}`.toUpperCase()}])) - - CliUx.ux.table(data, bigColumns) - expect(output.stdout).to.include('COL1') - }) - }) }) From 938a9ff4173ca8014a3ba1a8ec7eb46b04af5a2b Mon Sep 17 00:00:00 2001 From: mshanemc Date: Wed, 15 Jun 2022 13:48:47 -0500 Subject: [PATCH 5/7] test: e2e clone w/ https --- test/integration/plugins.e2e.ts | 2 +- test/integration/sf.e2e.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integration/plugins.e2e.ts b/test/integration/plugins.e2e.ts index 9b57c2921..331319926 100644 --- a/test/integration/plugins.e2e.ts +++ b/test/integration/plugins.e2e.ts @@ -6,7 +6,7 @@ describe('oclif plugins', () => { let executor: Executor before(async () => { executor = await setup(__filename, { - repo: 'git@github.com:oclif/hello-world.git', + repo: 'https://github.com/oclif/hello-world', plugins: [ '@oclif/plugin-autocomplete', '@oclif/plugin-commands', diff --git a/test/integration/sf.e2e.ts b/test/integration/sf.e2e.ts index 4383ebd52..22dd26e4b 100644 --- a/test/integration/sf.e2e.ts +++ b/test/integration/sf.e2e.ts @@ -15,7 +15,7 @@ describe('Salesforce CLI (sf)', () => { let executor: Executor before(async () => { process.env.SFDX_TELEMETRY_DISABLE_ACKNOWLEDGEMENT = 'true' - executor = await setup(__filename, {repo: 'git@github.com:salesforcecli/cli.git'}) + executor = await setup(__filename, {repo: 'https://github.com/salesforcecli/cli'}) }) it('should show custom help', async () => { From 31f27c6bf45978ada446ef1256221ee94368f52e Mon Sep 17 00:00:00 2001 From: mshanemc Date: Thu, 23 Jun 2022 13:32:56 -0500 Subject: [PATCH 6/7] fix: turn null/undefined into empty string --- src/cli-ux/styled/table.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/cli-ux/styled/table.ts b/src/cli-ux/styled/table.ts index f56fc465c..369201082 100644 --- a/src/cli-ux/styled/table.ts +++ b/src/cli-ux/styled/table.ts @@ -19,7 +19,8 @@ class Table> { this.columns = Object.keys(columns).map((key: string) => { const col = columns[key] const extended = col.extended ?? false - const get = col.get ?? ((row: any) => row[key]) + // turn null and undefined into empty strings by default + const get = col.get ?? ((row: any) => row[key] ?? '') const header = typeof col.header === 'string' ? col.header : capitalize(key.replace(/_/g, ' ')) const minWidth = Math.max(col.minWidth ?? 0, sw(header) + 1) From c989268c754ef1131945a2fcb09403b318d588c6 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Thu, 23 Jun 2022 14:07:36 -0500 Subject: [PATCH 7/7] test: omit null/undefined --- test/cli-ux/styled/table.e2e.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/cli-ux/styled/table.e2e.ts b/test/cli-ux/styled/table.e2e.ts index 0ecfd1b66..a741adf8b 100644 --- a/test/cli-ux/styled/table.e2e.ts +++ b/test/cli-ux/styled/table.e2e.ts @@ -2,6 +2,19 @@ import {expect, fancy} from 'fancy-test' import {CliUx} from '../../../src' describe('styled/table', () => { + describe('null/undefined handling', () => { + fancy + .stdout() + .end('omits nulls and undefined by default', output => { + const data = [{a: 1, b: '2', c: null, d: undefined}] + CliUx.ux.table(data, {a: {}, b: {}, c: {}, d: {}}) + expect(output.stdout).to.include('1') + expect(output.stdout).to.include('2') + expect(output.stdout).to.not.include('null') + expect(output.stdout).to.not.include('undefined') + }) + }) + describe('scale tests', () => { const bigRows = 150_000 fancy