Skip to content

Commit

Permalink
Deep copy inherited style (exceljs#1850)
Browse files Browse the repository at this point in the history
* Deep copy style when a row is inserted with inherited style (exceljs#1813)

* Add tests for row's style

* Move setIfExists

* Fix equal function
  • Loading branch information
ikzhr authored Oct 9, 2021
1 parent b19b4c0 commit da5e743
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 2 deletions.
5 changes: 3 additions & 2 deletions lib/doc/worksheet.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const Image = require('./image');
const Table = require('./table');
const DataValidations = require('./data-validations');
const Encryptor = require('../utils/encryptor');
const {copyStyle} = require('../utils/copy-style');

// Worksheet requirements
// Operate as sheet inside workbook or standalone
Expand Down Expand Up @@ -406,10 +407,10 @@ class Worksheet {
_copyStyle(src, dest, styleEmpty = false) {
const rSrc = this.getRow(src);
const rDst = this.getRow(dest);
rDst.style = Object.freeze({...rSrc.style});
rDst.style = copyStyle(rSrc.style);
// eslint-disable-next-line no-loop-func
rSrc.eachCell({includeEmpty: styleEmpty}, (cell, colNumber) => {
rDst.getCell(colNumber).style = Object.freeze({...cell.style});
rDst.getCell(colNumber).style = copyStyle(cell.style);
});
rDst.height = rSrc.height;
}
Expand Down
43 changes: 43 additions & 0 deletions lib/utils/copy-style.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
const oneDepthCopy = (obj, nestKeys) => ({
...obj,
...nestKeys.reduce((memo, key) => {
if (obj[key]) memo[key] = {...obj[key]};
return memo;
}, {}),
});

const setIfExists = (src, dst, key, nestKeys = []) => {
if (src[key]) dst[key] = oneDepthCopy(src[key], nestKeys);
};

const isEmptyObj = obj => Object.keys(obj).length === 0;

const copyStyle = style => {
if (!style) return style;
if (isEmptyObj(style)) return {};

const copied = {...style};

setIfExists(style, copied, 'font', ['color']);
setIfExists(style, copied, 'alignment');
setIfExists(style, copied, 'protection');
if (style.border) {
setIfExists(style, copied, 'border');
setIfExists(style.border, copied.border, 'top', ['color']);
setIfExists(style.border, copied.border, 'left', ['color']);
setIfExists(style.border, copied.border, 'bottom', ['color']);
setIfExists(style.border, copied.border, 'right', ['color']);
setIfExists(style.border, copied.border, 'diagonal', ['color']);
}

if (style.fill) {
setIfExists(style, copied, 'fill', ['fgColor', 'bgColor', 'center']);
if (style.fill.stops) {
copied.fill.stops = style.fill.stops.map(s => oneDepthCopy(s, ['color']));
}
}

return copied;
};

exports.copyStyle = copyStyle;
32 changes: 32 additions & 0 deletions spec/integration/worksheet.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,38 @@ describe('Worksheet', () => {
}
});

it('should style of the inserted row with inherited style be mutable', () => {
const wb = new ExcelJS.Workbook();
const ws = wb.addWorksheet('blort');

const dateValue1 = new Date(1970, 1, 1);
const dateValue2 = new Date(1965, 1, 7);

ws.addRow([1, 'John Doe', dateValue1]);
ws.getRow(1).font = testutils.styles.fonts.comicSansUdB16;

ws.insertRow(2, [3, 'Jane Doe', dateValue2], 'i');
ws.insertRow(2, [2, 'Jane Doe', dateValue2], 'o');

ws.getRow(2).font = testutils.styles.fonts.broadwayRedOutline20;
ws.getRow(3).font = testutils.styles.fonts.broadwayRedOutline20;
ws.getCell('A2').font = testutils.styles.fonts.arialBlackUI14;
ws.getCell('A3').font = testutils.styles.fonts.arialBlackUI14;

expect(ws.getRow(2).font).not.deep.equal(
testutils.styles.fonts.comicSansUdB16
);
expect(ws.getRow(3).font).not.deep.equal(
testutils.styles.fonts.comicSansUdB16
);
expect(ws.getCell('A2').font).not.deep.equal(
testutils.styles.fonts.comicSansUdB16
);
expect(ws.getCell('A3').font).not.deep.equal(
testutils.styles.fonts.comicSansUdB16
);
});

it('iterates over rows', () => {
const wb = new ExcelJS.Workbook();
const ws = wb.addWorksheet('blort');
Expand Down
39 changes: 39 additions & 0 deletions spec/unit/utils/copy-style.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
const testUtils = require('../../utils/index');

const {copyStyle} = verquire('utils/copy-style');

const style1 = {
numFmt: testUtils.styles.numFmts.numFmt1,
font: testUtils.styles.fonts.broadwayRedOutline20,
alignment: testUtils.styles.namedAlignments.topLeft,
border: testUtils.styles.borders.thickRainbow,
fill: testUtils.styles.fills.redGreenDarkTrellis,
};
const style2 = {
fill: testUtils.styles.fills.rgbPathGrad,
};

describe('copyStyle', () => {
it('should copy a style deeply', () => {
const copied = copyStyle(style1);
expect(copied).to.deep.equal(style1);
expect(copied.font).to.not.equal(style1.font);
expect(copied.alignment).to.not.equal(style1.alignment);
expect(copied.border).to.not.equal(style1.border);
expect(copied.fill).to.not.equal(style1.fill);

expect(copyStyle({})).to.deep.equal({});
});

it('should copy fill.stops deeply', () => {
const copied = copyStyle(style2);
expect(copied.fill.stops).to.deep.equal(style2.fill.stops);
expect(copied.fill.stops).to.not.equal(style2.fill.stops);
expect(copied.fill.stops[0]).to.not.equal(style2.fill.stops[0]);
});

it('should return the argument if a falsy value passed', () => {
expect(copyStyle(null)).to.equal(null);
expect(copyStyle(undefined)).to.equal(undefined);
});
});

0 comments on commit da5e743

Please sign in to comment.