Skip to content

Commit

Permalink
Sm/fix-table-stack-error (#432)
Browse files Browse the repository at this point in the history
* test: watchable ut

* fix: giant tables don't exceed stack depth

* test: timeouts for windows

* test: move scale tests to e2e because slow

* test: e2e clone w/ https
  • Loading branch information
mshanemc committed Jun 16, 2022
1 parent a8f51ed commit eb07bda
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 27 deletions.
4 changes: 4 additions & 0 deletions .mocharc.json
Expand Up @@ -9,5 +9,9 @@
"timeout": 60000,
"watch-extensions": [
"ts"
],
"watch-files": [
"src",
"test"
]
}
51 changes: 26 additions & 25 deletions src/cli-ux/styled/table.ts
Expand Up @@ -18,10 +18,10 @@ class Table<T extends Record<string, unknown>> {
// 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,
Expand All @@ -39,9 +39,9 @@ class Table<T extends Record<string, unknown>> {
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,
Expand Down Expand Up @@ -144,7 +144,7 @@ class Table<T extends Record<string, unknown>> {
return columns.reduce((obj, col) => {
return {
...obj,
[col.key]: d[col.key] || '',
[col.key]: d[col.key] ?? '',
}
}, {})
})
Expand Down Expand Up @@ -174,27 +174,18 @@ class Table<T extends Record<string, unknown>> {

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
Expand Down Expand Up @@ -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)
}
34 changes: 34 additions & 0 deletions 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<string, unknown>[]
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<string, unknown>[]
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')
})
})
})

1 change: 1 addition & 0 deletions test/cli-ux/styled/table.test.ts
Expand Up @@ -319,3 +319,4 @@ describe('styled/table', () => {
})
})
})

2 changes: 1 addition & 1 deletion test/integration/plugins.e2e.ts
Expand Up @@ -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',
Expand Down
2 changes: 1 addition & 1 deletion test/integration/sf.e2e.ts
Expand Up @@ -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 () => {
Expand Down

0 comments on commit eb07bda

Please sign in to comment.