diff --git a/src/calculateCellHeight.js b/src/calculateCellHeight.js index 94afd37..3eca8d7 100644 --- a/src/calculateCellHeight.js +++ b/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 @@ -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; }; diff --git a/src/mapDataUsingRowHeightIndex.js b/src/mapDataUsingRowHeightIndex.js index 89f1869..6562bdf 100644 --- a/src/mapDataUsingRowHeightIndex.js +++ b/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 @@ -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; }); }); diff --git a/src/wrapCell.js b/src/wrapCell.js new file mode 100644 index 0000000..0c66260 --- /dev/null +++ b/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; +}; diff --git a/test/calculateCellHeight.js b/test/calculateCellHeight.js index bd8a5f8..0cc5e6d 100644 --- a/test/calculateCellHeight.js +++ b/test/calculateCellHeight.js @@ -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', () => { diff --git a/test/mapDataUsingRowHeightIndex.js b/test/mapDataUsingRowHeightIndex.js index 8c6f084..a92c96a 100644 --- a/test/mapDataUsingRowHeightIndex.js +++ b/test/mapDataUsingRowHeightIndex.js @@ -1,6 +1,7 @@ import { expect } from 'chai'; +import chalk from 'chalk'; import mapDataUsingRowHeightIndex from '../src/mapDataUsingRowHeightIndex'; describe('mapDataUsingRowHeightIndex', () => { @@ -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 = {