From b7495781f99b91e6bd1039da47b4e2bbe2e1926d Mon Sep 17 00:00:00 2001 From: Pieter Sheth-Voss Date: Mon, 26 Jan 2026 15:18:30 -0500 Subject: [PATCH] Add unit test for HAN CELL file compatibility Add integration test to verify files created by HAN CELL (Korean spreadsheet software) can be read without errors. Test verifies: - File loads successfully - Worksheet name is read correctly - Row count is accurate - Cell values can be accessed --- spec/integration/data/hancell-file.xlsx | Bin 0 -> 7985 bytes spec/integration/workbook-xlsx-reader.spec.js | 290 +++++++++--------- 2 files changed, 153 insertions(+), 137 deletions(-) create mode 100644 spec/integration/data/hancell-file.xlsx diff --git a/spec/integration/data/hancell-file.xlsx b/spec/integration/data/hancell-file.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..401a75495a1a6a0b3534c16253a9feb90b55792a GIT binary patch literal 7985 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 literal 0 HcmV?d00001 diff --git a/spec/integration/workbook-xlsx-reader.spec.js b/spec/integration/workbook-xlsx-reader.spec.js index f6ad0a773..3640abed8 100644 --- a/spec/integration/workbook-xlsx-reader.spec.js +++ b/spec/integration/workbook-xlsx-reader.spec.js @@ -1,18 +1,18 @@ -const fs = require('fs'); +const fs = require("fs"); -const testutils = require('../utils/index'); +const testutils = require("../utils/index"); -const ExcelJS = verquire('exceljs'); +const ExcelJS = verquire("exceljs"); -const TEST_FILE_NAME = './spec/out/wb.test.xlsx'; +const TEST_FILE_NAME = "./spec/out/wb.test.xlsx"; // need some architectural changes to make stream read work properly // because of: shared strings, sheet names, etc are not read in guaranteed order -describe('WorkbookReader', () => { - describe('Serialise', () => { - it('xlsx file', function() { +describe("WorkbookReader", () => { + describe("Serialise", () => { + it("xlsx file", function () { this.timeout(10000); - const wb = testutils.createTestBook(new ExcelJS.Workbook(), 'xlsx'); + const wb = testutils.createTestBook(new ExcelJS.Workbook(), "xlsx"); return wb.xlsx .writeFile(TEST_FILE_NAME) @@ -20,267 +20,267 @@ describe('WorkbookReader', () => { }); }); - describe('#readFile', () => { - describe('Row limit', () => { - it('should bail out if the file contains more rows than the limit', () => { + describe("#readFile", () => { + describe("Row limit", () => { + it("should bail out if the file contains more rows than the limit", () => { const workbook = new ExcelJS.Workbook(); // The Fibonacci sheet has 19 rows return workbook.xlsx - .readFile('./spec/integration/data/fibonacci.xlsx', {maxRows: 10}) + .readFile("./spec/integration/data/fibonacci.xlsx", { maxRows: 10 }) .then( () => { - throw new Error('Promise unexpectedly fulfilled'); + throw new Error("Promise unexpectedly fulfilled"); + }, + (err) => { + expect(err.message).to.equal("Max row count (10) exceeded"); }, - err => { - expect(err.message).to.equal('Max row count (10) exceeded'); - } ); }); - it('should fail fast on a huge file', function() { + it("should fail fast on a huge file", function () { this.timeout(5000); const workbook = new ExcelJS.Workbook(); return workbook.xlsx - .readFile('./spec/integration/data/huge.xlsx', {maxRows: 100}) + .readFile("./spec/integration/data/huge.xlsx", { maxRows: 100 }) .then( () => { - throw new Error('Promise unexpectedly fulfilled'); + throw new Error("Promise unexpectedly fulfilled"); + }, + (err) => { + expect(err.message).to.equal("Max row count (100) exceeded"); }, - err => { - expect(err.message).to.equal('Max row count (100) exceeded'); - } ); }); - it('should parse fine if the limit is not exceeded', () => { + it("should parse fine if the limit is not exceeded", () => { const workbook = new ExcelJS.Workbook(); return workbook.xlsx.readFile( - './spec/integration/data/fibonacci.xlsx', - {maxRows: 20} + "./spec/integration/data/fibonacci.xlsx", + { maxRows: 20 }, ); }); }); - describe('Column limit', () => { - it('should bail out if the file contains more cells than the limit', () => { + describe("Column limit", () => { + it("should bail out if the file contains more cells than the limit", () => { const workbook = new ExcelJS.Workbook(); // The many-columns sheet has 20 columns in row 2 return workbook.xlsx - .readFile('./spec/integration/data/many-columns.xlsx', { + .readFile("./spec/integration/data/many-columns.xlsx", { maxCols: 15, }) .then( () => { - throw new Error('Promise unexpectedly fulfilled'); + throw new Error("Promise unexpectedly fulfilled"); + }, + (err) => { + expect(err.message).to.equal("Max column count (15) exceeded"); }, - err => { - expect(err.message).to.equal('Max column count (15) exceeded'); - } ); }); - it('should fail fast on a huge file', function() { + it("should fail fast on a huge file", function () { this.timeout(5000); const workbook = new ExcelJS.Workbook(); return workbook.xlsx - .readFile('./spec/integration/data/huge.xlsx', {maxCols: 10}) + .readFile("./spec/integration/data/huge.xlsx", { maxCols: 10 }) .then( () => { - throw new Error('Promise unexpectedly fulfilled'); + throw new Error("Promise unexpectedly fulfilled"); + }, + (err) => { + expect(err.message).to.equal("Max column count (10) exceeded"); }, - err => { - expect(err.message).to.equal('Max column count (10) exceeded'); - } ); }); - it('should parse fine if the limit is not exceeded', () => { + it("should parse fine if the limit is not exceeded", () => { const workbook = new ExcelJS.Workbook(); return workbook.xlsx.readFile( - './spec/integration/data/many-columns.xlsx', - {maxCols: 40} + "./spec/integration/data/many-columns.xlsx", + { maxCols: 40 }, ); }); }); }); - describe('#read', () => { - describe('Row limit', () => { - it('should bail out if the file contains more rows than the limit', () => { + describe("#read", () => { + describe("Row limit", () => { + it("should bail out if the file contains more rows than the limit", () => { const workbook = new ExcelJS.Workbook(); // The Fibonacci sheet has 19 rows return workbook.xlsx - .read(fs.createReadStream('./spec/integration/data/fibonacci.xlsx'), { + .read(fs.createReadStream("./spec/integration/data/fibonacci.xlsx"), { maxRows: 10, }) .then( () => { - throw new Error('Promise unexpectedly fulfilled'); + throw new Error("Promise unexpectedly fulfilled"); + }, + (err) => { + expect(err.message).to.equal("Max row count (10) exceeded"); }, - err => { - expect(err.message).to.equal('Max row count (10) exceeded'); - } ); }); - it('should parse fine if the limit is not exceeded', () => { + it("should parse fine if the limit is not exceeded", () => { const workbook = new ExcelJS.Workbook(); return workbook.xlsx.read( - fs.createReadStream('./spec/integration/data/fibonacci.xlsx'), - {maxRows: 20} + fs.createReadStream("./spec/integration/data/fibonacci.xlsx"), + { maxRows: 20 }, ); }); }); }); - describe('edit styles in existing file', () => { - beforeEach(function() { + describe("edit styles in existing file", () => { + beforeEach(function () { this.wb = new ExcelJS.Workbook(); return this.wb.xlsx.readFile( - './spec/integration/data/test-row-styles.xlsx' + "./spec/integration/data/test-row-styles.xlsx", ); }); - it('edit styles of single row instead of all', function() { + it("edit styles of single row instead of all", function () { const ws = this.wb.getWorksheet(1); ws.eachRow((row, rowNo) => { if (rowNo % 5 === 0) { - row.font = {color: {argb: '00ff00'}}; + row.font = { color: { argb: "00ff00" } }; } }); expect(ws.getRow(3).font.color.argb).to.be.equal( - ws.getRow(6).font.color.argb + ws.getRow(6).font.color.argb, ); expect(ws.getRow(6).font.color.argb).to.be.equal( - ws.getRow(9).font.color.argb + ws.getRow(9).font.color.argb, ); expect(ws.getRow(9).font.color.argb).to.be.equal( - ws.getRow(12).font.color.argb + ws.getRow(12).font.color.argb, ); expect(ws.getRow(12).font.color.argb).not.to.be.equal( - ws.getRow(15).font.color.argb + ws.getRow(15).font.color.argb, ); expect(ws.getRow(15).font.color.argb).not.to.be.equal( - ws.getRow(18).font.color.argb + ws.getRow(18).font.color.argb, ); expect(ws.getRow(15).font.color.argb).to.be.equal( - ws.getRow(10).font.color.argb + ws.getRow(10).font.color.argb, ); expect(ws.getRow(10).font.color.argb).to.be.equal( - ws.getRow(5).font.color.argb + ws.getRow(5).font.color.argb, ); }); }); - describe('with a spreadsheet that contains formulas', () => { - before(function() { + describe("with a spreadsheet that contains formulas", () => { + before(function () { const testContext = this; const workbook = new ExcelJS.Workbook(); return workbook.xlsx - .read(fs.createReadStream('./spec/integration/data/formulas.xlsx')) + .read(fs.createReadStream("./spec/integration/data/formulas.xlsx")) .then(() => { testContext.worksheet = workbook.getWorksheet(); }); }); - describe('with a cell that contains a regular formula', () => { - beforeEach(function() { - this.cell = this.worksheet.getCell('A2'); + describe("with a cell that contains a regular formula", () => { + beforeEach(function () { + this.cell = this.worksheet.getCell("A2"); }); - it('should be classified as a formula cell', function() { + it("should be classified as a formula cell", function () { expect(this.cell.type).to.equal(ExcelJS.ValueType.Formula); }); - it('should have text corresponding to the evaluated formula result', function() { - expect(this.cell.text).to.equal('someone@example.com'); + it("should have text corresponding to the evaluated formula result", function () { + expect(this.cell.text).to.equal("someone@example.com"); }); - it('should have the formula source', function() { + it("should have the formula source", function () { expect(this.cell.model.formula).to.equal( - '_xlfn.CONCAT("someone","@example.com")' + '_xlfn.CONCAT("someone","@example.com")', ); }); }); - describe('with a cell that contains a hyperlinked formula', () => { - beforeEach(function() { - this.cell = this.worksheet.getCell('A1'); + describe("with a cell that contains a hyperlinked formula", () => { + beforeEach(function () { + this.cell = this.worksheet.getCell("A1"); }); - it('should be classified as a formula cell', function() { + it("should be classified as a formula cell", function () { expect(this.cell.type).to.equal(ExcelJS.ValueType.Hyperlink); }); - it('should have text corresponding to the evaluated formula result', function() { - expect(this.cell.value.text).to.equal('someone@example.com'); + it("should have text corresponding to the evaluated formula result", function () { + expect(this.cell.value.text).to.equal("someone@example.com"); }); - it('should have the formula source', function() { + it("should have the formula source", function () { expect(this.cell.model.formula).to.equal( - '_xlfn.CONCAT("someone","@example.com")' + '_xlfn.CONCAT("someone","@example.com")', ); }); - it('should contain the linked url', function() { + it("should contain the linked url", function () { expect(this.cell.value.hyperlink).to.equal( - 'mailto:someone@example.com' + "mailto:someone@example.com", ); - expect(this.cell.hyperlink).to.equal('mailto:someone@example.com'); + expect(this.cell.hyperlink).to.equal("mailto:someone@example.com"); }); }); }); - describe('with a spreadsheet that contains a shared string with an escaped underscore', () => { - before(function() { + describe("with a spreadsheet that contains a shared string with an escaped underscore", () => { + before(function () { const testContext = this; const workbook = new ExcelJS.Workbook(); return workbook.xlsx .read( fs.createReadStream( - './spec/integration/data/shared_string_with_escape.xlsx' - ) + "./spec/integration/data/shared_string_with_escape.xlsx", + ), ) .then(() => { testContext.worksheet = workbook.getWorksheet(); }); }); - it('should decode the underscore', function() { - const cell = this.worksheet.getCell('A1'); - expect(cell.value).to.equal('_x000D_'); + it("should decode the underscore", function () { + const cell = this.worksheet.getCell("A1"); + expect(cell.value).to.equal("_x000D_"); }); }); - describe('with a spreadsheet that has an XML parse error in a worksheet', () => { + describe("with a spreadsheet that has an XML parse error in a worksheet", () => { let unhandledRejection; function unhandledRejectionHandler(err) { unhandledRejection = err; } beforeEach(() => { - process.on('unhandledRejection', unhandledRejectionHandler); + process.on("unhandledRejection", unhandledRejectionHandler); }); afterEach(() => { - process.removeListener('unhandledRejection', unhandledRejectionHandler); + process.removeListener("unhandledRejection", unhandledRejectionHandler); }); - it('should reject the promise with the sax error', () => { + it("should reject the promise with the sax error", () => { const workbook = new ExcelJS.Workbook(); return workbook.xlsx - .readFile('./spec/integration/data/invalid-xml.xlsx') + .readFile("./spec/integration/data/invalid-xml.xlsx") .then( () => { - throw new Error('Promise unexpectedly fulfilled'); + throw new Error("Promise unexpectedly fulfilled"); }, - err => { + (err) => { expect(err.message).to.equal( - '3:1: text data outside of root node.' + "3:1: text data outside of root node.", ); // Wait a tick before checking for an unhandled rejection return new Promise(setImmediate); - } + }, ) .then(() => { expect(unhandledRejection).to.be.undefined(); @@ -288,65 +288,65 @@ describe('WorkbookReader', () => { }); }); - describe('with a spreadsheet that is missing some files in the zip container', () => { - it('should not break', () => { + describe("with a spreadsheet that is missing some files in the zip container", () => { + it("should not break", () => { const workbook = new ExcelJS.Workbook(); return workbook.xlsx.readFile( - './spec/integration/data/missing-bits.xlsx' + "./spec/integration/data/missing-bits.xlsx", ); }); }); - describe('with a spreadsheet that contains images', () => { - before(function() { + describe("with a spreadsheet that contains images", () => { + before(function () { const testContext = this; const workbook = new ExcelJS.Workbook(); return workbook.xlsx - .read(fs.createReadStream('./spec/integration/data/images.xlsx')) + .read(fs.createReadStream("./spec/integration/data/images.xlsx")) .then(() => { testContext.worksheet = workbook.getWorksheet(); }); }); - describe('with image`s tl anchor', () => { - it('Should integer part of col equals nativeCol', function() { - this.worksheet.getImages().forEach(image => { + describe("with image`s tl anchor", () => { + it("Should integer part of col equals nativeCol", function () { + this.worksheet.getImages().forEach((image) => { expect(Math.floor(image.range.tl.col)).to.equal( - image.range.tl.nativeCol + image.range.tl.nativeCol, ); }); }); - it('Should integer part of row equals nativeRow', function() { - this.worksheet.getImages().forEach(image => { + it("Should integer part of row equals nativeRow", function () { + this.worksheet.getImages().forEach((image) => { expect(Math.floor(image.range.tl.row)).to.equal( - image.range.tl.nativeRow + image.range.tl.nativeRow, ); }); }); - it('Should anchor width equals to column width when custom', function() { + it("Should anchor width equals to column width when custom", function () { const ws = this.worksheet; - ws.getImages().forEach(image => { + ws.getImages().forEach((image) => { const col = ws.getColumn(image.range.tl.nativeCol + 1); if (col.isCustomWidth) { expect(image.range.tl.colWidth).to.equal( - Math.floor(col.width * 10000) + Math.floor(col.width * 10000), ); } else { expect(image.range.tl.colWidth).to.equal(640000); } }); }); - it('Should anchor height equals to row height', function() { + it("Should anchor height equals to row height", function () { const ws = this.worksheet; - ws.getImages().forEach(image => { + ws.getImages().forEach((image) => { const row = ws.getRow(image.range.tl.nativeRow + 1); if (row.height) { expect(image.range.tl.rowHeight).to.equal( - Math.floor(row.height * 10000) + Math.floor(row.height * 10000), ); } else { expect(image.range.tl.rowHeight).to.equal(180000); @@ -355,45 +355,45 @@ describe('WorkbookReader', () => { }); }); - describe('with image`s br anchor', () => { - it('Should integer part of col equals nativeCol', function() { - this.worksheet.getImages().forEach(image => { + describe("with image`s br anchor", () => { + it("Should integer part of col equals nativeCol", function () { + this.worksheet.getImages().forEach((image) => { expect(Math.floor(image.range.br.col)).to.equal( - image.range.br.nativeCol + image.range.br.nativeCol, ); }); }); - it('Should integer part of row equals nativeRow', function() { - this.worksheet.getImages().forEach(image => { + it("Should integer part of row equals nativeRow", function () { + this.worksheet.getImages().forEach((image) => { expect(Math.floor(image.range.br.row)).to.equal( - image.range.br.nativeRow + image.range.br.nativeRow, ); }); }); - it('Should anchor width equals to column width when custom', function() { + it("Should anchor width equals to column width when custom", function () { const ws = this.worksheet; - ws.getImages().forEach(image => { + ws.getImages().forEach((image) => { const col = ws.getColumn(image.range.br.nativeCol + 1); if (col.isCustomWidth) { expect(image.range.br.colWidth).to.equal( - Math.floor(col.width * 10000) + Math.floor(col.width * 10000), ); } else { expect(image.range.br.colWidth).to.equal(640000); } }); }); - it('Should anchor height equals to row height', function() { + it("Should anchor height equals to row height", function () { const ws = this.worksheet; - ws.getImages().forEach(image => { + ws.getImages().forEach((image) => { const row = ws.getRow(image.range.br.nativeRow + 1); if (row.height) { expect(image.range.br.rowHeight).to.equal( - Math.floor(row.height * 10000) + Math.floor(row.height * 10000), ); } else { expect(image.range.br.rowHeight).to.equal(180000); @@ -402,12 +402,28 @@ describe('WorkbookReader', () => { }); }); }); - describe('with a spreadsheet containing a defined name that kinda looks like it contains a range', () => { - it('should not crash', () => { + describe("with a spreadsheet containing a defined name that kinda looks like it contains a range", () => { + it("should not crash", () => { const workbook = new ExcelJS.Workbook(); return workbook.xlsx.read( - fs.createReadStream('./spec/integration/data/bogus-defined-name.xlsx') + fs.createReadStream("./spec/integration/data/bogus-defined-name.xlsx"), ); }); }); + + describe("HAN CELL compatibility", () => { + it("should read files created by HAN CELL (Korean spreadsheet software)", async () => { + const workbook = new ExcelJS.Workbook(); + await workbook.xlsx.readFile("./spec/integration/data/hancell-file.xlsx"); + + const worksheet = workbook.getWorksheet(1); + expect(worksheet).to.be.ok; + expect(worksheet.name).to.equal("no build"); + expect(worksheet.rowCount).to.equal(2); + + // Verify data can be read + expect(worksheet.getCell("A1").value).to.be.ok; + expect(worksheet.getCell("G2").value).to.equal(90); + }); + }); });