@elisra-devops/docgen-skins 0.22.0 → 0.24.0

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 (55) hide show
  1. package/.github/workflows/ci.yml +18 -9
  2. package/bin/index.d.ts +8 -1
  3. package/bin/index.js +86 -37
  4. package/bin/index.js.map +1 -1
  5. package/bin/models/json/JSONRun.js.map +1 -1
  6. package/bin/models/json/file/JSONFile.js.map +1 -1
  7. package/bin/models/json/paragraph/JSONParagraph.js.map +1 -1
  8. package/bin/models/json/paragraph/JSONRichTextParagraph.js.map +1 -1
  9. package/bin/models/json/paragraph/JSONTestSuiteHeaderParagraph.js.map +1 -1
  10. package/bin/models/json/table/JSONTable.d.ts +2 -1
  11. package/bin/models/json/table/JSONTable.js +20 -6
  12. package/bin/models/json/table/JSONTable.js.map +1 -1
  13. package/bin/models/json/table/JSONTableCell.js.map +1 -1
  14. package/bin/models/json/table/JSONTableRow.js.map +1 -1
  15. package/bin/services/logger.js +17 -7
  16. package/bin/services/logger.js.map +1 -1
  17. package/bin/test/JSONFile.test.js +48 -0
  18. package/bin/test/JSONFile.test.js.map +1 -0
  19. package/bin/test/JSONRun.test.d.ts +1 -0
  20. package/bin/test/JSONRun.test.js +93 -0
  21. package/bin/test/JSONRun.test.js.map +1 -0
  22. package/bin/test/JSONTable.test.d.ts +1 -0
  23. package/bin/test/JSONTable.test.js +79 -0
  24. package/bin/test/JSONTable.test.js.map +1 -0
  25. package/bin/test/JSONTableCell.test.d.ts +1 -0
  26. package/bin/test/JSONTableCell.test.js +129 -0
  27. package/bin/test/JSONTableCell.test.js.map +1 -0
  28. package/bin/test/JSONTableRow.test.d.ts +1 -0
  29. package/bin/test/JSONTableRow.test.js +40 -0
  30. package/bin/test/JSONTableRow.test.js.map +1 -0
  31. package/bin/test/JSONTestSuiteHeaderParagraph.test.d.ts +1 -0
  32. package/bin/test/JSONTestSuiteHeaderParagraph.test.js +39 -0
  33. package/bin/test/JSONTestSuiteHeaderParagraph.test.js.map +1 -0
  34. package/bin/test/SkinsEdgeCases.test.d.ts +1 -0
  35. package/bin/test/SkinsEdgeCases.test.js +399 -0
  36. package/bin/test/SkinsEdgeCases.test.js.map +1 -0
  37. package/bin/test/integration/skins.integration.test.d.ts +1 -0
  38. package/bin/test/integration/skins.integration.test.js +492 -0
  39. package/bin/test/integration/skins.integration.test.js.map +1 -0
  40. package/package.json +5 -5
  41. package/src/index.ts +94 -41
  42. package/src/models/json/table/JSONTable.ts +19 -3
  43. package/src/test/JSONFile.test.ts +59 -0
  44. package/src/test/JSONRun.test.ts +118 -0
  45. package/src/test/JSONTable.test.ts +126 -0
  46. package/src/test/JSONTableCell.test.ts +150 -0
  47. package/src/test/JSONTableRow.test.ts +42 -0
  48. package/src/test/JSONTestSuiteHeaderParagraph.test.ts +40 -0
  49. package/src/test/SkinsEdgeCases.test.ts +530 -0
  50. package/src/test/integration/skins.integration.test.ts +608 -0
  51. package/tsconfig.json +2 -2
  52. package/bin/test/skins.test.js +0 -126
  53. package/bin/test/skins.test.js.map +0 -1
  54. package/src/test/skins.test.ts +0 -189
  55. /package/bin/test/{skins.test.d.ts → JSONFile.test.d.ts} +0 -0
@@ -0,0 +1,530 @@
1
+ import Skins from '../index';
2
+ import logger from '../services/logger';
3
+ import JSONRichTextParagraph from '../models/json/paragraph/JSONRichTextParagraph';
4
+
5
+ jest.mock('../services/logger', () => ({
6
+ __esModule: true,
7
+ default: {
8
+ debug: jest.fn(),
9
+ info: jest.fn(),
10
+ warn: jest.fn(),
11
+ error: jest.fn(),
12
+ silly: jest.fn(),
13
+ },
14
+ }));
15
+
16
+ const getMockLogger = () =>
17
+ logger as unknown as {
18
+ debug: jest.Mock;
19
+ info: jest.Mock;
20
+ warn: jest.Mock;
21
+ error: jest.Mock;
22
+ silly: jest.Mock;
23
+ };
24
+
25
+ const baseStyles = {
26
+ isBold: false,
27
+ IsItalic: false,
28
+ IsUnderline: false,
29
+ Size: 10,
30
+ Uri: null as string | null,
31
+ Font: 'Arial',
32
+ InsertLineBreak: false,
33
+ InsertSpace: true,
34
+ };
35
+
36
+ const baseHeaderStyles = {
37
+ isBold: true,
38
+ IsItalic: false,
39
+ IsUnderline: false,
40
+ Size: 12,
41
+ Uri: null as string | null,
42
+ Font: 'Arial',
43
+ InsertLineBreak: false,
44
+ InsertSpace: false,
45
+ };
46
+
47
+ describe('Skins – edge cases and public API', () => {
48
+ beforeEach(() => {
49
+ jest.clearAllMocks();
50
+ });
51
+
52
+ test('addNewContentToDocumentSkin throws and logs for unknown skinType', async () => {
53
+ const skins = new Skins('json', 'c://template.dotx');
54
+
55
+ await expect(
56
+ skins.addNewContentToDocumentSkin(
57
+ 'section-1',
58
+ 'unknown-type',
59
+ [],
60
+ baseHeaderStyles as any,
61
+ baseStyles as any,
62
+ 0
63
+ )
64
+ ).rejects.toThrow('Unknown skinType : unknown-type - not appended to document skin');
65
+
66
+ expect(getMockLogger().error).toHaveBeenCalledWith(
67
+ expect.stringContaining('Fatal error happened when generate skin for : section-1')
68
+ );
69
+ });
70
+
71
+ test('generateQueryBasedTable throws and logs for invalid skin format', () => {
72
+ const skins = new Skins('xml', 'c://template.dotx');
73
+
74
+ const queryData = [
75
+ {
76
+ url: '',
77
+ level: 0,
78
+ Source: 1,
79
+ fields: [
80
+ { name: 'ID', value: 1, width: '' },
81
+ { name: 'Title', value: 'Item 1', width: '' },
82
+ ],
83
+ },
84
+ ];
85
+
86
+ expect(() =>
87
+ skins.generateQueryBasedTable(queryData as any, baseHeaderStyles as any, baseStyles as any, 1)
88
+ ).toThrow('Invalid skin format xml');
89
+
90
+ expect(getMockLogger().error).toHaveBeenCalledWith(
91
+ expect.stringContaining('Error occurred in generateQueryBasedTable: Invalid skin format xml')
92
+ );
93
+ });
94
+
95
+ test('generateInstallationSkin returns empty array and warns when no data', () => {
96
+ const skins = new Skins('json', 'c://template.dotx');
97
+
98
+ const result = skins.generateInstallationSkin([], baseStyles as any);
99
+
100
+ expect(result).toEqual([]);
101
+ expect(getMockLogger().warn).toHaveBeenCalledWith('No installation data provided to generate skin');
102
+ });
103
+
104
+ test('generateInstallationSkin converts attachments to JSONFile skins', () => {
105
+ const skins = new Skins('json', 'c://template.dotx');
106
+
107
+ const installationData = [
108
+ {
109
+ attachment: {
110
+ attachmentLink: 'http://server/file.docx',
111
+ attachmentFileName: 'file.docx',
112
+ attachmentType: 'asEmbedded',
113
+ includeAttachmentContent: true,
114
+ },
115
+ },
116
+ {
117
+ attachment: {
118
+ attachmentLink: 'http://server/image.png',
119
+ attachmentFileName: 'image.png',
120
+ attachmentType: 'asEmbedded',
121
+ includeAttachmentContent: true,
122
+ },
123
+ },
124
+ ];
125
+
126
+ const result = skins.generateInstallationSkin(installationData as any, baseStyles as any);
127
+
128
+ expect(result).toHaveLength(2);
129
+
130
+ expect(result[0]).toMatchObject({
131
+ type: 'File',
132
+ path: 'http://server/file.docx',
133
+ name: 'file',
134
+ isLinkedFile: true,
135
+ includeAttachmentContent: false,
136
+ });
137
+
138
+ expect(result[1]).toMatchObject({
139
+ type: 'Picture',
140
+ path: 'http://server/image.png',
141
+ name: 'image',
142
+ });
143
+ });
144
+
145
+ test('generateTestReporter returns [] when no data', () => {
146
+ const skins = new Skins('json', 'c://template.dotx');
147
+
148
+ const result = skins.generateTestReporter([], 'My Test Plan');
149
+ expect(result).toEqual([]);
150
+ });
151
+
152
+ test('generateTestReporter wraps data and plan name', () => {
153
+ const skins = new Skins('json', 'c://template.dotx');
154
+ const data = [{ id: 1, name: 'Suite 1' }];
155
+
156
+ const result = skins.generateTestReporter(data as any, 'Plan-1');
157
+
158
+ expect(result).toHaveLength(1);
159
+ expect(result[0]).toEqual({
160
+ type: 'testReporter',
161
+ testPlanName: 'Plan-1',
162
+ testSuites: data,
163
+ });
164
+ });
165
+
166
+ test('generateTraceTable builds header paragraph and table with grouped header', () => {
167
+ const skins = new Skins('json', 'c://template.dotx');
168
+
169
+ const traceData = {
170
+ title: {
171
+ fields: [{ name: 'Title', value: 'Trace Report' }],
172
+ level: 2,
173
+ },
174
+ adoptedData: [
175
+ {
176
+ url: '',
177
+ level: 0,
178
+ Source: 1,
179
+ fields: [
180
+ { name: 'ID', value: 1 },
181
+ { name: 'Name', value: 'Item 1' },
182
+ ],
183
+ },
184
+ ],
185
+ errorMessage: null,
186
+ groupedHeader: {
187
+ leftLabel: 'Requirements',
188
+ rightLabel: 'Tests',
189
+ },
190
+ };
191
+
192
+ const result = skins.generateTraceTable(traceData as any, baseHeaderStyles as any, baseStyles as any, 1);
193
+
194
+ expect(result).toHaveLength(2);
195
+ expect(result[0]).toMatchObject({ type: 'paragraph' });
196
+ expect(result[1]).toMatchObject({ type: 'table' });
197
+
198
+ const table: any = result[1];
199
+ expect(Array.isArray(table.Rows)).toBe(true);
200
+ const groupedRow = table.Rows[0];
201
+ expect(Array.isArray(groupedRow.Cells)).toBe(true);
202
+ expect(groupedRow.Cells[0].gridSpan).toBeGreaterThan(0);
203
+ });
204
+
205
+ test('generateTraceTable falls back to error paragraph when no adoptedData but errorMessage exists', () => {
206
+ const skins = new Skins('json', 'c://template.dotx');
207
+
208
+ const traceData = {
209
+ title: null,
210
+ adoptedData: [],
211
+ errorMessage: 'Something went wrong',
212
+ groupedHeader: undefined,
213
+ };
214
+
215
+ const result = skins.generateTraceTable(traceData as any, baseHeaderStyles as any, baseStyles as any, 0);
216
+
217
+ expect(result).toHaveLength(1);
218
+ expect(result[0]).toMatchObject({ type: 'paragraph' });
219
+ });
220
+
221
+ test('generateQueryBasedParagraphs handles Description rich text and normal fields', () => {
222
+ const skins = new Skins('json', 'c://template.dotx');
223
+
224
+ const data = [
225
+ {
226
+ url: '',
227
+ level: 1,
228
+ Source: 42,
229
+ fields: [
230
+ { name: 'Description: ', value: '<p>Hello</p>' },
231
+ { name: 'Other', value: 'Value' },
232
+ ],
233
+ },
234
+ ];
235
+
236
+ const result = skins.generateQueryBasedParagraphs(data as any, baseStyles as any, 1);
237
+
238
+ expect(result.length).toBe(2);
239
+ expect(Array.isArray(result[0])).toBe(true);
240
+ expect((result[1] as any).type).toBe('paragraph');
241
+ });
242
+
243
+ test('generateQueryBasedParagraphs throws and logs for invalid skin format', () => {
244
+ const skins = new Skins('xml', 'c://template.dotx');
245
+
246
+ const data: any[] = [];
247
+
248
+ expect(() => skins.generateQueryBasedParagraphs(data as any, baseStyles as any, 0)).toThrow(
249
+ 'Invalid skin format xml'
250
+ );
251
+
252
+ expect(getMockLogger().error).toHaveBeenCalledWith(
253
+ expect.stringContaining('Error occurred in generateQueryBasedParagraphs: Invalid skin format xml')
254
+ );
255
+ });
256
+
257
+ test('validateAndAppendContentControl creates and appends content controls', async () => {
258
+ const skins = new Skins('json', 'c://template.dotx');
259
+
260
+ const firstSkin = [{ type: 'paragraph', runs: [] }];
261
+ const secondSkin = [{ type: 'paragraph', runs: [{ text: 'second' }] }];
262
+
263
+ const firstResult = await skins.validateAndAppendContentControl('section-1', firstSkin);
264
+ expect(firstResult).toBe(true);
265
+
266
+ let documentSkin = skins.getDocumentSkin();
267
+ expect(documentSkin.contentControls).toHaveLength(1);
268
+ expect(documentSkin.contentControls[0].title).toBe('section-1');
269
+ expect(documentSkin.contentControls[0].wordObjects).toEqual(firstSkin);
270
+
271
+ const secondResult = await skins.validateAndAppendContentControl('section-1', secondSkin);
272
+ expect(secondResult).toBe(true);
273
+
274
+ documentSkin = skins.getDocumentSkin();
275
+ expect(documentSkin.contentControls).toHaveLength(1);
276
+ expect(documentSkin.contentControls[0].wordObjects).toEqual([...firstSkin, ...secondSkin]);
277
+ });
278
+
279
+ test('validateAndAppendContentControl logs error and returns undefined on failure', async () => {
280
+ const skins = new Skins('json', 'c://template.dotx');
281
+
282
+ const consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
283
+
284
+ (skins as any).documentSkin = null;
285
+
286
+ const result = await skins.validateAndAppendContentControl('bad-section', []);
287
+
288
+ expect(result).toBeUndefined();
289
+ expect(getMockLogger().error).toHaveBeenCalledWith('Error validating and appending to content control');
290
+
291
+ consoleSpy.mockRestore();
292
+ });
293
+
294
+ test('generateTestBasedSkin throws and logs when suites data is missing', () => {
295
+ const skins = new Skins('json', 'c://template.dotx');
296
+
297
+ expect(() =>
298
+ skins.generateTestBasedSkin(null as any, baseHeaderStyles as any, baseStyles as any)
299
+ ).toThrow('No data provided to generate test based skin');
300
+
301
+ expect(getMockLogger().error).toHaveBeenCalledWith(
302
+ expect.stringContaining(
303
+ 'One or more errors occurred in generateTestBasedSkin: No data provided to generate test based skin'
304
+ )
305
+ );
306
+ });
307
+
308
+ test('generateTestBasedSkin throws and logs for invalid skin format', () => {
309
+ const skins = new Skins('xml', 'c://template.dotx');
310
+
311
+ const suites: any[] = [];
312
+
313
+ expect(() =>
314
+ skins.generateTestBasedSkin(suites as any, baseHeaderStyles as any, baseStyles as any)
315
+ ).toThrow('Invalid skin format xml');
316
+
317
+ expect(getMockLogger().error).toHaveBeenCalledWith(
318
+ expect.stringContaining('One or more errors occurred in generateTestBasedSkin: Invalid skin format xml')
319
+ );
320
+ });
321
+
322
+ test('generateTestBasedSkin returns empty array when suites array is empty', () => {
323
+ const skins = new Skins('json', 'c://template.dotx');
324
+
325
+ const result = skins.generateTestBasedSkin([], baseHeaderStyles as any, baseStyles as any);
326
+
327
+ expect(result).toEqual([]);
328
+ expect(getMockLogger().error).not.toHaveBeenCalled();
329
+ });
330
+
331
+ test('generateTestBasedSkin inserts page break between single-test suites', () => {
332
+ const skins = new Skins('json', 'c://template.dotx');
333
+
334
+ const suites = [
335
+ {
336
+ suiteSkinData: {
337
+ fields: [
338
+ { name: 'Title', value: 'Suite 1' },
339
+ { name: 'ID', value: 1 },
340
+ ],
341
+ level: 1,
342
+ },
343
+ testCases: [
344
+ {
345
+ id: 101,
346
+ testCaseHeaderSkinData: {
347
+ fields: [
348
+ { name: 'Title', value: 'TC-1' },
349
+ { name: 'ID', value: 101 },
350
+ { name: 'Description: ', value: '<p>desc 1</p>' },
351
+ ],
352
+ level: 1,
353
+ },
354
+ testCaseRequirements: [],
355
+ testCaseLinkedMom: [],
356
+ testCaseBugs: [],
357
+ testCaseStepsSkinData: [],
358
+ testCaseAttachments: [],
359
+ },
360
+ ],
361
+ },
362
+ {
363
+ suiteSkinData: {
364
+ fields: [
365
+ { name: 'Title', value: 'Suite 2' },
366
+ { name: 'ID', value: 2 },
367
+ ],
368
+ level: 1,
369
+ },
370
+ testCases: [
371
+ {
372
+ id: 201,
373
+ testCaseHeaderSkinData: {
374
+ fields: [
375
+ { name: 'Title', value: 'TC-2' },
376
+ { name: 'ID', value: 201 },
377
+ { name: 'Description: ', value: '<p>desc 2</p>' },
378
+ ],
379
+ level: 1,
380
+ },
381
+ testCaseRequirements: [],
382
+ testCaseLinkedMom: [],
383
+ testCaseBugs: [],
384
+ testCaseStepsSkinData: [],
385
+ testCaseAttachments: [],
386
+ },
387
+ ],
388
+ },
389
+ ];
390
+
391
+ const result = skins.generateTestBasedSkin(suites as any, baseHeaderStyles as any, baseStyles as any, 0);
392
+
393
+ const pageBreakParagraphs = result.filter(
394
+ (x: any) => x.type === 'paragraph' && x.runs?.some((r: any) => r.InsertPageBreak)
395
+ );
396
+
397
+ expect(pageBreakParagraphs.length).toBe(1);
398
+ });
399
+
400
+ test('generateTestBasedSkin in html mode logs info and returns undefined', () => {
401
+ const skins = new Skins('html', 'c://template.dotx');
402
+
403
+ const suites = [
404
+ {
405
+ suiteSkinData: {
406
+ fields: [
407
+ { name: 'Title', value: 'Suite' },
408
+ { name: 'ID', value: 1 },
409
+ ],
410
+ level: 1,
411
+ },
412
+ testCases: [],
413
+ },
414
+ ];
415
+
416
+ const result = skins.generateTestBasedSkin(suites as any, baseHeaderStyles as any, baseStyles as any, 0);
417
+
418
+ expect(result).toBeUndefined();
419
+ expect(getMockLogger().info).toHaveBeenCalledWith('Generating html test data!');
420
+ });
421
+
422
+ test('AppendAttachmentContent logs error and records aggregated error when attachment build fails', () => {
423
+ const skins = new Skins('json', 'c://template.dotx');
424
+
425
+ const aggregatedErrors: string[] = [];
426
+ const testSkin: any[] = [];
427
+
428
+ const data = [
429
+ {
430
+ type: 'SubHeader',
431
+ field: undefined,
432
+ },
433
+ ];
434
+
435
+ (skins as any).AppendAttachmentContent(data, testSkin, aggregatedErrors);
436
+
437
+ expect(aggregatedErrors.length).toBeGreaterThan(0);
438
+ expect(getMockLogger().error).toHaveBeenCalledWith(
439
+ expect.stringContaining('Error occurred when building attachments')
440
+ );
441
+ });
442
+
443
+ test('generateQueryBasedTable in html mode logs info and returns empty array', () => {
444
+ const skins = new Skins('html', 'c://template.dotx');
445
+
446
+ const queryData = [
447
+ {
448
+ url: '',
449
+ level: 0,
450
+ Source: 1,
451
+ fields: [
452
+ { name: 'ID', value: 1 },
453
+ { name: 'Title', value: 'Item 1' },
454
+ ],
455
+ },
456
+ ];
457
+
458
+ const result = skins.generateQueryBasedTable(
459
+ queryData as any,
460
+ baseHeaderStyles as any,
461
+ baseStyles as any,
462
+ 1
463
+ );
464
+
465
+ expect(result).toEqual([]);
466
+ expect(getMockLogger().info).toHaveBeenCalledWith('Generating html table!');
467
+ });
468
+
469
+ test('generateCoverPageParagraphs in html mode logs info and returns empty array', () => {
470
+ const skins = new Skins('html', 'c://template.dotx');
471
+
472
+ const data = [
473
+ {
474
+ url: '',
475
+ level: 0,
476
+ Source: 1,
477
+ fields: [{ name: 'Title', value: 'Cover' }],
478
+ },
479
+ ];
480
+
481
+ const result = skins.generateCoverPageParagraphs(data as any, baseStyles as any, 0);
482
+
483
+ expect(result).toEqual([]);
484
+ expect(getMockLogger().info).toHaveBeenCalledWith('Generating html cover-page paragraphs!');
485
+ });
486
+
487
+ test('generateQueryBasedParagraphs in html mode logs info and returns empty array', () => {
488
+ const skins = new Skins('html', 'c://template.dotx');
489
+
490
+ const data = [
491
+ {
492
+ url: '',
493
+ level: 0,
494
+ Source: 1,
495
+ fields: [{ name: 'Title', value: 'Paragraph' }],
496
+ },
497
+ ];
498
+
499
+ const result = skins.generateQueryBasedParagraphs(data as any, baseStyles as any, 0);
500
+
501
+ expect(result).toEqual([]);
502
+ expect(getMockLogger().info).toHaveBeenCalledWith('Generating html paragraphs!');
503
+ });
504
+
505
+ test('generateTraceTable in html mode logs info and returns empty array', () => {
506
+ const skins = new Skins('html', 'c://template.dotx');
507
+
508
+ const traceData = {
509
+ title: null,
510
+ adoptedData: [],
511
+ errorMessage: null,
512
+ groupedHeader: undefined,
513
+ };
514
+
515
+ const result = skins.generateTraceTable(traceData as any, baseHeaderStyles as any, baseStyles as any, 0);
516
+
517
+ expect(result).toEqual([]);
518
+ expect(getMockLogger().info).toHaveBeenCalledWith('Generating html table!');
519
+ });
520
+
521
+ test('JSONRichTextParagraph throws for invalid field', () => {
522
+ expect(() => new JSONRichTextParagraph(null as any, baseStyles as any, 1, 0)).toThrow(
523
+ 'Invalid field or missing value'
524
+ );
525
+
526
+ expect(
527
+ () => new JSONRichTextParagraph({ name: 'Description: ', value: '' } as any, baseStyles as any, 1, 0)
528
+ ).toThrow('Invalid field or missing value');
529
+ });
530
+ });