Skip to content

Commit

Permalink
feat: add support for wrapping with newlines (#88)
Browse files Browse the repository at this point in the history
* Add support for wrapping with newlines

It can be useful to more accurately control formatting in table cells by
means of inserting newlines. For example, we can now put bulleted lists
or pretty-printed JSON objects in table cells.

This change makes it so that any '\n' characters encountered in the
cell's values will be translated into line breaks.

Implementation note: the cell wrapping logic that used to be duplicated
between `mapDataUsingRowHeightIndex` and `calculateCellHeight` has been
moved to `wrapCell` to be shared between them.

* Add test with color coding

* More readable way to write ANSI test
  • Loading branch information
rix0rrr authored and gajus committed Jan 11, 2019
1 parent 2417ad6 commit 3d1d8b9
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 18 deletions.
9 changes: 2 additions & 7 deletions src/calculateCellHeight.js
@@ -1,6 +1,5 @@
import _ from 'lodash';
import stringWidth from 'string-width';
import wrapWord from './wrapWord';
import wrapCell from './wrapCell';

/**
* @param {string} value
Expand All @@ -21,9 +20,5 @@ export default (value, columnWidth, useWrapWord = false) => {
throw new Error('Column width must be greater than 0.');
}

if (useWrapWord) {
return wrapWord(value, columnWidth).length;
}

return Math.ceil(stringWidth(value) / columnWidth);
return wrapCell(value, columnWidth, useWrapWord).length;
};
15 changes: 4 additions & 11 deletions src/mapDataUsingRowHeightIndex.js
@@ -1,6 +1,5 @@
import _ from 'lodash';
import wrapString from './wrapString';
import wrapWord from './wrapWord';
import wrapCell from './wrapCell';

/**
* @param {Array} unmappedRows
Expand All @@ -21,16 +20,10 @@ export default (unmappedRows, rowHeightIndex, config) => {
// [{cell index within a virtual row; index1}]

cells.forEach((value, index1) => {
let chunkedValue;
const cellLines = wrapCell(value, config.columns[index1].width, config.columns[index1].wrapWord);

if (config.columns[index1].wrapWord) {
chunkedValue = wrapWord(value, config.columns[index1].width);
} else {
chunkedValue = wrapString(value, config.columns[index1].width);
}

chunkedValue.forEach((part, index2) => {
rowHeight[index2][index1] = part;
cellLines.forEach((cellLine, index2) => {
rowHeight[index2][index1] = cellLine;
});
});

Expand Down
35 changes: 35 additions & 0 deletions src/wrapCell.js
@@ -0,0 +1,35 @@
import wrapString from './wrapString';
import wrapWord from './wrapWord';

/**
* Wrap a single cell value into a list of lines
*
* Always wraps on newlines, for the remainder uses either word or string wrapping
* depending on user configuration.
*
* @param {string} cellValue
* @param {number} columnWidth
* @param {boolean} useWrapWord
* @returns {Array}
*/
export default (cellValue, columnWidth, useWrapWord) => {
// First split on literal newlines
const cellLines = cellValue.split('\n');

// Then iterate over the list and word-wrap every remaining line if necessary.
for (let lineNr = 0; lineNr < cellLines.length;) {
let lineChunks;

if (useWrapWord) {
lineChunks = wrapWord(cellLines[lineNr], columnWidth);
} else {
lineChunks = wrapString(cellLines[lineNr], columnWidth);
}

// Replace our original array element with whatever the wrapping returned
cellLines.splice(lineNr, 1, ...lineChunks);
lineNr += lineChunks.length;
}

return cellLines;
};
6 changes: 6 additions & 0 deletions test/calculateCellHeight.js
Expand Up @@ -14,6 +14,12 @@ describe('calculateCellHeight', () => {
}).to.throw(Error, 'Value must be a string.');
});
});
it('contains newlines', () => {
expect(calculateCellHeight('a\nb\nc', 10)).to.equal(3);
});
it('contains newlines and will be wrapped', () => {
expect(calculateCellHeight('aa\nbbb\nc', 2)).to.equal(4);
});
});
describe('context width', () => {
context('is not an integer', () => {
Expand Down
63 changes: 63 additions & 0 deletions test/mapDataUsingRowHeightIndex.js
@@ -1,6 +1,7 @@
import {
expect
} from 'chai';
import chalk from 'chalk';
import mapDataUsingRowHeightIndex from '../src/mapDataUsingRowHeightIndex';

describe('mapDataUsingRowHeightIndex', () => {
Expand Down Expand Up @@ -66,6 +67,68 @@ describe('mapDataUsingRowHeightIndex', () => {
});
});

context('single cell contains newlines', () => {
it('maps data to multiple rows', () => {
const config = {
columns: {
0: {
width: 100
}
}
};

const rowSpanIndex = [
5
];

const data = [
[
'aa\nbb\ncc\ndd\nee'
]
];

const mappedData = mapDataUsingRowHeightIndex(data, rowSpanIndex, config);

expect(mappedData).to.deep.equal([
['aa'],
['bb'],
['cc'],
['dd'],
['ee']
]);
});

it('maps data with color coding to multiple rows', () => {
const config = {
columns: {
0: {
width: 100
}
}
};

const rowSpanIndex = [
5
];

const data = [
[
chalk.red('aa\nbb\ncc\ndd\nee')
]
];

const mappedData = mapDataUsingRowHeightIndex(data, rowSpanIndex, config);

expect(mappedData).to.deep.equal([
[chalk.red('aa')],
[chalk.red('bb')],
[chalk.red('cc')],
[chalk.red('dd')],
[chalk.red('ee')]
]);
});
});

context('multiple cells spans multiple rows', () => {
it('maps data to multiple rows', () => {
const config = {
Expand Down

0 comments on commit 3d1d8b9

Please sign in to comment.