diff --git a/packages/excel-builder-vanilla/src/Excel/Drawing/Picture.ts b/packages/excel-builder-vanilla/src/Excel/Drawing/Picture.ts
index 67e8302..aedb59e 100644
--- a/packages/excel-builder-vanilla/src/Excel/Drawing/Picture.ts
+++ b/packages/excel-builder-vanilla/src/Excel/Drawing/Picture.ts
@@ -1,8 +1,8 @@
-import { Drawing } from './Drawing.js';
import { uniqueId } from '../../utilities/uniqueId.js';
import { Util } from '../Util.js';
import type { MediaMeta } from '../Workbook.js';
import type { XMLDOM } from '../XMLDOM.js';
+import { Drawing } from './Drawing.js';
export class Picture extends Drawing {
id = uniqueId('Picture');
diff --git a/packages/excel-builder-vanilla/src/Excel/Drawing/TwoCellAnchor.ts b/packages/excel-builder-vanilla/src/Excel/Drawing/TwoCellAnchor.ts
index 7ca19a9..b4853d9 100644
--- a/packages/excel-builder-vanilla/src/Excel/Drawing/TwoCellAnchor.ts
+++ b/packages/excel-builder-vanilla/src/Excel/Drawing/TwoCellAnchor.ts
@@ -8,7 +8,7 @@ export class TwoCellAnchor {
constructor(config: DualAnchorOption) {
if (config) {
- this.setFrom(config.from.x, config.from.y, config.to.xOff, config.to.yOff);
+ this.setFrom(config.from.x, config.from.y, config.from.xOff, config.from.yOff);
this.setTo(config.to.x, config.to.y, config.to.xOff, config.to.yOff);
}
}
diff --git a/packages/excel-builder-vanilla/src/Excel/Drawing/__tests__/OneCellAnchor.spec.ts b/packages/excel-builder-vanilla/src/Excel/Drawing/__tests__/OneCellAnchor.spec.ts
new file mode 100644
index 0000000..9a85804
--- /dev/null
+++ b/packages/excel-builder-vanilla/src/Excel/Drawing/__tests__/OneCellAnchor.spec.ts
@@ -0,0 +1,69 @@
+import { describe, expect, it } from 'vitest';
+
+import { Util } from '../../Util';
+import { OneCellAnchor } from '../OneCellAnchor';
+
+describe('OneCellAnchor', () => {
+ it('should set xOff and yOff when provided', () => {
+ const anchor = new OneCellAnchor({ x: 1, y: 2, xOff: true, yOff: false, width: 10, height: 20 });
+ expect(anchor.xOff).toBe(true);
+ expect(anchor.yOff).toBe(false);
+ });
+
+ it('should not set xOff and yOff when not provided', () => {
+ const anchor = new OneCellAnchor({ x: 1, y: 2, width: 10, height: 20 });
+ expect(anchor.xOff).toBeNull();
+ expect(anchor.yOff).toBeNull();
+ });
+
+ it('should set xOff and yOff via setPos', () => {
+ const anchor = new OneCellAnchor({ x: 1, y: 2, width: 10, height: 20 });
+ anchor.setPos(3, 4, false, true);
+ expect(anchor.xOff).toBe(false);
+ expect(anchor.yOff).toBe(true);
+ });
+
+ it('should set and get position and dimensions correctly', () => {
+ const anchor = new OneCellAnchor({ x: 5, y: 6, width: 100, height: 200 });
+ expect(anchor.x).toBe(5);
+ expect(anchor.y).toBe(6);
+ expect(anchor.width).toBe(100);
+ expect(anchor.height).toBe(200);
+ anchor.setPos(7, 8, true, false);
+ expect(anchor.x).toBe(7);
+ expect(anchor.y).toBe(8);
+ expect(anchor.xOff).toBe(true);
+ expect(anchor.yOff).toBe(false);
+ anchor.setDimensions(300, 400);
+ expect(anchor.width).toBe(300);
+ expect(anchor.height).toBe(400);
+ });
+
+ it('should create correct XML structure in toXML', () => {
+ // Minimal mock for XMLDOM and Util
+ const xmlDoc = {
+ createElement: (nodeName: string) => ({
+ nodeName,
+ children: [] as any[],
+ appendChild(child: any) {
+ (this.children as any[]).push(child);
+ },
+ setAttribute() {},
+ toString() {
+ return `<${nodeName}/>`;
+ },
+ }),
+ createTextNode: (text: string) => ({ text }),
+ };
+ // Patch Util.createElement to use our mock
+ const origCreateElement = Util.createElement;
+ Util.createElement = (doc: any, name: string) => doc.createElement(name);
+ const anchor = new OneCellAnchor({ x: 2, y: 3, xOff: true, yOff: false, width: 50, height: 60 });
+ const xml = anchor.toXML(xmlDoc as any, {});
+ // Check structure
+ expect(xml.nodeName).toBe('xdr:oneCellAnchor');
+ expect(xml.children.length).toBeGreaterThan(0);
+ // Restore Util.createElement
+ Util.createElement = origCreateElement;
+ });
+});
diff --git a/packages/excel-builder-vanilla/src/Excel/Drawing/__tests__/Picture.spec.ts b/packages/excel-builder-vanilla/src/Excel/Drawing/__tests__/Picture.spec.ts
new file mode 100644
index 0000000..00107f8
--- /dev/null
+++ b/packages/excel-builder-vanilla/src/Excel/Drawing/__tests__/Picture.spec.ts
@@ -0,0 +1,73 @@
+import { describe, expect, it } from 'vitest';
+
+import { Util } from '../../Util';
+import { Picture } from '../Picture';
+
+describe('Picture', () => {
+ it('should initialize with unique ids and default values', () => {
+ const pic = new Picture();
+ expect(typeof pic.id).toBe('string');
+ expect(typeof pic.pictureId).toBe('number');
+ expect(pic.fill).toEqual({});
+ expect(pic.mediaData).toBeNull();
+ expect(pic.description).toBe('');
+ });
+
+ it('should set media, description, fill type, and fill config', () => {
+ const pic = new Picture();
+ const media = { fileName: 'img.png', rId: 'rId1', id: '1', data: '', contentType: 'image/png', extension: 'png' };
+ pic.setMedia(media);
+ expect(pic.mediaData).toBe(media);
+ pic.setDescription('desc');
+ expect(pic.description).toBe('desc');
+ pic.setFillType('solid');
+ expect(pic.fill.type).toBe('solid');
+ pic.setFillConfig({ color: 'red', opacity: 0.5 });
+ expect(pic.fill.color).toBe('red');
+ expect(pic.fill.opacity).toBe(0.5);
+ });
+
+ it('should get media type and data', () => {
+ const pic = new Picture();
+ const media = { fileName: 'img.png', rId: 'rId1', id: '2', data: '', contentType: 'image/png', extension: 'png' };
+ pic.setMedia(media);
+ expect(pic.getMediaType()).toBe('image');
+ expect(pic.getMediaData()).toBe(media);
+ });
+
+ it('should set relationship id on mediaData', () => {
+ const pic = new Picture();
+ const media = { fileName: 'img.png', rId: '', id: '3', data: '', contentType: 'image/png', extension: 'png' };
+ pic.setMedia(media);
+ pic.setRelationshipId('rId2');
+ expect(pic.mediaData!.rId).toBe('rId2');
+ });
+
+ it('should create correct XML structure in toXML', () => {
+ // Minimal mock for XMLDOM, Util, and anchor
+ const xmlDoc = {
+ createElement: (nodeName: string) => ({
+ nodeName,
+ children: [] as any[],
+ appendChild(child: any) {
+ (this.children as any[]).push(child);
+ },
+ setAttribute() {},
+ toString() {
+ return `<${nodeName}/>`;
+ },
+ }),
+ createTextNode: (text: string) => ({ text }),
+ };
+ const origCreateElement = Util.createElement;
+ Util.createElement = (doc: any, name: string, attrs?: any) => doc.createElement(name);
+ const pic = new Picture();
+ pic.anchor = { toXML: (doc: any, node: any) => ({ nodeName: 'anchored', children: [node] }) } as any;
+ pic.setMedia({ fileName: 'img.png', rId: 'rId1', id: '4', data: '', contentType: 'image/png', extension: 'png' });
+ pic.setDescription('desc');
+ const xml = pic.toXML(xmlDoc as any);
+ expect(xml.nodeName).toBe('anchored');
+ expect(xml.children[0].nodeName).toBe('xdr:pic');
+ Util.createElement = origCreateElement;
+ });
+});
diff --git a/packages/excel-builder-vanilla/src/Excel/Drawing/__tests__/TwoCellAnchor.spec.ts b/packages/excel-builder-vanilla/src/Excel/Drawing/__tests__/TwoCellAnchor.spec.ts
new file mode 100644
index 0000000..a20626d
--- /dev/null
+++ b/packages/excel-builder-vanilla/src/Excel/Drawing/__tests__/TwoCellAnchor.spec.ts
@@ -0,0 +1,68 @@
+import { describe, expect, it } from 'vitest';
+
+import { Util } from '../../Util';
+import { TwoCellAnchor } from '../TwoCellAnchor';
+
+describe('TwoCellAnchor', () => {
+ it('should set from and to positions and offsets via constructor', () => {
+ const anchor = new TwoCellAnchor({
+ from: { x: 1, y: 2, xOff: true, yOff: false, width: 10, height: 20 },
+ to: { x: 3, y: 4, xOff: false, yOff: true, width: 30, height: 40 },
+ });
+ expect(anchor.from.x).toBe(1);
+ expect(anchor.from.y).toBe(2);
+ expect(anchor.from.xOff).toBe(true);
+ expect(anchor.from.yOff).toBe(false);
+ expect(anchor.to.x).toBe(3);
+ expect(anchor.to.y).toBe(4);
+ expect(anchor.to.xOff).toBe(false);
+ expect(anchor.to.yOff).toBe(true);
+ });
+
+ it('should set from and to via setFrom and setTo', () => {
+ const anchor = new TwoCellAnchor({
+ from: { x: 0, y: 0, width: 1, height: 1 },
+ to: { x: 0, y: 0, width: 1, height: 1 },
+ });
+ anchor.setFrom(5, 6, true, false);
+ anchor.setTo(7, 8, false, true);
+ expect(anchor.from.x).toBe(5);
+ expect(anchor.from.y).toBe(6);
+ expect(anchor.from.xOff).toBe(true);
+ expect(anchor.from.yOff).toBe(false);
+ expect(anchor.to.x).toBe(7);
+ expect(anchor.to.y).toBe(8);
+ expect(anchor.to.xOff).toBe(false);
+ expect(anchor.to.yOff).toBe(true);
+ });
+
+ it('should create correct XML structure in toXML', () => {
+ // Minimal mock for XMLDOM and Util
+ const xmlDoc = {
+ createElement: (nodeName: string) => ({
+ nodeName,
+ children: [] as any[],
+ appendChild(child: any) {
+ (this.children as any[]).push(child);
+ },
+ setAttribute() {},
+ toString() {
+ return `<${nodeName}/>`;
+ },
+ }),
+ createTextNode: (text: string) => ({ text }),
+ };
+ // Patch Util.createElement to use our mock
+ const origCreateElement = Util.createElement;
+ Util.createElement = (doc: any, name: string) => doc.createElement(name);
+ const anchor = new TwoCellAnchor({
+ from: { x: 1, y: 2, xOff: true, yOff: false, width: 10, height: 20 },
+ to: { x: 3, y: 4, xOff: false, yOff: true, width: 30, height: 40 },
+ });
+ const xml = anchor.toXML(xmlDoc as any, { nodeName: 'content' });
+ expect(xml.nodeName).toBe('xdr:twoCellAnchor');
+ expect(xml.children.length).toBeGreaterThan(0);
+ // Restore Util.createElement
+ Util.createElement = origCreateElement;
+ });
+});
diff --git a/packages/excel-builder-vanilla/src/__tests__/Drawings.spec.ts b/packages/excel-builder-vanilla/src/Excel/__tests__/Drawings.spec.ts
similarity index 92%
rename from packages/excel-builder-vanilla/src/__tests__/Drawings.spec.ts
rename to packages/excel-builder-vanilla/src/Excel/__tests__/Drawings.spec.ts
index d4e1191..43d5c48 100644
--- a/packages/excel-builder-vanilla/src/__tests__/Drawings.spec.ts
+++ b/packages/excel-builder-vanilla/src/Excel/__tests__/Drawings.spec.ts
@@ -1,9 +1,9 @@
import { describe, expect, test } from 'vitest';
-import { Picture } from '../Excel/Drawing/Picture.js';
-import { Drawings } from '../Excel/Drawings.js';
-import { Positioning } from '../Excel/Positioning.js';
-import { createWorkbook } from '../factory.js';
+import { createWorkbook } from '../../factory.js';
+import { Picture } from '../Drawing/Picture.js';
+import { Drawings } from '../Drawings.js';
+import { Positioning } from '../Positioning.js';
describe('Drawings', () => {
test('Drawings', async () => {
@@ -76,6 +76,7 @@ describe('Drawings', () => {
const wsXML = fruitWorkbook.toXML();
expect(wsXML.documentElement.children.length).toBe(2);
+ expect(drawings.getCount()).toBe(3);
});
test('toXML with missing relationship', () => {
diff --git a/packages/excel-builder-vanilla/src/__tests__/Pane.spec.ts b/packages/excel-builder-vanilla/src/Excel/__tests__/Pane.spec.ts
similarity index 70%
rename from packages/excel-builder-vanilla/src/__tests__/Pane.spec.ts
rename to packages/excel-builder-vanilla/src/Excel/__tests__/Pane.spec.ts
index aa1328c..a8ee1d6 100644
--- a/packages/excel-builder-vanilla/src/__tests__/Pane.spec.ts
+++ b/packages/excel-builder-vanilla/src/Excel/__tests__/Pane.spec.ts
@@ -1,6 +1,6 @@
import { describe, expect, test } from 'vitest';
-import { Pane } from '../Excel/Pane.js';
+import { Pane } from '../Pane.js';
describe('Pane', () => {
test('Pane with invalid state', () => {
@@ -17,4 +17,10 @@ describe('Pane', () => {
const doc = { createElement: () => ({ setAttribute: () => {} }) };
expect(() => pane.exportXML(doc as any)).not.toThrow();
});
+
+ test('freezePane sets _freezePane correctly', () => {
+ const pane = new Pane();
+ pane.freezePane(2, 3, 'B2');
+ expect(pane._freezePane).toEqual({ xSplit: 2, ySplit: 3, cell: 'B2' });
+ });
});
diff --git a/packages/excel-builder-vanilla/src/__tests__/RelationshipManager.spec.ts b/packages/excel-builder-vanilla/src/Excel/__tests__/RelationshipManager.spec.ts
similarity index 83%
rename from packages/excel-builder-vanilla/src/__tests__/RelationshipManager.spec.ts
rename to packages/excel-builder-vanilla/src/Excel/__tests__/RelationshipManager.spec.ts
index 1304c11..f6c26ff 100644
--- a/packages/excel-builder-vanilla/src/__tests__/RelationshipManager.spec.ts
+++ b/packages/excel-builder-vanilla/src/Excel/__tests__/RelationshipManager.spec.ts
@@ -1,6 +1,6 @@
import { describe, expect, test } from 'vitest';
-import { RelationshipManager } from '../Excel/RelationshipManager.js';
+import { RelationshipManager } from '../RelationshipManager.js';
describe('RelationshipManager', () => {
test('toXML with targetMode', () => {
diff --git a/packages/excel-builder-vanilla/src/__tests__/SharedStrings.spec.ts b/packages/excel-builder-vanilla/src/Excel/__tests__/SharedStrings.spec.ts
similarity index 53%
rename from packages/excel-builder-vanilla/src/__tests__/SharedStrings.spec.ts
rename to packages/excel-builder-vanilla/src/Excel/__tests__/SharedStrings.spec.ts
index e1a4e05..dca69ca 100644
--- a/packages/excel-builder-vanilla/src/__tests__/SharedStrings.spec.ts
+++ b/packages/excel-builder-vanilla/src/Excel/__tests__/SharedStrings.spec.ts
@@ -1,6 +1,6 @@
import { describe, expect, test } from 'vitest';
-import { SharedStrings } from '../Excel/SharedStrings.js';
+import { SharedStrings } from '../SharedStrings.js';
describe('SharedStrings', () => {
test('toXML with whitespace string', () => {
@@ -8,4 +8,10 @@ describe('SharedStrings', () => {
ss.stringArray = ['with space'];
expect(() => ss.toXML()).not.toThrow();
});
+
+ test('exportData returns strings object', () => {
+ const ss = new SharedStrings();
+ ss.addString('foo');
+ expect(ss.exportData()).toEqual({ foo: 0 });
+ });
});
diff --git a/packages/excel-builder-vanilla/src/__tests__/SheetView.spec.ts b/packages/excel-builder-vanilla/src/Excel/__tests__/SheetView.spec.ts
similarity index 94%
rename from packages/excel-builder-vanilla/src/__tests__/SheetView.spec.ts
rename to packages/excel-builder-vanilla/src/Excel/__tests__/SheetView.spec.ts
index 704e892..7a07a1e 100644
--- a/packages/excel-builder-vanilla/src/__tests__/SheetView.spec.ts
+++ b/packages/excel-builder-vanilla/src/Excel/__tests__/SheetView.spec.ts
@@ -1,6 +1,6 @@
import { describe, expect, test } from 'vitest';
-import { SheetView } from '../Excel/SheetView.js';
+import { SheetView } from '../SheetView.js';
describe('SheetView', () => {
test('exportXML with all options', () => {
diff --git a/packages/excel-builder-vanilla/src/__tests__/StyleSheet.spec.ts b/packages/excel-builder-vanilla/src/Excel/__tests__/StyleSheet.spec.ts
similarity index 99%
rename from packages/excel-builder-vanilla/src/__tests__/StyleSheet.spec.ts
rename to packages/excel-builder-vanilla/src/Excel/__tests__/StyleSheet.spec.ts
index 1b135bf..00abcdf 100644
--- a/packages/excel-builder-vanilla/src/__tests__/StyleSheet.spec.ts
+++ b/packages/excel-builder-vanilla/src/Excel/__tests__/StyleSheet.spec.ts
@@ -1,7 +1,7 @@
import { describe, expect, test } from 'vitest';
-import { StyleSheet } from '../Excel/StyleSheet.js';
-import { XMLNode } from '../Excel/XMLDOM.js';
+import { StyleSheet } from '../StyleSheet.js';
+import { XMLNode } from '../XMLDOM.js';
describe('StyleSheet', () => {
test('createFormat with empty object', () => {
diff --git a/packages/excel-builder-vanilla/src/Excel/__tests__/Table.spec.ts b/packages/excel-builder-vanilla/src/Excel/__tests__/Table.spec.ts
new file mode 100644
index 0000000..7c86746
--- /dev/null
+++ b/packages/excel-builder-vanilla/src/Excel/__tests__/Table.spec.ts
@@ -0,0 +1,181 @@
+import { describe, expect, it, test, vi } from 'vitest';
+
+import { Table } from '../Table';
+
+// Minimal mocks for Util (replace jest.fn with vi.fn)
+vi.mock('../Excel/Util', () => ({
+ Util: {
+ // Mock createXmlDoc to return an object with both documentElement and createElement
+ createXmlDoc: (_ns: string, _root: string) => {
+ // Mock element with setAttribute and appendChild
+ const mockElement = {
+ setAttribute: vi.fn(),
+ appendChild: vi.fn(),
+ };
+ return {
+ documentElement: mockElement,
+ createElement: vi.fn(() => ({
+ setAttribute: vi.fn(),
+ appendChild: vi.fn(),
+ })),
+ };
+ },
+ positionToLetterRef: (_row: number, _col: number) => 'R1C1',
+ schemas: { spreadsheetml: 'ns' },
+ },
+}));
+
+describe('Table', () => {
+ it('should generate XML with all attributes and children', () => {
+ const t = new Table();
+ t.ref = [
+ [1, 2],
+ [3, 4],
+ ];
+ t.headerRowCount = 1;
+ t.totalsRowCount = 1;
+ t.headerRowDxfId = 5;
+ t.headerRowBorderDxfId = 6;
+ t.tableColumns = [{ name: 'Col1' }];
+ t.styleInfo = {
+ themeStyle: 'TableStyle',
+ showFirstColumn: true,
+ showLastColumn: false,
+ showColumnStripes: true,
+ showRowStripes: false,
+ };
+ // Should not throw and should call all attribute/child code
+ expect(() => t.toXML()).not.toThrow();
+ });
+
+ describe('Table', () => {
+ it('should initialize with default and config values', () => {
+ const t = new Table({ headerRowCount: 2, totalsRowCount: 1 });
+ expect(t.headerRowCount).toBe(2);
+ expect(t.totalsRowCount).toBe(1);
+ expect(t.name).toContain('Table');
+ expect(t.displayName).toBe(t.name);
+ expect(t.id).toBe(t.name);
+ expect(t.tableId).toBe(t.id.replace('Table', ''));
+ });
+
+ it('should set reference range', () => {
+ const t = new Table();
+ t.setReferenceRange([1, 2], [3, 4]);
+ expect(t.ref).toEqual([
+ [1, 2],
+ [3, 4],
+ ]);
+ });
+
+ it('should add and set table columns', () => {
+ const t = new Table();
+ t.setTableColumns(['Col1', { name: 'Col2', totalsRowFunction: 'sum' }]);
+ expect(t.tableColumns.length).toBe(2);
+ expect(t.tableColumns[0].name).toBe('Col1');
+ expect(t.tableColumns[1].totalsRowFunction).toBe('sum');
+ });
+
+ it('should throw if addTableColumn called without name', () => {
+ const t = new Table();
+ expect(() => t.addTableColumn({} as any)).toThrow();
+ });
+
+ it('should set sort state', () => {
+ const t = new Table();
+ t.setSortState({ dataRange: [1, 2], sortDirection: 'asc' } as any);
+ expect(t.sortState).toEqual({ dataRange: [1, 2], sortDirection: 'asc' });
+ });
+
+ it('should add auto filter', () => {
+ const t = new Table();
+ t.addAutoFilter([1, 2], [3, 4]);
+ expect(t.autoFilter).toEqual([
+ [1, 2],
+ [3, 4],
+ ]);
+ });
+
+ it('should export table columns with totalsRowFunction and totalsRowLabel', () => {
+ const t = new Table();
+ t.tableColumns = [{ name: 'Col1', totalsRowFunction: 'sum', totalsRowLabel: 'Total' }, { name: 'Col2' }];
+ const doc = {
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ createElement: (_name: string) => ({
+ setAttribute: vi.fn(),
+ appendChild: vi.fn(),
+ }),
+ } as any;
+ const result = t.exportTableColumns(doc);
+ expect(result).toBeDefined();
+ });
+
+ it('should export auto filter', () => {
+ const t = new Table();
+ t.autoFilter = [
+ [1, 2],
+ [3, 4],
+ ];
+ t.totalsRowCount = 1;
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const doc = { createElement: (_name: string) => ({ setAttribute: vi.fn() }) } as any;
+ const result = t.exportAutoFilter(doc);
+ expect(result).toBeDefined();
+ });
+
+ it('should export table style info', () => {
+ const t = new Table();
+ t.styleInfo = {
+ themeStyle: 'TableStyle',
+ showFirstColumn: true,
+ showLastColumn: false,
+ showColumnStripes: true,
+ showRowStripes: false,
+ };
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const doc = { createElement: (_name: string) => ({ setAttribute: vi.fn() }) } as any;
+ const result = t.exportTableStyleInfo(doc);
+ expect(result).toBeDefined();
+ });
+
+ it('should throw in toXML if ref is missing', () => {
+ const t = new Table();
+ expect(() => t.toXML()).toThrow('Needs at least a reference range');
+ });
+
+ test('Table with totals row and custom functions', () => {
+ const table = new Table();
+ table.setTableColumns([
+ { name: 'Col1', totalsRowLabel: 'Sum' },
+ { name: 'Col2', totalsRowFunction: 'sum' },
+ ]);
+ table.totalsRowCount = 1;
+ table.setReferenceRange([1, 1], [2, 5]);
+ expect(table.tableColumns[1].totalsRowFunction).toBe('sum');
+ expect(table.totalsRowCount).toBe(1);
+ });
+
+ test('Table autoFilter and sortState', () => {
+ const table = new Table();
+ table.autoFilter = { ref: 'A1:B2' };
+ table.sortState = { columnSort: true };
+ expect(table.autoFilter.ref).toBe('A1:B2');
+ expect(table.sortState.columnSort).toBe(true);
+ });
+
+ test('Table error handling for invalid reference', () => {
+ const table = new Table();
+ expect(() => table.setReferenceRange([1], [2, 3])).not.toThrow();
+ // The toXML method throws if ref is not set properly
+ table.ref = null;
+ expect(() => table.toXML()).toThrow();
+ });
+
+ test('exportTableStyleInfo with missing styleInfo', () => {
+ const table = new Table();
+ table.styleInfo = {};
+ const doc = { createElement: () => ({ setAttribute: () => {} }) };
+ expect(() => table.exportTableStyleInfo(doc as any)).not.toThrow();
+ });
+ });
+});
diff --git a/packages/excel-builder-vanilla/src/Excel/__tests__/Workbook.spec.ts b/packages/excel-builder-vanilla/src/Excel/__tests__/Workbook.spec.ts
new file mode 100644
index 0000000..ac6a0bf
--- /dev/null
+++ b/packages/excel-builder-vanilla/src/Excel/__tests__/Workbook.spec.ts
@@ -0,0 +1,133 @@
+import { describe, expect, it, vi } from 'vitest';
+
+import { Workbook } from '../Workbook.js';
+import { Paths } from '../Paths.js';
+
+describe('Workbook', () => {
+ it('should initialize with default properties', () => {
+ const wb = new Workbook();
+ expect(wb.worksheets).toEqual([]);
+ expect(wb.tables).toEqual([]);
+ expect(wb.drawings).toEqual([]);
+ expect(typeof wb.styleSheet).toBe('object');
+ expect(typeof wb.sharedStrings).toBe('object');
+ expect(typeof wb.relations).toBe('object');
+ });
+
+ it('should create a worksheet with default name', () => {
+ const wb = new Workbook();
+ const ws = wb.createWorksheet();
+ expect(ws.name).toBe('Sheet 1');
+ });
+
+ it('should add a worksheet and set sharedStrings', () => {
+ const wb = new Workbook();
+ const ws = wb.createWorksheet({ name: 'TestSheet' });
+ wb.addWorksheet(ws);
+ expect(wb.worksheets[0]).toBe(ws);
+ expect(ws.sharedStrings).toBe(wb.sharedStrings);
+ });
+
+ it('should add a table', () => {
+ const wb = new Workbook();
+ const table = { id: 't1' } as any;
+ wb.addTable(table);
+ expect(wb.tables[0]).toBe(table);
+ });
+
+ it('should add drawings', () => {
+ const wb = new Workbook();
+ const drawing = { id: 'd1' } as any;
+ wb.addDrawings(drawing);
+ expect(wb.drawings[0]).toBe(drawing);
+ });
+
+ it('should set print title top and left', () => {
+ const wb = new Workbook();
+ wb.setPrintTitleTop('Sheet1', 5);
+ wb.setPrintTitleLeft('Sheet1', 2);
+ expect(wb.printTitles.Sheet1.top).toBe(5);
+ expect(wb.printTitles.Sheet1.left).toBe('B');
+ });
+
+ it('should add media and return correct meta', () => {
+ const wb = new Workbook();
+ const meta = wb.addMedia('image', 'pic.jpg', 'data');
+ expect(meta.fileName).toBe('pic.jpg');
+ expect(meta.contentType).toBe('image/jpeg');
+ expect(wb.media['pic.jpg']).toBe(meta);
+ });
+
+ it('should serialize header and footer', () => {
+ const wb = new Workbook();
+ expect(wb.serializeHeader()).toContain('