@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.
- package/.github/workflows/ci.yml +18 -9
- package/bin/index.d.ts +8 -1
- package/bin/index.js +86 -37
- package/bin/index.js.map +1 -1
- package/bin/models/json/JSONRun.js.map +1 -1
- package/bin/models/json/file/JSONFile.js.map +1 -1
- package/bin/models/json/paragraph/JSONParagraph.js.map +1 -1
- package/bin/models/json/paragraph/JSONRichTextParagraph.js.map +1 -1
- package/bin/models/json/paragraph/JSONTestSuiteHeaderParagraph.js.map +1 -1
- package/bin/models/json/table/JSONTable.d.ts +2 -1
- package/bin/models/json/table/JSONTable.js +20 -6
- package/bin/models/json/table/JSONTable.js.map +1 -1
- package/bin/models/json/table/JSONTableCell.js.map +1 -1
- package/bin/models/json/table/JSONTableRow.js.map +1 -1
- package/bin/services/logger.js +17 -7
- package/bin/services/logger.js.map +1 -1
- package/bin/test/JSONFile.test.js +48 -0
- package/bin/test/JSONFile.test.js.map +1 -0
- package/bin/test/JSONRun.test.d.ts +1 -0
- package/bin/test/JSONRun.test.js +93 -0
- package/bin/test/JSONRun.test.js.map +1 -0
- package/bin/test/JSONTable.test.d.ts +1 -0
- package/bin/test/JSONTable.test.js +79 -0
- package/bin/test/JSONTable.test.js.map +1 -0
- package/bin/test/JSONTableCell.test.d.ts +1 -0
- package/bin/test/JSONTableCell.test.js +129 -0
- package/bin/test/JSONTableCell.test.js.map +1 -0
- package/bin/test/JSONTableRow.test.d.ts +1 -0
- package/bin/test/JSONTableRow.test.js +40 -0
- package/bin/test/JSONTableRow.test.js.map +1 -0
- package/bin/test/JSONTestSuiteHeaderParagraph.test.d.ts +1 -0
- package/bin/test/JSONTestSuiteHeaderParagraph.test.js +39 -0
- package/bin/test/JSONTestSuiteHeaderParagraph.test.js.map +1 -0
- package/bin/test/SkinsEdgeCases.test.d.ts +1 -0
- package/bin/test/SkinsEdgeCases.test.js +399 -0
- package/bin/test/SkinsEdgeCases.test.js.map +1 -0
- package/bin/test/integration/skins.integration.test.d.ts +1 -0
- package/bin/test/integration/skins.integration.test.js +492 -0
- package/bin/test/integration/skins.integration.test.js.map +1 -0
- package/package.json +5 -5
- package/src/index.ts +94 -41
- package/src/models/json/table/JSONTable.ts +19 -3
- package/src/test/JSONFile.test.ts +59 -0
- package/src/test/JSONRun.test.ts +118 -0
- package/src/test/JSONTable.test.ts +126 -0
- package/src/test/JSONTableCell.test.ts +150 -0
- package/src/test/JSONTableRow.test.ts +42 -0
- package/src/test/JSONTestSuiteHeaderParagraph.test.ts +40 -0
- package/src/test/SkinsEdgeCases.test.ts +530 -0
- package/src/test/integration/skins.integration.test.ts +608 -0
- package/tsconfig.json +2 -2
- package/bin/test/skins.test.js +0 -126
- package/bin/test/skins.test.js.map +0 -1
- package/src/test/skins.test.ts +0 -189
- /package/bin/test/{skins.test.d.ts → JSONFile.test.d.ts} +0 -0
package/src/index.ts
CHANGED
|
@@ -20,7 +20,11 @@ export default class Skins {
|
|
|
20
20
|
SKIN_TYPE_TRACE = 'trace-table';
|
|
21
21
|
SKIN_TYPE_TABLE_STR = 'str-table';
|
|
22
22
|
SKIN_TYPE_PARAGRAPH = 'paragraph';
|
|
23
|
+
SKIN_TYPE_COVER_PAGE = 'cover-page';
|
|
23
24
|
SKIN_TYPE_TEST_PLAN = 'test-plan';
|
|
25
|
+
SKIN_TYPE_TEST_STD = 'test-std';
|
|
26
|
+
SKIN_TYPE_TEST_STP = 'test-stp';
|
|
27
|
+
SKIN_TYPE_TEST_STR = 'test-str';
|
|
24
28
|
SKIN_TYPE_SYSTEM_OVERVIEW = 'system-overview';
|
|
25
29
|
SKIN_TYPE_INSTALLATION = 'installation';
|
|
26
30
|
SKIN_TYPE_TEST_REPORTER = 'test-reporter';
|
|
@@ -37,6 +41,16 @@ export default class Skins {
|
|
|
37
41
|
this.skinFormat = skinFormat;
|
|
38
42
|
}
|
|
39
43
|
|
|
44
|
+
private resolveSkinTypeAlias(skinType: string) {
|
|
45
|
+
if (skinType === this.SKIN_TYPE_TEST_STD || skinType === this.SKIN_TYPE_TEST_STP) {
|
|
46
|
+
return this.SKIN_TYPE_TEST_PLAN;
|
|
47
|
+
}
|
|
48
|
+
if (skinType === this.SKIN_TYPE_TEST_STR) {
|
|
49
|
+
return this.SKIN_TYPE_TABLE_STR;
|
|
50
|
+
}
|
|
51
|
+
return skinType;
|
|
52
|
+
}
|
|
53
|
+
|
|
40
54
|
async addNewContentToDocumentSkin(
|
|
41
55
|
contentControlTitle: string,
|
|
42
56
|
skinType: string,
|
|
@@ -50,8 +64,10 @@ export default class Skins {
|
|
|
50
64
|
): Promise<any[]> {
|
|
51
65
|
try {
|
|
52
66
|
let populatedSkin: any[] = [];
|
|
67
|
+
const isStpTestSkin = skinType === this.SKIN_TYPE_TEST_STP;
|
|
68
|
+
const normalizedSkinType = this.resolveSkinTypeAlias(skinType);
|
|
53
69
|
|
|
54
|
-
switch (
|
|
70
|
+
switch (normalizedSkinType) {
|
|
55
71
|
case this.SKIN_TYPE_TABLE:
|
|
56
72
|
populatedSkin = this.generateQueryBasedTable(data, headerStyles, styles, headingLvl);
|
|
57
73
|
|
|
@@ -73,6 +89,9 @@ export default class Skins {
|
|
|
73
89
|
case this.SKIN_TYPE_PARAGRAPH:
|
|
74
90
|
populatedSkin = this.generateQueryBasedParagraphs(data, styles, headingLvl);
|
|
75
91
|
break;
|
|
92
|
+
case this.SKIN_TYPE_COVER_PAGE:
|
|
93
|
+
populatedSkin = this.generateCoverPageParagraphs(data, styles, headingLvl);
|
|
94
|
+
break;
|
|
76
95
|
case this.SKIN_TYPE_TEST_PLAN:
|
|
77
96
|
populatedSkin = this.generateTestBasedSkin(
|
|
78
97
|
data,
|
|
@@ -80,7 +99,8 @@ export default class Skins {
|
|
|
80
99
|
styles,
|
|
81
100
|
headingLvl,
|
|
82
101
|
includeAttachments,
|
|
83
|
-
isFlattened
|
|
102
|
+
isFlattened,
|
|
103
|
+
isStpTestSkin ? 1 : 0
|
|
84
104
|
);
|
|
85
105
|
break;
|
|
86
106
|
case this.SKIN_TYPE_SYSTEM_OVERVIEW:
|
|
@@ -327,49 +347,63 @@ export default class Skins {
|
|
|
327
347
|
}
|
|
328
348
|
}
|
|
329
349
|
|
|
350
|
+
private generateParagraphsFromData(data: any, styles: StyleOptions, headingLvl: number = 0): any[] {
|
|
351
|
+
const paragraphs: any[] = [];
|
|
352
|
+
|
|
353
|
+
data.forEach((wi: WIData) => {
|
|
354
|
+
wi.fields.forEach((field: WIProperty) => {
|
|
355
|
+
// Description and Test Description are handled via rich-text handler
|
|
356
|
+
if (field.name === 'Description: ' || field.name === 'Test Description:') {
|
|
357
|
+
const paragraphSkin = new JSONRichTextParagraph(field, styles, wi.Source, headingLvl + wi.level);
|
|
358
|
+
paragraphs.push(paragraphSkin.getJSONRichTextParagraph());
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Exclude ID / Description / Test Description from the normal paragraph path
|
|
362
|
+
if (field.name !== 'ID' && field.name !== 'Description: ' && field.name !== 'Test Description:') {
|
|
363
|
+
const paragraphSkin = new JSONParagraph(field, styles, wi.Source, headingLvl + wi.level);
|
|
364
|
+
paragraphs.push(paragraphSkin.getJSONParagraph());
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
return paragraphs;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
generateCoverPageParagraphs(data: any, styles: StyleOptions, headingLvl: number = 0): any[] {
|
|
373
|
+
logger.debug(`Generating cover-page paragraphs as ${this.skinFormat}`);
|
|
374
|
+
try {
|
|
375
|
+
switch (this.skinFormat) {
|
|
376
|
+
case 'json':
|
|
377
|
+
return this.generateParagraphsFromData(data, styles, headingLvl);
|
|
378
|
+
case 'html':
|
|
379
|
+
logger.info(`Generating html cover-page paragraphs!`);
|
|
380
|
+
return [];
|
|
381
|
+
default:
|
|
382
|
+
throw new Error(`Invalid skin format ${this.skinFormat}`);
|
|
383
|
+
}
|
|
384
|
+
} catch (error: any) {
|
|
385
|
+
logger.error(`Error occurred in generateCoverPageParagraphs: ${error.message}`);
|
|
386
|
+
throw error;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
330
390
|
generateQueryBasedParagraphs(data: any, styles: StyleOptions, headingLvl: number = 0): any[] {
|
|
331
391
|
logger.debug(`Generating paragraph as ${this.skinFormat}`);
|
|
332
392
|
try {
|
|
333
393
|
switch (this.skinFormat) {
|
|
334
394
|
case 'json':
|
|
335
|
-
|
|
336
|
-
data.forEach((wi: WIData) => {
|
|
337
|
-
wi.fields.forEach((field: WIProperty) => {
|
|
338
|
-
//Description and Test Description are handled in richText handler
|
|
339
|
-
if (field.name === 'Description: ' || field.name === 'Test Description:') {
|
|
340
|
-
let paragraphSkin = new JSONRichTextParagraph(
|
|
341
|
-
field,
|
|
342
|
-
styles,
|
|
343
|
-
wi.Source,
|
|
344
|
-
headingLvl + wi.level
|
|
345
|
-
);
|
|
346
|
-
paragraphs.push(paragraphSkin.getJSONRichTextParagraph());
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
//making sure ID is not printed
|
|
350
|
-
//excluding Description and Test Description are handled in richText handler
|
|
351
|
-
if (
|
|
352
|
-
field.name !== 'ID' &&
|
|
353
|
-
field.name !== 'Description: ' &&
|
|
354
|
-
field.name !== 'Test Description:'
|
|
355
|
-
) {
|
|
356
|
-
let paragraphSkin = new JSONParagraph(field, styles, wi.Source, headingLvl + wi.level);
|
|
357
|
-
paragraphs.push(paragraphSkin.getJSONParagraph());
|
|
358
|
-
}
|
|
359
|
-
});
|
|
360
|
-
});
|
|
361
|
-
return paragraphs;
|
|
395
|
+
return this.generateParagraphsFromData(data, styles, headingLvl);
|
|
362
396
|
case 'html':
|
|
363
397
|
logger.info(`Generating html paragraphs!`);
|
|
364
398
|
return [];
|
|
365
399
|
default:
|
|
366
400
|
throw new Error(`Invalid skin format ${this.skinFormat}`);
|
|
367
|
-
}
|
|
368
|
-
} catch (error) {
|
|
401
|
+
}
|
|
402
|
+
} catch (error: any) {
|
|
369
403
|
logger.error(`Error occurred in generateQueryBasedParagraphs: ${error.message}`);
|
|
370
404
|
throw error;
|
|
371
405
|
}
|
|
372
|
-
}
|
|
406
|
+
}
|
|
373
407
|
|
|
374
408
|
generateTraceTable(
|
|
375
409
|
data: any,
|
|
@@ -397,7 +431,8 @@ export default class Skins {
|
|
|
397
431
|
undefined,
|
|
398
432
|
undefined,
|
|
399
433
|
false,
|
|
400
|
-
groupedHeader
|
|
434
|
+
groupedHeader,
|
|
435
|
+
true
|
|
401
436
|
);
|
|
402
437
|
traceSkin.push(tableSkin.getJSONTable());
|
|
403
438
|
} else if (errorMessage !== null) {
|
|
@@ -423,7 +458,8 @@ export default class Skins {
|
|
|
423
458
|
styles: StyleOptions,
|
|
424
459
|
headingLvl: number = 0,
|
|
425
460
|
includeAttachments: boolean = true,
|
|
426
|
-
isFlattened = false
|
|
461
|
+
isFlattened = false,
|
|
462
|
+
headingLevelOffset = 0
|
|
427
463
|
): any[] {
|
|
428
464
|
logger.debug(`Generating testSkin as ${this.skinFormat}`);
|
|
429
465
|
let aggregatedErrors: string[] = [];
|
|
@@ -450,7 +486,7 @@ export default class Skins {
|
|
|
450
486
|
testSuite?.suiteSkinData?.fields && testSuite.suiteSkinData.fields.length > 1
|
|
451
487
|
? Number(testSuite.suiteSkinData.fields[1]?.value)
|
|
452
488
|
: undefined;
|
|
453
|
-
|
|
489
|
+
|
|
454
490
|
// Check if suite should be included (not flattened)
|
|
455
491
|
if (testSuite.suiteSkinData) {
|
|
456
492
|
let SuiteStyles = {
|
|
@@ -468,10 +504,10 @@ export default class Skins {
|
|
|
468
504
|
SuiteStyles,
|
|
469
505
|
suiteId || 0,
|
|
470
506
|
testSuite.suiteSkinData.level
|
|
471
|
-
? headingLvl + testSuite.suiteSkinData.level
|
|
507
|
+
? headingLvl + testSuite.suiteSkinData.level + headingLevelOffset
|
|
472
508
|
: headingLvl
|
|
473
|
-
? headingLvl
|
|
474
|
-
: 1
|
|
509
|
+
? headingLvl + headingLevelOffset
|
|
510
|
+
: 1 + headingLevelOffset
|
|
475
511
|
);
|
|
476
512
|
testSkin.push(testSuiteParagraphSkin.getJSONParagraph());
|
|
477
513
|
}
|
|
@@ -497,10 +533,10 @@ export default class Skins {
|
|
|
497
533
|
TestCaseStyles,
|
|
498
534
|
testcase.id || 0,
|
|
499
535
|
testcase.testCaseHeaderSkinData.level
|
|
500
|
-
? headingLvl + testcase.testCaseHeaderSkinData.level
|
|
536
|
+
? headingLvl + testcase.testCaseHeaderSkinData.level + headingLevelOffset
|
|
501
537
|
: headingLvl
|
|
502
|
-
? headingLvl
|
|
503
|
-
: 1
|
|
538
|
+
? headingLvl + headingLevelOffset
|
|
539
|
+
: 1 + headingLevelOffset
|
|
504
540
|
);
|
|
505
541
|
testSkin.push(testCaseParagraphSkin.getJSONParagraph());
|
|
506
542
|
|
|
@@ -521,6 +557,23 @@ export default class Skins {
|
|
|
521
557
|
let richTextSkin: any[] = testDescriptionParagraph.getJSONRichTextParagraph();
|
|
522
558
|
testSkin = [...testSkin, ...richTextSkin];
|
|
523
559
|
|
|
560
|
+
const additionalTestCaseFields = Array.isArray(testcase.testCaseHeaderSkinData?.fields)
|
|
561
|
+
? testcase.testCaseHeaderSkinData.fields.slice(3)
|
|
562
|
+
: [];
|
|
563
|
+
additionalTestCaseFields.forEach((field: any) => {
|
|
564
|
+
const name = String(field?.name || '').trim();
|
|
565
|
+
const value = String(field?.value ?? '').trim();
|
|
566
|
+
if (!value) return;
|
|
567
|
+
const combinedValue = name ? `${name}: ${value}` : value;
|
|
568
|
+
const paragraph = new JSONParagraph(
|
|
569
|
+
{ name: '', value: combinedValue },
|
|
570
|
+
styles,
|
|
571
|
+
testcase.id || 0,
|
|
572
|
+
0
|
|
573
|
+
);
|
|
574
|
+
testSkin.push(paragraph.getJSONParagraph());
|
|
575
|
+
});
|
|
576
|
+
|
|
524
577
|
if (testcase.testCaseRequirements) {
|
|
525
578
|
if (testcase.testCaseRequirements.length > 0) {
|
|
526
579
|
let testDescriptionTitleParagraph = new JSONParagraph(
|
|
@@ -12,6 +12,7 @@ import JSONTableRow from './JSONTableRow';
|
|
|
12
12
|
export default class JSONTable {
|
|
13
13
|
tableTemplate: Table;
|
|
14
14
|
tableStyles: StyleOptions;
|
|
15
|
+
private readonly enableVerticalMerge: boolean;
|
|
15
16
|
|
|
16
17
|
constructor(
|
|
17
18
|
data: WIQueryResults,
|
|
@@ -29,8 +30,10 @@ export default class JSONTable {
|
|
|
29
30
|
shading?: Shading;
|
|
30
31
|
leftShading?: Shading;
|
|
31
32
|
rightShading?: Shading;
|
|
32
|
-
}
|
|
33
|
+
},
|
|
34
|
+
enableVerticalMerge: boolean = false
|
|
33
35
|
) {
|
|
36
|
+
this.enableVerticalMerge = enableVerticalMerge;
|
|
34
37
|
this.tableStyles = tableStyles;
|
|
35
38
|
this.tableTemplate = {
|
|
36
39
|
type: 'table',
|
|
@@ -106,8 +109,10 @@ export default class JSONTable {
|
|
|
106
109
|
rows.push(row.getRow());
|
|
107
110
|
});
|
|
108
111
|
|
|
109
|
-
|
|
110
|
-
|
|
112
|
+
if (this.enableVerticalMerge) {
|
|
113
|
+
// Apply vertical merging for grouped source cells (first row shows value; subsequent are empty)
|
|
114
|
+
this.applyVerticalMerges(rows);
|
|
115
|
+
}
|
|
111
116
|
|
|
112
117
|
return rows;
|
|
113
118
|
} //generateJsonRows
|
|
@@ -179,6 +184,17 @@ export default class JSONTable {
|
|
|
179
184
|
|
|
180
185
|
const getCellText = (cell: any): string => {
|
|
181
186
|
try {
|
|
187
|
+
const htmlRaw = String(cell?.Html?.Html || '').trim();
|
|
188
|
+
if (htmlRaw) {
|
|
189
|
+
const htmlText = htmlRaw
|
|
190
|
+
.replace(/<[^>]*>/g, ' ')
|
|
191
|
+
.replace(/ /gi, ' ')
|
|
192
|
+
.replace(/\s+/g, ' ')
|
|
193
|
+
.trim();
|
|
194
|
+
if (htmlText) {
|
|
195
|
+
return htmlText;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
182
198
|
const runs = cell?.Paragraphs?.[0]?.Runs || [];
|
|
183
199
|
return runs.map((r: any) => (r?.text ?? '')).join('').trim();
|
|
184
200
|
} catch {
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import JSONFile from '../models/json/file/JSONFile';
|
|
2
|
+
import logger from '../services/logger';
|
|
3
|
+
|
|
4
|
+
jest.mock('../services/logger', () => ({
|
|
5
|
+
__esModule: true,
|
|
6
|
+
default: {
|
|
7
|
+
debug: jest.fn(),
|
|
8
|
+
info: jest.fn(),
|
|
9
|
+
warn: jest.fn(),
|
|
10
|
+
error: jest.fn(),
|
|
11
|
+
},
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
const getMockLogger = () =>
|
|
15
|
+
logger as unknown as {
|
|
16
|
+
debug: jest.Mock;
|
|
17
|
+
info: jest.Mock;
|
|
18
|
+
warn: jest.Mock;
|
|
19
|
+
error: jest.Mock;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
describe('JSONFile', () => {
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
jest.clearAllMocks();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('removeFileExtension returns original name when there is no dot', () => {
|
|
28
|
+
const data: any = {
|
|
29
|
+
attachmentLink: 'http://server/file',
|
|
30
|
+
attachmentFileName: 'file',
|
|
31
|
+
attachmentType: 'asEmbedded',
|
|
32
|
+
includeAttachmentContent: true,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const jsonFile = new JSONFile(data);
|
|
36
|
+
const attachment = jsonFile.getFileAttachment as any;
|
|
37
|
+
|
|
38
|
+
expect(attachment.type).toBe('File');
|
|
39
|
+
expect(attachment.name).toBe('file');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('isPictureUri handles invalid uri and logs error', () => {
|
|
43
|
+
const data: any = {
|
|
44
|
+
attachmentLink: undefined,
|
|
45
|
+
attachmentFileName: 'file.docx',
|
|
46
|
+
attachmentType: 'asEmbedded',
|
|
47
|
+
includeAttachmentContent: true,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const jsonFile = new JSONFile(data);
|
|
51
|
+
const attachment = jsonFile.getFileAttachment as any;
|
|
52
|
+
|
|
53
|
+
expect(attachment.type).toBe('File');
|
|
54
|
+
expect(attachment.name).toBe('file');
|
|
55
|
+
expect(getMockLogger().error).toHaveBeenCalledWith(
|
|
56
|
+
expect.stringContaining('error parsing file extension for')
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import JSONRun from '../models/json/JSONRun';
|
|
2
|
+
import { defaultJsonRun } from '../models/json/default';
|
|
3
|
+
import logger from '../services/logger';
|
|
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: true,
|
|
27
|
+
IsItalic: false,
|
|
28
|
+
IsUnderline: false,
|
|
29
|
+
Size: 12,
|
|
30
|
+
Uri: 'http://example.com',
|
|
31
|
+
Font: 'Arial',
|
|
32
|
+
InsertLineBreak: false,
|
|
33
|
+
InsertSpace: true,
|
|
34
|
+
InsertPageBreak: false as boolean | undefined,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
describe('JSONRun', () => {
|
|
38
|
+
beforeEach(() => {
|
|
39
|
+
jest.clearAllMocks();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('splits multi-line text into separate runs with correct line breaks', () => {
|
|
43
|
+
const value = 'line1\nline2\nline3';
|
|
44
|
+
|
|
45
|
+
const jsonRun = new JSONRun(value, { ...baseStyles });
|
|
46
|
+
const runs = jsonRun.getRun();
|
|
47
|
+
|
|
48
|
+
expect(runs).toHaveLength(3);
|
|
49
|
+
|
|
50
|
+
expect(runs[0].InsertLineBreak).toBe(true);
|
|
51
|
+
expect(runs[1].InsertLineBreak).toBe(true);
|
|
52
|
+
expect(runs[2].InsertLineBreak).toBe(baseStyles.InsertLineBreak);
|
|
53
|
+
|
|
54
|
+
runs.forEach((r) => {
|
|
55
|
+
expect(r.Bold).toBe(baseStyles.isBold);
|
|
56
|
+
expect(r.Italic).toBe(baseStyles.IsItalic);
|
|
57
|
+
expect(r.Underline).toBe(baseStyles.IsUnderline);
|
|
58
|
+
expect(r.Size).toBe(baseStyles.Size);
|
|
59
|
+
expect(r.Font).toBe(baseStyles.Font);
|
|
60
|
+
expect(r.InsertSpace).toBe(baseStyles.InsertSpace);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('uses striphtml replaceBr callback to convert <br> to newline', () => {
|
|
65
|
+
const value = 'Hello<br>World';
|
|
66
|
+
|
|
67
|
+
const jsonRun = new JSONRun(value, { ...baseStyles });
|
|
68
|
+
const runs = jsonRun.getRun();
|
|
69
|
+
|
|
70
|
+
expect(runs).toHaveLength(1);
|
|
71
|
+
expect(runs[0].text).toContain('\n');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('sets Uri to null when default run Uri is empty string in normal flow', () => {
|
|
75
|
+
const prevUri = defaultJsonRun.Uri;
|
|
76
|
+
defaultJsonRun.Uri = '' as any;
|
|
77
|
+
|
|
78
|
+
const jsonRun = new JSONRun('plain text', { ...baseStyles });
|
|
79
|
+
const runs = jsonRun.getRun();
|
|
80
|
+
|
|
81
|
+
runs.forEach((r) => {
|
|
82
|
+
expect(r.Uri).toBeNull();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
defaultJsonRun.Uri = prevUri;
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('does not strip HTML when fieldtype is SuiteHeaderParagraphTitle', () => {
|
|
89
|
+
const value = '<b>Bold Title</b>';
|
|
90
|
+
|
|
91
|
+
const jsonRun = new JSONRun(value, { ...baseStyles }, 'SuiteHeaderParagraphTitle');
|
|
92
|
+
const runs = jsonRun.getRun();
|
|
93
|
+
|
|
94
|
+
expect(runs).toHaveLength(1);
|
|
95
|
+
expect(runs[0].text).toContain('<b>Bold Title</b>');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('falls back to default run and logs when value cannot be split', () => {
|
|
99
|
+
const badValue: any = null;
|
|
100
|
+
|
|
101
|
+
const prevUri = defaultJsonRun.Uri;
|
|
102
|
+
defaultJsonRun.Uri = '' as any;
|
|
103
|
+
|
|
104
|
+
const jsonRun = new JSONRun(badValue, { ...baseStyles });
|
|
105
|
+
const runs = jsonRun.getRun();
|
|
106
|
+
|
|
107
|
+
expect(getMockLogger().silly).toHaveBeenCalled();
|
|
108
|
+
expect(runs).toHaveLength(1);
|
|
109
|
+
expect(runs[0].text).toBe('');
|
|
110
|
+
expect(runs[0].Bold).toBe(baseStyles.isBold);
|
|
111
|
+
expect(runs[0].Italic).toBe(baseStyles.IsItalic);
|
|
112
|
+
expect(runs[0].Underline).toBe(baseStyles.IsUnderline);
|
|
113
|
+
expect(runs[0].Size).toBe(baseStyles.Size);
|
|
114
|
+
expect(runs[0].Uri).toBeNull();
|
|
115
|
+
|
|
116
|
+
defaultJsonRun.Uri = prevUri;
|
|
117
|
+
});
|
|
118
|
+
});
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import JSONTable from '../models/json/table/JSONTable';
|
|
2
|
+
|
|
3
|
+
const baseStyles = {
|
|
4
|
+
isBold: false,
|
|
5
|
+
IsItalic: false,
|
|
6
|
+
IsUnderline: false,
|
|
7
|
+
Size: 10,
|
|
8
|
+
Uri: null as string | null,
|
|
9
|
+
Font: 'Arial',
|
|
10
|
+
InsertLineBreak: false,
|
|
11
|
+
InsertSpace: false,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const headerStyles = {
|
|
15
|
+
...baseStyles,
|
|
16
|
+
isBold: true,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const buildData = (secondColValue: string) => [
|
|
20
|
+
{
|
|
21
|
+
url: '',
|
|
22
|
+
Source: 1,
|
|
23
|
+
level: 1,
|
|
24
|
+
fields: [
|
|
25
|
+
{ name: '#', value: 1, width: '8%' },
|
|
26
|
+
{ name: 'Description', value: 'No description' },
|
|
27
|
+
],
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
url: '',
|
|
31
|
+
Source: 2,
|
|
32
|
+
level: 1,
|
|
33
|
+
fields: [
|
|
34
|
+
{ name: '#', value: 2, width: '8%' },
|
|
35
|
+
{ name: 'Description', value: secondColValue },
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
describe('JSONTable vertical merge behavior', () => {
|
|
41
|
+
test('uses explicit grouped-header column spans when provided', () => {
|
|
42
|
+
const table = new JSONTable(
|
|
43
|
+
[
|
|
44
|
+
{
|
|
45
|
+
url: '',
|
|
46
|
+
Source: 1,
|
|
47
|
+
level: 1,
|
|
48
|
+
fields: [
|
|
49
|
+
{ name: 'Req ID', value: 1 },
|
|
50
|
+
{ name: 'Title', value: 'Req' },
|
|
51
|
+
{ name: 'Customer ID', value: 'C1' },
|
|
52
|
+
{ name: 'Test Case ID', value: 11 },
|
|
53
|
+
{ name: 'Title', value: 'TC' },
|
|
54
|
+
],
|
|
55
|
+
},
|
|
56
|
+
] as any,
|
|
57
|
+
headerStyles as any,
|
|
58
|
+
baseStyles as any,
|
|
59
|
+
0,
|
|
60
|
+
false,
|
|
61
|
+
false,
|
|
62
|
+
false,
|
|
63
|
+
{
|
|
64
|
+
leftLabel: 'Requirement',
|
|
65
|
+
rightLabel: 'Test Case',
|
|
66
|
+
leftColumns: 3,
|
|
67
|
+
rightColumns: 2,
|
|
68
|
+
},
|
|
69
|
+
false
|
|
70
|
+
).getJSONTable() as any;
|
|
71
|
+
|
|
72
|
+
expect(table.Rows[0].Cells[0].gridSpan).toBe(3);
|
|
73
|
+
expect(table.Rows[0].Cells[1].gridSpan).toBe(2);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('does not merge empty cells when vertical merge is disabled', () => {
|
|
77
|
+
const table = new JSONTable(
|
|
78
|
+
buildData('') as any,
|
|
79
|
+
headerStyles as any,
|
|
80
|
+
baseStyles as any,
|
|
81
|
+
0,
|
|
82
|
+
false,
|
|
83
|
+
false,
|
|
84
|
+
false,
|
|
85
|
+
undefined,
|
|
86
|
+
false
|
|
87
|
+
).getJSONTable() as any;
|
|
88
|
+
|
|
89
|
+
expect(table.Rows[1].Cells[1].vMerge).toBeUndefined();
|
|
90
|
+
expect(table.Rows[2].Cells[1].vMerge).toBeUndefined();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('merges empty cells when vertical merge is enabled', () => {
|
|
94
|
+
const table = new JSONTable(
|
|
95
|
+
buildData('') as any,
|
|
96
|
+
headerStyles as any,
|
|
97
|
+
baseStyles as any,
|
|
98
|
+
0,
|
|
99
|
+
false,
|
|
100
|
+
false,
|
|
101
|
+
false,
|
|
102
|
+
undefined,
|
|
103
|
+
true
|
|
104
|
+
).getJSONTable() as any;
|
|
105
|
+
|
|
106
|
+
expect(table.Rows[1].Cells[1].vMerge).toBe('restart');
|
|
107
|
+
expect(table.Rows[2].Cells[1].vMerge).toBe('continue');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test('does not treat HTML cell content as empty during merge detection', () => {
|
|
111
|
+
const table = new JSONTable(
|
|
112
|
+
buildData('<div>Some Description for suite</div>') as any,
|
|
113
|
+
headerStyles as any,
|
|
114
|
+
baseStyles as any,
|
|
115
|
+
0,
|
|
116
|
+
false,
|
|
117
|
+
false,
|
|
118
|
+
false,
|
|
119
|
+
undefined,
|
|
120
|
+
true
|
|
121
|
+
).getJSONTable() as any;
|
|
122
|
+
|
|
123
|
+
expect(table.Rows[2].Cells[1].vMerge).toBeUndefined();
|
|
124
|
+
expect(table.Rows[2].Cells[1].Html?.Html).toBe('<div>Some Description for suite</div>');
|
|
125
|
+
});
|
|
126
|
+
});
|