diff --git a/packages/common/src/interfaces/excelExportOption.interface.ts b/packages/common/src/interfaces/excelExportOption.interface.ts index 186665d6d..96c91be9f 100644 --- a/packages/common/src/interfaces/excelExportOption.interface.ts +++ b/packages/common/src/interfaces/excelExportOption.interface.ts @@ -28,6 +28,14 @@ export interface ExcelExportOption { /** file type format, .xls/.xlsx (this will provide the extension) */ format?: FileType.xls | FileType.xlsx; + /** + * file MIME type could be provided by the user. + * - when undefined it will detect the type depending on its extension unless user defines it. + * - user could also be set to an empty string, which in this case would lead to an empty MIME type: + * - ie Salesforce restricts Excel MIME types, however we can go around this issue by not providing any MIME type + */ + mimeType?: string; + /** The column header title (at A0 in Excel) of the Group by. If nothing is provided it will use "Group By" (which is a translated value of GROUP_BY i18n) */ groupingColumnHeaderTitle?: string; diff --git a/packages/excel-export/src/excelExport.service.spec.ts b/packages/excel-export/src/excelExport.service.spec.ts index bdfbe938c..c9f9a98d1 100644 --- a/packages/excel-export/src/excelExport.service.spec.ts +++ b/packages/excel-export/src/excelExport.service.spec.ts @@ -16,6 +16,7 @@ import { SortComparers, SortDirectionNumber, } from '@slickgrid-universal/common'; +import * as ExcelBuilder from 'excel-builder-webpacker'; import { ContainerServiceStub } from '../../../test/containerServiceStub'; import { TranslateServiceStub } from '../../../test/translateServiceStub'; import { ExcelExportService } from './excelExport.service'; @@ -143,6 +144,10 @@ describe('ExcelExportService', () => { jest.spyOn(gridStub, 'getColumns').mockReturnValue(mockColumns); }); + afterEach(() => { + jest.clearAllMocks(); + }); + it('should throw an error when trying call exportToExcel" without a grid and/or dataview object initialized', async () => { try { service.init(null as any, container); @@ -174,6 +179,7 @@ describe('ExcelExportService', () => { }); it('should call "URL.createObjectURL" with a Blob and xlsx file when browser is not IE11 (basically any other browser) when exporting as xlsx', async () => { + const excelBuilderSpy = jest.spyOn(ExcelBuilder.Builder, 'createFile'); const optionExpectation = { filename: 'export.xlsx', format: FileType.xlsx }; const pubSubSpy = jest.spyOn(pubSubServiceStub, 'publish'); const spyUrlCreate = jest.spyOn(URL, 'createObjectURL'); @@ -184,8 +190,40 @@ describe('ExcelExportService', () => { expect(result).toBeTruthy(); expect(pubSubSpy).toHaveBeenCalledWith(`onAfterExportToExcel`, optionExpectation); expect(spyUrlCreate).toHaveBeenCalledWith(mockExcelBlob); + expect(excelBuilderSpy).toHaveBeenCalledWith(expect.anything(), { type: 'blob', mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }); }); + it('should call "URL.createObjectURL" with a Blob and xlsx file without any MIME type when providing an empty string as a mime type', async () => { + const excelBuilderSpy = jest.spyOn(ExcelBuilder.Builder, 'createFile'); + mockGridOptions.excelExportOptions = { mimeType: '' }; + const optionExpectation = { filename: 'export.xlsx', format: FileType.xlsx }; + const pubSubSpy = jest.spyOn(pubSubServiceStub, 'publish'); + const spyUrlCreate = jest.spyOn(URL, 'createObjectURL'); + + service.init(gridStub, container); + const result = await service.exportToExcel(mockExportExcelOptions); + + expect(result).toBeTruthy(); + expect(pubSubSpy).toHaveBeenCalledWith(`onAfterExportToExcel`, optionExpectation); + expect(spyUrlCreate).toHaveBeenCalledWith(mockExcelBlob); + expect(excelBuilderSpy).toHaveBeenCalledWith(expect.anything(), { type: 'blob' }); + }); + + it('should call "URL.createObjectURL" with a Blob and expect same mime type when provided', async () => { + const excelBuilderSpy = jest.spyOn(ExcelBuilder.Builder, 'createFile'); + mockGridOptions.excelExportOptions = { mimeType: 'application/some-excel-format' }; + const optionExpectation = { filename: 'export.xlsx', format: FileType.xlsx }; + const pubSubSpy = jest.spyOn(pubSubServiceStub, 'publish'); + const spyUrlCreate = jest.spyOn(URL, 'createObjectURL'); + + service.init(gridStub, container); + const result = await service.exportToExcel(mockExportExcelOptions); + + expect(result).toBeTruthy(); + expect(pubSubSpy).toHaveBeenCalledWith(`onAfterExportToExcel`, optionExpectation); + expect(spyUrlCreate).toHaveBeenCalledWith(mockExcelBlob); + expect(excelBuilderSpy).toHaveBeenCalledWith(expect.anything(), { type: 'blob', mimeType: 'application/some-excel-format' }); + }); it('should call "msSaveOrOpenBlob" with a Blob and xlsx file when browser is IE11 when exporting as xlsx', async () => { const optionExpectation = { filename: 'export.xlsx', format: FileType.xlsx }; diff --git a/packages/excel-export/src/excelExport.service.ts b/packages/excel-export/src/excelExport.service.ts index 9bff88241..aba6af04e 100644 --- a/packages/excel-export/src/excelExport.service.ts +++ b/packages/excel-export/src/excelExport.service.ts @@ -169,9 +169,16 @@ export class ExcelExportService implements ExternalResource, BaseExcelExportServ this._workbook.addWorksheet(this._sheet); // using ExcelBuilder.Builder.createFile with WebPack but ExcelBuilder.createFile with RequireJS/SystemJS - const createFileFn = ExcelBuilder.Builder && ExcelBuilder.Builder.createFile ? ExcelBuilder.Builder.createFile : ExcelBuilder.createFile; - const mimeType = this._fileFormat === FileType.xls ? 'application/vnd.ms-excel' : 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet '; - const excelBlob = await createFileFn(this._workbook, { type: 'blob', mimeType }); + const createFileFn = ExcelBuilder.Builder?.createFile ?? ExcelBuilder.createFile; + + // MIME type could be undefined, if that's the case we'll detect the type by its file extension + // user could also provide its own mime type, if however an empty string is provided we will consider to be without any MIME type) + let mimeType = this._excelExportOptions?.mimeType; + if (mimeType === undefined) { + mimeType = this._fileFormat === FileType.xls ? 'application/vnd.ms-excel' : 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'; + } + const createFileOptions = mimeType === '' ? { type: 'blob' } : { type: 'blob', mimeType }; + const excelBlob = await createFileFn(this._workbook, createFileOptions); const downloadOptions = { filename: `${this._excelExportOptions.filename}.${this._fileFormat}`, format: this._fileFormat