react-native-nitro-xlsx 0.2.6 → 0.2.7

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 (57) hide show
  1. package/README.md +20 -8
  2. package/cpp/HybridNitroXlsx.cpp +25 -1
  3. package/cpp/HybridXlsxCellFormat.cpp +46 -146
  4. package/cpp/HybridXlsxCellFormat.hpp +9 -41
  5. package/cpp/HybridXlsxConditionalFormat.cpp +4 -1
  6. package/cpp/HybridXlsxConditionalFormats.cpp +5 -1
  7. package/cpp/HybridXlsxWorkbook.cpp +38 -8
  8. package/cpp/HybridXlsxWorkbook.hpp +10 -1
  9. package/cpp/HybridXlsxWorksheet.cpp +422 -141
  10. package/cpp/HybridXlsxWorksheet.hpp +4 -1
  11. package/cpp/XlsxCacheDir.cpp +25 -2
  12. package/cpp/XlsxCacheDir.hpp +3 -1
  13. package/cpp/XlsxCellStyle.hpp +360 -0
  14. package/cpp/XlsxJson.cpp +29 -8
  15. package/package.json +1 -1
  16. package/scripts/sync-openxlsx-headers.sh +34 -0
  17. package/src/__mocks__/react-native-nitro-modules.ts +523 -20
  18. package/src/__tests__/index.test.ts +124 -16
  19. package/src/index.ts +13 -13
  20. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/IZipArchive.hpp +361 -0
  21. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/OpenXLSX-Exports.hpp +12 -0
  22. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLCell.hpp +367 -0
  23. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLCellIterator.hpp +252 -0
  24. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLCellRange.hpp +223 -0
  25. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLCellReference.hpp +317 -0
  26. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLCellValue.hpp +793 -0
  27. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLColor.hpp +232 -0
  28. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLColumn.hpp +159 -0
  29. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLCommandQuery.hpp +228 -0
  30. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLComments.hpp +270 -0
  31. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLConstants.hpp +18 -0
  32. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLContentTypes.hpp +264 -0
  33. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLDateTime.hpp +175 -0
  34. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLDocument.hpp +505 -0
  35. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLDrawing.hpp +452 -0
  36. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLException.hpp +161 -0
  37. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLFormula.hpp +375 -0
  38. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLIterator.hpp +40 -0
  39. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLMergeCells.hpp +216 -0
  40. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLProperties.hpp +348 -0
  41. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLRelationships.hpp +348 -0
  42. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLRow.hpp +721 -0
  43. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLRowData.hpp +488 -0
  44. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLSharedStrings.hpp +204 -0
  45. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLSheet.hpp +1640 -0
  46. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLStyles.hpp +2390 -0
  47. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLTables.hpp +132 -0
  48. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLWorkbook.hpp +404 -0
  49. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLXmlData.hpp +283 -0
  50. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLXmlFile.hpp +194 -0
  51. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLXmlParser.hpp +282 -0
  52. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLXmlParserForwardDeclarations.hpp +85 -0
  53. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/XLZipArchive.hpp +232 -0
  54. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/detail/LibZip.hpp +447 -0
  55. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/detail/OpenXLSXFileSystemTools.hpp +207 -0
  56. package/thirdparty/OpenXLSX/OpenXLSX/headers/OpenXLSX/detail/Zippy.hpp +1375 -0
  57. package/thirdparty/README.md +7 -3
@@ -1,29 +1,532 @@
1
- export type HybridObject<T> = object;
1
+ // Manual mock for `react-native-nitro-modules` (mapped via jest.config.js moduleNameMapper).
2
+ // `NitroModules.createHybridObject` hands out typed fakes whose methods are `jest.fn`s with
3
+ // realistic behavior, so tests can assert on forwarded arguments (`mock.calls`) and return shapes.
4
+ import type {
5
+ CellType,
6
+ NitroXlsx,
7
+ XlsxCellFormat,
8
+ XlsxConditionalFormat,
9
+ XlsxConditionalFormats,
10
+ XlsxWorkbook,
11
+ XlsxWorksheet,
12
+ } from '../specs/NitroXlsx.nitro'
2
13
 
3
- export interface ArrayBuffer {
4
- data(): Uint8Array;
5
- size(): number;
14
+ export type HybridObject<T> = { __platforms?: T }
15
+
16
+ export type AnyMap = Record<string, unknown>
17
+
18
+ type RowJSON = Record<string, string | number | boolean | null>
19
+
20
+ interface CellState {
21
+ value: string | number | boolean | null
22
+ type: CellType
23
+ formula?: string
24
+ }
25
+
26
+ interface SheetState {
27
+ name: string
28
+ cells: Map<string, CellState>
29
+ comments: Map<string, string>
30
+ commentAuthors: string[]
31
+ }
32
+
33
+ interface RuleState {
34
+ type: number
35
+ dxfId: number
36
+ formulas: string[]
37
+ operator: number
38
+ text: string
39
+ priority: number
40
+ stopIfTrue: boolean
41
+ timePeriod: number
42
+ rank: number
43
+ stdDev: number
44
+ aboveAverage: boolean
45
+ percent: boolean
46
+ bottom: boolean
47
+ equalAverage: boolean
48
+ }
49
+
50
+ const cellKey = (row: number, col: number): string => `${row}:${col}`
51
+
52
+ function rowsFromCells(cells: Map<string, CellState>): ReturnType<XlsxWorksheet['toJSON']> {
53
+ const rows = new Map<number, RowJSON>()
54
+ for (const [key, cell] of cells) {
55
+ const sep = key.indexOf(':')
56
+ const row = Number(key.slice(0, sep))
57
+ const col = key.slice(sep + 1)
58
+ const entry = rows.get(row) ?? {}
59
+ entry[col] = cell.value
60
+ rows.set(row, entry)
61
+ }
62
+ return [...rows.entries()]
63
+ .sort((a, b) => a[0] - b[0])
64
+ .map(([, values]) => values)
65
+ }
66
+
67
+ function createFakeCellFormat(): XlsxCellFormat {
68
+ const state = {
69
+ fontName: 'Calibri',
70
+ fontSize: 11,
71
+ fontColor: 0,
72
+ bgColor: 0xffffff,
73
+ bold: false,
74
+ italic: false,
75
+ numFormat: 'General',
76
+ }
77
+ const cellFormat: XlsxCellFormat = {
78
+ name: 'CellFormat',
79
+ toString: jest.fn((): string => '[HybridObject CellFormat]'),
80
+ equals: jest.fn((other: unknown): boolean => other === cellFormat),
81
+ dispose: jest.fn((): void => undefined),
82
+
83
+ setFontName: jest.fn((fontName: string): void => {
84
+ state.fontName = fontName
85
+ }),
86
+ setFontSize: jest.fn((fontSize: number): void => {
87
+ state.fontSize = fontSize
88
+ }),
89
+ setFontColor: jest.fn((fontColor: number): void => {
90
+ state.fontColor = fontColor
91
+ }),
92
+ setFontScript: jest.fn((): void => undefined),
93
+ setBold: jest.fn((): void => {
94
+ state.bold = true
95
+ }),
96
+ setItalic: jest.fn((): void => {
97
+ state.italic = true
98
+ }),
99
+ setUnderline: jest.fn((): void => undefined),
100
+ setStrikeout: jest.fn((): void => undefined),
101
+
102
+ setAlign: jest.fn((): void => undefined),
103
+ setVerticalAlign: jest.fn((): void => undefined),
104
+ setTextWrap: jest.fn((): void => undefined),
105
+ setRotation: jest.fn((): void => undefined),
106
+ setIndent: jest.fn((): void => undefined),
107
+ setShrink: jest.fn((): void => undefined),
108
+
109
+ setNumFormat: jest.fn((format: string): void => {
110
+ state.numFormat = format
111
+ }),
112
+ setNumFormatIndex: jest.fn((index: number): void => {
113
+ state.numFormat = `#${index}`
114
+ }),
115
+
116
+ setBgColor: jest.fn((bgColor: number): void => {
117
+ state.bgColor = bgColor
118
+ }),
119
+ setFgColor: jest.fn((): void => undefined),
120
+ setPattern: jest.fn((): void => undefined),
121
+
122
+ setBorder: jest.fn((): void => undefined),
123
+ setBorderColor: jest.fn((): void => undefined),
124
+ setBottom: jest.fn((): void => undefined),
125
+ setTop: jest.fn((): void => undefined),
126
+ setLeft: jest.fn((): void => undefined),
127
+ setRight: jest.fn((): void => undefined),
128
+ setBottomColor: jest.fn((): void => undefined),
129
+ setTopColor: jest.fn((): void => undefined),
130
+ setLeftColor: jest.fn((): void => undefined),
131
+ setRightColor: jest.fn((): void => undefined),
132
+ setDiagonal: jest.fn((): void => undefined),
133
+ setDiagonalColor: jest.fn((): void => undefined),
134
+
135
+ setLocked: jest.fn((): void => undefined),
136
+ setUnlocked: jest.fn((): void => undefined),
137
+
138
+ setHyperlink: jest.fn((): void => undefined),
139
+ setFontOnly: jest.fn((): void => undefined),
140
+
141
+ getFontName: jest.fn((): string => state.fontName),
142
+ getFontSize: jest.fn((): number => state.fontSize),
143
+ getFontColor: jest.fn((): number => state.fontColor),
144
+ getIsBold: jest.fn((): boolean => state.bold),
145
+ getIsItalic: jest.fn((): boolean => state.italic),
146
+ getNumFormat: jest.fn((): string => state.numFormat),
147
+ getBgColor: jest.fn((): number => state.bgColor),
148
+ }
149
+ return cellFormat
150
+ }
151
+
152
+ function createFakeConditionalFormat(initialSqref: string): XlsxConditionalFormat {
153
+ let sqref = initialSqref
154
+ const rules: RuleState[] = []
155
+ const ruleAt = (index: number): RuleState => {
156
+ const rule = rules[index]
157
+ if (!rule) throw new Error(`getRule: INDEX_OUT_OF_RANGE: no rule at index ${index}`)
158
+ return rule
159
+ }
160
+ const conditionalFormat: XlsxConditionalFormat = {
161
+ name: 'ConditionalFormat',
162
+ toString: jest.fn((): string => '[HybridObject ConditionalFormat]'),
163
+ equals: jest.fn((other: unknown): boolean => other === conditionalFormat),
164
+ dispose: jest.fn((): void => undefined),
165
+
166
+ getSqref: jest.fn((): string => sqref),
167
+ setSqref: jest.fn((next: string): void => {
168
+ sqref = next
169
+ }),
170
+
171
+ getRuleCount: jest.fn((): number => rules.length),
172
+ createRule: jest.fn((): number => {
173
+ rules.push({
174
+ type: 0,
175
+ dxfId: -1,
176
+ formulas: ['', '', ''],
177
+ operator: 0,
178
+ text: '',
179
+ priority: rules.length,
180
+ stopIfTrue: false,
181
+ timePeriod: 0,
182
+ rank: 0,
183
+ stdDev: 0,
184
+ aboveAverage: false,
185
+ percent: false,
186
+ bottom: false,
187
+ equalAverage: false,
188
+ })
189
+ return rules.length - 1
190
+ }),
191
+ deleteRule: jest.fn((index: number): boolean => {
192
+ if (index < 0 || index >= rules.length) return false
193
+ rules.splice(index, 1)
194
+ return true
195
+ }),
196
+
197
+ getRuleType: jest.fn((index: number): number => ruleAt(index).type),
198
+ setRuleType: jest.fn((index: number, type: number): void => {
199
+ ruleAt(index).type = type
200
+ }),
201
+ getRuleDxfId: jest.fn((index: number): number => ruleAt(index).dxfId),
202
+ setRuleDxfId: jest.fn((index: number, dxfId: number): void => {
203
+ ruleAt(index).dxfId = dxfId
204
+ }),
205
+
206
+ getRuleFormula: jest.fn((index: number, formulaIndex: number): string => ruleAt(index).formulas[formulaIndex] ?? ''),
207
+ setRuleFormula: jest.fn((index: number, formulaIndex: number, formula: string): void => {
208
+ if (formulaIndex >= 0 && formulaIndex < 3) ruleAt(index).formulas[formulaIndex] = formula
209
+ }),
210
+
211
+ getRuleOperator: jest.fn((index: number): number => ruleAt(index).operator),
212
+ setRuleOperator: jest.fn((index: number, op: number): void => {
213
+ ruleAt(index).operator = op
214
+ }),
215
+ getRuleText: jest.fn((index: number): string => ruleAt(index).text),
216
+ setRuleText: jest.fn((index: number, text: string): void => {
217
+ ruleAt(index).text = text
218
+ }),
219
+ getRulePriority: jest.fn((index: number): number => ruleAt(index).priority),
220
+ setRulePriority: jest.fn((index: number, priority: number): void => {
221
+ ruleAt(index).priority = priority
222
+ }),
223
+ getRuleStopIfTrue: jest.fn((index: number): boolean => ruleAt(index).stopIfTrue),
224
+ setRuleStopIfTrue: jest.fn((index: number, stop: boolean): void => {
225
+ ruleAt(index).stopIfTrue = stop
226
+ }),
227
+ getRuleTimePeriod: jest.fn((index: number): number => ruleAt(index).timePeriod),
228
+ setRuleTimePeriod: jest.fn((index: number, period: number): void => {
229
+ ruleAt(index).timePeriod = period
230
+ }),
231
+ getRuleRank: jest.fn((index: number): number => ruleAt(index).rank),
232
+ setRuleRank: jest.fn((index: number, rank: number): void => {
233
+ ruleAt(index).rank = rank
234
+ }),
235
+ getRuleStdDev: jest.fn((index: number): number => ruleAt(index).stdDev),
236
+ setRuleStdDev: jest.fn((index: number, stdDev: number): void => {
237
+ ruleAt(index).stdDev = stdDev
238
+ }),
239
+ getRuleAboveAverage: jest.fn((index: number): boolean => ruleAt(index).aboveAverage),
240
+ setRuleAboveAverage: jest.fn((index: number, set: boolean): void => {
241
+ ruleAt(index).aboveAverage = set
242
+ }),
243
+ getRulePercent: jest.fn((index: number): boolean => ruleAt(index).percent),
244
+ setRulePercent: jest.fn((index: number, set: boolean): void => {
245
+ ruleAt(index).percent = set
246
+ }),
247
+ getRuleBottom: jest.fn((index: number): boolean => ruleAt(index).bottom),
248
+ setRuleBottom: jest.fn((index: number, set: boolean): void => {
249
+ ruleAt(index).bottom = set
250
+ }),
251
+ getRuleEqualAverage: jest.fn((index: number): boolean => ruleAt(index).equalAverage),
252
+ setRuleEqualAverage: jest.fn((index: number, set: boolean): void => {
253
+ ruleAt(index).equalAverage = set
254
+ }),
255
+
256
+ summary: jest.fn((): string => `ConditionalFormat(${sqref}, rules=${rules.length})`),
257
+ }
258
+ return conditionalFormat
259
+ }
260
+
261
+ function createFakeConditionalFormats(): XlsxConditionalFormats {
262
+ const formats: XlsxConditionalFormat[] = []
263
+ const conditionalFormats: XlsxConditionalFormats = {
264
+ name: 'ConditionalFormats',
265
+ toString: jest.fn((): string => '[HybridObject ConditionalFormats]'),
266
+ equals: jest.fn((other: unknown): boolean => other === conditionalFormats),
267
+ dispose: jest.fn((): void => undefined),
268
+
269
+ getCount: jest.fn((): number => formats.length),
270
+ create: jest.fn((sqref: string): XlsxConditionalFormat => {
271
+ const format = createFakeConditionalFormat(sqref)
272
+ formats.push(format)
273
+ return format
274
+ }),
275
+ getByIndex: jest.fn((index: number): XlsxConditionalFormat => {
276
+ const format = formats[index]
277
+ if (!format) throw new Error(`getByIndex: INDEX_OUT_OF_RANGE: no conditional format at index ${index}`)
278
+ return format
279
+ }),
280
+ summary: jest.fn((): string => `ConditionalFormats(count=${formats.length})`),
281
+ }
282
+ return conditionalFormats
283
+ }
284
+
285
+ function createFakeWorksheet(state: SheetState): XlsxWorksheet {
286
+ const conditionalFormats = createFakeConditionalFormats()
287
+ const cellAt = (row: number, col: number): CellState | undefined => state.cells.get(cellKey(row, col))
288
+ const writeCell = (row: number, col: number, cell: CellState): void => {
289
+ state.cells.set(cellKey(row, col), cell)
290
+ }
291
+
292
+ const worksheet: XlsxWorksheet = {
293
+ get name(): string {
294
+ return state.name
295
+ },
296
+ toString: jest.fn((): string => `[HybridObject Worksheet ${state.name}]`),
297
+ equals: jest.fn((other: unknown): boolean => other === worksheet),
298
+ dispose: jest.fn((): void => undefined),
299
+
300
+ writeString: jest.fn((row: number, col: number, value: string): void => {
301
+ writeCell(row, col, { value, type: 'string' })
302
+ }),
303
+ writeNumber: jest.fn((row: number, col: number, value: number): void => {
304
+ writeCell(row, col, { value, type: 'number' })
305
+ }),
306
+ writeBoolean: jest.fn((row: number, col: number, value: boolean): void => {
307
+ writeCell(row, col, { value, type: 'boolean' })
308
+ }),
309
+ writeBlank: jest.fn((row: number, col: number): void => {
310
+ writeCell(row, col, { value: null, type: 'blank' })
311
+ }),
312
+
313
+ writeFormula: jest.fn((row: number, col: number, formula: string): void => {
314
+ writeCell(row, col, { value: null, type: 'formula', formula })
315
+ }),
316
+ writeFormulaNum: jest.fn((row: number, col: number, formula: string, number: number): void => {
317
+ writeCell(row, col, { value: number, type: 'formula', formula })
318
+ }),
319
+ writeFormulaString: jest.fn((row: number, col: number, formula: string, str: string): void => {
320
+ writeCell(row, col, { value: str, type: 'formula', formula })
321
+ }),
322
+ writeFormulaBoolean: jest.fn((row: number, col: number, formula: string, boolVal: boolean): void => {
323
+ writeCell(row, col, { value: boolVal, type: 'formula', formula })
324
+ }),
325
+ writeArrayFormula: jest.fn((firstRow: number, firstCol: number, _lastRow: number, _lastCol: number, formula: string): void => {
326
+ writeCell(firstRow, firstCol, { value: null, type: 'formula', formula })
327
+ }),
328
+
329
+ writeDatetime: jest.fn((row: number, col: number, datetime: number): void => {
330
+ writeCell(row, col, { value: datetime, type: 'date' })
331
+ }),
332
+ writeURL: jest.fn((row: number, col: number, url: string): void => {
333
+ writeCell(row, col, { value: url, type: 'string' })
334
+ }),
335
+
336
+ setColumn: jest.fn((): void => undefined),
337
+ setRow: jest.fn((): void => undefined),
338
+ deleteRow: jest.fn((): boolean => true),
339
+ deleteColumn: jest.fn((): boolean => true),
340
+
341
+ mergeRange: jest.fn((): void => undefined),
342
+ mergeRangeNum: jest.fn((): void => undefined),
343
+ unmergeCells: jest.fn((): void => undefined),
344
+
345
+ hide: jest.fn((): void => undefined),
346
+ activate: jest.fn((): void => undefined),
347
+
348
+ protect: jest.fn((): void => undefined),
349
+ protectSheet: jest.fn((): void => undefined),
350
+ protectObjects: jest.fn((): void => undefined),
351
+ protectScenarios: jest.fn((): void => undefined),
352
+
353
+ setPassword: jest.fn((): void => undefined),
354
+ setPasswordHash: jest.fn((): void => undefined),
355
+ clearPassword: jest.fn((): void => undefined),
356
+ clearSheetProtection: jest.fn((): void => undefined),
357
+
358
+ allowInsertColumns: jest.fn((): void => undefined),
359
+ allowInsertRows: jest.fn((): void => undefined),
360
+ allowDeleteColumns: jest.fn((): void => undefined),
361
+ allowDeleteRows: jest.fn((): void => undefined),
362
+ allowSelectLockedCells: jest.fn((): void => undefined),
363
+ allowSelectUnlockedCells: jest.fn((): void => undefined),
364
+
365
+ denyInsertColumns: jest.fn((): void => undefined),
366
+ denyInsertRows: jest.fn((): void => undefined),
367
+ denyDeleteColumns: jest.fn((): void => undefined),
368
+ denyDeleteRows: jest.fn((): void => undefined),
369
+ denySelectLockedCells: jest.fn((): void => undefined),
370
+ denySelectUnlockedCells: jest.fn((): void => undefined),
371
+
372
+ sheetProtected: jest.fn((): boolean => false),
373
+ objectsProtected: jest.fn((): boolean => false),
374
+ scenariosProtected: jest.fn((): boolean => false),
375
+ insertColumnsAllowed: jest.fn((): boolean => true),
376
+ insertRowsAllowed: jest.fn((): boolean => true),
377
+ deleteColumnsAllowed: jest.fn((): boolean => true),
378
+ deleteRowsAllowed: jest.fn((): boolean => true),
379
+ selectLockedCellsAllowed: jest.fn((): boolean => true),
380
+ selectUnlockedCellsAllowed: jest.fn((): boolean => true),
381
+ passwordIsSet: jest.fn((): boolean => false),
382
+ passwordHash: jest.fn((): string => ''),
383
+ sheetProtectionSummary: jest.fn((): string => ''),
384
+
385
+ setColumnHidden: jest.fn((): void => undefined),
386
+ setRowHidden: jest.fn((): void => undefined),
387
+
388
+ setComment: jest.fn((row: number, col: number, text: string): void => {
389
+ state.comments.set(cellKey(row, col), text)
390
+ }),
391
+ getComment: jest.fn((row: number, col: number): string => state.comments.get(cellKey(row, col)) ?? ''),
392
+ hasComment: jest.fn((row: number, col: number): boolean => state.comments.has(cellKey(row, col))),
393
+ deleteComment: jest.fn((row: number, col: number): boolean => state.comments.delete(cellKey(row, col))),
394
+ getCommentCount: jest.fn((): number => state.comments.size),
395
+ addCommentAuthor: jest.fn((author: string): number => {
396
+ state.commentAuthors.push(author)
397
+ return state.commentAuthors.length - 1
398
+ }),
399
+ getCommentAuthor: jest.fn((authorId: number): string => state.commentAuthors[authorId] ?? ''),
400
+
401
+ getConditionalFormats: jest.fn((): XlsxConditionalFormats => conditionalFormats),
402
+
403
+ getCellValue: jest.fn((row: number, col: number): string | number | boolean | null => cellAt(row, col)?.value ?? null),
404
+ getCellString: jest.fn((row: number, col: number): string => {
405
+ const cell = cellAt(row, col)
406
+ return cell && cell.value !== null ? String(cell.value) : ''
407
+ }),
408
+ getCellRawValue: jest.fn((row: number, col: number): string => {
409
+ const cell = cellAt(row, col)
410
+ return cell && cell.value !== null ? String(cell.value) : ''
411
+ }),
412
+ getCellType: jest.fn((row: number, col: number): CellType => cellAt(row, col)?.type ?? 'empty'),
413
+ getCellFormat: jest.fn((): XlsxCellFormat => createFakeCellFormat()),
414
+ hasFormula: jest.fn((row: number, col: number): boolean => cellAt(row, col)?.formula != null),
415
+ formula: jest.fn((row: number, col: number): string => cellAt(row, col)?.formula ?? ''),
416
+ findCell: jest.fn((row: number, col: number): boolean => state.cells.has(cellKey(row, col))),
417
+
418
+ getRowCount: jest.fn((): number => {
419
+ const rows = new Set<number>()
420
+ for (const key of state.cells.keys()) rows.add(Number(key.slice(0, key.indexOf(':'))))
421
+ return rows.size
422
+ }),
423
+ getColumnCount: jest.fn((): number => {
424
+ const cols = new Set<number>()
425
+ for (const key of state.cells.keys()) cols.add(Number(key.slice(key.indexOf(':') + 1)))
426
+ return cols.size
427
+ }),
428
+ getLastRow: jest.fn((): number => {
429
+ let last = 0
430
+ for (const key of state.cells.keys()) {
431
+ const row = Number(key.slice(0, key.indexOf(':')))
432
+ if (row > last) last = row
433
+ }
434
+ return last
435
+ }),
436
+ getLastColumn: jest.fn((): number => {
437
+ let last = 0
438
+ for (const key of state.cells.keys()) {
439
+ const col = Number(key.slice(key.indexOf(':') + 1))
440
+ if (col > last) last = col
441
+ }
442
+ return last
443
+ }),
444
+ getName: jest.fn((): string => state.name),
445
+
446
+ toJSON: jest.fn((): ReturnType<XlsxWorksheet['toJSON']> => rowsFromCells(state.cells)),
447
+ fromJSON: jest.fn((): void => undefined),
448
+ }
449
+ return worksheet
6
450
  }
7
451
 
8
- export interface Promise<T> {
9
- then<R>(onFulfilled: (value: T) => R): Promise<R>;
10
- catch<R>(onRejected: (reason: any) => R): Promise<R>;
11
- resolve(value: T): Promise<T>;
12
- reject(reason: any): Promise<T>;
13
- create(): Promise<T>;
452
+ function createFakeWorkbook(): XlsxWorkbook {
453
+ interface SheetEntry {
454
+ state: SheetState
455
+ worksheet: XlsxWorksheet
456
+ }
457
+ const sheets: SheetEntry[] = []
458
+
459
+ const addSheet = (name: string, cells?: Map<string, CellState>): SheetEntry => {
460
+ const state: SheetState = {
461
+ name,
462
+ cells: cells ?? new Map(),
463
+ comments: new Map(),
464
+ commentAuthors: [],
465
+ }
466
+ const entry: SheetEntry = { state, worksheet: createFakeWorksheet(state) }
467
+ sheets.push(entry)
468
+ return entry
469
+ }
470
+ const requireSheet = (methodName: string, name: string): SheetEntry => {
471
+ const entry = sheets.find((sheet) => sheet.state.name === name)
472
+ if (!entry) throw new Error(`${methodName}: SHEET_NOT_FOUND: worksheet not found: ${name}`)
473
+ return entry
474
+ }
475
+
476
+ const workbook: XlsxWorkbook = {
477
+ name: 'Workbook',
478
+ toString: jest.fn((): string => '[HybridObject Workbook]'),
479
+ equals: jest.fn((other: unknown): boolean => other === workbook),
480
+ dispose: jest.fn((): void => undefined),
481
+
482
+ addWorksheet: jest.fn((name?: string): XlsxWorksheet => addSheet(name ?? `Sheet${sheets.length + 1}`).worksheet),
483
+ getWorksheet: jest.fn((index: number): XlsxWorksheet => {
484
+ const entry = sheets[index]
485
+ if (!entry) throw new Error(`getWorksheet: INDEX_OUT_OF_RANGE: no worksheet at index ${index}`)
486
+ return entry.worksheet
487
+ }),
488
+ getWorksheetByName: jest.fn((name: string): XlsxWorksheet => requireSheet('getWorksheetByName', name).worksheet),
489
+ getOrAddWorksheet: jest.fn((name: string): XlsxWorksheet => {
490
+ const existing = sheets.find((sheet) => sheet.state.name === name)
491
+ return (existing ?? addSheet(name)).worksheet
492
+ }),
493
+ getWorksheetCount: jest.fn((): number => sheets.length),
494
+ deleteSheet: jest.fn((name: string): void => {
495
+ const entry = requireSheet('deleteSheet', name)
496
+ sheets.splice(sheets.indexOf(entry), 1)
497
+ }),
498
+ updateSheetName: jest.fn((oldName: string, newName: string): void => {
499
+ requireSheet('updateSheetName', oldName).state.name = newName
500
+ }),
501
+ clone: jest.fn((existingName: string, newName: string): XlsxWorksheet =>
502
+ addSheet(newName, new Map(requireSheet('clone', existingName).state.cells)).worksheet
503
+ ),
504
+ addCellFormat: jest.fn((): XlsxCellFormat => createFakeCellFormat()),
505
+ getBuffer: jest.fn((): Promise<ArrayBuffer> => Promise.resolve(new ArrayBuffer(8))),
506
+ toJSON: jest.fn((): ReturnType<XlsxWorkbook['toJSON']> =>
507
+ Object.fromEntries(sheets.map((sheet) => [sheet.state.name, rowsFromCells(sheet.state.cells)]))
508
+ ),
509
+ }
510
+ return workbook
14
511
  }
15
512
 
16
- export namespace margelo {
17
- export namespace nitro {
18
- export class HybridObject<T> {}
513
+ function createFakeNitroXlsx(hybridName: string): NitroXlsx {
514
+ const nitroXlsx: NitroXlsx = {
515
+ name: hybridName,
516
+ toString: jest.fn((): string => `[HybridObject ${hybridName}]`),
517
+ equals: jest.fn((other: unknown): boolean => other === nitroXlsx),
518
+ dispose: jest.fn((): void => undefined),
519
+
520
+ createWorkbook: jest.fn((): XlsxWorkbook => createFakeWorkbook()),
521
+ openWorkbook: jest.fn((): XlsxWorkbook => createFakeWorkbook()),
522
+ openWorkbookFromBuffer: jest.fn((): XlsxWorkbook => createFakeWorkbook()),
523
+ fromJSON: jest.fn((): XlsxWorkbook => createFakeWorkbook()),
19
524
  }
525
+ return nitroXlsx
20
526
  }
21
527
 
22
528
  export const NitroModules = {
23
- createHybridObject: <T>(name: string) => ({
24
- createWorkbook: jest.fn(),
25
- openWorkbook: jest.fn(),
26
- openWorkbookFromBuffer: jest.fn(),
27
- fromJSON: jest.fn(),
28
- }),
29
- };
529
+ createHybridObject<T>(name: string): T {
530
+ return createFakeNitroXlsx(name) as unknown as T
531
+ },
532
+ }