/**
* @jest-environment node
*
* XlsxExporter tests: verify the generated OOXML package structure by
* reopening the buffer with JSZip and checking the key parts (workbook sheet
* list, per-sheet cell XML, content types). Uses only string assertions —
* no Excel engine needed.
*/
const JSZip = require('jszip');
const {
buildXlsx,
buildSheetXml,
columnLetter,
looksNumeric,
} = require('../../src/main/XlsxExporter');
describe('XlsxExporter', () => {
describe('columnLetter', () => {
it('maps 0-based indexes to Excel letters', () => {
expect(columnLetter(0)).toBe('A');
expect(columnLetter(25)).toBe('Z');
expect(columnLetter(26)).toBe('AA');
expect(columnLetter(27)).toBe('AB');
expect(columnLetter(52)).toBe('BA');
});
});
describe('looksNumeric', () => {
it('accepts integers and decimals, rejects text and leading zeros', () => {
expect(looksNumeric('42')).toBe(true);
expect(looksNumeric('-3.14')).toBe(true);
expect(looksNumeric('007')).toBe(false); // keeps identifier-looking values as text
expect(looksNumeric('1,000')).toBe(false);
expect(looksNumeric('abc')).toBe(false);
expect(looksNumeric('')).toBe(false);
});
});
describe('buildSheetXml', () => {
it('emits inline strings and numeric cells with row/column refs', () => {
const xml = buildSheetXml([
['Name', 'Score'],
['Ada', '99'],
]);
expect(xml).toContain(
'Name'
);
expect(xml).toContain('99');
expect(xml).toContain('');
});
it('escapes XML-significant characters in cells', () => {
const xml = buildSheetXml([['&"quotes"']]);
expect(xml).toContain('<b>&"quotes"</b>');
expect(xml).not.toContain('');
});
});
describe('buildXlsx', () => {
it('creates a workbook with one sheet per table and all required parts', async () => {
const buffer = await buildXlsx([
[
['h1', 'h2'],
['a', '1'],
],
[['only', 'column']],
]);
const zip = await JSZip.loadAsync(buffer);
const parts = Object.keys(zip.files).sort();
expect(parts).toContain('[Content_Types].xml');
expect(parts).toContain('_rels/.rels');
expect(parts).toContain('xl/workbook.xml');
expect(parts).toContain('xl/_rels/workbook.xml.rels');
expect(parts).toContain('xl/styles.xml');
expect(parts).toContain('xl/worksheets/sheet1.xml');
expect(parts).toContain('xl/worksheets/sheet2.xml');
const workbook = await zip.file('xl/workbook.xml').async('string');
expect(workbook).toContain('name="Table 1"');
expect(workbook).toContain('name="Table 2"');
expect(workbook).toContain('r:id="rId2"');
const rels = await zip.file('xl/_rels/workbook.xml.rels').async('string');
expect(rels).toContain('Target="worksheets/sheet1.xml"');
expect(rels).toContain('Target="styles.xml"');
const sheet1 = await zip.file('xl/worksheets/sheet1.xml').async('string');
expect(sheet1).toContain('inlineStr');
});
it('rejects empty table lists', async () => {
await expect(buildXlsx([])).rejects.toThrow(/No tables/);
});
});
});