@simplysm/excel 13.0.100 → 14.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/README.md +66 -78
  2. package/dist/excel-cell.d.ts +28 -28
  3. package/dist/excel-cell.d.ts.map +1 -1
  4. package/dist/excel-cell.js +273 -264
  5. package/dist/excel-cell.js.map +1 -6
  6. package/dist/excel-col.d.ts +4 -4
  7. package/dist/excel-col.d.ts.map +1 -1
  8. package/dist/excel-col.js +33 -35
  9. package/dist/excel-col.js.map +1 -6
  10. package/dist/excel-row.d.ts +3 -3
  11. package/dist/excel-row.d.ts.map +1 -1
  12. package/dist/excel-row.js +28 -30
  13. package/dist/excel-row.js.map +1 -6
  14. package/dist/excel-workbook.d.ts +23 -23
  15. package/dist/excel-workbook.d.ts.map +1 -1
  16. package/dist/excel-workbook.js +151 -125
  17. package/dist/excel-workbook.js.map +1 -6
  18. package/dist/excel-worksheet.d.ts +32 -32
  19. package/dist/excel-worksheet.d.ts.map +1 -1
  20. package/dist/excel-worksheet.js +281 -253
  21. package/dist/excel-worksheet.js.map +1 -6
  22. package/dist/excel-wrapper.d.ts +7 -7
  23. package/dist/excel-wrapper.d.ts.map +1 -1
  24. package/dist/excel-wrapper.js +190 -226
  25. package/dist/excel-wrapper.js.map +1 -6
  26. package/dist/index.js +4 -1
  27. package/dist/index.js.map +1 -6
  28. package/dist/types.d.ts +13 -13
  29. package/dist/types.d.ts.map +1 -1
  30. package/dist/types.js +3 -1
  31. package/dist/types.js.map +1 -6
  32. package/dist/utils/excel-utils.d.ts +23 -23
  33. package/dist/utils/excel-utils.d.ts.map +1 -1
  34. package/dist/utils/excel-utils.js +183 -151
  35. package/dist/utils/excel-utils.js.map +1 -6
  36. package/dist/utils/zip-cache.d.ts +6 -6
  37. package/dist/utils/zip-cache.js +78 -60
  38. package/dist/utils/zip-cache.js.map +1 -6
  39. package/dist/xml/excel-xml-content-type.d.ts +2 -2
  40. package/dist/xml/excel-xml-content-type.js +55 -53
  41. package/dist/xml/excel-xml-content-type.js.map +1 -6
  42. package/dist/xml/excel-xml-drawing.d.ts +2 -2
  43. package/dist/xml/excel-xml-drawing.js +74 -73
  44. package/dist/xml/excel-xml-drawing.js.map +1 -6
  45. package/dist/xml/excel-xml-relationship.d.ts +2 -2
  46. package/dist/xml/excel-xml-relationship.js +67 -67
  47. package/dist/xml/excel-xml-relationship.js.map +1 -6
  48. package/dist/xml/excel-xml-shared-string.d.ts +2 -2
  49. package/dist/xml/excel-xml-shared-string.js +57 -55
  50. package/dist/xml/excel-xml-shared-string.js.map +1 -6
  51. package/dist/xml/excel-xml-style.d.ts +2 -2
  52. package/dist/xml/excel-xml-style.js +311 -295
  53. package/dist/xml/excel-xml-style.js.map +1 -6
  54. package/dist/xml/excel-xml-unknown.d.ts +2 -2
  55. package/dist/xml/excel-xml-unknown.js +11 -10
  56. package/dist/xml/excel-xml-unknown.js.map +1 -6
  57. package/dist/xml/excel-xml-workbook.d.ts +3 -2
  58. package/dist/xml/excel-xml-workbook.d.ts.map +1 -1
  59. package/dist/xml/excel-xml-workbook.js +95 -90
  60. package/dist/xml/excel-xml-workbook.js.map +1 -6
  61. package/dist/xml/excel-xml-worksheet.d.ts +6 -6
  62. package/dist/xml/excel-xml-worksheet.js +450 -393
  63. package/dist/xml/excel-xml-worksheet.js.map +1 -6
  64. package/docs/core.md +147 -0
  65. package/docs/types.md +201 -212
  66. package/docs/wrapper.md +32 -62
  67. package/package.json +8 -6
  68. package/src/excel-cell.ts +36 -36
  69. package/src/excel-col.ts +4 -4
  70. package/src/excel-row.ts +3 -3
  71. package/src/excel-workbook.ts +38 -38
  72. package/src/excel-worksheet.ts +69 -51
  73. package/src/excel-wrapper.ts +55 -50
  74. package/src/index.ts +3 -3
  75. package/src/types.ts +17 -17
  76. package/src/utils/excel-utils.ts +47 -41
  77. package/src/utils/zip-cache.ts +6 -6
  78. package/src/xml/excel-xml-content-type.ts +3 -3
  79. package/src/xml/excel-xml-drawing.ts +2 -2
  80. package/src/xml/excel-xml-relationship.ts +3 -3
  81. package/src/xml/excel-xml-shared-string.ts +2 -2
  82. package/src/xml/excel-xml-style.ts +13 -13
  83. package/src/xml/excel-xml-unknown.ts +2 -2
  84. package/src/xml/excel-xml-workbook.ts +14 -6
  85. package/src/xml/excel-xml-worksheet.ts +43 -43
  86. package/docs/core-classes.md +0 -541
  87. package/docs/utilities.md +0 -128
  88. package/tests/excel-cell.spec.ts +0 -393
  89. package/tests/excel-col.spec.ts +0 -81
  90. package/tests/excel-row.spec.ts +0 -61
  91. package/tests/excel-workbook.spec.ts +0 -205
  92. package/tests/excel-worksheet.spec.ts +0 -469
  93. package/tests/excel-wrapper.spec.ts +0 -273
  94. package/tests/fixtures/logo.png +0 -0
  95. package/tests/fixtures//354/264/210/352/270/260/355/231/224.xlsx +0 -0
  96. package/tests/image-insert.spec.ts +0 -190
  97. package/tests/utils/excel-utils.spec.ts +0 -198
@@ -1,273 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { z } from "zod";
3
- import { ExcelWrapper } from "../src/excel-wrapper";
4
- import { DateOnly, DateTime, Time } from "@simplysm/core-common";
5
-
6
- describe("ExcelWrapper", () => {
7
- const testSchema = z.object({
8
- name: z.string().describe("Name"),
9
- age: z.number().describe("Age"),
10
- email: z.string().optional().describe("Email"),
11
- active: z.boolean().default(false).describe("Active"),
12
- });
13
-
14
- describe("write", () => {
15
- it("can convert records to Excel", async () => {
16
- const wrapper = new ExcelWrapper(testSchema);
17
-
18
- const records = [
19
- { name: "John Doe", age: 30, email: "john@test.com", active: true },
20
- { name: "Jane Smith", age: 25 },
21
- ];
22
-
23
- const wb = await wrapper.write("Users", records);
24
- const ws = await wb.getWorksheet("Users");
25
-
26
- // Check headers
27
- expect(await ws.cell(0, 0).getValue()).toBe("Name");
28
- expect(await ws.cell(0, 1).getValue()).toBe("Age");
29
- expect(await ws.cell(0, 2).getValue()).toBe("Email");
30
- expect(await ws.cell(0, 3).getValue()).toBe("Active");
31
-
32
- // Check data
33
- expect(await ws.cell(1, 0).getValue()).toBe("John Doe");
34
- expect(await ws.cell(1, 1).getValue()).toBe(30);
35
- expect(await ws.cell(2, 0).getValue()).toBe("Jane Smith");
36
- expect(await ws.cell(2, 1).getValue()).toBe(25);
37
-
38
- await wb.close();
39
- });
40
-
41
- it("applies yellow background to required fields", async () => {
42
- const wrapper = new ExcelWrapper(testSchema);
43
- const wb = await wrapper.write("Test", [{ name: "Test", age: 20 }]);
44
- const ws = await wb.getWorksheet("Test");
45
-
46
- // Required fields (name, age) have style applied
47
- const nameStyleId = await ws.cell(0, 0).getStyleId();
48
- const ageStyleId = await ws.cell(0, 1).getStyleId();
49
-
50
- expect(nameStyleId).toBeDefined();
51
- expect(ageStyleId).toBeDefined();
52
-
53
- await wb.close();
54
- });
55
- });
56
-
57
- describe("read", () => {
58
- it("can read records from Excel", async () => {
59
- const wrapper = new ExcelWrapper(testSchema);
60
-
61
- // Create Excel first
62
- const records = [
63
- { name: "John Doe", age: 30, email: "john@test.com", active: true },
64
- { name: "Jane Smith", age: 25, active: false },
65
- ];
66
-
67
- const wb = await wrapper.write("Users", records);
68
- const buffer = await wb.toBytes();
69
- await wb.close();
70
-
71
- // Read from Excel
72
- const readRecords = await wrapper.read(buffer, "Users");
73
-
74
- expect(readRecords.length).toBe(2);
75
- expect(readRecords[0].name).toBe("John Doe");
76
- expect(readRecords[0].age).toBe(30);
77
- expect(readRecords[0].email).toBe("john@test.com");
78
- expect(readRecords[0].active).toBe(true);
79
- expect(readRecords[1].name).toBe("Jane Smith");
80
- expect(readRecords[1].age).toBe(25);
81
- });
82
-
83
- it("can specify worksheet by index", async () => {
84
- const wrapper = new ExcelWrapper(testSchema);
85
-
86
- const records = [{ name: "Test", age: 20 }];
87
- const wb = await wrapper.write("Sheet1", records);
88
- const buffer = await wb.toBytes();
89
- await wb.close();
90
-
91
- const readRecords = await wrapper.read(buffer, 0);
92
- expect(readRecords.length).toBe(1);
93
- expect(readRecords[0].name).toBe("Test");
94
- });
95
- });
96
-
97
- describe("Type conversion", () => {
98
- it("applies default values", async () => {
99
- const wrapper = new ExcelWrapper(testSchema);
100
-
101
- // Save without active field
102
- const wb = await wrapper.write("Test", [{ name: "Test", age: 20 }]);
103
- const buffer = await wb.toBytes();
104
- await wb.close();
105
-
106
- const records = await wrapper.read(buffer);
107
- expect(records[0].active).toBe(false); // Default value
108
- });
109
- });
110
-
111
- describe("Date type support", () => {
112
- const dateSchema = z.object({
113
- title: z.string().describe("Title"),
114
- date: z.instanceof(DateOnly).optional().describe("Date"),
115
- });
116
-
117
- it("can read and write DateOnly type", async () => {
118
- const wrapper = new ExcelWrapper(dateSchema);
119
-
120
- const records = [{ title: "Event 1", date: new DateOnly(2024, 6, 15) }, { title: "Event 2" }];
121
-
122
- const wb = await wrapper.write("Events", records);
123
- const buffer = await wb.toBytes();
124
- await wb.close();
125
-
126
- const readRecords = await wrapper.read(buffer, "Events");
127
-
128
- expect(readRecords[0].title).toBe("Event 1");
129
- expect(readRecords[0].date).toBeInstanceOf(DateOnly);
130
- expect(readRecords[0].date!.year).toBe(2024);
131
- expect(readRecords[0].date!.month).toBe(6);
132
- expect(readRecords[0].date!.day).toBe(15);
133
- expect(readRecords[1].date).toBeUndefined();
134
- });
135
-
136
- it("can read and write DateTime type", async () => {
137
- const dateTimeSchema = z.object({
138
- title: z.string().describe("Title"),
139
- datetime: z.instanceof(DateTime).optional().describe("DateTime"),
140
- });
141
-
142
- const wrapper = new ExcelWrapper(dateTimeSchema);
143
-
144
- const records = [{ title: "Meeting", datetime: new DateTime(2024, 6, 15, 14, 30, 0) }];
145
-
146
- const wb = await wrapper.write("Events", records);
147
- const buffer = await wb.toBytes();
148
- await wb.close();
149
-
150
- const readRecords = await wrapper.read(buffer, "Events");
151
-
152
- expect(readRecords[0].datetime).toBeInstanceOf(DateTime);
153
- expect(readRecords[0].datetime!.year).toBe(2024);
154
- expect(readRecords[0].datetime!.month).toBe(6);
155
- expect(readRecords[0].datetime!.day).toBe(15);
156
- expect(readRecords[0].datetime!.hour).toBe(14);
157
- expect(readRecords[0].datetime!.minute).toBe(30);
158
- });
159
-
160
- it("can read and write Time type", async () => {
161
- const timeSchema = z.object({
162
- title: z.string().describe("Title"),
163
- time: z.instanceof(Time).optional().describe("Time"),
164
- });
165
-
166
- const wrapper = new ExcelWrapper(timeSchema);
167
-
168
- const records = [{ title: "Alarm", time: new Time(9, 30, 0) }];
169
-
170
- const wb = await wrapper.write("Events", records);
171
- const buffer = await wb.toBytes();
172
- await wb.close();
173
-
174
- const readRecords = await wrapper.read(buffer, "Events");
175
-
176
- expect(readRecords[0].time).toBeInstanceOf(Time);
177
- expect(readRecords[0].time!.hour).toBe(9);
178
- expect(readRecords[0].time!.minute).toBe(30);
179
- });
180
- });
181
-
182
- describe("Error handling", () => {
183
- it("throws error when reading empty data", async () => {
184
- const wrapper = new ExcelWrapper(testSchema);
185
-
186
- // Create empty Excel with only headers
187
- const wb = await wrapper.write("Empty", []);
188
- const buffer = await wb.toBytes();
189
- await wb.close();
190
-
191
- await expect(wrapper.read(buffer, "Empty")).rejects.toThrow(
192
- "No data found in Excel file",
193
- );
194
- });
195
-
196
- it("throws error when reading with non-existent worksheet name", async () => {
197
- const wrapper = new ExcelWrapper(testSchema);
198
-
199
- const wb = await wrapper.write("Test", [{ name: "Test", age: 20 }]);
200
- const buffer = await wb.toBytes();
201
- await wb.close();
202
-
203
- await expect(wrapper.read(buffer, "NotExist")).rejects.toThrow();
204
- });
205
-
206
- it("throws error with worksheet name and detailed error when schema validation fails", async () => {
207
- const strictSchema = z.object({
208
- name: z.string().min(5).describe("이름"), // At least 5 characters
209
- age: z.number().min(0).max(150).describe("나이"), // Between 0 and 150
210
- });
211
-
212
- // Create Excel with valid data
213
- const wrapper = new ExcelWrapper(strictSchema);
214
- const wb = await wrapper.write("Validation", [{ name: "홍길동홍길동", age: 30 }]);
215
-
216
- // Modify data directly to trigger validation failure
217
- const ws = await wb.getWorksheet("Validation");
218
- await ws.cell(1, 0).setValue("AB"); // Change to less than 5 characters
219
-
220
- const buffer = await wb.toBytes();
221
- await wb.close();
222
-
223
- // Should throw validation error
224
- await expect(wrapper.read(buffer, "Validation")).rejects.toThrow();
225
- });
226
- });
227
-
228
- describe("excludes option", () => {
229
- const fullSchema = z.object({
230
- name: z.string().describe("Name"),
231
- age: z.number().describe("Age"),
232
- email: z.string().optional().describe("Email"),
233
- phone: z.string().optional().describe("Phone"),
234
- });
235
-
236
- it("excludes specified fields from columns on write", async () => {
237
- const wrapper = new ExcelWrapper(fullSchema);
238
-
239
- const records = [{ name: "John Doe", age: 30, email: "john@test.com", phone: "010-1234-5678" }];
240
- const wb = await wrapper.write("Test", records, { excludes: ["email", "phone"] });
241
- const ws = await wb.getWorksheet("Test");
242
-
243
- // Headers: only name and age exist
244
- expect(await ws.cell(0, 0).getValue()).toBe("Name");
245
- expect(await ws.cell(0, 1).getValue()).toBe("Age");
246
- expect(await ws.cell(0, 2).getValue()).toBeUndefined();
247
-
248
- // Check data
249
- expect(await ws.cell(1, 0).getValue()).toBe("John Doe");
250
- expect(await ws.cell(1, 1).getValue()).toBe(30);
251
-
252
- await wb.close();
253
- });
254
-
255
- it("ignores excluded fields on read", async () => {
256
- const wrapper = new ExcelWrapper(fullSchema);
257
-
258
- // Create Excel with all fields
259
- const records = [{ name: "John Doe", age: 30, email: "john@test.com", phone: "010-1234-5678" }];
260
- const wb = await wrapper.write("Test", records);
261
- const buffer = await wb.toBytes();
262
- await wb.close();
263
-
264
- // Read with excludes
265
- const readRecords = await wrapper.read(buffer, "Test", { excludes: ["email", "phone"] });
266
-
267
- expect(readRecords[0].name).toBe("John Doe");
268
- expect(readRecords[0].age).toBe(30);
269
- expect(readRecords[0].email).toBeUndefined();
270
- expect(readRecords[0].phone).toBeUndefined();
271
- });
272
- });
273
- });
Binary file
@@ -1,190 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { ExcelWorkbook } from "../src/excel-workbook";
3
-
4
- // If globalThis.window doesn't exist, it's a Node.js environment
5
- declare const window: unknown;
6
- // Node.js only types (to pass type checking in browser environment as well)
7
- declare const require: (id: string) => unknown;
8
-
9
- /**
10
- * Load PNG file (branching between Node/browser environments)
11
- */
12
- async function loadPngFile(): Promise<Uint8Array> {
13
- const url = new URL("./fixtures/logo.png", import.meta.url);
14
-
15
- // Node environment: use fs
16
- if (typeof window === "undefined") {
17
- const fs = require("fs") as { readFileSync: (path: string) => Uint8Array };
18
- const { fileURLToPath } = require("url") as { fileURLToPath: (url: URL) => string };
19
- const filePath = fileURLToPath(url);
20
- return new Uint8Array(fs.readFileSync(filePath));
21
- }
22
-
23
- // Browser environment: use fetch
24
- const response = await fetch(url);
25
- const arrayBuffer = await response.arrayBuffer();
26
- return new Uint8Array(arrayBuffer);
27
- }
28
-
29
- /**
30
- * Integration test: verify ExcelWorksheet.addImage behavior
31
- */
32
-
33
- describe("ExcelWorksheet.addImage integration", () => {
34
- it("should write media, drawing, drawing rels, worksheet rel and content types", async () => {
35
- const wb = new ExcelWorkbook();
36
- const ws = await wb.addWorksheet("Sheet1");
37
-
38
- const bytes = await loadPngFile();
39
-
40
- // call addImage (the single entry API)
41
- await ws.addImage({
42
- bytes,
43
- ext: "png",
44
- from: { r: 0, c: 0 },
45
- to: { r: 2, c: 2 },
46
- });
47
-
48
- // --- 1) Check media exists and its content
49
- const mediaPath = `xl/media/image1.png`;
50
- const mediaObj = await (ws as any)._zipCache.get(mediaPath);
51
- expect(mediaObj).toBeDefined();
52
- expect(mediaObj instanceof Uint8Array).toBe(true);
53
- expect(mediaObj).toEqual(bytes);
54
-
55
- // --- 2) Check Content Types has media / drawing override
56
- const types = await (ws as any)._zipCache.get("[Content_Types].xml");
57
- expect(types).toBeDefined();
58
- // types is likely an ExcelXmlContentType instance
59
- const overrides = types.data?.Types?.Override ?? [];
60
- expect(overrides.some((o: any) => o.$.PartName === "/xl/media/image1.png")).toBeTruthy();
61
- expect(overrides.some((o: any) => o.$.PartName === "/xl/drawings/drawing1.xml")).toBeTruthy();
62
-
63
- // --- 3) Check drawing xml exists and a:blip r:embed is set as rId
64
- const drawingPath = "xl/drawings/drawing1.xml";
65
- const drawingObj = await (ws as any)._zipCache.get(drawingPath);
66
- expect(drawingObj).toBeDefined();
67
-
68
- // drawingObj is likely an ExcelXmlDrawing object — check internal structure
69
- const wsDr = drawingObj?.data?.wsDr;
70
- expect(wsDr).toBeDefined();
71
- const anchors = wsDr.twoCellAnchor ?? [];
72
- expect(anchors.length).toBeGreaterThan(0);
73
- const pic = anchors[0].pic?.[0];
74
- expect(pic).toBeDefined();
75
- const aBlip = pic?.blipFill?.[0]?.["a:blip"]?.[0];
76
- expect(aBlip).toBeDefined();
77
- const embedVal = aBlip?.$?.["r:embed"];
78
- expect(typeof embedVal === "string" && /^rId\d+$/.test(embedVal)).toBeTruthy();
79
-
80
- // --- 4) Check drawing rels exists and references media target
81
- const drawingRels = await (ws as any)._zipCache.get("xl/drawings/_rels/drawing1.xml.rels");
82
- expect(drawingRels).toBeDefined();
83
- const relsArr = drawingRels?.data?.Relationships?.Relationship ?? [];
84
- expect(relsArr.some((r: any) => r.$.Target === "../media/image1.png")).toBeTruthy();
85
-
86
- // --- 5) Check worksheet rels has drawing rel added and worksheet xml has <drawing r:id="..."/>
87
- const sheetFileName = (ws as any)._targetFileName; // e.g., "sheet1.xml"
88
- const sheetRelsPath = `xl/worksheets/_rels/${sheetFileName}.rels`;
89
- const sheetRels = await (ws as any)._zipCache.get(sheetRelsPath);
90
- expect(sheetRels).toBeDefined();
91
- const sheetRelsArr = sheetRels?.data?.Relationships?.Relationship ?? [];
92
- expect(
93
- sheetRelsArr.some(
94
- (r: any) => r.$.Target != null && r.$.Target.indexOf("/drawings/drawing") !== -1,
95
- ),
96
- ).toBeTruthy();
97
-
98
- const wsXml = await (ws as any)._zipCache.get(`xl/worksheets/${sheetFileName}`);
99
- expect(wsXml).toBeDefined();
100
- expect(Array.isArray(wsXml.data?.worksheet?.drawing)).toBeTruthy();
101
- const drawingElems = wsXml.data.worksheet.drawing;
102
- expect(drawingElems.some((d: any) => d.$ != null && d.$["r:id"] != null)).toBeTruthy();
103
-
104
- // Verify buffer creation
105
- const resultBuffer = await wb.toBytes();
106
- expect(resultBuffer).toBeDefined();
107
- expect(resultBuffer.length).toBeGreaterThan(0);
108
- });
109
-
110
- it("can insert multiple images into the same worksheet", async () => {
111
- const wb = new ExcelWorkbook();
112
- const ws = await wb.addWorksheet("Sheet1");
113
-
114
- const bytes = await loadPngFile();
115
-
116
- // Insert first image
117
- await ws.addImage({
118
- bytes,
119
- ext: "png",
120
- from: { r: 0, c: 0 },
121
- to: { r: 2, c: 2 },
122
- });
123
-
124
- // Insert second image (different location)
125
- await ws.addImage({
126
- bytes,
127
- ext: "png",
128
- from: { r: 3, c: 0 },
129
- to: { r: 5, c: 2 },
130
- });
131
-
132
- // --- 1) Check that two media files were created
133
- const media1 = await (ws as any)._zipCache.get("xl/media/image1.png");
134
- const media2 = await (ws as any)._zipCache.get("xl/media/image2.png");
135
- expect(media1).toBeDefined();
136
- expect(media2).toBeDefined();
137
- expect(media1 instanceof Uint8Array).toBe(true);
138
- expect(media2 instanceof Uint8Array).toBe(true);
139
-
140
- // --- 2) Check that both images are registered in Content Types
141
- const types = await (ws as any)._zipCache.get("[Content_Types].xml");
142
- const overrides = types.data?.Types?.Override ?? [];
143
- expect(overrides.some((o: any) => o.$.PartName === "/xl/media/image1.png")).toBeTruthy();
144
- expect(overrides.some((o: any) => o.$.PartName === "/xl/media/image2.png")).toBeTruthy();
145
-
146
- // --- 3) Check that drawing xml has two anchors
147
- const drawingObj = await (ws as any)._zipCache.get("xl/drawings/drawing1.xml");
148
- const anchors = drawingObj?.data?.wsDr?.twoCellAnchor ?? [];
149
- expect(anchors.length).toBe(2);
150
-
151
- // First image anchor
152
- const pic1 = anchors[0].pic?.[0];
153
- expect(pic1).toBeDefined();
154
- const embed1 = pic1?.blipFill?.[0]?.["a:blip"]?.[0]?.$?.["r:embed"];
155
- expect(embed1).toBeDefined();
156
-
157
- // Second image anchor
158
- const pic2 = anchors[1].pic?.[0];
159
- expect(pic2).toBeDefined();
160
- const embed2 = pic2?.blipFill?.[0]?.["a:blip"]?.[0]?.$?.["r:embed"];
161
- expect(embed2).toBeDefined();
162
-
163
- // Check that rIds are different
164
- expect(embed1).not.toBe(embed2);
165
-
166
- // --- 4) Check that drawing rels have relationships for both images
167
- const drawingRels = await (ws as any)._zipCache.get("xl/drawings/_rels/drawing1.xml.rels");
168
- const relsArr = drawingRels?.data?.Relationships?.Relationship ?? [];
169
- expect(relsArr.some((r: any) => r.$.Target === "../media/image1.png")).toBeTruthy();
170
- expect(relsArr.some((r: any) => r.$.Target === "../media/image2.png")).toBeTruthy();
171
-
172
- // Verify buffer creation
173
- const resultBuffer = await wb.toBytes();
174
- expect(resultBuffer).toBeDefined();
175
- expect(resultBuffer.length).toBeGreaterThan(0);
176
- });
177
-
178
- it("throws error for unsupported file extensions", async () => {
179
- const wb = new ExcelWorkbook();
180
- const ws = await wb.addWorksheet("Sheet1");
181
-
182
- await expect(
183
- ws.addImage({
184
- bytes: new Uint8Array([0, 1, 2, 3]),
185
- ext: "xyz123",
186
- from: { r: 0, c: 0 },
187
- }),
188
- ).rejects.toThrow("Cannot determine MIME type");
189
- });
190
- });
@@ -1,198 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { ExcelUtils } from "../../src/utils/excel-utils";
3
-
4
- describe("ExcelUtils", () => {
5
- describe("stringifyColAddr / parseColAddr", () => {
6
- it("converts 26 and above to AA, AB, etc.", () => {
7
- expect(ExcelUtils.stringifyColAddr(26)).toBe("AA");
8
- expect(ExcelUtils.stringifyColAddr(27)).toBe("AB");
9
- expect(ExcelUtils.stringifyColAddr(51)).toBe("AZ");
10
- expect(ExcelUtils.stringifyColAddr(52)).toBe("BA");
11
- });
12
-
13
- it("handles large column indices", () => {
14
- // 702 = AAA (26^2 + 26 = 702)
15
- expect(ExcelUtils.stringifyColAddr(702)).toBe("AAA");
16
- });
17
-
18
- it("handles Excel maximum column index (XFD, 16383)", () => {
19
- // Excel maximum column is XFD (index 16383, 0-based)
20
- expect(ExcelUtils.stringifyColAddr(16383)).toBe("XFD");
21
- expect(ExcelUtils.parseColAddr("XFD")).toBe(16383);
22
- });
23
-
24
- it("round-trip: stringify → parse returns original value", () => {
25
- for (let i = 0; i < 100; i++) {
26
- const stringified = ExcelUtils.stringifyColAddr(i);
27
- const parsed = ExcelUtils.parseColAddr(stringified);
28
- expect(parsed).toBe(i);
29
- }
30
- });
31
-
32
- it("throws error on negative column index input", () => {
33
- expect(() => ExcelUtils.stringifyColAddr(-1)).toThrow();
34
- expect(() => ExcelUtils.stringifyColAddr(-100)).toThrow();
35
- });
36
- });
37
-
38
- describe("stringifyRowAddr / parseRowAddr", () => {
39
- it("converts 0-based index to 1-based string", () => {
40
- expect(ExcelUtils.stringifyRowAddr(0)).toBe("1");
41
- expect(ExcelUtils.stringifyRowAddr(9)).toBe("10");
42
- expect(ExcelUtils.stringifyRowAddr(99)).toBe("100");
43
- });
44
-
45
- it("parses 1-based string to 0-based index", () => {
46
- expect(ExcelUtils.parseRowAddr("A1")).toBe(0);
47
- expect(ExcelUtils.parseRowAddr("B10")).toBe(9);
48
- expect(ExcelUtils.parseRowAddr("AA100")).toBe(99);
49
- });
50
- });
51
-
52
- describe("stringifyAddr / parseCellAddr", () => {
53
- it("correctly converts cell address", () => {
54
- expect(ExcelUtils.stringifyAddr({ r: 0, c: 0 })).toBe("A1");
55
- expect(ExcelUtils.stringifyAddr({ r: 9, c: 1 })).toBe("B10");
56
- expect(ExcelUtils.stringifyAddr({ r: 99, c: 26 })).toBe("AA100");
57
- });
58
-
59
- it("correctly parses cell address", () => {
60
- expect(ExcelUtils.parseCellAddr("A1")).toEqual({ r: 0, c: 0 });
61
- expect(ExcelUtils.parseCellAddr("B10")).toEqual({ r: 9, c: 1 });
62
- expect(ExcelUtils.parseCellAddr("AA100")).toEqual({ r: 99, c: 26 });
63
- });
64
- });
65
-
66
- describe("parseRangeAddr / stringifyRangeAddr", () => {
67
- it("parses single cell range", () => {
68
- const range = ExcelUtils.parseRangeAddr("A1");
69
- expect(range.s).toEqual({ r: 0, c: 0 });
70
- expect(range.e).toEqual({ r: 0, c: 0 });
71
- });
72
-
73
- it("parses multi-cell range", () => {
74
- const range = ExcelUtils.parseRangeAddr("A1:C3");
75
- expect(range.s).toEqual({ r: 0, c: 0 });
76
- expect(range.e).toEqual({ r: 2, c: 2 });
77
- });
78
-
79
- it("converts single cell range to string", () => {
80
- const addr = ExcelUtils.stringifyRangeAddr({
81
- s: { r: 0, c: 0 },
82
- e: { r: 0, c: 0 },
83
- });
84
- expect(addr).toBe("A1");
85
- });
86
-
87
- it("converts multi-cell range to string", () => {
88
- const addr = ExcelUtils.stringifyRangeAddr({
89
- s: { r: 0, c: 0 },
90
- e: { r: 2, c: 2 },
91
- });
92
- expect(addr).toBe("A1:C3");
93
- });
94
- });
95
-
96
- describe("convertTimeTickToNumber / convertNumberToTimeTick", () => {
97
- it("correctly converts 1970-01-01", () => {
98
- const date = new Date(Date.UTC(1970, 0, 1, 0, 0, 0));
99
- const tick = date.getTime();
100
- const excelNum = ExcelUtils.convertTimeTickToNumber(tick);
101
- // 1970-01-01 is 25569 days in Excel's date system
102
- expect(excelNum).toBeCloseTo(25569, 0);
103
- });
104
-
105
- it("round-trip: tick → number → tick returns original value", () => {
106
- const originalDate = new Date(Date.UTC(2024, 5, 15, 14, 30, 45));
107
- const tick = originalDate.getTime();
108
- const excelNum = ExcelUtils.convertTimeTickToNumber(tick);
109
- const recoveredTick = ExcelUtils.convertNumberToTimeTick(excelNum);
110
- // May not match exactly to milliseconds, so compare at second level
111
- expect(Math.floor(recoveredTick / 1000)).toBe(Math.floor(tick / 1000));
112
- });
113
- });
114
-
115
- describe("convertNumFmtCodeToName", () => {
116
- it("recognizes General as number", () => {
117
- expect(ExcelUtils.convertNumFmtCodeToName("General")).toBe("number");
118
- });
119
-
120
- it("recognizes date pattern as DateOnly", () => {
121
- expect(ExcelUtils.convertNumFmtCodeToName("yyyy-mm-dd")).toBe("DateOnly");
122
- expect(ExcelUtils.convertNumFmtCodeToName("yy/mm/dd")).toBe("DateOnly");
123
- expect(ExcelUtils.convertNumFmtCodeToName("dd-mmm-yyyy")).toBe("DateOnly");
124
- });
125
-
126
- // NOTE: In the current implementation, 'mm' is used in both date (month) and time (minute)
127
- // Regex: hasDate = /yy|dd|mm/i, hasTime = /hh|ss/i
128
- // Therefore "hh:mm:ss" has hasDate(mm)=true, hasTime(hh,ss)=true → DateTime
129
- // "h:mm" has hasDate(mm)=true, hasTime=false (h≠hh) → DateOnly
130
- it("handles time pattern (note mm ambiguity)", () => {
131
- // "hh:mm:ss": hasDate(mm)=true, hasTime(hh,ss)=true → DateTime
132
- expect(ExcelUtils.convertNumFmtCodeToName("hh:mm:ss")).toBe("DateTime");
133
- // "h:mm": hasDate(mm)=true, hasTime=false (h≠hh) → DateOnly
134
- expect(ExcelUtils.convertNumFmtCodeToName("h:mm")).toBe("DateOnly");
135
- // Pure time format (only ss, no mm)
136
- expect(ExcelUtils.convertNumFmtCodeToName("[h]:ss")).toBe("Time");
137
- });
138
-
139
- it("recognizes date+time pattern as DateTime", () => {
140
- expect(ExcelUtils.convertNumFmtCodeToName("yyyy-mm-dd hh:mm:ss")).toBe("DateTime");
141
- // "yy/mm/dd h:mm" detects only date (h:mm has no hh and no ss)
142
- expect(ExcelUtils.convertNumFmtCodeToName("yy/mm/dd h:mm")).toBe("DateOnly");
143
- // Clear DateTime pattern
144
- expect(ExcelUtils.convertNumFmtCodeToName("yyyy/mm/dd hh:mm:ss")).toBe("DateTime");
145
- });
146
-
147
- it("recognizes number pattern as number", () => {
148
- expect(ExcelUtils.convertNumFmtCodeToName("#,##0")).toBe("number");
149
- expect(ExcelUtils.convertNumFmtCodeToName("0.00")).toBe("number");
150
- expect(ExcelUtils.convertNumFmtCodeToName("#,0")).toBe("number");
151
- });
152
-
153
- it("throws error on unknown format code input", () => {
154
- expect(() => ExcelUtils.convertNumFmtCodeToName("unknown-format-xyz")).toThrow();
155
- });
156
- });
157
-
158
- describe("convertNumFmtIdToName", () => {
159
- it("recognizes common number formats", () => {
160
- expect(ExcelUtils.convertNumFmtIdToName(0)).toBe("number");
161
- expect(ExcelUtils.convertNumFmtIdToName(1)).toBe("number");
162
- expect(ExcelUtils.convertNumFmtIdToName(2)).toBe("number");
163
- });
164
-
165
- it("recognizes date format", () => {
166
- expect(ExcelUtils.convertNumFmtIdToName(14)).toBe("DateOnly");
167
- expect(ExcelUtils.convertNumFmtIdToName(15)).toBe("DateOnly");
168
- });
169
-
170
- it("recognizes date+time format", () => {
171
- expect(ExcelUtils.convertNumFmtIdToName(22)).toBe("DateTime");
172
- });
173
-
174
- it("recognizes time format", () => {
175
- expect(ExcelUtils.convertNumFmtIdToName(18)).toBe("Time");
176
- expect(ExcelUtils.convertNumFmtIdToName(19)).toBe("Time");
177
- });
178
-
179
- it("recognizes text format", () => {
180
- expect(ExcelUtils.convertNumFmtIdToName(49)).toBe("string");
181
- });
182
-
183
- it("throws error on unknown numFmtId input", () => {
184
- expect(() => ExcelUtils.convertNumFmtIdToName(23)).toThrow();
185
- expect(() => ExcelUtils.convertNumFmtIdToName(100)).toThrow();
186
- });
187
- });
188
-
189
- describe("convertNumFmtNameToId", () => {
190
- it("converts format name to ID", () => {
191
- expect(ExcelUtils.convertNumFmtNameToId("number")).toBe(0);
192
- expect(ExcelUtils.convertNumFmtNameToId("DateOnly")).toBe(14);
193
- expect(ExcelUtils.convertNumFmtNameToId("DateTime")).toBe(22);
194
- expect(ExcelUtils.convertNumFmtNameToId("Time")).toBe(18);
195
- expect(ExcelUtils.convertNumFmtNameToId("string")).toBe(49);
196
- });
197
- });
198
- });