@elisra-devops/docgen-skins 0.23.0 → 0.25.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 (65) hide show
  1. package/.github/workflows/ci.yml +18 -9
  2. package/.github/workflows/release.yml +22 -6
  3. package/Readme.md +11 -0
  4. package/bin/index.d.ts +12 -1
  5. package/bin/index.js +201 -16
  6. package/bin/index.js.map +1 -1
  7. package/bin/models/json/JSONRun.js +2 -0
  8. package/bin/models/json/JSONRun.js.map +1 -1
  9. package/bin/models/json/file/JSONFile.js.map +1 -1
  10. package/bin/models/json/paragraph/JSONParagraph.js.map +1 -1
  11. package/bin/models/json/paragraph/JSONRichTextParagraph.js.map +1 -1
  12. package/bin/models/json/paragraph/JSONTestSuiteHeaderParagraph.js +3 -0
  13. package/bin/models/json/paragraph/JSONTestSuiteHeaderParagraph.js.map +1 -1
  14. package/bin/models/json/table/JSONTable.d.ts +2 -1
  15. package/bin/models/json/table/JSONTable.js +20 -6
  16. package/bin/models/json/table/JSONTable.js.map +1 -1
  17. package/bin/models/json/table/JSONTableCell.js +2 -0
  18. package/bin/models/json/table/JSONTableCell.js.map +1 -1
  19. package/bin/models/json/table/JSONTableRow.js.map +1 -1
  20. package/bin/models/json/wordJsonModels.d.ts +5 -0
  21. package/bin/services/logger.js +17 -7
  22. package/bin/services/logger.js.map +1 -1
  23. package/bin/test/JSONFile.test.js +48 -0
  24. package/bin/test/JSONFile.test.js.map +1 -0
  25. package/bin/test/JSONRun.test.d.ts +1 -0
  26. package/bin/test/JSONRun.test.js +93 -0
  27. package/bin/test/JSONRun.test.js.map +1 -0
  28. package/bin/test/JSONTable.test.d.ts +1 -0
  29. package/bin/test/JSONTable.test.js +79 -0
  30. package/bin/test/JSONTable.test.js.map +1 -0
  31. package/bin/test/JSONTableCell.test.d.ts +1 -0
  32. package/bin/test/JSONTableCell.test.js +154 -0
  33. package/bin/test/JSONTableCell.test.js.map +1 -0
  34. package/bin/test/JSONTableRow.test.d.ts +1 -0
  35. package/bin/test/JSONTableRow.test.js +40 -0
  36. package/bin/test/JSONTableRow.test.js.map +1 -0
  37. package/bin/test/JSONTestSuiteHeaderParagraph.test.d.ts +1 -0
  38. package/bin/test/JSONTestSuiteHeaderParagraph.test.js +57 -0
  39. package/bin/test/JSONTestSuiteHeaderParagraph.test.js.map +1 -0
  40. package/bin/test/SkinsEdgeCases.test.d.ts +1 -0
  41. package/bin/test/SkinsEdgeCases.test.js +399 -0
  42. package/bin/test/SkinsEdgeCases.test.js.map +1 -0
  43. package/bin/test/integration/skins.integration.test.d.ts +1 -0
  44. package/bin/test/integration/skins.integration.test.js +533 -0
  45. package/bin/test/integration/skins.integration.test.js.map +1 -0
  46. package/package.json +5 -5
  47. package/src/index.ts +247 -10
  48. package/src/models/json/JSONRun.ts +2 -0
  49. package/src/models/json/paragraph/JSONTestSuiteHeaderParagraph.ts +4 -0
  50. package/src/models/json/table/JSONTable.ts +19 -3
  51. package/src/models/json/table/JSONTableCell.ts +1 -0
  52. package/src/models/json/wordJsonModels.ts +5 -0
  53. package/src/test/JSONFile.test.ts +59 -0
  54. package/src/test/JSONRun.test.ts +118 -0
  55. package/src/test/JSONTable.test.ts +126 -0
  56. package/src/test/JSONTableCell.test.ts +181 -0
  57. package/src/test/JSONTableRow.test.ts +42 -0
  58. package/src/test/JSONTestSuiteHeaderParagraph.test.ts +64 -0
  59. package/src/test/SkinsEdgeCases.test.ts +530 -0
  60. package/src/test/integration/skins.integration.test.ts +660 -0
  61. package/tsconfig.json +2 -2
  62. package/bin/test/skins.test.js +0 -126
  63. package/bin/test/skins.test.js.map +0 -1
  64. package/src/test/skins.test.ts +0 -189
  65. /package/bin/test/{skins.test.d.ts → JSONFile.test.d.ts} +0 -0
@@ -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
+ });
@@ -0,0 +1,181 @@
1
+ import JSONTableCell from '../models/json/table/JSONTableCell';
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
+ const baseStyles = {
23
+ isBold: false,
24
+ IsItalic: false,
25
+ IsUnderline: false,
26
+ Size: 10,
27
+ Uri: null as string | null,
28
+ Font: 'Arial',
29
+ InsertLineBreak: false,
30
+ InsertSpace: true,
31
+ };
32
+
33
+ describe('JSONTableCell', () => {
34
+ beforeEach(() => {
35
+ jest.clearAllMocks();
36
+ });
37
+
38
+ test('creates picture attachment when value contains picture link and respects isFlattened', () => {
39
+ const shading = { color: 'auto', fill: 'FF0000', themeFillShade: 'BF' } as any;
40
+
41
+ const data: any = {
42
+ name: 'AttachmentCell',
43
+ value: [
44
+ {
45
+ attachmentLink: 'http://server/image.png',
46
+ tableCellAttachmentLink: 'http://server/image-render.png',
47
+ attachmentFileName: 'image.png',
48
+ },
49
+ ],
50
+ attachmentType: 'asEmbedded',
51
+ includeAttachmentContent: true,
52
+ width: '50%',
53
+ };
54
+
55
+ const cell = new JSONTableCell(data, { ...baseStyles }, false, shading, true);
56
+ const json = cell.getJsonCell() as any;
57
+
58
+ expect(json.shading).toEqual(shading);
59
+ expect(json.attachments).toHaveLength(1);
60
+ expect(json.attachments[0]).toMatchObject({
61
+ type: 'Picture',
62
+ path: 'http://server/image-render.png',
63
+ name: 'image',
64
+ isFlattened: true,
65
+ });
66
+ });
67
+
68
+ test('creates file attachment with link flags for non-picture attachment', () => {
69
+ const data: any = {
70
+ name: 'AttachmentCell',
71
+ value: [
72
+ {
73
+ attachmentLink: 'http://server/doc1.pdf',
74
+ attachmentFileName: 'doc1.pdf',
75
+ },
76
+ ],
77
+ attachmentType: 'asLink',
78
+ includeAttachmentContent: false,
79
+ width: '50%',
80
+ };
81
+
82
+ const cell = new JSONTableCell(data, { ...baseStyles }, false, undefined, true);
83
+ const json = cell.getJsonCell() as any;
84
+
85
+ expect(json.attachments).toHaveLength(1);
86
+ expect(json.attachments[0]).toMatchObject({
87
+ type: 'File',
88
+ path: 'http://server/doc1.pdf',
89
+ name: 'doc1',
90
+ isLinkedFile: true,
91
+ includeAttachmentContent: false,
92
+ isFlattened: true,
93
+ });
94
+ });
95
+
96
+ test('creates HTML cell when value contains HTML markup', () => {
97
+ const shading = { color: 'auto', fill: '00FF00' } as any;
98
+
99
+ const data: any = {
100
+ name: 'HtmlCell',
101
+ value: '<p>Hello</p>',
102
+ width: '30%',
103
+ };
104
+
105
+ const cell = new JSONTableCell(data, { ...baseStyles }, false, shading, false);
106
+ const json = cell.getJsonCell() as any;
107
+
108
+ expect(json.Html).toMatchObject({
109
+ type: 'html',
110
+ Html: '<p>Hello</p>',
111
+ font: 'Arial',
112
+ fontSize: 10,
113
+ });
114
+ expect(json.shading).toEqual(shading);
115
+ expect(json.attachments).toHaveLength(0);
116
+ });
117
+
118
+ test('creates text runs when value is simple text', () => {
119
+ const data: any = {
120
+ name: 'TextCell',
121
+ value: 'Plain text',
122
+ width: '20%',
123
+ url: 'http://example.com/field',
124
+ };
125
+
126
+ const cell = new JSONTableCell(data, { ...baseStyles }, false, undefined, false);
127
+ const json = cell.getJsonCell() as any;
128
+
129
+ expect(json.Html).toBeUndefined();
130
+ expect(json.attachments).toHaveLength(0);
131
+ expect(json.Paragraphs[0].Runs.length).toBeGreaterThan(0);
132
+ const text = json.Paragraphs[0].Runs.map((r: any) => r.text).join('');
133
+ expect(text).toContain('Plain text');
134
+ });
135
+
136
+ test('logs error and falls back to error run when value is invalid non-array object', () => {
137
+ const data: any = {
138
+ name: 'BadCell',
139
+ value: { not: 'an array' },
140
+ width: '10%',
141
+ };
142
+
143
+ const cell = new JSONTableCell(data, { ...baseStyles }, false, undefined, false);
144
+ const json = cell.getJsonCell() as any;
145
+
146
+ expect(getMockLogger().error).toHaveBeenCalled();
147
+ const text = json.Paragraphs[0].Runs.map((r: any) => r.text).join('');
148
+ expect(text).toContain('Docgen Error: Invalid data value');
149
+ });
150
+
151
+ test('threads sectionRefId from data to run SectionRefId', () => {
152
+ const data: any = {
153
+ name: 'Test Case ID',
154
+ value: '123',
155
+ sectionRefId: '123',
156
+ url: 'http://tfs/link',
157
+ shading: { color: 'auto', fill: 'FFFFFF' },
158
+ };
159
+
160
+ const cell = new JSONTableCell(data, { ...baseStyles }, false, undefined, false);
161
+ const json = cell.getJsonCell() as any;
162
+ const runs = json.Paragraphs[0].Runs;
163
+
164
+ expect(runs.length).toBeGreaterThan(0);
165
+ expect(runs[0].SectionRefId).toBe('123');
166
+ });
167
+
168
+ test('SectionRefId is null when not provided on data', () => {
169
+ const data: any = {
170
+ name: 'Req ID',
171
+ value: '456',
172
+ shading: { color: 'auto', fill: 'FFFFFF' },
173
+ };
174
+
175
+ const cell = new JSONTableCell(data, { ...baseStyles }, false, undefined, false);
176
+ const json = cell.getJsonCell() as any;
177
+ const runs = json.Paragraphs[0].Runs;
178
+
179
+ expect(runs[0].SectionRefId).toBeNull();
180
+ });
181
+ });
@@ -0,0 +1,42 @@
1
+ import JSONTableRow from '../models/json/table/JSONTableRow';
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 baseStyles = {
15
+ isBold: false,
16
+ IsItalic: false,
17
+ IsUnderline: false,
18
+ Size: 10,
19
+ Uri: null as string | null,
20
+ Font: 'Arial',
21
+ InsertLineBreak: false,
22
+ InsertSpace: true,
23
+ };
24
+
25
+ describe('JSONTableRow', () => {
26
+ beforeEach(() => {
27
+ jest.clearAllMocks();
28
+ });
29
+
30
+ test('throws error when WIData has no fields', () => {
31
+ const data: any = {
32
+ url: '',
33
+ level: 0,
34
+ Source: 1,
35
+ // fields is intentionally missing to trigger the error path
36
+ };
37
+
38
+ expect(() => new JSONTableRow(data, { ...baseStyles } as any)).toThrow(
39
+ 'no fields to add for the table row'
40
+ );
41
+ });
42
+ });
@@ -0,0 +1,64 @@
1
+ import JSONTestSuiteHeaderParagraph from '../models/json/paragraph/JSONTestSuiteHeaderParagraph';
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 baseStyles = {
15
+ isBold: true,
16
+ IsItalic: false,
17
+ IsUnderline: false,
18
+ Size: 14,
19
+ Uri: null as string | null,
20
+ Font: 'Arial',
21
+ InsertLineBreak: false,
22
+ InsertSpace: true,
23
+ };
24
+
25
+ describe('JSONTestSuiteHeaderParagraph', () => {
26
+ beforeEach(() => {
27
+ jest.clearAllMocks();
28
+ });
29
+
30
+ test('creates runs for non-title/id/description fields and sets heading level', () => {
31
+ const fields: any[] = [{ name: 'Custom', value: 'Value', url: 'http://example.com' }];
32
+
33
+ const paragraph = new JSONTestSuiteHeaderParagraph(fields, { ...baseStyles }, 1, 2);
34
+ const json = paragraph.getJSONParagraph() as any;
35
+
36
+ expect(json.type).toBe('paragraph');
37
+ expect(json.headingLevel).toBe(2);
38
+ expect(json.runs.length).toBeGreaterThan(0);
39
+ });
40
+
41
+ test('emits bookmarkName when wiId is truthy', () => {
42
+ const fields: any[] = [{ name: 'Custom', value: 'Value' }];
43
+ const paragraph = new JSONTestSuiteHeaderParagraph(fields, { ...baseStyles }, 42, 2);
44
+ const json = paragraph.getJSONParagraph();
45
+
46
+ expect(json.bookmarkName).toBe('_WI42');
47
+ });
48
+
49
+ test('does not emit bookmarkName when wiId is 0 or falsy', () => {
50
+ const fields: any[] = [{ name: 'Custom', value: 'Value' }];
51
+ const paragraph = new JSONTestSuiteHeaderParagraph(fields, { ...baseStyles }, 0, 2);
52
+ const json = paragraph.getJSONParagraph();
53
+
54
+ expect(json.bookmarkName).toBeUndefined();
55
+ });
56
+
57
+ test('does not emit bookmarkName when wiId is undefined', () => {
58
+ const fields: any[] = [{ name: 'Custom', value: 'Value' }];
59
+ const paragraph = new JSONTestSuiteHeaderParagraph(fields, { ...baseStyles }, undefined as any, 2);
60
+ const json = paragraph.getJSONParagraph();
61
+
62
+ expect(json.bookmarkName).toBeUndefined();
63
+ });
64
+ });