Skip to content

Commit

Permalink
Merge pull request #730 from ghiscoding/bugfix/export-colspan
Browse files Browse the repository at this point in the history
fix(exports): grid with colspan should be export accordingly
  • Loading branch information
ghiscoding authored Apr 16, 2021
2 parents c1fdde9 + c282dff commit 003738c
Show file tree
Hide file tree
Showing 8 changed files with 396 additions and 151 deletions.
16 changes: 8 additions & 8 deletions src/app/examples/grid-colspan.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export class GridColspanComponent implements OnInit {
this.gridOptions1 = {
enableAutoResize: false,
enableCellNavigation: true,
enableExport: true,
enableSorting: true,
createPreHeaderPanel: true,
showPreHeaderPanel: true,
Expand Down Expand Up @@ -127,14 +128,13 @@ export class GridColspanComponent implements OnInit {
}
}
};
} else {
return {
columns: {
0: {
colspan: '*' // starting at column index 0, we will span accross all column (*)
}
}
};
}
return {
columns: {
0: {
colspan: '*' // starting at column index 0, we will span accross all column (*)
}
}
};
}
}
14 changes: 14 additions & 0 deletions src/app/modules/angular-slickgrid/index.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import * as entry from './index';

describe('Testing library entry point', () => {
it('should have an index entry point defined', () => {
expect(entry).toBeTruthy();
});

it('should have all exported object defined', () => {
expect(typeof entry.AngularSlickgridComponent).toBe('function');
expect(typeof entry.AngularSlickgridModule).toBe('function');
expect(typeof entry.SlickgridConfig).toBe('function');
expect(typeof entry.SlickPaginationComponent).toBe('function');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ import * as moment from 'moment-mini';

import {
Column,
ExcelExportOption,
FieldType,
FileType,
Formatter,
GridOption,
FieldType,
GroupTotalsFormatter,
ItemMetadata,
SortDirectionNumber,
ExcelExportOption,
} from '../../models';
import { ExcelExportService } from '../excelExport.service';
import { Formatters } from '../../formatters/index';
Expand All @@ -20,8 +21,8 @@ import { GroupTotalFormatters } from '../..';
// URL object is not supported in JSDOM, we can simply mock it
(global as any).URL.createObjectURL = jest.fn();

const myBoldHtmlFormatter: Formatter = (row, cell, value, columnDef, dataContext) => value !== null ? { text: `<b>${value}</b>` } : null;
const myUppercaseFormatter: Formatter = (row, cell, value, columnDef, dataContext) => value ? { text: value.toUpperCase() } : null;
const myBoldHtmlFormatter: Formatter = (row, cell, value) => value !== null ? { text: `<b>${value}</b>` } : '';
const myUppercaseFormatter: Formatter = (row, cell, value) => value ? { text: value.toUpperCase() } : '';
const myUppercaseGroupTotalFormatter: GroupTotalsFormatter = (totals: any, columnDef: Column) => {
const field = columnDef.field || '';
const val = totals.sum && totals.sum[field];
Expand All @@ -44,6 +45,7 @@ const myCustomObjectFormatter: Formatter = (row: number, cell: number, value: an
const dataViewStub = {
getGrouping: jest.fn(),
getItem: jest.fn(),
getItemMetadata: jest.fn(),
getLength: jest.fn(),
setGrouping: jest.fn(),
};
Expand All @@ -58,6 +60,7 @@ const gridStub = {
getColumnIndex: jest.fn(),
getOptions: () => mockGridOptions,
getColumns: jest.fn(),
getData: () => dataViewStub,
getGrouping: jest.fn(),
};

Expand All @@ -72,7 +75,7 @@ describe('ExcelExportService', () => {
beforeEach(() => {
// @ts-ignore
navigator.__defineGetter__('appName', () => 'Netscape');
navigator.msSaveOrOpenBlob = undefined;
navigator.msSaveOrOpenBlob = undefined as any;
mockExcelBlob = new Blob(['', ''], { type: `text/xlsx;charset=utf-8;` });

mockExportExcelOptions = {
Expand Down Expand Up @@ -535,7 +538,6 @@ describe('ExcelExportService', () => {
jest.spyOn(gridStub, 'getColumns').mockReturnValue(mockColumns);
});


it(`should expect Date exported correctly when Field Type is provided and we use "exportWithFormatter" set to True & False`, async () => {
mockCollection = [
{ id: 0, userId: '1E06', firstName: 'John', lastName: 'Z', position: 'SALES_REP', startDate: '2005-12-20T18:19:19.992Z', endDate: null },
Expand Down Expand Up @@ -696,10 +698,10 @@ describe('ExcelExportService', () => {
aggregateEmpty: false,
aggregators: [{ _count: 2, _field: 'order', _nonNullCount: 2, _sum: 4, }],
collapsed: false,
comparer: (a, b) => Sorters.numeric(a.value, b.value, SortDirectionNumber.asc),
comparer: (a: any, b: any) => Sorters.numeric(a.value, b.value, SortDirectionNumber.asc),
compiledAccumulators: [jest.fn(), jest.fn()],
displayTotalsRow: true,
formatter: (g) => `Order: ${g.value} <span style="color:green">(${g.count} items)</span>`,
formatter: (g: any) => `Order: ${g.value} <span style="color:green">(${g.count} items)</span>`,
getter: 'order',
getterIsAFn: false,
lazyTotalsCalculation: true,
Expand Down Expand Up @@ -790,10 +792,10 @@ describe('ExcelExportService', () => {
aggregateEmpty: false,
aggregators: [{ _count: 2, _field: 'order', _nonNullCount: 2, _sum: 4, }],
collapsed: false,
comparer: (a, b) => Sorters.numeric(a.value, b.value, SortDirectionNumber.asc),
comparer: (a: any, b: any) => Sorters.numeric(a.value, b.value, SortDirectionNumber.asc),
compiledAccumulators: [jest.fn(), jest.fn()],
displayTotalsRow: true,
formatter: (g) => `Order: ${g.value} <span style="color:green">(${g.count} items)</span>`,
formatter: (g: any) => `Order: ${g.value} <span style="color:green">(${g.count} items)</span>`,
getter: 'order',
getterIsAFn: false,
lazyTotalsCalculation: true,
Expand Down Expand Up @@ -888,10 +890,10 @@ describe('ExcelExportService', () => {
aggregateEmpty: false,
aggregators: [{ _count: 2, _field: 'order', _nonNullCount: 2, _sum: 4, }],
collapsed: false,
comparer: (a, b) => Sorters.numeric(a.value, b.value, SortDirectionNumber.asc),
comparer: (a: any, b: any) => Sorters.numeric(a.value, b.value, SortDirectionNumber.asc),
compiledAccumulators: [jest.fn(), jest.fn()],
displayTotalsRow: true,
formatter: (g) => `Order: ${g.value} <span style="color:green">(${g.count} items)</span>`,
formatter: (g: any) => `Order: ${g.value} <span style="color:green">(${g.count} items)</span>`,
getter: 'order',
getterIsAFn: false,
lazyTotalsCalculation: true,
Expand All @@ -904,10 +906,10 @@ describe('ExcelExportService', () => {
aggregateEmpty: false,
aggregators: [{ _count: 1, _field: 'lastName', _nonNullCount: 2, _sum: 4, }],
collapsed: false,
comparer: (a, b) => Sorters.numeric(a.value, b.value, SortDirectionNumber.asc),
comparer: (a: any, b: any) => Sorters.numeric(a.value, b.value, SortDirectionNumber.asc),
compiledAccumulators: [jest.fn(), jest.fn()],
displayTotalsRow: true,
formatter: (g) => `Last Name: ${g.value} <span style="color:green">(${g.count} items)</span>`,
formatter: (g: any) => `Last Name: ${g.value} <span style="color:green">(${g.count} items)</span>`,
getter: 'lastName',
getterIsAFn: false,
lazyTotalsCalculation: true,
Expand Down Expand Up @@ -998,7 +1000,7 @@ describe('ExcelExportService', () => {
it(`should have a xlsx export with grouping but without indentation when "addGroupIndentation" is set to False
and field should be exported as metadata when "exportWithFormatter" is false and the field type is number`, async () => {
mockColumns[5].exportWithFormatter = false; // "order" field that is of type number will be exported as a number cell format metadata
mockGridOptions.excelExportOptions.addGroupIndentation = false;
mockGridOptions.excelExportOptions!.addGroupIndentation = false;
const spyOnAfter = jest.spyOn(service.onGridAfterExportToExcel, 'next');
const spyUrlCreate = jest.spyOn(URL, 'createObjectURL');
const spyDownload = jest.spyOn(service, 'startDownloadFile');
Expand All @@ -1022,9 +1024,9 @@ describe('ExcelExportService', () => {
],
['Order: 20 (2 items)'],
['Last Name: Z (1 items)'],
['', '1E06', 'John', 'Z', 'Sales Rep.', { metadata: { style: 3 }, value: '10', }],
['', '1E06', 'John', 'Z', 'Sales Rep.', { metadata: { style: 3, type: 'number' }, value: 10, }],
['Last Name: Doe (1 items)'],
['', '2B02', 'Jane', 'DOE', 'Finance Manager', { metadata: { style: 3 }, value: '10', }],
['', '2B02', 'Jane', 'DOE', 'Finance Manager', { metadata: { style: 3, type: 'number' }, value: 10, }],
['Last Name: null (0 items)'],
['', '', '', '', '', '20'],
['', '', '', '', '', '10'],
Expand Down Expand Up @@ -1367,7 +1369,7 @@ describe('ExcelExportService', () => {
});

it(`should have the LastName header title translated when defined as a "headerKey" and "i18n" is set in grid option`, async () => {
mockGridOptions.excelExportOptions.sanitizeDataExport = false;
mockGridOptions.excelExportOptions!.sanitizeDataExport = false;
mockCollection2 = [{ id: 0, userId: '1E06', firstName: 'John', lastName: 'Z', position: 'SALES_REP', order: 10 }];
jest.spyOn(dataViewStub, 'getLength').mockReturnValue(mockCollection2.length);
jest.spyOn(dataViewStub, 'getItem').mockReturnValue(null).mockReturnValueOnce(mockCollection2[0]);
Expand Down Expand Up @@ -1404,6 +1406,83 @@ describe('ExcelExportService', () => {
});
});
});

describe('grid with colspan', () => {
let mockCollection;
let oddMetatadata = { columns: { lastName: { colspan: 2 } } } as ItemMetadata;
let evenMetatadata = { columns: { 0: { colspan: '*' } } } as ItemMetadata;

beforeEach(() => {
mockGridOptions.enableTranslate = true;
mockGridOptions.i18n = translate;
mockGridOptions.excelExportOptions = {};
mockGridOptions.createPreHeaderPanel = false;
mockGridOptions.showPreHeaderPanel = false;
mockGridOptions.colspanCallback = (item: any) => (item.id % 2 === 1) ? evenMetatadata : oddMetatadata;

mockColumns = [
{ id: 'userId', field: 'userId', name: 'User Id', width: 100 },
{ id: 'firstName', nameKey: 'FIRST_NAME', width: 100, formatter: myBoldHtmlFormatter },
{ id: 'lastName', field: 'lastName', nameKey: 'LAST_NAME', width: 100, formatter: myBoldHtmlFormatter, exportCustomFormatter: myUppercaseFormatter, sanitizeDataExport: true, exportWithFormatter: true },
{ id: 'position', field: 'position', name: 'Position', width: 100, formatter: Formatters.translate, exportWithFormatter: true },
{ id: 'order', field: 'order', width: 100, },
] as Column[];

jest.spyOn(gridStub, 'getColumns').mockReturnValue(mockColumns);
});

afterEach(() => {
jest.clearAllMocks();
});

it('should return associated Excel column name when calling "getExcelColumnNameByIndex" method with a column index', () => {
const excelColumnA = service.getExcelColumnNameByIndex(1);
const excelColumnZ = service.getExcelColumnNameByIndex(26);
const excelColumnAA = service.getExcelColumnNameByIndex(27);
const excelColumnCA = service.getExcelColumnNameByIndex(79);

expect(excelColumnA).toBe('A');
expect(excelColumnZ).toBe('Z');
expect(excelColumnAA).toBe('AA');
expect(excelColumnCA).toBe('CA');
});

it(`should export same colspan in the export excel as defined in the grid`, async () => {
mockCollection = [
{ id: 0, userId: '1E06', firstName: 'John', lastName: 'Z', position: 'SALES_REP', order: 10 },
{ id: 1, userId: '1E09', firstName: 'Jane', lastName: 'Doe', position: 'DEVELOPER', order: 15 },
{ id: 2, userId: '2ABC', firstName: 'Sponge', lastName: 'Bob', position: 'IT_ADMIN', order: 33 },
];
jest.spyOn(dataViewStub, 'getLength').mockReturnValue(mockCollection.length);
jest.spyOn(dataViewStub, 'getItem').mockReturnValue(null).mockReturnValueOnce(mockCollection[0]).mockReturnValueOnce(mockCollection[1]).mockReturnValueOnce(mockCollection[2]);
jest.spyOn(dataViewStub, 'getItemMetadata').mockReturnValue(oddMetatadata).mockReturnValueOnce(evenMetatadata).mockReturnValueOnce(oddMetatadata).mockReturnValueOnce(evenMetatadata);
const spyOnAfter = jest.spyOn(service.onGridAfterExportToExcel, 'next');
const spyUrlCreate = jest.spyOn(URL, 'createObjectURL');
const spyDownload = jest.spyOn(service, 'startDownloadFile');

const optionExpectation = { filename: 'export.xlsx', format: FileType.xlsx };

service.init(gridStub, dataViewStub);
await service.exportToExcel(mockExportExcelOptions);

expect(spyOnAfter).toHaveBeenCalledWith(optionExpectation);
expect(spyUrlCreate).toHaveBeenCalledWith(mockExcelBlob);
expect(spyDownload).toHaveBeenCalledWith({
...optionExpectation, blob: new Blob(), data: [
[
{ metadata: { style: 1, }, value: 'User Id', },
{ metadata: { style: 1, }, value: 'First Name', },
{ metadata: { style: 1, }, value: 'Last Name', },
{ metadata: { style: 1, }, value: 'Position', },
{ metadata: { style: 1, }, value: 'Order', },
],
['1E06', '', '', ''],
['1E09', 'Jane', 'DOE', '', 15],
['2ABC', '', '', ''],
]
});
});
});
});

describe('without ngx-translate', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
import { exportWithFormatterWhenDefined } from '../export-utilities';
import { Column, Formatter, SlickGrid } from '../../models/index';

const mockDataView = {
constructor: jest.fn(),
init: jest.fn(),
destroy: jest.fn(),
getItemMetadata: jest.fn(),
};

const gridStub = {
getData: () => mockDataView,
};

describe('Export Utilities', () => {
let mockItem;
let mockItem: any;
let mockColumn: Column;
const myBoldHtmlFormatter: Formatter = (_row, _cell, value) => value !== null ? { text: value ? `<b>${value}</b>` : '' } : null as any;
const myUppercaseFormatter: Formatter = (_row, _cell, value) => value ? { text: value.toUpperCase() } : null as any;
Expand All @@ -14,65 +25,65 @@ describe('Export Utilities', () => {

describe('exportWithFormatterWhenDefined method', () => {
it('should NOT enable exportWithFormatter and expect the firstName to returned', () => {
const output = exportWithFormatterWhenDefined(1, 1, mockItem, mockColumn, {} as SlickGrid, { exportWithFormatter: false });
const output = exportWithFormatterWhenDefined(1, 1, mockItem, mockColumn, gridStub as SlickGrid, { exportWithFormatter: false });
expect(output).toBe('John');
});

it('should provide a column definition field defined with a dot (.) notation and expect a complex object result', () => {
const output = exportWithFormatterWhenDefined(1, 1, mockItem, { ...mockColumn, field: 'address.zip' }, {} as SlickGrid, {});
const output = exportWithFormatterWhenDefined(1, 1, mockItem, { ...mockColumn, field: 'address.zip' }, gridStub as SlickGrid, {});
expect(output).toEqual({ zip: 12345 });
});

it('should provide a column definition field defined with a dot (.) notation and expect an empty string when the complex result is an empty object', () => {
const output = exportWithFormatterWhenDefined(1, 1, mockItem, { ...mockColumn, field: 'empty' }, {} as SlickGrid, {});
const output = exportWithFormatterWhenDefined(1, 1, mockItem, { ...mockColumn, field: 'empty' }, gridStub as SlickGrid, {});
expect(output).toEqual('');
});

it('should provide a column definition field defined with a dot (.) notation and expect an empty string when the complex result is an empty object', () => {
const output = exportWithFormatterWhenDefined(1, 1, mockItem, { ...mockColumn, field: 'empty' }, {} as SlickGrid, {});
const output = exportWithFormatterWhenDefined(1, 1, mockItem, { ...mockColumn, field: 'empty' }, gridStub as SlickGrid, {});
expect(output).toEqual('');
});

it('should provide a exportCustomFormatter in the column definition and expect the output to be formatted', () => {
const output = exportWithFormatterWhenDefined(1, 1, mockItem, { ...mockColumn, exportCustomFormatter: myBoldHtmlFormatter }, {} as SlickGrid, { exportWithFormatter: true });
const output = exportWithFormatterWhenDefined(1, 1, mockItem, { ...mockColumn, exportCustomFormatter: myBoldHtmlFormatter }, gridStub as SlickGrid, { exportWithFormatter: true });
expect(output).toBe('<b>John</b>');
});

it('should provide a exportCustomFormatter in the column definition and expect empty string when associated item property is null', () => {
const output = exportWithFormatterWhenDefined(1, 1, { ...mockItem, firstName: null }, { ...mockColumn, exportCustomFormatter: myBoldHtmlFormatter }, {} as SlickGrid, { exportWithFormatter: true });
const output = exportWithFormatterWhenDefined(1, 1, { ...mockItem, firstName: null }, { ...mockColumn, exportCustomFormatter: myBoldHtmlFormatter }, gridStub as SlickGrid, { exportWithFormatter: true });
expect(output).toBe('');
});

it('should provide a exportCustomFormatter in the column definition and expect empty string when associated item property is undefined', () => {
const output = exportWithFormatterWhenDefined(1, 1, { ...mockItem, firstName: undefined }, { ...mockColumn, exportCustomFormatter: myBoldHtmlFormatter }, {} as SlickGrid, { exportWithFormatter: true });
const output = exportWithFormatterWhenDefined(1, 1, { ...mockItem, firstName: undefined }, { ...mockColumn, exportCustomFormatter: myBoldHtmlFormatter }, gridStub as SlickGrid, { exportWithFormatter: true });
expect(output).toBe('');
});

it('should enable exportWithFormatter as an exportOption and expect the firstName to be formatted', () => {
const output = exportWithFormatterWhenDefined(1, 1, mockItem, mockColumn, {} as SlickGrid, { exportWithFormatter: true });
const output = exportWithFormatterWhenDefined(1, 1, mockItem, mockColumn, gridStub as SlickGrid, { exportWithFormatter: true });
expect(output).toBe('JOHN');
});

it('should enable exportWithFormatter as a grid option and expect the firstName to be formatted', () => {
mockColumn.exportWithFormatter = true;
const output = exportWithFormatterWhenDefined(1, 1, mockItem, mockColumn, {} as SlickGrid, { exportWithFormatter: true });
const output = exportWithFormatterWhenDefined(1, 1, mockItem, mockColumn, gridStub as SlickGrid, { exportWithFormatter: true });
expect(output).toBe('JOHN');
});

it('should enable exportWithFormatter as a grid option and expect empty string when associated item property is null', () => {
mockColumn.exportWithFormatter = true;
const output = exportWithFormatterWhenDefined(1, 1, { ...mockItem, firstName: null }, mockColumn, {} as SlickGrid, { exportWithFormatter: true });
const output = exportWithFormatterWhenDefined(1, 1, { ...mockItem, firstName: null }, mockColumn, gridStub as SlickGrid, { exportWithFormatter: true });
expect(output).toBe('');
});

it('should enable exportWithFormatter as a grid option and expect empty string when associated item property is undefined', () => {
mockColumn.exportWithFormatter = true;
const output = exportWithFormatterWhenDefined(1, 1, { ...mockItem, firstName: undefined }, mockColumn, {} as SlickGrid, { exportWithFormatter: true });
const output = exportWithFormatterWhenDefined(1, 1, { ...mockItem, firstName: undefined }, mockColumn, gridStub as SlickGrid, { exportWithFormatter: true });
expect(output).toBe('');
});

it('should expect empty string when associated item property is undefined and has no formatter defined', () => {
const output = exportWithFormatterWhenDefined(1, 1, { ...mockItem, firstName: undefined }, mockColumn, {} as SlickGrid, {});
const output = exportWithFormatterWhenDefined(1, 1, { ...mockItem, firstName: undefined }, mockColumn, gridStub as SlickGrid, {});
expect(output).toBe('');
});
});
Expand Down
Loading

0 comments on commit 003738c

Please sign in to comment.