mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-10-01 17:29:29 +05:30
- AI Assistant plugin: multi-provider chat (OpenAI/Anthropic/Ollama/LM Studio), summarize/improve/translate commands, proofread via ai:analyze; calls proxied through main so API keys stay out of the renderer - Collaboration plugin: anchor-based comments in .comments/ sidecars with drift detection and F8 navigation - Local knowledge base: [[wiki-links]] with click-to-create + Backlinks panel - Crash recovery: debounced session snapshots with restore prompt on launch - Version history: pre-save snapshots, History panel with restore/diff/delete - Real PDF encryption: swap pdf-lib for @cantoo/pdf-lib (probe-driven UI) - XLSX export (native workbooks via JSZip), ODT headers/footers + page size - Offline KaTeX (bundled CSS+fonts), local-first PlantUML rendering - Editor: vim mode toggle, snippet Tab-expansion, zen word-goal setter, writing heatmap, writing-studio panels wired with rail icons - Quick Note global scratchpad (Ctrl+Alt+Q), markdownconverter:// deep links, REPL first-run confirmation - Fix: Ctrl+Shift+P collision, pandoc converter availability check, CLI dangling --css/--reference-doc flags, dead converter button 8 new test suites; 613 tests green; lint clean
97 lines
3.3 KiB
JavaScript
97 lines
3.3 KiB
JavaScript
/**
|
|
* @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(
|
|
'<c r="A1" t="inlineStr"><is><t xml:space="preserve">Name</t></is></c>'
|
|
);
|
|
expect(xml).toContain('<c r="B2" t="n"><v>99</v></c>');
|
|
expect(xml).toContain('<row r="1">');
|
|
});
|
|
|
|
it('escapes XML-significant characters in cells', () => {
|
|
const xml = buildSheetXml([['<b>&"quotes"</b>']]);
|
|
expect(xml).toContain('<b>&"quotes"</b>');
|
|
expect(xml).not.toContain('<b>');
|
|
});
|
|
});
|
|
|
|
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/);
|
|
});
|
|
});
|
|
});
|