@slickgrid-universal/pdf-export 10.0.0 → 10.1.1
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/README.md +13 -0
- package/dist/pdfExport.service.d.ts +8 -0
- package/dist/pdfExport.service.d.ts.map +1 -1
- package/dist/pdfExport.service.js +119 -19
- package/dist/pdfExport.service.js.map +1 -1
- package/package.json +4 -4
- package/src/pdfExport.service.spec.ts +827 -5
- package/src/pdfExport.service.ts +136 -22
|
@@ -989,13 +989,15 @@ describe('PdfExportService', () => {
|
|
|
989
989
|
|
|
990
990
|
describe('PdfExportService Coverage', () => {
|
|
991
991
|
describe('PdfExportService with jsPDF-AutoTable', () => {
|
|
992
|
-
|
|
992
|
+
// Helper to create a fresh PdfExportService that sees doc.autoTable
|
|
993
|
+
async function createAutoTableService() {
|
|
993
994
|
vi.resetModules();
|
|
994
995
|
const autoTableSpy = vi.fn();
|
|
996
|
+
const setDocumentPropertiesSpy = vi.fn();
|
|
995
997
|
function jsPDFMockWithAutoTable(this: any) {
|
|
996
998
|
this.save = vi.fn();
|
|
997
999
|
this.setFontSize = vi.fn();
|
|
998
|
-
this.getTextWidth = vi.fn((txt) => txt.length * 6);
|
|
1000
|
+
this.getTextWidth = vi.fn((txt: string) => txt.length * 6);
|
|
999
1001
|
this.internal = {
|
|
1000
1002
|
pageSize: {
|
|
1001
1003
|
getWidth: () => 595.28,
|
|
@@ -1008,9 +1010,43 @@ describe('PdfExportService', () => {
|
|
|
1008
1010
|
this.text = vi.fn();
|
|
1009
1011
|
this.addPage = vi.fn();
|
|
1010
1012
|
this.autoTable = autoTableSpy;
|
|
1013
|
+
this.setDocumentProperties = setDocumentPropertiesSpy;
|
|
1011
1014
|
}
|
|
1012
1015
|
vi.doMock('jspdf', () => ({ __esModule: true, default: jsPDFMockWithAutoTable }));
|
|
1013
|
-
const { PdfExportService:
|
|
1016
|
+
const { PdfExportService: Svc } = await import('./pdfExport.service.js');
|
|
1017
|
+
return { PdfExportService: Svc, autoTableSpy, setDocumentPropertiesSpy };
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
function createStubs(columns: any[], rowCount = 1, rowFactory?: (idx: number) => any) {
|
|
1021
|
+
const defaultRow = columns.reduce(
|
|
1022
|
+
(acc: any, col: any) => {
|
|
1023
|
+
acc[col.field] = col.field + '_val';
|
|
1024
|
+
return acc;
|
|
1025
|
+
},
|
|
1026
|
+
{ id: 1 }
|
|
1027
|
+
);
|
|
1028
|
+
const dataViewStub = {
|
|
1029
|
+
getGrouping: () => [],
|
|
1030
|
+
getLength: () => rowCount,
|
|
1031
|
+
getItem: (idx: number) => (rowFactory ? rowFactory(idx) : defaultRow),
|
|
1032
|
+
getItemMetadata: vi.fn().mockReturnValue({}),
|
|
1033
|
+
};
|
|
1034
|
+
const gridStub = {
|
|
1035
|
+
getColumns: () => columns,
|
|
1036
|
+
getOptions: () => ({ includeColumnHeaders: true }),
|
|
1037
|
+
getData: () => dataViewStub,
|
|
1038
|
+
};
|
|
1039
|
+
const pubSubService = { publish: vi.fn() };
|
|
1040
|
+
const container = { get: () => pubSubService };
|
|
1041
|
+
return { dataViewStub, gridStub, pubSubService, container };
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
afterEach(() => {
|
|
1045
|
+
vi.resetModules();
|
|
1046
|
+
});
|
|
1047
|
+
|
|
1048
|
+
it('should cover jsPDF-AutoTable branch and pre-header row drawing', async () => {
|
|
1049
|
+
const { PdfExportService: PdfExportServiceWithAutoTable, autoTableSpy } = await createAutoTableService();
|
|
1014
1050
|
const columns = [
|
|
1015
1051
|
{ id: 'col1', field: 'col1', name: 'Col1', columnGroup: 'GroupA' },
|
|
1016
1052
|
{ id: 'col2', field: 'col2', name: 'Col2', columnGroup: 'GroupA' },
|
|
@@ -1031,12 +1067,493 @@ describe('PdfExportService', () => {
|
|
|
1031
1067
|
const container = { get: () => pubSubService };
|
|
1032
1068
|
const service = new PdfExportServiceWithAutoTable();
|
|
1033
1069
|
service.init(gridStub as any, container as any);
|
|
1034
|
-
// Set grouped headers to trigger pre-header row logic
|
|
1035
1070
|
(service as any)._groupedColumnHeaders = groupedHeaders;
|
|
1036
1071
|
const result = await service.exportToPdf({ filename: 'autotable-preheader', includeColumnHeaders: true });
|
|
1037
1072
|
expect(result).toBe(true);
|
|
1038
1073
|
expect(autoTableSpy).toHaveBeenCalled();
|
|
1039
|
-
|
|
1074
|
+
});
|
|
1075
|
+
|
|
1076
|
+
it('should pass columnStyles with per-column textAlign to autoTable', async () => {
|
|
1077
|
+
const { PdfExportService: Svc, autoTableSpy } = await createAutoTableService();
|
|
1078
|
+
const columns = [
|
|
1079
|
+
{ id: 'desc', field: 'desc', name: 'Description', width: 200 },
|
|
1080
|
+
{ id: 'amount', field: 'amount', name: 'Amount', width: 100, pdfExportOptions: { textAlign: 'right' } },
|
|
1081
|
+
];
|
|
1082
|
+
const { gridStub, container } = createStubs(columns);
|
|
1083
|
+
const service = new Svc();
|
|
1084
|
+
service.init(gridStub as any, container as any);
|
|
1085
|
+
await service.exportToPdf({ filename: 'col-styles' });
|
|
1086
|
+
|
|
1087
|
+
expect(autoTableSpy).toHaveBeenCalled();
|
|
1088
|
+
const opts = autoTableSpy.mock.calls[0][0];
|
|
1089
|
+
expect(opts.columnStyles).toBeDefined();
|
|
1090
|
+
// Column 1 (amount) should have halign 'right'
|
|
1091
|
+
expect(opts.columnStyles[1]).toEqual({ halign: 'right' });
|
|
1092
|
+
// Column 0 (desc) uses default 'left', so no entry
|
|
1093
|
+
expect(opts.columnStyles[0]).toBeUndefined();
|
|
1094
|
+
});
|
|
1095
|
+
|
|
1096
|
+
it('should pass empty columnStyles when all columns match global textAlign', async () => {
|
|
1097
|
+
const { PdfExportService: Svc, autoTableSpy } = await createAutoTableService();
|
|
1098
|
+
const columns = [
|
|
1099
|
+
{ id: 'col1', field: 'col1', name: 'Col1', width: 100 },
|
|
1100
|
+
{ id: 'col2', field: 'col2', name: 'Col2', width: 100 },
|
|
1101
|
+
];
|
|
1102
|
+
const { gridStub, container } = createStubs(columns);
|
|
1103
|
+
const service = new Svc();
|
|
1104
|
+
service.init(gridStub as any, container as any);
|
|
1105
|
+
await service.exportToPdf({ filename: 'no-col-styles' });
|
|
1106
|
+
|
|
1107
|
+
const opts = autoTableSpy.mock.calls[0][0];
|
|
1108
|
+
expect(opts.columnStyles).toEqual({});
|
|
1109
|
+
});
|
|
1110
|
+
|
|
1111
|
+
it('should handle mixed column alignments correctly', async () => {
|
|
1112
|
+
const { PdfExportService: Svc, autoTableSpy } = await createAutoTableService();
|
|
1113
|
+
const columns = [
|
|
1114
|
+
{ id: 'left', field: 'left', name: 'Left', width: 100 },
|
|
1115
|
+
{ id: 'right', field: 'right', name: 'Right', width: 100, pdfExportOptions: { textAlign: 'right' } },
|
|
1116
|
+
{ id: 'center', field: 'center', name: 'Center', width: 100, pdfExportOptions: { textAlign: 'center' } },
|
|
1117
|
+
];
|
|
1118
|
+
const { gridStub, container } = createStubs(columns);
|
|
1119
|
+
const service = new Svc();
|
|
1120
|
+
service.init(gridStub as any, container as any);
|
|
1121
|
+
await service.exportToPdf({ filename: 'mixed-align' });
|
|
1122
|
+
|
|
1123
|
+
const opts = autoTableSpy.mock.calls[0][0];
|
|
1124
|
+
expect(opts.columnStyles[0]).toBeUndefined();
|
|
1125
|
+
expect(opts.columnStyles[1]).toEqual({ halign: 'right' });
|
|
1126
|
+
expect(opts.columnStyles[2]).toEqual({ halign: 'center' });
|
|
1127
|
+
});
|
|
1128
|
+
|
|
1129
|
+
it('should offset columnStyles indices by 1 when grid has grouped items', async () => {
|
|
1130
|
+
const { PdfExportService: Svc, autoTableSpy } = await createAutoTableService();
|
|
1131
|
+
const columns = [
|
|
1132
|
+
{ id: 'desc', field: 'desc', name: 'Description', width: 200 },
|
|
1133
|
+
{ id: 'amount', field: 'amount', name: 'Amount', width: 100, pdfExportOptions: { textAlign: 'right' } },
|
|
1134
|
+
];
|
|
1135
|
+
const dataViewStub = {
|
|
1136
|
+
getGrouping: () => [{ getter: 'category' }],
|
|
1137
|
+
getLength: () => 1,
|
|
1138
|
+
getItem: () => ({ id: 1, desc: 'Test', amount: '100' }),
|
|
1139
|
+
getItemMetadata: vi.fn().mockReturnValue({}),
|
|
1140
|
+
};
|
|
1141
|
+
const gridStub = {
|
|
1142
|
+
getColumns: () => columns,
|
|
1143
|
+
getOptions: () => ({ includeColumnHeaders: true }),
|
|
1144
|
+
getData: () => dataViewStub,
|
|
1145
|
+
};
|
|
1146
|
+
const pubSubService = { publish: vi.fn() };
|
|
1147
|
+
const container = { get: () => pubSubService };
|
|
1148
|
+
const service = new Svc();
|
|
1149
|
+
service.init(gridStub as any, container as any);
|
|
1150
|
+
await service.exportToPdf({ filename: 'grouped-offset' });
|
|
1151
|
+
|
|
1152
|
+
const opts = autoTableSpy.mock.calls[0][0];
|
|
1153
|
+
// Group offset = 1, so amount (visible index 1) → columnStyles key 2
|
|
1154
|
+
expect(opts.columnStyles[2]).toEqual({ halign: 'right' });
|
|
1155
|
+
expect(opts.columnStyles[1]).toBeUndefined();
|
|
1156
|
+
});
|
|
1157
|
+
|
|
1158
|
+
it('should skip excluded columns in columnStyles indexing', async () => {
|
|
1159
|
+
const { PdfExportService: Svc, autoTableSpy } = await createAutoTableService();
|
|
1160
|
+
const columns = [
|
|
1161
|
+
{ id: 'hidden', field: 'hidden', name: 'Hidden', excludeFromExport: true },
|
|
1162
|
+
{ id: 'desc', field: 'desc', name: 'Description', width: 200 },
|
|
1163
|
+
{ id: 'amount', field: 'amount', name: 'Amount', width: 100, pdfExportOptions: { textAlign: 'right' } },
|
|
1164
|
+
];
|
|
1165
|
+
const { gridStub, container } = createStubs(columns);
|
|
1166
|
+
const service = new Svc();
|
|
1167
|
+
service.init(gridStub as any, container as any);
|
|
1168
|
+
await service.exportToPdf({ filename: 'skip-excluded' });
|
|
1169
|
+
|
|
1170
|
+
const opts = autoTableSpy.mock.calls[0][0];
|
|
1171
|
+
// 'hidden' is excluded, so visible columns are [desc, amount] → amount is index 1
|
|
1172
|
+
expect(opts.columnStyles[1]).toEqual({ halign: 'right' });
|
|
1173
|
+
expect(opts.columnStyles[0]).toBeUndefined();
|
|
1174
|
+
});
|
|
1175
|
+
|
|
1176
|
+
it('should skip zero-width columns in columnStyles indexing', async () => {
|
|
1177
|
+
const { PdfExportService: Svc, autoTableSpy } = await createAutoTableService();
|
|
1178
|
+
const columns = [
|
|
1179
|
+
{ id: 'zeroWidth', field: 'zeroWidth', name: 'Zero', width: 0 },
|
|
1180
|
+
{ id: 'desc', field: 'desc', name: 'Description', width: 200 },
|
|
1181
|
+
{ id: 'amount', field: 'amount', name: 'Amount', width: 100, pdfExportOptions: { textAlign: 'right' } },
|
|
1182
|
+
];
|
|
1183
|
+
const { gridStub, container } = createStubs(columns);
|
|
1184
|
+
const service = new Svc();
|
|
1185
|
+
service.init(gridStub as any, container as any);
|
|
1186
|
+
await service.exportToPdf({ filename: 'skip-zero-width' });
|
|
1187
|
+
|
|
1188
|
+
const opts = autoTableSpy.mock.calls[0][0];
|
|
1189
|
+
// zeroWidth is excluded, so visible columns are [desc, amount] → amount is index 1
|
|
1190
|
+
expect(opts.columnStyles[1]).toEqual({ halign: 'right' });
|
|
1191
|
+
expect(opts.columnStyles[0]).toBeUndefined();
|
|
1192
|
+
});
|
|
1193
|
+
|
|
1194
|
+
it('should include didParseCell hook that aligns header cells to match column textAlign', async () => {
|
|
1195
|
+
const { PdfExportService: Svc, autoTableSpy } = await createAutoTableService();
|
|
1196
|
+
const columns = [
|
|
1197
|
+
{ id: 'desc', field: 'desc', name: 'Description', width: 200 },
|
|
1198
|
+
{ id: 'amount', field: 'amount', name: 'Amount', width: 100, pdfExportOptions: { textAlign: 'right' as const } },
|
|
1199
|
+
{ id: 'pct', field: 'pct', name: 'Percent', width: 100, pdfExportOptions: { textAlign: 'center' as const } },
|
|
1200
|
+
];
|
|
1201
|
+
const { gridStub, container } = createStubs(columns);
|
|
1202
|
+
const service = new Svc();
|
|
1203
|
+
service.init(gridStub as any, container as any);
|
|
1204
|
+
await service.exportToPdf({ filename: 'header-align' });
|
|
1205
|
+
|
|
1206
|
+
const opts = autoTableSpy.mock.calls[0][0];
|
|
1207
|
+
expect(typeof opts.didParseCell).toBe('function');
|
|
1208
|
+
|
|
1209
|
+
// Simulate a header cell for column 1 (amount) — should set halign to 'right'
|
|
1210
|
+
const headerCell1 = { styles: { halign: 'left' } };
|
|
1211
|
+
opts.didParseCell({ section: 'head', row: { index: 0 }, column: { index: 1 }, cell: headerCell1 });
|
|
1212
|
+
expect(headerCell1.styles.halign).toBe('right');
|
|
1213
|
+
|
|
1214
|
+
// Simulate a header cell for column 2 (pct) — should set halign to 'center'
|
|
1215
|
+
const headerCell2 = { styles: { halign: 'left' } };
|
|
1216
|
+
opts.didParseCell({ section: 'head', row: { index: 0 }, column: { index: 2 }, cell: headerCell2 });
|
|
1217
|
+
expect(headerCell2.styles.halign).toBe('center');
|
|
1218
|
+
|
|
1219
|
+
// Simulate a header cell for column 0 (desc) — should NOT change (no override)
|
|
1220
|
+
const headerCell0 = { styles: { halign: 'left' } };
|
|
1221
|
+
opts.didParseCell({ section: 'head', row: { index: 0 }, column: { index: 0 }, cell: headerCell0 });
|
|
1222
|
+
expect(headerCell0.styles.halign).toBe('left');
|
|
1223
|
+
|
|
1224
|
+
// Simulate a body cell for column 1 — should NOT change (only head section is affected)
|
|
1225
|
+
const bodyCell = { styles: { halign: 'left' } };
|
|
1226
|
+
opts.didParseCell({ section: 'body', row: { index: 0 }, column: { index: 1 }, cell: bodyCell });
|
|
1227
|
+
expect(bodyCell.styles.halign).toBe('left');
|
|
1228
|
+
});
|
|
1229
|
+
|
|
1230
|
+
it('should not set didParseCell when no columns have custom textAlign', async () => {
|
|
1231
|
+
const { PdfExportService: Svc, autoTableSpy } = await createAutoTableService();
|
|
1232
|
+
const columns = [
|
|
1233
|
+
{ id: 'col1', field: 'col1', name: 'Col1', width: 100 },
|
|
1234
|
+
{ id: 'col2', field: 'col2', name: 'Col2', width: 100 },
|
|
1235
|
+
];
|
|
1236
|
+
const { gridStub, container } = createStubs(columns);
|
|
1237
|
+
const service = new Svc();
|
|
1238
|
+
service.init(gridStub as any, container as any);
|
|
1239
|
+
await service.exportToPdf({ filename: 'no-header-align' });
|
|
1240
|
+
|
|
1241
|
+
const opts = autoTableSpy.mock.calls[0][0];
|
|
1242
|
+
// didParseCell is still set (it's a no-op for default-aligned columns)
|
|
1243
|
+
expect(typeof opts.didParseCell).toBe('function');
|
|
1244
|
+
|
|
1245
|
+
// Calling it on a default column should not change anything
|
|
1246
|
+
const cell = { styles: { halign: 'left' } };
|
|
1247
|
+
opts.didParseCell({ section: 'head', row: { index: 0 }, column: { index: 0 }, cell });
|
|
1248
|
+
expect(cell.styles.halign).toBe('left');
|
|
1249
|
+
});
|
|
1250
|
+
|
|
1251
|
+
it('should include pre-header row in AutoTable head when grouped column headers are present', async () => {
|
|
1252
|
+
const { PdfExportService: Svc, autoTableSpy } = await createAutoTableService();
|
|
1253
|
+
const columns = [
|
|
1254
|
+
{ id: 'col1', field: 'col1', name: 'Col1', width: 100, columnGroup: 'Group A' },
|
|
1255
|
+
{ id: 'col2', field: 'col2', name: 'Col2', width: 100, columnGroup: 'Group A' },
|
|
1256
|
+
{ id: 'col3', field: 'col3', name: 'Col3', width: 100, columnGroup: 'Group B' },
|
|
1257
|
+
];
|
|
1258
|
+
const dataViewStub = {
|
|
1259
|
+
getGrouping: () => [],
|
|
1260
|
+
getLength: () => 1,
|
|
1261
|
+
getItem: () => ({ id: 1, col1: 'A', col2: 'B', col3: 'C' }),
|
|
1262
|
+
getItemMetadata: vi.fn().mockReturnValue({}),
|
|
1263
|
+
};
|
|
1264
|
+
const gridStub = {
|
|
1265
|
+
getColumns: () => columns,
|
|
1266
|
+
getOptions: () => ({ createPreHeaderPanel: true, showPreHeaderPanel: true }),
|
|
1267
|
+
getData: () => dataViewStub,
|
|
1268
|
+
};
|
|
1269
|
+
const pubSubService = { publish: vi.fn() };
|
|
1270
|
+
const container = { get: () => pubSubService };
|
|
1271
|
+
const service = new Svc();
|
|
1272
|
+
service.init(gridStub as any, container as any);
|
|
1273
|
+
await service.exportToPdf({ filename: 'preheader-autotable' });
|
|
1274
|
+
|
|
1275
|
+
const opts = autoTableSpy.mock.calls[0][0];
|
|
1276
|
+
// head should have 2 rows: pre-header and header
|
|
1277
|
+
expect(opts.head.length).toBe(2);
|
|
1278
|
+
// Pre-header row: Group A spans 2 cols, Group B spans 1
|
|
1279
|
+
const preHeaderRow = opts.head[0];
|
|
1280
|
+
expect(preHeaderRow).toEqual([{ content: 'Group A', colSpan: 2 }, 'Group B']);
|
|
1281
|
+
// Header row: column names
|
|
1282
|
+
expect(opts.head[1]).toEqual(['Col1', 'Col2', 'Col3']);
|
|
1283
|
+
});
|
|
1284
|
+
|
|
1285
|
+
it('should style pre-header row with preHeaderBackgroundColor via didParseCell', async () => {
|
|
1286
|
+
const { PdfExportService: Svc, autoTableSpy } = await createAutoTableService();
|
|
1287
|
+
const columns = [
|
|
1288
|
+
{ id: 'col1', field: 'col1', name: 'Col1', width: 100, columnGroup: 'Group A' },
|
|
1289
|
+
{ id: 'col2', field: 'col2', name: 'Col2', width: 100, columnGroup: 'Group A' },
|
|
1290
|
+
];
|
|
1291
|
+
const dataViewStub = {
|
|
1292
|
+
getGrouping: () => [],
|
|
1293
|
+
getLength: () => 1,
|
|
1294
|
+
getItem: () => ({ id: 1, col1: 'A', col2: 'B' }),
|
|
1295
|
+
getItemMetadata: vi.fn().mockReturnValue({}),
|
|
1296
|
+
};
|
|
1297
|
+
const gridStub = {
|
|
1298
|
+
getColumns: () => columns,
|
|
1299
|
+
getOptions: () => ({ createPreHeaderPanel: true, showPreHeaderPanel: true }),
|
|
1300
|
+
getData: () => dataViewStub,
|
|
1301
|
+
};
|
|
1302
|
+
const pubSubService = { publish: vi.fn() };
|
|
1303
|
+
const container = { get: () => pubSubService };
|
|
1304
|
+
const service = new Svc();
|
|
1305
|
+
service.init(gridStub as any, container as any);
|
|
1306
|
+
await service.exportToPdf({
|
|
1307
|
+
filename: 'preheader-colors',
|
|
1308
|
+
preHeaderBackgroundColor: [50, 60, 70],
|
|
1309
|
+
preHeaderTextColor: [200, 210, 220],
|
|
1310
|
+
});
|
|
1311
|
+
|
|
1312
|
+
const opts = autoTableSpy.mock.calls[0][0];
|
|
1313
|
+
expect(typeof opts.didParseCell).toBe('function');
|
|
1314
|
+
|
|
1315
|
+
// Pre-header row (row.index = 0) should get pre-header colors
|
|
1316
|
+
const preHeaderCell = { styles: { fillColor: [0, 0, 0], textColor: [0, 0, 0], halign: 'left' } };
|
|
1317
|
+
opts.didParseCell({ section: 'head', row: { index: 0 }, column: { index: 0 }, cell: preHeaderCell });
|
|
1318
|
+
expect(preHeaderCell.styles.fillColor).toEqual([50, 60, 70]);
|
|
1319
|
+
expect(preHeaderCell.styles.textColor).toEqual([200, 210, 220]);
|
|
1320
|
+
expect(preHeaderCell.styles.halign).toBe('center');
|
|
1321
|
+
|
|
1322
|
+
// Column header row (row.index = 1) should NOT get pre-header colors
|
|
1323
|
+
const headerCell = { styles: { fillColor: [66, 139, 202], textColor: [255, 255, 255], halign: 'left' } };
|
|
1324
|
+
opts.didParseCell({ section: 'head', row: { index: 1 }, column: { index: 0 }, cell: headerCell });
|
|
1325
|
+
expect(headerCell.styles.fillColor).toEqual([66, 139, 202]); // unchanged
|
|
1326
|
+
});
|
|
1327
|
+
|
|
1328
|
+
it('should not include pre-header row when no column groups exist', async () => {
|
|
1329
|
+
const { PdfExportService: Svc, autoTableSpy } = await createAutoTableService();
|
|
1330
|
+
const columns = [
|
|
1331
|
+
{ id: 'col1', field: 'col1', name: 'Col1', width: 100 },
|
|
1332
|
+
{ id: 'col2', field: 'col2', name: 'Col2', width: 100 },
|
|
1333
|
+
];
|
|
1334
|
+
const { gridStub, container } = createStubs(columns);
|
|
1335
|
+
const service = new Svc();
|
|
1336
|
+
service.init(gridStub as any, container as any);
|
|
1337
|
+
await service.exportToPdf({ filename: 'no-preheader' });
|
|
1338
|
+
|
|
1339
|
+
const opts = autoTableSpy.mock.calls[0][0];
|
|
1340
|
+
// head should have only 1 row (no pre-header)
|
|
1341
|
+
expect(opts.head.length).toBe(1);
|
|
1342
|
+
expect(opts.head[0]).toEqual(['Col1', 'Col2']);
|
|
1343
|
+
});
|
|
1344
|
+
|
|
1345
|
+
it('should call autoTableOptions callback and use its return value', async () => {
|
|
1346
|
+
const { PdfExportService: Svc, autoTableSpy } = await createAutoTableService();
|
|
1347
|
+
const columns = [{ id: 'col1', field: 'col1', name: 'Col1', width: 100 }];
|
|
1348
|
+
const { gridStub, container } = createStubs(columns);
|
|
1349
|
+
const service = new Svc();
|
|
1350
|
+
service.init(gridStub as any, container as any);
|
|
1351
|
+
|
|
1352
|
+
const callbackSpy = vi.fn((opts: Record<string, unknown>) => {
|
|
1353
|
+
opts.theme = 'striped';
|
|
1354
|
+
opts.margin = { left: 20, right: 20 };
|
|
1355
|
+
return opts;
|
|
1356
|
+
});
|
|
1357
|
+
|
|
1358
|
+
await service.exportToPdf({ filename: 'callback-test', autoTableOptions: callbackSpy });
|
|
1359
|
+
|
|
1360
|
+
expect(callbackSpy).toHaveBeenCalledTimes(1);
|
|
1361
|
+
// Verify the callback received the built options
|
|
1362
|
+
const cbArg = callbackSpy.mock.calls[0][0];
|
|
1363
|
+
expect(cbArg.head).toBeDefined();
|
|
1364
|
+
expect(cbArg.body).toBeDefined();
|
|
1365
|
+
expect(cbArg.columnStyles).toBeDefined();
|
|
1366
|
+
expect(cbArg.styles).toBeDefined();
|
|
1367
|
+
|
|
1368
|
+
// Verify autoTable received the mutated options
|
|
1369
|
+
const finalOpts = autoTableSpy.mock.calls[0][0];
|
|
1370
|
+
expect(finalOpts.theme).toBe('striped');
|
|
1371
|
+
expect(finalOpts.margin).toEqual({ left: 20, right: 20 });
|
|
1372
|
+
});
|
|
1373
|
+
|
|
1374
|
+
it('should allow autoTableOptions callback to override columnStyles', async () => {
|
|
1375
|
+
const { PdfExportService: Svc, autoTableSpy } = await createAutoTableService();
|
|
1376
|
+
const columns = [
|
|
1377
|
+
{ id: 'desc', field: 'desc', name: 'Description', width: 200 },
|
|
1378
|
+
{ id: 'amount', field: 'amount', name: 'Amount', width: 100, pdfExportOptions: { textAlign: 'right' } },
|
|
1379
|
+
];
|
|
1380
|
+
const { gridStub, container } = createStubs(columns);
|
|
1381
|
+
const service = new Svc();
|
|
1382
|
+
service.init(gridStub as any, container as any);
|
|
1383
|
+
|
|
1384
|
+
await service.exportToPdf({
|
|
1385
|
+
filename: 'callback-override',
|
|
1386
|
+
autoTableOptions: (opts) => {
|
|
1387
|
+
// Override the auto-built columnStyles
|
|
1388
|
+
opts.columnStyles = { 0: { halign: 'center' }, 1: { halign: 'center' } };
|
|
1389
|
+
return opts;
|
|
1390
|
+
},
|
|
1391
|
+
});
|
|
1392
|
+
|
|
1393
|
+
const finalOpts = autoTableSpy.mock.calls[0][0];
|
|
1394
|
+
expect(finalOpts.columnStyles).toEqual({ 0: { halign: 'center' }, 1: { halign: 'center' } });
|
|
1395
|
+
});
|
|
1396
|
+
|
|
1397
|
+
it('should not call autoTableOptions callback when it is not provided', async () => {
|
|
1398
|
+
const { PdfExportService: Svc, autoTableSpy } = await createAutoTableService();
|
|
1399
|
+
const columns = [{ id: 'col1', field: 'col1', name: 'Col1', width: 100 }];
|
|
1400
|
+
const { gridStub, container } = createStubs(columns);
|
|
1401
|
+
const service = new Svc();
|
|
1402
|
+
service.init(gridStub as any, container as any);
|
|
1403
|
+
await service.exportToPdf({ filename: 'no-callback' });
|
|
1404
|
+
|
|
1405
|
+
// autoTable should still be called with default options
|
|
1406
|
+
expect(autoTableSpy).toHaveBeenCalled();
|
|
1407
|
+
const opts = autoTableSpy.mock.calls[0][0];
|
|
1408
|
+
expect(opts.theme).toBe('grid');
|
|
1409
|
+
expect(opts.margin).toEqual({ left: 40, right: 40 });
|
|
1410
|
+
});
|
|
1411
|
+
|
|
1412
|
+
it('should allow autoTableOptions callback to add didDrawCell hook', async () => {
|
|
1413
|
+
const { PdfExportService: Svc, autoTableSpy } = await createAutoTableService();
|
|
1414
|
+
const columns = [{ id: 'col1', field: 'col1', name: 'Col1', width: 100 }];
|
|
1415
|
+
const { gridStub, container } = createStubs(columns);
|
|
1416
|
+
const service = new Svc();
|
|
1417
|
+
service.init(gridStub as any, container as any);
|
|
1418
|
+
|
|
1419
|
+
const didDrawCell = vi.fn();
|
|
1420
|
+
await service.exportToPdf({
|
|
1421
|
+
filename: 'hook-test',
|
|
1422
|
+
autoTableOptions: (opts) => {
|
|
1423
|
+
opts.didDrawCell = didDrawCell;
|
|
1424
|
+
return opts;
|
|
1425
|
+
},
|
|
1426
|
+
});
|
|
1427
|
+
|
|
1428
|
+
const finalOpts = autoTableSpy.mock.calls[0][0];
|
|
1429
|
+
expect(finalOpts.didDrawCell).toBe(didDrawCell);
|
|
1430
|
+
});
|
|
1431
|
+
|
|
1432
|
+
it('should use custom headerBackgroundColor and headerTextColor in autoTable headStyles', async () => {
|
|
1433
|
+
const { PdfExportService: Svc, autoTableSpy } = await createAutoTableService();
|
|
1434
|
+
const columns = [{ id: 'col1', field: 'col1', name: 'Col1', width: 100 }];
|
|
1435
|
+
const { gridStub, container } = createStubs(columns);
|
|
1436
|
+
const service = new Svc();
|
|
1437
|
+
service.init(gridStub as any, container as any);
|
|
1438
|
+
await service.exportToPdf({
|
|
1439
|
+
filename: 'custom-header-colors',
|
|
1440
|
+
headerBackgroundColor: [200, 50, 50],
|
|
1441
|
+
headerTextColor: [0, 0, 0],
|
|
1442
|
+
});
|
|
1443
|
+
|
|
1444
|
+
const opts = autoTableSpy.mock.calls[0][0];
|
|
1445
|
+
expect(opts.headStyles.fillColor).toEqual([200, 50, 50]);
|
|
1446
|
+
expect(opts.headStyles.textColor).toEqual([0, 0, 0]);
|
|
1447
|
+
});
|
|
1448
|
+
|
|
1449
|
+
it('should use default header colors when no custom colors are provided', async () => {
|
|
1450
|
+
const { PdfExportService: Svc, autoTableSpy } = await createAutoTableService();
|
|
1451
|
+
const columns = [{ id: 'col1', field: 'col1', name: 'Col1', width: 100 }];
|
|
1452
|
+
const { gridStub, container } = createStubs(columns);
|
|
1453
|
+
const service = new Svc();
|
|
1454
|
+
service.init(gridStub as any, container as any);
|
|
1455
|
+
await service.exportToPdf({ filename: 'default-header-colors' });
|
|
1456
|
+
|
|
1457
|
+
const opts = autoTableSpy.mock.calls[0][0];
|
|
1458
|
+
expect(opts.headStyles.fillColor).toEqual([66, 139, 202]);
|
|
1459
|
+
expect(opts.headStyles.textColor).toEqual([255, 255, 255]);
|
|
1460
|
+
});
|
|
1461
|
+
|
|
1462
|
+
it('should use custom alternateRowColor in autoTable alternateRowStyles', async () => {
|
|
1463
|
+
const { PdfExportService: Svc, autoTableSpy } = await createAutoTableService();
|
|
1464
|
+
const columns = [{ id: 'col1', field: 'col1', name: 'Col1', width: 100 }];
|
|
1465
|
+
const { gridStub, container } = createStubs(columns);
|
|
1466
|
+
const service = new Svc();
|
|
1467
|
+
service.init(gridStub as any, container as any);
|
|
1468
|
+
await service.exportToPdf({
|
|
1469
|
+
filename: 'custom-alt-row',
|
|
1470
|
+
alternateRowColor: [230, 240, 255],
|
|
1471
|
+
});
|
|
1472
|
+
|
|
1473
|
+
const opts = autoTableSpy.mock.calls[0][0];
|
|
1474
|
+
expect(opts.alternateRowStyles.fillColor).toEqual([230, 240, 255]);
|
|
1475
|
+
});
|
|
1476
|
+
|
|
1477
|
+
it('should use custom cellPadding in autoTable styles', async () => {
|
|
1478
|
+
const { PdfExportService: Svc, autoTableSpy } = await createAutoTableService();
|
|
1479
|
+
const columns = [{ id: 'col1', field: 'col1', name: 'Col1', width: 100 }];
|
|
1480
|
+
const { gridStub, container } = createStubs(columns);
|
|
1481
|
+
const service = new Svc();
|
|
1482
|
+
service.init(gridStub as any, container as any);
|
|
1483
|
+
await service.exportToPdf({
|
|
1484
|
+
filename: 'custom-padding',
|
|
1485
|
+
cellPadding: 8,
|
|
1486
|
+
});
|
|
1487
|
+
|
|
1488
|
+
const opts = autoTableSpy.mock.calls[0][0];
|
|
1489
|
+
expect(opts.styles.cellPadding).toBe(8);
|
|
1490
|
+
});
|
|
1491
|
+
|
|
1492
|
+
it('should use default cellPadding of 4 when not provided', async () => {
|
|
1493
|
+
const { PdfExportService: Svc, autoTableSpy } = await createAutoTableService();
|
|
1494
|
+
const columns = [{ id: 'col1', field: 'col1', name: 'Col1', width: 100 }];
|
|
1495
|
+
const { gridStub, container } = createStubs(columns);
|
|
1496
|
+
const service = new Svc();
|
|
1497
|
+
service.init(gridStub as any, container as any);
|
|
1498
|
+
await service.exportToPdf({ filename: 'default-padding' });
|
|
1499
|
+
|
|
1500
|
+
const opts = autoTableSpy.mock.calls[0][0];
|
|
1501
|
+
expect(opts.styles.cellPadding).toBe(4);
|
|
1502
|
+
});
|
|
1503
|
+
|
|
1504
|
+
it('should call setDocumentProperties when documentProperties option is provided', async () => {
|
|
1505
|
+
const { PdfExportService: Svc, setDocumentPropertiesSpy } = await createAutoTableService();
|
|
1506
|
+
const columns = [{ id: 'col1', field: 'col1', name: 'Col1', width: 100 }];
|
|
1507
|
+
const { gridStub, container } = createStubs(columns);
|
|
1508
|
+
const service = new Svc();
|
|
1509
|
+
service.init(gridStub as any, container as any);
|
|
1510
|
+
const docProps = { title: 'My Report', author: 'Test User', subject: 'Finance', keywords: 'report,pdf', creator: 'SlickGrid' };
|
|
1511
|
+
await service.exportToPdf({ filename: 'doc-props', documentProperties: docProps });
|
|
1512
|
+
|
|
1513
|
+
expect(setDocumentPropertiesSpy).toHaveBeenCalledWith(docProps);
|
|
1514
|
+
});
|
|
1515
|
+
|
|
1516
|
+
it('should not call setDocumentProperties when documentProperties option is not provided', async () => {
|
|
1517
|
+
const { PdfExportService: Svc, setDocumentPropertiesSpy } = await createAutoTableService();
|
|
1518
|
+
const columns = [{ id: 'col1', field: 'col1', name: 'Col1', width: 100 }];
|
|
1519
|
+
const { gridStub, container } = createStubs(columns);
|
|
1520
|
+
const service = new Svc();
|
|
1521
|
+
service.init(gridStub as any, container as any);
|
|
1522
|
+
await service.exportToPdf({ filename: 'no-doc-props' });
|
|
1523
|
+
|
|
1524
|
+
expect(setDocumentPropertiesSpy).not.toHaveBeenCalled();
|
|
1525
|
+
});
|
|
1526
|
+
|
|
1527
|
+
it('should include empty group column in AutoTable pre-header when grid has grouping', async () => {
|
|
1528
|
+
const { PdfExportService: Svc, autoTableSpy } = await createAutoTableService();
|
|
1529
|
+
const columns = [
|
|
1530
|
+
{ id: 'col1', field: 'col1', name: 'Col1', columnGroup: 'GroupA' },
|
|
1531
|
+
{ id: 'col2', field: 'col2', name: 'Col2', columnGroup: 'GroupA' },
|
|
1532
|
+
];
|
|
1533
|
+
const dataViewStub = {
|
|
1534
|
+
getGrouping: () => [{ getter: 'category' }],
|
|
1535
|
+
getLength: () => 1,
|
|
1536
|
+
getItem: () => ({ id: 1, col1: 'A', col2: 'B' }),
|
|
1537
|
+
getItemMetadata: vi.fn().mockReturnValue({}),
|
|
1538
|
+
};
|
|
1539
|
+
const gridStub = {
|
|
1540
|
+
getColumns: () => columns,
|
|
1541
|
+
getOptions: () => ({ createPreHeaderPanel: true, showPreHeaderPanel: true }),
|
|
1542
|
+
getData: () => dataViewStub,
|
|
1543
|
+
};
|
|
1544
|
+
const pubSubService = { publish: vi.fn() };
|
|
1545
|
+
const container = { get: () => pubSubService };
|
|
1546
|
+
const service = new Svc();
|
|
1547
|
+
service.init(gridStub as any, container as any);
|
|
1548
|
+
const result = await service.exportToPdf({ filename: 'autotable-grouped-preheader' });
|
|
1549
|
+
|
|
1550
|
+
expect(result).toBe(true);
|
|
1551
|
+
expect(autoTableSpy).toHaveBeenCalled();
|
|
1552
|
+
const opts = autoTableSpy.mock.calls[0][0];
|
|
1553
|
+
// Pre-header row should contain 2 rows (pre-header + header)
|
|
1554
|
+
expect(opts.head.length).toBe(2);
|
|
1555
|
+
// First cell of pre-header is an empty string placeholder for the group-by column
|
|
1556
|
+
expect(opts.head[0][0]).toBe('');
|
|
1040
1557
|
});
|
|
1041
1558
|
});
|
|
1042
1559
|
|
|
@@ -2077,4 +2594,309 @@ describe('PdfExportService', () => {
|
|
|
2077
2594
|
// No assertion needed, coverage is enough
|
|
2078
2595
|
});
|
|
2079
2596
|
});
|
|
2597
|
+
|
|
2598
|
+
describe('Manual fallback styling options', () => {
|
|
2599
|
+
// Helper to create a fresh PdfExportService WITHOUT autoTable (manual fallback)
|
|
2600
|
+
async function createManualService() {
|
|
2601
|
+
vi.resetModules();
|
|
2602
|
+
const setFillColorSpy = vi.fn();
|
|
2603
|
+
const setTextColorSpy = vi.fn();
|
|
2604
|
+
const setDocumentPropertiesSpy = vi.fn();
|
|
2605
|
+
function jsPDFMockManual(this: any) {
|
|
2606
|
+
this.save = vi.fn();
|
|
2607
|
+
this.setFontSize = vi.fn();
|
|
2608
|
+
this.getTextWidth = vi.fn((txt: string) => txt.length * 6);
|
|
2609
|
+
this.internal = {
|
|
2610
|
+
pageSize: {
|
|
2611
|
+
getWidth: () => 595.28,
|
|
2612
|
+
getHeight: () => 841.89,
|
|
2613
|
+
},
|
|
2614
|
+
};
|
|
2615
|
+
this.setFillColor = setFillColorSpy;
|
|
2616
|
+
this.setTextColor = setTextColorSpy;
|
|
2617
|
+
this.rect = vi.fn();
|
|
2618
|
+
this.text = vi.fn();
|
|
2619
|
+
this.addPage = vi.fn();
|
|
2620
|
+
this.setDocumentProperties = setDocumentPropertiesSpy;
|
|
2621
|
+
// No autoTable → forces manual fallback
|
|
2622
|
+
}
|
|
2623
|
+
vi.doMock('jspdf', () => ({ __esModule: true, default: jsPDFMockManual }));
|
|
2624
|
+
const { PdfExportService: Svc } = await import('./pdfExport.service.js');
|
|
2625
|
+
return { PdfExportService: Svc, setFillColorSpy, setTextColorSpy, setDocumentPropertiesSpy };
|
|
2626
|
+
}
|
|
2627
|
+
|
|
2628
|
+
afterEach(() => {
|
|
2629
|
+
vi.resetModules();
|
|
2630
|
+
});
|
|
2631
|
+
|
|
2632
|
+
it('should use custom headerBackgroundColor and headerTextColor in manual header rendering', async () => {
|
|
2633
|
+
const { PdfExportService: Svc, setFillColorSpy, setTextColorSpy } = await createManualService();
|
|
2634
|
+
|
|
2635
|
+
const columns = [{ id: 'col1', field: 'col1', name: 'Col1', width: 100 }];
|
|
2636
|
+
const dataViewStub = {
|
|
2637
|
+
getGrouping: () => [],
|
|
2638
|
+
getLength: () => 1,
|
|
2639
|
+
getItem: () => ({ id: 1, col1: 'A' }),
|
|
2640
|
+
getItemMetadata: vi.fn().mockReturnValue({}),
|
|
2641
|
+
};
|
|
2642
|
+
const gridStub = {
|
|
2643
|
+
getColumns: () => columns,
|
|
2644
|
+
getOptions: () => ({}),
|
|
2645
|
+
getData: () => dataViewStub,
|
|
2646
|
+
};
|
|
2647
|
+
const pubSubService = { publish: vi.fn() };
|
|
2648
|
+
const container = { get: () => pubSubService };
|
|
2649
|
+
const service = new Svc();
|
|
2650
|
+
service.init(gridStub as any, container as any);
|
|
2651
|
+
|
|
2652
|
+
await service.exportToPdf({
|
|
2653
|
+
filename: 'manual-header-colors',
|
|
2654
|
+
headerBackgroundColor: [10, 20, 30],
|
|
2655
|
+
headerTextColor: [200, 200, 200],
|
|
2656
|
+
});
|
|
2657
|
+
|
|
2658
|
+
expect(setFillColorSpy).toHaveBeenCalledWith(10, 20, 30);
|
|
2659
|
+
expect(setTextColorSpy).toHaveBeenCalledWith(200, 200, 200);
|
|
2660
|
+
});
|
|
2661
|
+
|
|
2662
|
+
it('should use custom preHeaderBackgroundColor and preHeaderTextColor in manual pre-header rendering', async () => {
|
|
2663
|
+
const { PdfExportService: Svc, setFillColorSpy, setTextColorSpy } = await createManualService();
|
|
2664
|
+
|
|
2665
|
+
const columns = [
|
|
2666
|
+
{ id: 'col1', field: 'col1', name: 'Col1', width: 100, columnGroup: 'GroupA' },
|
|
2667
|
+
{ id: 'col2', field: 'col2', name: 'Col2', width: 100, columnGroup: 'GroupA' },
|
|
2668
|
+
];
|
|
2669
|
+
const dataViewStub = {
|
|
2670
|
+
getGrouping: () => [],
|
|
2671
|
+
getLength: () => 1,
|
|
2672
|
+
getItem: () => ({ id: 1, col1: 'A', col2: 'B' }),
|
|
2673
|
+
getItemMetadata: vi.fn().mockReturnValue({}),
|
|
2674
|
+
};
|
|
2675
|
+
const gridStub = {
|
|
2676
|
+
getColumns: () => columns,
|
|
2677
|
+
getOptions: () => ({ createPreHeaderPanel: true, showPreHeaderPanel: true }),
|
|
2678
|
+
getData: () => dataViewStub,
|
|
2679
|
+
};
|
|
2680
|
+
const pubSubService = { publish: vi.fn() };
|
|
2681
|
+
const container = { get: () => pubSubService };
|
|
2682
|
+
const service = new Svc();
|
|
2683
|
+
service.init(gridStub as any, container as any);
|
|
2684
|
+
|
|
2685
|
+
await service.exportToPdf({
|
|
2686
|
+
filename: 'manual-preheader-colors',
|
|
2687
|
+
preHeaderBackgroundColor: [50, 60, 70],
|
|
2688
|
+
preHeaderTextColor: [100, 110, 120],
|
|
2689
|
+
});
|
|
2690
|
+
|
|
2691
|
+
// Pre-header colors should have been called
|
|
2692
|
+
expect(setFillColorSpy).toHaveBeenCalledWith(50, 60, 70);
|
|
2693
|
+
expect(setTextColorSpy).toHaveBeenCalledWith(100, 110, 120);
|
|
2694
|
+
});
|
|
2695
|
+
|
|
2696
|
+
it('should use custom alternateRowColor in manual fallback alternate row rendering', async () => {
|
|
2697
|
+
const { PdfExportService: Svc, setFillColorSpy } = await createManualService();
|
|
2698
|
+
|
|
2699
|
+
const columns = [{ id: 'col1', field: 'col1', name: 'Col1', width: 100 }];
|
|
2700
|
+
const dataViewStub = {
|
|
2701
|
+
getGrouping: () => [],
|
|
2702
|
+
getLength: () => 3,
|
|
2703
|
+
getItem: (idx: number) => ({ id: idx, col1: `Val${idx}` }),
|
|
2704
|
+
getItemMetadata: vi.fn().mockReturnValue({}),
|
|
2705
|
+
};
|
|
2706
|
+
const gridStub = {
|
|
2707
|
+
getColumns: () => columns,
|
|
2708
|
+
getOptions: () => ({}),
|
|
2709
|
+
getData: () => dataViewStub,
|
|
2710
|
+
};
|
|
2711
|
+
const pubSubService = { publish: vi.fn() };
|
|
2712
|
+
const container = { get: () => pubSubService };
|
|
2713
|
+
const service = new Svc();
|
|
2714
|
+
service.init(gridStub as any, container as any);
|
|
2715
|
+
|
|
2716
|
+
await service.exportToPdf({
|
|
2717
|
+
filename: 'manual-alt-row',
|
|
2718
|
+
alternateRowColor: [220, 230, 240],
|
|
2719
|
+
});
|
|
2720
|
+
|
|
2721
|
+
// Alternate row (row index 1) should use the custom color
|
|
2722
|
+
expect(setFillColorSpy).toHaveBeenCalledWith(220, 230, 240);
|
|
2723
|
+
});
|
|
2724
|
+
|
|
2725
|
+
it('should align header cells according to per-column textAlign in manual fallback', async () => {
|
|
2726
|
+
vi.resetModules();
|
|
2727
|
+
const textSpy = vi.fn();
|
|
2728
|
+
function jsPDFMockManual(this: any) {
|
|
2729
|
+
this.save = vi.fn();
|
|
2730
|
+
this.setFontSize = vi.fn();
|
|
2731
|
+
this.getTextWidth = vi.fn((txt: string) => txt.length * 6);
|
|
2732
|
+
this.internal = {
|
|
2733
|
+
pageSize: {
|
|
2734
|
+
getWidth: () => 595.28,
|
|
2735
|
+
getHeight: () => 841.89,
|
|
2736
|
+
},
|
|
2737
|
+
};
|
|
2738
|
+
this.setFillColor = vi.fn();
|
|
2739
|
+
this.setTextColor = vi.fn();
|
|
2740
|
+
this.rect = vi.fn();
|
|
2741
|
+
this.text = textSpy;
|
|
2742
|
+
this.addPage = vi.fn();
|
|
2743
|
+
}
|
|
2744
|
+
vi.doMock('jspdf', () => ({ __esModule: true, default: jsPDFMockManual }));
|
|
2745
|
+
const { PdfExportService: Svc } = await import('./pdfExport.service.js');
|
|
2746
|
+
|
|
2747
|
+
const columns = [
|
|
2748
|
+
{ id: 'desc', field: 'desc', name: 'Description', width: 200 },
|
|
2749
|
+
{ id: 'amount', field: 'amount', name: 'Amount', width: 100, pdfExportOptions: { textAlign: 'right' as const } },
|
|
2750
|
+
{ id: 'pct', field: 'pct', name: 'Percent', width: 100, pdfExportOptions: { textAlign: 'center' as const } },
|
|
2751
|
+
];
|
|
2752
|
+
const dataViewStub = {
|
|
2753
|
+
getGrouping: () => [],
|
|
2754
|
+
getLength: () => 1,
|
|
2755
|
+
getItem: () => ({ id: 1, desc: 'A', amount: '100', pct: '50%' }),
|
|
2756
|
+
getItemMetadata: vi.fn().mockReturnValue({}),
|
|
2757
|
+
};
|
|
2758
|
+
const gridStub = {
|
|
2759
|
+
getColumns: () => columns,
|
|
2760
|
+
getOptions: () => ({}),
|
|
2761
|
+
getData: () => dataViewStub,
|
|
2762
|
+
};
|
|
2763
|
+
const pubSubService = { publish: vi.fn() };
|
|
2764
|
+
const container = { get: () => pubSubService };
|
|
2765
|
+
const service = new Svc();
|
|
2766
|
+
service.init(gridStub as any, container as any);
|
|
2767
|
+
|
|
2768
|
+
await service.exportToPdf({ filename: 'manual-header-align' });
|
|
2769
|
+
|
|
2770
|
+
// Find header text calls — the first 3 text calls after rect are headers
|
|
2771
|
+
// Headers: Description (left), Amount (right), Percent (center)
|
|
2772
|
+
const headerCalls = textSpy.mock.calls.filter((call: any[]) => call[0] === 'Description' || call[0] === 'Amount' || call[0] === 'Percent');
|
|
2773
|
+
expect(headerCalls.length).toBeGreaterThanOrEqual(3);
|
|
2774
|
+
|
|
2775
|
+
// Description header should be left-aligned
|
|
2776
|
+
const descCall = headerCalls.find((c: any[]) => c[0] === 'Description');
|
|
2777
|
+
expect(descCall[3].align).toBe('left');
|
|
2778
|
+
|
|
2779
|
+
// Amount header should be right-aligned
|
|
2780
|
+
const amountCall = headerCalls.find((c: any[]) => c[0] === 'Amount');
|
|
2781
|
+
expect(amountCall[3].align).toBe('right');
|
|
2782
|
+
|
|
2783
|
+
// Percent header should be center-aligned
|
|
2784
|
+
const pctCall = headerCalls.find((c: any[]) => c[0] === 'Percent');
|
|
2785
|
+
expect(pctCall[3].align).toBe('center');
|
|
2786
|
+
});
|
|
2787
|
+
|
|
2788
|
+
it('should call setDocumentProperties in manual fallback when documentProperties is provided', async () => {
|
|
2789
|
+
const { PdfExportService: Svc, setDocumentPropertiesSpy } = await createManualService();
|
|
2790
|
+
const columns = [{ id: 'col1', field: 'col1', name: 'Col1', width: 100 }];
|
|
2791
|
+
const dataViewStub = {
|
|
2792
|
+
getGrouping: () => [],
|
|
2793
|
+
getLength: () => 1,
|
|
2794
|
+
getItem: () => ({ id: 1, col1: 'A' }),
|
|
2795
|
+
getItemMetadata: vi.fn().mockReturnValue({}),
|
|
2796
|
+
};
|
|
2797
|
+
const gridStub = {
|
|
2798
|
+
getColumns: () => columns,
|
|
2799
|
+
getOptions: () => ({}),
|
|
2800
|
+
getData: () => dataViewStub,
|
|
2801
|
+
};
|
|
2802
|
+
const pubSubService = { publish: vi.fn() };
|
|
2803
|
+
const container = { get: () => pubSubService };
|
|
2804
|
+
const service = new Svc();
|
|
2805
|
+
service.init(gridStub as any, container as any);
|
|
2806
|
+
const docProps = { title: 'Manual Report', author: 'Jane Doe' };
|
|
2807
|
+
await service.exportToPdf({ filename: 'manual-doc-props', documentProperties: docProps });
|
|
2808
|
+
|
|
2809
|
+
expect(setDocumentPropertiesSpy).toHaveBeenCalledWith(docProps);
|
|
2810
|
+
});
|
|
2811
|
+
|
|
2812
|
+
it('should not call setDocumentProperties in manual fallback when documentProperties is not provided', async () => {
|
|
2813
|
+
const { PdfExportService: Svc, setDocumentPropertiesSpy } = await createManualService();
|
|
2814
|
+
const columns = [{ id: 'col1', field: 'col1', name: 'Col1', width: 100 }];
|
|
2815
|
+
const dataViewStub = {
|
|
2816
|
+
getGrouping: () => [],
|
|
2817
|
+
getLength: () => 1,
|
|
2818
|
+
getItem: () => ({ id: 1, col1: 'A' }),
|
|
2819
|
+
getItemMetadata: vi.fn().mockReturnValue({}),
|
|
2820
|
+
};
|
|
2821
|
+
const gridStub = {
|
|
2822
|
+
getColumns: () => columns,
|
|
2823
|
+
getOptions: () => ({}),
|
|
2824
|
+
getData: () => dataViewStub,
|
|
2825
|
+
};
|
|
2826
|
+
const pubSubService = { publish: vi.fn() };
|
|
2827
|
+
const container = { get: () => pubSubService };
|
|
2828
|
+
const service = new Svc();
|
|
2829
|
+
service.init(gridStub as any, container as any);
|
|
2830
|
+
await service.exportToPdf({ filename: 'no-manual-doc-props' });
|
|
2831
|
+
|
|
2832
|
+
expect(setDocumentPropertiesSpy).not.toHaveBeenCalled();
|
|
2833
|
+
});
|
|
2834
|
+
|
|
2835
|
+
it('should fall back to default textAlign when column has no id in headerAligns', async () => {
|
|
2836
|
+
const { PdfExportService: Svc } = await createManualService();
|
|
2837
|
+
// Column without an id — getColumnHeaders still includes it, but headerAligns fallback (line 343) is hit
|
|
2838
|
+
const columns = [{ field: 'col1', name: 'Col1', width: 100 } as any, { id: 'col2', field: 'col2', name: 'Col2', width: 100 }];
|
|
2839
|
+
const dataViewStub = {
|
|
2840
|
+
getGrouping: () => [],
|
|
2841
|
+
getLength: () => 1,
|
|
2842
|
+
getItem: () => ({ col1: 'A', col2: 'B', id: 1 }),
|
|
2843
|
+
getItemMetadata: vi.fn().mockReturnValue({}),
|
|
2844
|
+
};
|
|
2845
|
+
const gridStub = {
|
|
2846
|
+
getColumns: () => columns,
|
|
2847
|
+
getOptions: () => ({}),
|
|
2848
|
+
getData: () => dataViewStub,
|
|
2849
|
+
};
|
|
2850
|
+
const pubSubService = { publish: vi.fn() };
|
|
2851
|
+
const container = { get: () => pubSubService };
|
|
2852
|
+
const service = new Svc();
|
|
2853
|
+
service.init(gridStub as any, container as any);
|
|
2854
|
+
// Should not throw; the fallback 'left' alignment is used for the id-less column (no textAlign set)
|
|
2855
|
+
const result = await service.exportToPdf({ filename: 'no-id-col' });
|
|
2856
|
+
expect(result).toBe(true);
|
|
2857
|
+
});
|
|
2858
|
+
});
|
|
2859
|
+
|
|
2860
|
+
describe('private _drawPreHeaderRow and _drawHeaderRow direct coverage', () => {
|
|
2861
|
+
function makeDoc() {
|
|
2862
|
+
return {
|
|
2863
|
+
setFontSize: vi.fn(),
|
|
2864
|
+
setFillColor: vi.fn(),
|
|
2865
|
+
setTextColor: vi.fn(),
|
|
2866
|
+
rect: vi.fn(),
|
|
2867
|
+
text: vi.fn(),
|
|
2868
|
+
};
|
|
2869
|
+
}
|
|
2870
|
+
|
|
2871
|
+
it('should use default color/font values in _drawPreHeaderRow when _exportOptions has none', () => {
|
|
2872
|
+
const service = new PdfExportService();
|
|
2873
|
+
const doc = makeDoc();
|
|
2874
|
+
// empty _exportOptions ensures all ?? and || defaults fire
|
|
2875
|
+
(service as any)._exportOptions = {};
|
|
2876
|
+
(service as any)._groupedColumnHeaders = [{ title: 'GroupA', span: 2 }];
|
|
2877
|
+
(service as any)._hasGroupedItems = false;
|
|
2878
|
+
(service as any)._drawPreHeaderRow(doc, 50, [100, 100], 40, -10, 0);
|
|
2879
|
+
// headerFontSize || 11
|
|
2880
|
+
expect(doc.setFontSize).toHaveBeenCalledWith(11);
|
|
2881
|
+
// preHeaderBackgroundColor ?? [108, 117, 125]
|
|
2882
|
+
expect(doc.setFillColor).toHaveBeenCalledWith(108, 117, 125);
|
|
2883
|
+
// preHeaderTextColor ?? [255, 255, 255]
|
|
2884
|
+
expect(doc.setTextColor).toHaveBeenCalledWith(255, 255, 255);
|
|
2885
|
+
});
|
|
2886
|
+
|
|
2887
|
+
it('should use default color values in _drawHeaderRow when _exportOptions has none, and use left align when headerAligns is not provided', () => {
|
|
2888
|
+
const service = new PdfExportService();
|
|
2889
|
+
const doc = makeDoc();
|
|
2890
|
+
(service as any)._exportOptions = {};
|
|
2891
|
+
// no headerAligns argument → || 'left' branch fires
|
|
2892
|
+
(service as any)._drawHeaderRow(doc, 50, ['Col1', 'Col2'], [100, 100], 40, -10, 0);
|
|
2893
|
+
// headerBackgroundColor ?? [66, 139, 202]
|
|
2894
|
+
expect(doc.setFillColor).toHaveBeenCalledWith(66, 139, 202);
|
|
2895
|
+
// headerTextColor ?? [255, 255, 255]
|
|
2896
|
+
expect(doc.setTextColor).toHaveBeenCalledWith(255, 255, 255);
|
|
2897
|
+
// all headers use 'left' because headerAligns is undefined
|
|
2898
|
+
const textCalls = (doc.text as ReturnType<typeof vi.fn>).mock.calls;
|
|
2899
|
+
textCalls.forEach((call: any[]) => expect(call[3].align).toBe('left'));
|
|
2900
|
+
});
|
|
2901
|
+
});
|
|
2080
2902
|
});
|