From 5f6f3b072145f48e354e97fbef04ce2051a8e041 Mon Sep 17 00:00:00 2001 From: Ghislain B Date: Fri, 7 Jun 2019 09:04:40 -0400 Subject: [PATCH] feat(tests): add more Extensions unit tests (#184) --- .../cellExternalCopyManagerExtension.spec.ts | 6 +- .../__tests__/columnPickerExtension.spec.ts | 139 +++++ .../draggableGroupingExtension.spec.ts | 114 ++++ .../__tests__/extensionUtility.spec.ts | 98 +++ .../__tests__/gridMenuExtension.spec.ts | 590 ++++++++++++++++++ .../__tests__/headerMenuExtension.spec.ts | 508 +++++++++++++++ .../__tests__/rowMoveManagerExtension.spec.ts | 136 ++++ .../extensions/columnPickerExtension.ts | 38 +- .../extensions/draggableGroupingExtension.ts | 32 +- .../extensions/extensionUtility.ts | 21 +- .../extensions/gridMenuExtension.ts | 232 +++---- .../extensions/headerMenuExtension.ts | 220 +++---- .../extensions/rowMoveManagerExtension.ts | 30 +- .../models/columnPicker.interface.ts | 6 + .../services/filter.service.ts | 2 +- .../services/grid.service.ts | 8 +- 16 files changed, 1878 insertions(+), 302 deletions(-) create mode 100644 src/aurelia-slickgrid/extensions/__tests__/columnPickerExtension.spec.ts create mode 100644 src/aurelia-slickgrid/extensions/__tests__/draggableGroupingExtension.spec.ts create mode 100644 src/aurelia-slickgrid/extensions/__tests__/extensionUtility.spec.ts create mode 100644 src/aurelia-slickgrid/extensions/__tests__/gridMenuExtension.spec.ts create mode 100644 src/aurelia-slickgrid/extensions/__tests__/headerMenuExtension.spec.ts create mode 100644 src/aurelia-slickgrid/extensions/__tests__/rowMoveManagerExtension.spec.ts diff --git a/src/aurelia-slickgrid/extensions/__tests__/cellExternalCopyManagerExtension.spec.ts b/src/aurelia-slickgrid/extensions/__tests__/cellExternalCopyManagerExtension.spec.ts index dccb75cda..3b5ac7d68 100644 --- a/src/aurelia-slickgrid/extensions/__tests__/cellExternalCopyManagerExtension.spec.ts +++ b/src/aurelia-slickgrid/extensions/__tests__/cellExternalCopyManagerExtension.spec.ts @@ -51,8 +51,8 @@ describe('cellExternalCopyManagerExtension', () => { } as GridOption; beforeEach(() => { - extensionUtility = new ExtensionUtility({} as I18N, sharedService); sharedService = new SharedService(); + extensionUtility = new ExtensionUtility({} as I18N, sharedService); extension = new CellExternalCopyManagerExtension(extensionUtility, sharedService); }); @@ -73,11 +73,11 @@ describe('cellExternalCopyManagerExtension', () => { it('should register the addon', () => { const pluginSpy = jest.spyOn(SharedService.prototype.grid, 'registerPlugin'); - const optionSpy = jest.spyOn(SharedService.prototype.gridOptions.excelCopyBufferOptions, 'onExtensionRegistered'); + const onRegisteredSpy = jest.spyOn(SharedService.prototype.gridOptions.excelCopyBufferOptions, 'onExtensionRegistered'); const instance = extension.register(); - expect(optionSpy).toHaveBeenCalledWith(instance); + expect(onRegisteredSpy).toHaveBeenCalledWith(instance); expect(pluginSpy).toHaveBeenCalledWith(instance); expect(mockSelectionModel).toHaveBeenCalled(); expect(mockAddon).toHaveBeenCalledWith({ diff --git a/src/aurelia-slickgrid/extensions/__tests__/columnPickerExtension.spec.ts b/src/aurelia-slickgrid/extensions/__tests__/columnPickerExtension.spec.ts new file mode 100644 index 000000000..be798ea2a --- /dev/null +++ b/src/aurelia-slickgrid/extensions/__tests__/columnPickerExtension.spec.ts @@ -0,0 +1,139 @@ +import { I18N } from 'aurelia-i18n'; +import { EventAggregator } from 'aurelia-event-aggregator'; +import { BindingSignaler } from 'aurelia-templating-resources'; +import { GridOption } from '../../models/gridOption.interface'; +import { ColumnPickerExtension } from '../columnPickerExtension'; +import { ExtensionUtility } from '../extensionUtility'; +import { SharedService } from '../../services/shared.service'; +import { Column } from '../../models'; + +declare var Slick: any; + +const gridStub = { + getOptions: jest.fn(), + registerPlugin: jest.fn(), +}; + +const mockAddon = jest.fn().mockImplementation(() => ({ + init: jest.fn(), + destroy: jest.fn(), + onColumnsChanged: new Slick.Event(), +})); + +jest.mock('slickgrid/controls/slick.columnpicker', () => mockAddon); +Slick.Controls = { + ColumnPicker: mockAddon +}; + +describe('columnPickerExtension', () => { + const columnsMock: Column[] = [{ id: 'field1', field: 'field1', width: 100, headerKey: 'TITLE' }, { id: 'field2', field: 'field2', width: 75 }]; + let extensionUtility: ExtensionUtility; + let i18n: I18N; + let extension: ColumnPickerExtension; + let sharedService: SharedService; + + const gridOptionsMock = { + enableColumnPicker: true, + enableTranslate: true, + columnPicker: { + hideForceFitButton: false, + hideSyncResizeButton: true, + onExtensionRegistered: jest.fn(), + onColumnsChanged: (e, args: { columns: Column[] }) => { } + }, + } as GridOption; + + beforeEach(() => { + sharedService = new SharedService(); + i18n = new I18N(new EventAggregator(), new BindingSignaler()); + extensionUtility = new ExtensionUtility(i18n, sharedService); + extension = new ColumnPickerExtension(extensionUtility, sharedService); + i18n.setup({ + resources: { + en: { + translation: { + TITLE: 'Title', COMMANDS: 'Commands', COLUMNS: 'Columns', FORCE_FIT_COLUMNS: 'Force fit columns', SYNCHRONOUS_RESIZE: 'Synchronous resize' + } + }, + fr: { + translation: { + TITLE: 'Titre', COMMANDS: 'Commandes', COLUMNS: 'Colonnes', FORCE_FIT_COLUMNS: 'Ajustement forcé des colonnes', SYNCHRONOUS_RESIZE: 'Redimension synchrone' + } + } + }, + lng: '0', + fallbackLng: 'en', + debug: false + }); + i18n.setLocale('fr'); + }); + + it('should return null when either the grid object or the grid options is missing', () => { + const output = extension.register(); + expect(output).toBeNull(); + }); + + describe('registered addon', () => { + beforeEach(() => { + jest.spyOn(SharedService.prototype, 'grid', 'get').mockReturnValue(gridStub); + jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(gridOptionsMock); + jest.spyOn(SharedService.prototype, 'allColumns', 'get').mockReturnValue(columnsMock); + jest.spyOn(SharedService.prototype, 'columnDefinitions', 'get').mockReturnValue(columnsMock); + }); + + it('should register the addon', () => { + const onRegisteredSpy = jest.spyOn(SharedService.prototype.gridOptions.columnPicker, 'onExtensionRegistered'); + const instance = extension.register(); + + expect(instance).not.toBeNull(); + expect(onRegisteredSpy).toHaveBeenCalledWith(instance); + expect(mockAddon).toHaveBeenCalledWith(columnsMock, gridStub, gridOptionsMock); + }); + + it('should call internal event handler subscribe and expect the "onColumnSpy" option to be called when addon notify is called', () => { + const handlerSpy = jest.spyOn(extension.eventHandler, 'subscribe'); + const onColumnSpy = jest.spyOn(SharedService.prototype.gridOptions.columnPicker, 'onColumnsChanged'); + + const instance = extension.register(); + instance.onColumnsChanged.notify(columnsMock.slice(0, 1), new Slick.EventData(), gridStub); + + expect(handlerSpy).toHaveBeenCalledTimes(1); + expect(handlerSpy).toHaveBeenCalledWith( + { notify: expect.anything(), subscribe: expect.anything(), unsubscribe: expect.anything(), }, + expect.anything() + ); + expect(onColumnSpy).toHaveBeenCalledWith(expect.anything(), columnsMock.slice(0, 1)); + }); + + it('should dispose of the addon', () => { + const instance = extension.register(); + const destroySpy = jest.spyOn(instance, 'destroy'); + + extension.dispose(); + + expect(destroySpy).toHaveBeenCalled(); + }); + }); + + describe('translateColumnPicker method', () => { + it('should translate the column picker header titles', () => { + const utilitySpy = jest.spyOn(extensionUtility, 'getPickerTitleOutputString'); + const translateSpy = jest.spyOn(extensionUtility, 'translateItems'); + + const instance = extension.register(); + extension.translateColumnPicker(); + const initSpy = jest.spyOn(instance, 'init'); + + expect(utilitySpy).toHaveBeenCalled(); + expect(translateSpy).toHaveBeenCalled(); + expect(initSpy).toHaveBeenCalledWith(gridStub); + expect(SharedService.prototype.gridOptions.columnPicker.columnTitle).toBe('Colonnes'); + expect(SharedService.prototype.gridOptions.columnPicker.forceFitTitle).toBe('Ajustement forcé des colonnes'); + expect(SharedService.prototype.gridOptions.columnPicker.syncResizeTitle).toBe('Redimension synchrone'); + expect(columnsMock).toEqual([ + { id: 'field1', field: 'field1', width: 100, name: 'Titre', headerKey: 'TITLE' }, + { id: 'field2', field: 'field2', width: 75 } + ]); + }); + }); +}); diff --git a/src/aurelia-slickgrid/extensions/__tests__/draggableGroupingExtension.spec.ts b/src/aurelia-slickgrid/extensions/__tests__/draggableGroupingExtension.spec.ts new file mode 100644 index 000000000..234e0d243 --- /dev/null +++ b/src/aurelia-slickgrid/extensions/__tests__/draggableGroupingExtension.spec.ts @@ -0,0 +1,114 @@ +import { I18N } from 'aurelia-i18n'; +import { GridOption } from '../../models/gridOption.interface'; +import { DraggableGroupingExtension } from '../draggableGroupingExtension'; +import { ExtensionUtility } from '../extensionUtility'; +import { SharedService } from '../../services/shared.service'; +import { Grouping } from '../../models'; + +declare var Slick: any; + +const gridStub = { + getOptions: jest.fn(), + registerPlugin: jest.fn(), +}; + +const mockAddon = jest.fn().mockImplementation(() => ({ + init: jest.fn(), + destroy: jest.fn(), + onGroupChanged: new Slick.Event(), +})); + +jest.mock('slickgrid/plugins/slick.draggablegrouping', () => mockAddon); +Slick.DraggableGrouping = mockAddon; + +describe('draggableGroupingExtension', () => { + let extensionUtility: ExtensionUtility; + let sharedService: SharedService; + let extension: DraggableGroupingExtension; + const gridOptionsMock = { + enableDraggableGrouping: true, + draggableGrouping: { + deleteIconCssClass: 'class', + dropPlaceHolderText: 'test', + groupIconCssClass: 'group-class', + onExtensionRegistered: jest.fn(), + onGroupChanged: (e: Event, args: { caller?: string; groupColumns: Grouping[] }) => { }, + } + } as GridOption; + + beforeEach(() => { + sharedService = new SharedService(); + extensionUtility = new ExtensionUtility({} as I18N, sharedService); + extension = new DraggableGroupingExtension(extensionUtility, sharedService); + }); + + it('should return null when either the grid object or the grid options is missing', () => { + const output = extension.register(); + expect(output).toBeNull(); + }); + + describe('registered addon', () => { + beforeEach(() => { + jest.spyOn(SharedService.prototype, 'grid', 'get').mockReturnValue(gridStub); + jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(gridOptionsMock); + }); + + it('should register the addon', () => { + const onRegisteredSpy = jest.spyOn(SharedService.prototype.gridOptions.draggableGrouping, 'onExtensionRegistered'); + const pluginSpy = jest.spyOn(SharedService.prototype.grid, 'registerPlugin'); + + const instance = extension.create(gridOptionsMock); + const addon = extension.register(); + + expect(addon).not.toBeNull(); + expect(mockAddon).toHaveBeenCalledWith({ + deleteIconCssClass: 'class', + dropPlaceHolderText: 'test', + groupIconCssClass: 'group-class', + onExtensionRegistered: expect.anything(), + onGroupChanged: expect.anything(), + }); + expect(onRegisteredSpy).toHaveBeenCalledWith(instance); + expect(pluginSpy).toHaveBeenCalledWith(instance); + }); + + it('should dispose of the addon', () => { + const instance = extension.create(gridOptionsMock); + const destroySpy = jest.spyOn(instance, 'destroy'); + + extension.dispose(); + + expect(destroySpy).toHaveBeenCalled(); + }); + + it('should provide addon options and expect them to be called in the addon constructor', () => { + const optionMock = { + deleteIconCssClass: 'different-class', + dropPlaceHolderText: 'different-test', + groupIconCssClass: 'different-group-class', + }; + const addonOptions = { ...gridOptionsMock, draggableGrouping: optionMock }; + jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(addonOptions); + + extension.create(addonOptions); + extension.register(); + + expect(mockAddon).toHaveBeenCalledWith(optionMock); + }); + + it('should call internal event handler subscribe and expect the "onGroupChanged" option to be called when addon notify is called', () => { + const handlerSpy = jest.spyOn(extension.eventHandler, 'subscribe'); + const onColumnSpy = jest.spyOn(SharedService.prototype.gridOptions.draggableGrouping, 'onGroupChanged'); + + const instance = extension.create(gridOptionsMock); + extension.register(); + instance.onGroupChanged.notify({ caller: 'clear-all', groupColumns: [] }, new Slick.EventData(), gridStub); + + expect(handlerSpy).toHaveBeenCalledWith( + { notify: expect.anything(), subscribe: expect.anything(), unsubscribe: expect.anything(), }, + expect.anything() + ); + expect(onColumnSpy).toHaveBeenCalledWith(expect.anything(), { caller: 'clear-all', groupColumns: [] }); + }); + }); +}); diff --git a/src/aurelia-slickgrid/extensions/__tests__/extensionUtility.spec.ts b/src/aurelia-slickgrid/extensions/__tests__/extensionUtility.spec.ts new file mode 100644 index 000000000..012649b45 --- /dev/null +++ b/src/aurelia-slickgrid/extensions/__tests__/extensionUtility.spec.ts @@ -0,0 +1,98 @@ +import { I18N } from 'aurelia-i18n'; +import { EventAggregator } from 'aurelia-event-aggregator'; +import { BindingSignaler } from 'aurelia-templating-resources'; +import { GridOption } from '../../models/gridOption.interface'; +import { ExtensionUtility } from '../extensionUtility'; +import { SharedService } from '../../services/shared.service'; +import { ExtensionName } from '../../models'; + +declare var Slick: any; + +const mockAddon = jest.fn().mockImplementation(() => ({ + init: jest.fn(), + destroy: jest.fn() +})); + +jest.mock('slickgrid/slick.groupitemmetadataprovider', () => mockAddon); +Slick.Data = { + GroupItemMetadataProvider: mockAddon +}; + +describe('extensionUtility', () => { + let i18n: I18N; + let sharedService: SharedService; + let utility: ExtensionUtility; + + beforeEach(async () => { + sharedService = new SharedService(); + i18n = new I18N(new EventAggregator(), new BindingSignaler()); + utility = new ExtensionUtility(i18n, sharedService); + i18n.setup({ + resources: { en: { translation: { TITLE: 'Title' } }, fr: { translation: { TITLE: 'Titre' } } }, + lng: '0', + fallbackLng: 'en', + debug: false + }); + await i18n.setLocale('fr'); + }); + + describe('arrayRemoveItemByIndex method', () => { + it('should remove an item from the array', () => { + const input = [{ field: 'field1', name: 'Field 1' }, { field: 'field2', name: 'Field 2' }, { field: 'field3', name: 'Field 3' }]; + const expected = [{ field: 'field1', name: 'Field 1' }, { field: 'field3', name: 'Field 3' }]; + + const output = utility.arrayRemoveItemByIndex(input, 1); + expect(output).toEqual(expected); + }); + }); + + describe('loadExtensionDynamically method', () => { + // we can test only the GroupItemMetadataProvider which is not tested in any other extension test + it('should check that groupItemMetaProvider gets loaded', () => { + utility.loadExtensionDynamically(ExtensionName.groupItemMetaProvider); + const groupItemMetadataProvider = new Slick.Data.GroupItemMetadataProvider(); + expect(mockAddon).toHaveBeenCalled(); + expect(groupItemMetadataProvider).not.toBeNull(); + }); + }); + + describe('getPickerTitleOutputString method', () => { + it('should translate titleKey when there is one', () => { + const gridOptionsMock = { enableTranslate: true, enableGridMenu: true, gridMenu: { hideForceFitButton: false, hideSyncResizeButton: true, columnTitleKey: 'TITLE' } } as GridOption; + jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(gridOptionsMock); + + const output = utility.getPickerTitleOutputString('columnTitle', 'gridMenu'); + + expect(output).toEqual('Titre'); + }); + + it('should return undefined when the given property is not found', () => { + const gridOptionsMock = { enableTranslate: true, enableGridMenu: true, gridMenu: { hideForceFitButton: false, hideSyncResizeButton: true } } as GridOption; + jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(gridOptionsMock); + + const output = utility.getPickerTitleOutputString('unknown', 'gridMenu'); + + expect(output).toEqual(undefined); + }); + }); + + describe('sortItems method', () => { + it('should sort the items by their order property', () => { + const inputArray = [{ field: 'field1', order: 3 }, { field: 'field2', order: 1 }, { field: 'field3', order: 2 }]; + const expectedArray = [{ field: 'field2', order: 1 }, { field: 'field3', order: 2 }, { field: 'field1', order: 3 }]; + + utility.sortItems(inputArray, 'order'); + + expect(inputArray).toEqual(expectedArray); + }); + + it('should sort the items by their order property when found and then return the object without the property', () => { + const inputArray = [{ field: 'field1', order: 3 }, { field: 'field3', order: 2 }, { field: 'field2' }]; + const expectedArray = [{ field: 'field3', order: 2 }, { field: 'field1', order: 3 }, { field: 'field2' }]; + + utility.sortItems(inputArray, 'order'); + + expect(inputArray).toEqual(expectedArray); + }); + }); +}); diff --git a/src/aurelia-slickgrid/extensions/__tests__/gridMenuExtension.spec.ts b/src/aurelia-slickgrid/extensions/__tests__/gridMenuExtension.spec.ts new file mode 100644 index 000000000..e484d5710 --- /dev/null +++ b/src/aurelia-slickgrid/extensions/__tests__/gridMenuExtension.spec.ts @@ -0,0 +1,590 @@ +import { I18N } from 'aurelia-i18n'; +import { EventAggregator } from 'aurelia-event-aggregator'; +import { BindingSignaler } from 'aurelia-templating-resources'; +import { GridOption } from '../../models/gridOption.interface'; +import { GridMenuExtension } from '../gridMenuExtension'; +import { ExtensionUtility } from '../extensionUtility'; +import { SharedService } from '../../services/shared.service'; +import { Column, DelimiterType, FileType } from '../../models'; +import { ExportService, FilterService, SortService } from '../../services'; + +declare var Slick: any; +jest.mock('flatpickr', () => { }); + +const gridId = 'grid1'; +const gridUid = 'slickgrid_124343'; +const containerId = 'demo-container'; +const exportServiceStub = { + exportToFile: jest.fn(), +} as unknown as ExportService; + +const filterServiceStub = { + clearFilters: jest.fn(), +} as unknown as FilterService; + +const sortServiceStub = { + clearSorting: jest.fn(), +} as unknown as SortService; + +const dataViewStub = { + refresh: jest.fn(), +}; + +const gridStub = { + autosizeColumns: jest.fn(), + getColumnIndex: jest.fn(), + getColumns: jest.fn(), + getOptions: jest.fn(), + getUID: () => gridUid, + registerPlugin: jest.fn(), + setColumns: jest.fn(), + setHeaderRowVisibility: jest.fn(), + setTopPanelVisibility: jest.fn(), + setPreHeaderPanelVisibility: jest.fn(), +}; + +const mockAddon = jest.fn().mockImplementation(() => ({ + init: jest.fn(), + destroy: jest.fn(), + showGridMenu: jest.fn(), + onColumnsChanged: new Slick.Event(), + onCommand: new Slick.Event(), + onBeforeMenuShow: new Slick.Event(), + onMenuClose: new Slick.Event(), +})); + +jest.mock('slickgrid/controls/slick.gridmenu', () => mockAddon); +Slick.Controls = { + GridMenu: mockAddon +}; + +// define a
container to simulate the grid container +const template = + `
+
+
+
+
`; + +describe('gridMenuExtension', () => { + const columnsMock: Column[] = [{ id: 'field1', field: 'field1', width: 100, headerKey: 'TITLE' }, { id: 'field2', field: 'field2', width: 75 }]; + let extensionUtility: ExtensionUtility; + let i18n: I18N; + let extension: GridMenuExtension; + let sharedService: SharedService; + + const gridOptionsMock = { + enableAutoSizeColumns: true, + enableGridMenu: true, + enableTranslate: true, + backendServiceApi: { + service: { + buildQuery: jest.fn(), + }, + internalPostProcess: jest.fn(), + preProcess: jest.fn(), + process: jest.fn(), + postProcess: jest.fn(), + }, + gridMenu: { + customItems: [], + hideClearAllFiltersCommand: false, + hideForceFitButton: false, + hideSyncResizeButton: true, + onExtensionRegistered: jest.fn(), + onCommand: (e, args: { command: any, item: any, grid: any }) => { }, + onColumnsChanged: (e, args: { columns: Column[], grid: any }) => { }, + onBeforeMenuShow: (e, args: { menu: any, grid: any }) => { }, + onMenuClose: (e, args: { menu: any, grid: any }) => { }, + }, + pagination: { + totalItems: 0 + }, + showHeaderRow: false, + showTopPanel: false, + showPreHeaderPanel: false + } as unknown as GridOption; + + beforeEach(() => { + const div = document.createElement('div'); + div.innerHTML = template; + document.body.appendChild(div); + + sharedService = new SharedService(); + i18n = new I18N(new EventAggregator(), new BindingSignaler()); + extensionUtility = new ExtensionUtility(i18n, sharedService); + extension = new GridMenuExtension(exportServiceStub, extensionUtility, filterServiceStub, i18n, sharedService, sortServiceStub); + i18n.setup({ + resources: { + en: { + translation: { + TITLE: 'Title', COMMANDS: 'Commands', COLUMNS: 'Columns', FORCE_FIT_COLUMNS: 'Force fit columns', SYNCHRONOUS_RESIZE: 'Synchronous resize' + } + }, + fr: { + translation: { + TITLE: 'Titre', + CLEAR_ALL_FILTERS: 'Supprimer tous les filtres', + CLEAR_ALL_SORTING: 'Supprimer tous les tris', + EXPORT_TO_CSV: 'Exporter en format CSV', + EXPORT_TO_TAB_DELIMITED: 'Exporter en format texte (délimité par tabulation)', + COMMANDS: 'Commandes', + COLUMNS: 'Colonnes', + FORCE_FIT_COLUMNS: 'Ajustement forcé des colonnes', + SYNCHRONOUS_RESIZE: 'Redimension synchrone', + TOGGLE_FILTER_ROW: 'Basculer la ligne des filtres', + REFRESH_DATASET: 'Rafraîchir les données', + TOGGLE_PRE_HEADER_ROW: 'Basculer la ligne de pré-en-tête', + + } + } + }, + lng: '0', + fallbackLng: 'en', + debug: false + }); + i18n.setLocale('fr'); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should return null when either the grid object or the grid options is missing', () => { + const output = extension.register(); + expect(output).toBeNull(); + }); + + describe('registered addon', () => { + beforeEach(() => { + jest.spyOn(SharedService.prototype, 'dataView', 'get').mockReturnValue(dataViewStub); + jest.spyOn(SharedService.prototype, 'grid', 'get').mockReturnValue(gridStub); + jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(gridOptionsMock); + jest.spyOn(SharedService.prototype, 'allColumns', 'get').mockReturnValue(columnsMock); + jest.spyOn(SharedService.prototype, 'visibleColumns', 'get').mockReturnValue(columnsMock); + jest.spyOn(SharedService.prototype, 'columnDefinitions', 'get').mockReturnValue(columnsMock); + }); + + it('should register the addon', () => { + const onRegisteredSpy = jest.spyOn(SharedService.prototype.gridOptions.gridMenu, 'onExtensionRegistered'); + const instance = extension.register(); + + expect(instance).not.toBeNull(); + expect(onRegisteredSpy).toHaveBeenCalledWith(instance); + expect(mockAddon).toHaveBeenCalledWith(columnsMock, gridStub, gridOptionsMock); + }); + + it('should call internal event handler subscribe and expect the "onColumnsChanged" option to be called when addon notify is called', () => { + const handlerSpy = jest.spyOn(extension.eventHandler, 'subscribe'); + const onColumnSpy = jest.spyOn(SharedService.prototype.gridOptions.gridMenu, 'onColumnsChanged'); + const onBeforeSpy = jest.spyOn(SharedService.prototype.gridOptions.gridMenu, 'onBeforeMenuShow'); + const onCloseSpy = jest.spyOn(SharedService.prototype.gridOptions.gridMenu, 'onMenuClose'); + const onCommandSpy = jest.spyOn(SharedService.prototype.gridOptions.gridMenu, 'onCommand'); + + const instance = extension.register(); + instance.onColumnsChanged.notify(columnsMock.slice(0, 1), new Slick.EventData(), gridStub); + + expect(handlerSpy).toHaveBeenCalledTimes(4); + expect(handlerSpy).toHaveBeenCalledWith( + { notify: expect.anything(), subscribe: expect.anything(), unsubscribe: expect.anything(), }, + expect.anything() + ); + expect(onColumnSpy).toHaveBeenCalledWith(expect.anything(), columnsMock.slice(0, 1)); + expect(onBeforeSpy).not.toHaveBeenCalled(); + expect(onCloseSpy).not.toHaveBeenCalled(); + expect(onCommandSpy).not.toHaveBeenCalled(); + }); + + it('should call internal event handler subscribe and expect the "onBeforeMenuShow" option to be called when addon notify is called', () => { + const handlerSpy = jest.spyOn(extension.eventHandler, 'subscribe'); + const onColumnSpy = jest.spyOn(SharedService.prototype.gridOptions.gridMenu, 'onColumnsChanged'); + const onBeforeSpy = jest.spyOn(SharedService.prototype.gridOptions.gridMenu, 'onBeforeMenuShow'); + const onCloseSpy = jest.spyOn(SharedService.prototype.gridOptions.gridMenu, 'onMenuClose'); + const onCommandSpy = jest.spyOn(SharedService.prototype.gridOptions.gridMenu, 'onCommand'); + + const instance = extension.register(); + instance.onBeforeMenuShow.notify({ grid: gridStub, menu: {} }, new Slick.EventData(), gridStub); + + expect(handlerSpy).toHaveBeenCalledTimes(4); + expect(handlerSpy).toHaveBeenCalledWith( + { notify: expect.anything(), subscribe: expect.anything(), unsubscribe: expect.anything(), }, + expect.anything() + ); + expect(onBeforeSpy).toHaveBeenCalledWith(expect.anything(), { grid: gridStub, menu: {} }); + expect(onColumnSpy).not.toHaveBeenCalled(); + expect(onCloseSpy).not.toHaveBeenCalled(); + expect(onCommandSpy).not.toHaveBeenCalled(); + }); + + it('should call internal event handler subscribe and expect the "onMenuClose" option to be called when addon notify is called', () => { + const handlerSpy = jest.spyOn(extension.eventHandler, 'subscribe'); + const onColumnSpy = jest.spyOn(SharedService.prototype.gridOptions.gridMenu, 'onColumnsChanged'); + const onBeforeSpy = jest.spyOn(SharedService.prototype.gridOptions.gridMenu, 'onBeforeMenuShow'); + const onCloseSpy = jest.spyOn(SharedService.prototype.gridOptions.gridMenu, 'onMenuClose'); + const onCommandSpy = jest.spyOn(SharedService.prototype.gridOptions.gridMenu, 'onCommand'); + + const instance = extension.register(); + instance.onMenuClose.notify({ grid: gridStub, menu: {} }, new Slick.EventData(), gridStub); + + expect(handlerSpy).toHaveBeenCalledTimes(4); + expect(handlerSpy).toHaveBeenCalledWith( + { notify: expect.anything(), subscribe: expect.anything(), unsubscribe: expect.anything(), }, + expect.anything() + ); + expect(onCloseSpy).toHaveBeenCalledWith(expect.anything(), { grid: gridStub, menu: {} }); + expect(onColumnSpy).not.toHaveBeenCalled(); + expect(onBeforeSpy).not.toHaveBeenCalled(); + expect(onCommandSpy).not.toHaveBeenCalled(); + }); + + it('should call internal event handler subscribe and expect the "onCommand" option to be called when addon notify is called', () => { + const handlerSpy = jest.spyOn(extension.eventHandler, 'subscribe'); + const onColumnSpy = jest.spyOn(SharedService.prototype.gridOptions.gridMenu, 'onColumnsChanged'); + const onBeforeSpy = jest.spyOn(SharedService.prototype.gridOptions.gridMenu, 'onBeforeMenuShow'); + const onCloseSpy = jest.spyOn(SharedService.prototype.gridOptions.gridMenu, 'onMenuClose'); + const onCommandSpy = jest.spyOn(SharedService.prototype.gridOptions.gridMenu, 'onCommand'); + + const instance = extension.register(); + instance.onCommand.notify({ grid: gridStub, command: 'help' }, new Slick.EventData(), gridStub); + + expect(handlerSpy).toHaveBeenCalledTimes(4); + expect(handlerSpy).toHaveBeenCalledWith( + { notify: expect.anything(), subscribe: expect.anything(), unsubscribe: expect.anything(), }, + expect.anything() + ); + expect(onCommandSpy).toHaveBeenCalledWith(expect.anything(), { grid: gridStub, command: 'help' }); + expect(onColumnSpy).not.toHaveBeenCalled(); + expect(onBeforeSpy).not.toHaveBeenCalled(); + expect(onCloseSpy).not.toHaveBeenCalled(); + }); + + it('should call "autosizeColumns" method when the "onMenuClose" event was triggered and the columns are different', () => { + const handlerSpy = jest.spyOn(extension.eventHandler, 'subscribe'); + const onColumnSpy = jest.spyOn(SharedService.prototype.gridOptions.gridMenu, 'onColumnsChanged'); + const onCloseSpy = jest.spyOn(SharedService.prototype.gridOptions.gridMenu, 'onMenuClose'); + const autoSizeSpy = jest.spyOn(gridStub, 'autosizeColumns'); + + const instance = extension.register(); + instance.onColumnsChanged.notify(columnsMock.slice(0, 1), new Slick.EventData(), gridStub); + instance.onMenuClose.notify({ grid: gridStub, menu: {} }, new Slick.EventData(), gridStub); + + expect(handlerSpy).toHaveBeenCalled(); + expect(onCloseSpy).toHaveBeenCalled(); + expect(onColumnSpy).toHaveBeenCalled(); + expect(autoSizeSpy).toHaveBeenCalled(); + }); + + it('should dispose of the addon', () => { + const instance = extension.register(); + const destroySpy = jest.spyOn(instance, 'destroy'); + + extension.dispose(); + + expect(destroySpy).toHaveBeenCalled(); + }); + }); + + describe('addGridMenuCustomCommands method', () => { + afterEach(() => { + jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(gridOptionsMock); + }); + + it('should expect an empty "customItems" array when both Filter & Sort are disabled', () => { + extension.register(); + expect(SharedService.prototype.gridOptions.gridMenu.customItems).toEqual([]); + }); + + it('should expect all menu related to Filter when "enableFilering" is set', () => { + const copyGridOptionsMock = { ...gridOptionsMock, enableFiltering: true } as unknown as GridOption; + jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); + extension.register(); + expect(SharedService.prototype.gridOptions.gridMenu.customItems).toEqual([ + { iconCssClass: 'fa fa-filter text-danger', title: 'Supprimer tous les filtres', disabled: false, command: 'clear-filter', positionOrder: 50 }, + { iconCssClass: 'fa fa-random', title: 'Basculer la ligne des filtres', disabled: false, command: 'toggle-filter', positionOrder: 52 }, + { iconCssClass: 'fa fa-refresh', title: 'Rafraîchir les données', disabled: false, command: 'refresh-dataset', positionOrder: 54 } + ]); + }); + + it('should have only 1 menu "clear-filter" when all other menus are defined as hidden & when "enableFilering" is set', () => { + const copyGridOptionsMock = { ...gridOptionsMock, enableFiltering: true, gridMenu: { hideToggleFilterCommand: true, hideRefreshDatasetCommand: true } } as unknown as GridOption; + jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); + extension.register(); + expect(SharedService.prototype.gridOptions.gridMenu.customItems).toEqual([ + { iconCssClass: 'fa fa-filter text-danger', title: 'Supprimer tous les filtres', disabled: false, command: 'clear-filter', positionOrder: 50 } + ]); + }); + + it('should have only 1 menu "toggle-filter" when all other menus are defined as hidden & when "enableFilering" is set', () => { + const copyGridOptionsMock = { ...gridOptionsMock, enableFiltering: true, gridMenu: { hideClearAllFiltersCommand: true, hideRefreshDatasetCommand: true } } as unknown as GridOption; + jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); + extension.register(); + expect(SharedService.prototype.gridOptions.gridMenu.customItems).toEqual([ + { iconCssClass: 'fa fa-random', title: 'Basculer la ligne des filtres', disabled: false, command: 'toggle-filter', positionOrder: 52 }, + ]); + }); + + it('should have only 1 menu "refresh-dataset" when all other menus are defined as hidden & when "enableFilering" is set', () => { + const copyGridOptionsMock = { ...gridOptionsMock, enableFiltering: true, gridMenu: { hideClearAllFiltersCommand: true, hideToggleFilterCommand: true } } as unknown as GridOption; + jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); + extension.register(); + expect(SharedService.prototype.gridOptions.gridMenu.customItems).toEqual([ + { iconCssClass: 'fa fa-refresh', title: 'Rafraîchir les données', disabled: false, command: 'refresh-dataset', positionOrder: 54 } + ]); + }); + + it('should have the "toggle-preheader" menu command when "showPreHeaderPanel" is set', () => { + const copyGridOptionsMock = { ...gridOptionsMock, showPreHeaderPanel: true } as unknown as GridOption; + jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); + extension.register(); + expect(SharedService.prototype.gridOptions.gridMenu.customItems).toEqual([ + { iconCssClass: 'fa fa-random', title: 'Basculer la ligne de pré-en-tête', disabled: false, command: 'toggle-preheader', positionOrder: 52 } + ]); + }); + + it('should not have the "toggle-preheader" menu command when "showPreHeaderPanel" and "hideTogglePreHeaderCommand" are set', () => { + const copyGridOptionsMock = { ...gridOptionsMock, showPreHeaderPanel: true, gridMenu: { hideTogglePreHeaderCommand: true } } as unknown as GridOption; + jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); + extension.register(); + expect(SharedService.prototype.gridOptions.gridMenu.customItems).toEqual([]); + }); + + it('should have the "clear-sorting" menu command when "enableSorting" is set', () => { + const copyGridOptionsMock = { ...gridOptionsMock, enableSorting: true } as unknown as GridOption; + jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); + extension.register(); + expect(SharedService.prototype.gridOptions.gridMenu.customItems).toEqual([ + { iconCssClass: 'fa fa-unsorted text-danger', title: 'Supprimer tous les tris', disabled: false, command: 'clear-sorting', positionOrder: 51 } + ]); + }); + + it('should not have the "clear-sorting" menu command when "enableSorting" and "hideClearAllSortingCommand" are set', () => { + const copyGridOptionsMock = { ...gridOptionsMock, enableSorting: true, gridMenu: { hideClearAllSortingCommand: true } } as unknown as GridOption; + jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); + extension.register(); + expect(SharedService.prototype.gridOptions.gridMenu.customItems).toEqual([]); + }); + + it('should have the "export-csv" menu command when "enableExport" is set', () => { + const copyGridOptionsMock = { ...gridOptionsMock, enableExport: true, gridMenu: { hideExportTextDelimitedCommand: true } } as unknown as GridOption; + jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); + extension.register(); + expect(SharedService.prototype.gridOptions.gridMenu.customItems).toEqual([ + { iconCssClass: 'fa fa-download', title: 'Exporter en format CSV', disabled: false, command: 'export-csv', positionOrder: 53 } + ]); + }); + + it('should not have the "export-csv" menu command when "enableExport" and "hideExportCsvCommand" are set', () => { + const copyGridOptionsMock = { ...gridOptionsMock, enableExport: true, gridMenu: { hideExportCsvCommand: true, hideExportTextDelimitedCommand: true } } as unknown as GridOption; + jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); + extension.register(); + expect(SharedService.prototype.gridOptions.gridMenu.customItems).toEqual([]); + }); + + it('should have the "export-text-delimited" menu command when "enableExport" is set', () => { + const copyGridOptionsMock = { ...gridOptionsMock, enableExport: true, gridMenu: { hideExportCsvCommand: true } } as unknown as GridOption; + jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); + extension.register(); + expect(SharedService.prototype.gridOptions.gridMenu.customItems).toEqual([ + { iconCssClass: 'fa fa-download', title: 'Exporter en format texte (délimité par tabulation)', disabled: false, command: 'export-text-delimited', positionOrder: 54 } + ]); + }); + + it('should not have the "export-text-delimited" menu command when "enableExport" and "hideExportCsvCommand" are set', () => { + const copyGridOptionsMock = { ...gridOptionsMock, enableExport: true, gridMenu: { hideExportCsvCommand: true, hideExportTextDelimitedCommand: true } } as unknown as GridOption; + jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); + extension.register(); + expect(SharedService.prototype.gridOptions.gridMenu.customItems).toEqual([]); + }); + }); + + describe('refreshBackendDataset method', () => { + afterEach(() => { + jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(gridOptionsMock); + }); + + it('should throw an error when backendServiceApi is not provided in the grid options', () => { + const copyGridOptionsMock = { ...gridOptionsMock, backendServiceApi: {} } as unknown as GridOption; + jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); + expect(() => extension.refreshBackendDataset()).toThrowError(`BackendServiceApi requires at least a "process" function and a "service" defined`); + }); + + it('should call the backend service API to refresh the dataset', (done) => { + const now = new Date(); + const query = `query { users (first:20,offset:0) { totalCount, nodes { id,name,gender,company } } }`; + const processResult = { + data: { users: { nodes: [] }, pageInfo: { hasNextPage: true }, totalCount: 0 }, + statistics: { startTime: now, endTime: now, executionTime: 0, totalItemCount: 0 } + }; + const preSpy = jest.spyOn(gridOptionsMock.backendServiceApi, 'preProcess'); + const postSpy = jest.spyOn(gridOptionsMock.backendServiceApi, 'postProcess'); + const promise = new Promise((resolve) => setTimeout(() => resolve(processResult), 1)); + const processSpy = jest.spyOn(gridOptionsMock.backendServiceApi, 'process').mockReturnValue(promise); + jest.spyOn(gridOptionsMock.backendServiceApi.service, 'buildQuery').mockReturnValue(query); + extension.refreshBackendDataset({ enableAddRow: true }); + expect(preSpy).toHaveBeenCalled(); + expect(processSpy).toHaveBeenCalled(); + promise.then(() => { + expect(postSpy).toHaveBeenCalledWith(processResult); + done(); + }); + }); + }); + + describe('executeGridMenuInternalCustomCommands method', () => { + it('should call "clearFilters" and dataview refresh when the command triggered is "clear-filter"', () => { + const filterSpy = jest.spyOn(filterServiceStub, 'clearFilters'); + const refreshSpy = jest.spyOn(SharedService.prototype.dataView, 'refresh'); + const onCommandSpy = jest.spyOn(SharedService.prototype.gridOptions.gridMenu, 'onCommand'); + + const instance = extension.register(); + instance.onCommand.notify({ grid: gridStub, command: 'clear-filter' }, new Slick.EventData(), gridStub); + + expect(onCommandSpy).toHaveBeenCalled(); + expect(filterSpy).toHaveBeenCalled(); + expect(refreshSpy).toHaveBeenCalled(); + }); + + it('should call "clearSorting" and dataview refresh when the command triggered is "clear-sorting"', () => { + const sortSpy = jest.spyOn(sortServiceStub, 'clearSorting'); + const refreshSpy = jest.spyOn(SharedService.prototype.dataView, 'refresh'); + const onCommandSpy = jest.spyOn(SharedService.prototype.gridOptions.gridMenu, 'onCommand'); + + const instance = extension.register(); + instance.onCommand.notify({ grid: gridStub, command: 'clear-sorting' }, new Slick.EventData(), gridStub); + + expect(onCommandSpy).toHaveBeenCalled(); + expect(sortSpy).toHaveBeenCalled(); + expect(refreshSpy).toHaveBeenCalled(); + }); + + it('should call "exportToFile" with CSV set when the command triggered is "export-csv"', () => { + const exportSpy = jest.spyOn(exportServiceStub, 'exportToFile'); + const onCommandSpy = jest.spyOn(SharedService.prototype.gridOptions.gridMenu, 'onCommand'); + + const instance = extension.register(); + instance.onCommand.notify({ grid: gridStub, command: 'export-csv' }, new Slick.EventData(), gridStub); + + expect(onCommandSpy).toHaveBeenCalled(); + expect(exportSpy).toHaveBeenCalledWith({ + delimiter: DelimiterType.comma, + filename: 'export', + format: FileType.csv, + useUtf8WithBom: true + }); + }); + + it('should call "exportToFile" with CSV set when the command triggered is "export-text-delimited"', () => { + const exportSpy = jest.spyOn(exportServiceStub, 'exportToFile'); + const onCommandSpy = jest.spyOn(SharedService.prototype.gridOptions.gridMenu, 'onCommand'); + + const instance = extension.register(); + instance.onCommand.notify({ grid: gridStub, command: 'export-text-delimited' }, new Slick.EventData(), gridStub); + + expect(onCommandSpy).toHaveBeenCalled(); + expect(exportSpy).toHaveBeenCalledWith({ + delimiter: DelimiterType.tab, + filename: 'export', + format: FileType.txt, + useUtf8WithBom: true + }); + }); + + it('should call the grid "setHeaderRowVisibility" method when the command triggered is "toggle-filter"', () => { + gridOptionsMock.showHeaderRow = false; + const gridSpy = jest.spyOn(SharedService.prototype.grid, 'setHeaderRowVisibility'); + const onCommandSpy = jest.spyOn(SharedService.prototype.gridOptions.gridMenu, 'onCommand'); + + const instance = extension.register(); + instance.onCommand.notify({ grid: gridStub, command: 'toggle-filter' }, new Slick.EventData(), gridStub); + + expect(onCommandSpy).toHaveBeenCalled(); + expect(gridSpy).toHaveBeenCalledWith(true); + + gridOptionsMock.showHeaderRow = true; + instance.onCommand.notify({ grid: gridStub, command: 'toggle-filter' }, new Slick.EventData(), gridStub); + + expect(onCommandSpy).toHaveBeenCalled(); + expect(gridSpy).toHaveBeenCalledWith(false); + }); + + it('should call the grid "setTopPanelVisibility" method when the command triggered is "toggle-toppanel"', () => { + gridOptionsMock.showTopPanel = false; + const gridSpy = jest.spyOn(SharedService.prototype.grid, 'setTopPanelVisibility'); + const onCommandSpy = jest.spyOn(SharedService.prototype.gridOptions.gridMenu, 'onCommand'); + + const instance = extension.register(); + instance.onCommand.notify({ grid: gridStub, command: 'toggle-toppanel' }, new Slick.EventData(), gridStub); + + expect(onCommandSpy).toHaveBeenCalled(); + expect(gridSpy).toHaveBeenCalledWith(true); + + gridOptionsMock.showTopPanel = true; + instance.onCommand.notify({ grid: gridStub, command: 'toggle-toppanel' }, new Slick.EventData(), gridStub); + + expect(onCommandSpy).toHaveBeenCalled(); + expect(gridSpy).toHaveBeenCalledWith(false); + }); + + it('should call the grid "setPreHeaderPanelVisibility" method when the command triggered is "toggle-preheader"', () => { + gridOptionsMock.showPreHeaderPanel = false; + const gridSpy = jest.spyOn(SharedService.prototype.grid, 'setPreHeaderPanelVisibility'); + const onCommandSpy = jest.spyOn(SharedService.prototype.gridOptions.gridMenu, 'onCommand'); + + const instance = extension.register(); + instance.onCommand.notify({ grid: gridStub, command: 'toggle-preheader' }, new Slick.EventData(), gridStub); + + expect(onCommandSpy).toHaveBeenCalled(); + expect(gridSpy).toHaveBeenCalledWith(true); + + gridOptionsMock.showPreHeaderPanel = true; + instance.onCommand.notify({ grid: gridStub, command: 'toggle-preheader' }, new Slick.EventData(), gridStub); + + expect(onCommandSpy).toHaveBeenCalled(); + expect(gridSpy).toHaveBeenCalledWith(false); + }); + + it('should call "refreshBackendDataset" method when the command triggered is "refresh-dataset"', () => { + const refreshSpy = jest.spyOn(extension, 'refreshBackendDataset'); + const onCommandSpy = jest.spyOn(SharedService.prototype.gridOptions.gridMenu, 'onCommand'); + + const instance = extension.register(); + instance.onCommand.notify({ grid: gridStub, command: 'refresh-dataset' }, new Slick.EventData(), gridStub); + + expect(onCommandSpy).toHaveBeenCalled(); + expect(refreshSpy).toHaveBeenCalled(); + }); + }); + + describe('translateGridMenu method', () => { + it('should translate the column picker header titles', () => { + const utilitySpy = jest.spyOn(extensionUtility, 'getPickerTitleOutputString'); + const translateSpy = jest.spyOn(extensionUtility, 'translateItems'); + + const instance = extension.register(); + extension.translateGridMenu(); + const initSpy = jest.spyOn(instance, 'init'); + + expect(utilitySpy).toHaveBeenCalled(); + expect(translateSpy).toHaveBeenCalled(); + expect(initSpy).toHaveBeenCalledWith(gridStub); + expect(SharedService.prototype.gridOptions.gridMenu.columnTitle).toBe('Colonnes'); + expect(SharedService.prototype.gridOptions.gridMenu.forceFitTitle).toBe('Ajustement forcé des colonnes'); + expect(SharedService.prototype.gridOptions.gridMenu.syncResizeTitle).toBe('Redimension synchrone'); + expect(columnsMock).toEqual([ + { id: 'field1', field: 'field1', width: 100, name: 'Titre', headerKey: 'TITLE' }, + { id: 'field2', field: 'field2', width: 75 } + ]); + }); + }); + + describe('showGridMenu method', () => { + it('should call the show grid menu', () => { + const instance = extension.register(); + + const showSpy = jest.spyOn(instance, 'showGridMenu'); + extension.showGridMenu(null); + + expect(showSpy).toHaveBeenCalled(); + }); + }); +}); diff --git a/src/aurelia-slickgrid/extensions/__tests__/headerMenuExtension.spec.ts b/src/aurelia-slickgrid/extensions/__tests__/headerMenuExtension.spec.ts new file mode 100644 index 000000000..e6adbbab8 --- /dev/null +++ b/src/aurelia-slickgrid/extensions/__tests__/headerMenuExtension.spec.ts @@ -0,0 +1,508 @@ +import { I18N } from 'aurelia-i18n'; +import { EventAggregator } from 'aurelia-event-aggregator'; +import { BindingSignaler } from 'aurelia-templating-resources'; +import { GridOption } from '../../models/gridOption.interface'; +import { HeaderMenuExtension } from '../headerMenuExtension'; +import { ExtensionUtility } from '../extensionUtility'; +import { SharedService } from '../../services/shared.service'; +import { Column, ColumnSort } from '../../models'; +import { ExportService, FilterService, SortService } from '../../services'; + +declare var Slick: any; +jest.mock('flatpickr', () => { }); + +const exportServiceStub = { + exportToFile: jest.fn(), +} as unknown as ExportService; + +const filterServiceStub = { + clearFilterByColumnId: jest.fn(), +} as unknown as FilterService; + +const sortServiceStub = { + clearSorting: jest.fn(), + getPreviousColumnSorts: jest.fn(), + onBackendSortChanged: jest.fn(), + onLocalSortChanged: jest.fn(), +} as unknown as SortService; + +const dataViewStub = { + refresh: jest.fn(), +}; + +const gridStub = { + autosizeColumns: jest.fn(), + getColumnIndex: jest.fn(), + getColumns: jest.fn(), + getOptions: jest.fn(), + registerPlugin: jest.fn(), + setColumns: jest.fn(), + setHeaderRowVisibility: jest.fn(), + setTopPanelVisibility: jest.fn(), + setPreHeaderPanelVisibility: jest.fn(), + setSortColumns: jest.fn(), + onSort: new Slick.Event(), +}; + +const mockAddon = jest.fn().mockImplementation(() => ({ + init: jest.fn(), + destroy: jest.fn(), + onBeforeMenuShow: new Slick.Event(), + onColumnsChanged: new Slick.Event(), + onCommand: new Slick.Event(), +})); + +jest.mock('slickgrid/plugins/slick.headermenu', () => mockAddon); +Slick.Plugins = { + HeaderMenu: mockAddon +}; + +describe('headerMenuExtension', () => { + const columnsMock: Column[] = [{ id: 'field1', field: 'field1', width: 100, headerKey: 'TITLE' }, { id: 'field2', field: 'field2', width: 75 }]; + let extensionUtility: ExtensionUtility; + let i18n: I18N; + let extension: HeaderMenuExtension; + let sharedService: SharedService; + + const gridOptionsMock = { + enableAutoSizeColumns: true, + enableHeaderMenu: true, + enableTranslate: true, + backendServiceApi: { + service: { + buildQuery: jest.fn(), + }, + internalPostProcess: jest.fn(), + preProcess: jest.fn(), + process: jest.fn(), + postProcess: jest.fn(), + }, + headerMenu: { + hideForceFitButton: false, + hideSyncResizeButton: true, + onExtensionRegistered: jest.fn(), + onCommand: (e, args: { command: any, item: any, grid: any }) => { }, + onBeforeMenuShow: (e, args: { menu: any, grid: any }) => { }, + }, + multiColumnSort: true, + pagination: { + totalItems: 0 + }, + showHeaderRow: false, + showTopPanel: false, + showPreHeaderPanel: false + } as unknown as GridOption; + + beforeEach(() => { + sharedService = new SharedService(); + i18n = new I18N(new EventAggregator(), new BindingSignaler()); + extensionUtility = new ExtensionUtility(i18n, sharedService); + extension = new HeaderMenuExtension(extensionUtility, filterServiceStub, i18n, sharedService, sortServiceStub); + i18n.setup({ + resources: { + en: { + translation: { + TITLE: 'Title', + COMMANDS: 'Commands', + COLUMNS: 'Columns', + FORCE_FIT_COLUMNS: 'Force fit columns', + SYNCHRONOUS_RESIZE: 'Synchronous resize', + HIDE_COLUMN: 'Hide Column', + REMOVE_FILTER: 'Remove Filter', + REMOVE_SORT: 'Remove Sort', + SORT_ASCENDING: 'Sort Ascending', + SORT_DESCENDING: 'Sort Descending', + } + }, + fr: { + translation: { + TITLE: 'Titre', + COMMANDS: 'Commandes', + COLUMNS: 'Colonnes', + FORCE_FIT_COLUMNS: 'Ajustement forcé des colonnes', + SYNCHRONOUS_RESIZE: 'Redimension synchrone', + HIDE_COLUMN: 'Cacher la colonne', + REMOVE_FILTER: 'Supprimer le filtre', + REMOVE_SORT: 'Supprimer le tri', + SORT_ASCENDING: 'Trier par ordre croissant', + SORT_DESCENDING: 'Trier par ordre décroissant', + } + } + }, + lng: '0', + fallbackLng: 'en', + debug: false + }); + i18n.setLocale('fr'); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should return null when either the grid object or the grid options is missing', () => { + const output = extension.register(); + expect(output).toBeNull(); + }); + + describe('registered addon', () => { + beforeEach(() => { + jest.spyOn(SharedService.prototype, 'dataView', 'get').mockReturnValue(dataViewStub); + jest.spyOn(SharedService.prototype, 'grid', 'get').mockReturnValue(gridStub); + jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(gridOptionsMock); + jest.spyOn(SharedService.prototype, 'allColumns', 'get').mockReturnValue(columnsMock); + jest.spyOn(SharedService.prototype, 'visibleColumns', 'get').mockReturnValue(columnsMock); + jest.spyOn(SharedService.prototype, 'columnDefinitions', 'get').mockReturnValue(columnsMock); + }); + + it('should register the addon', () => { + const pluginSpy = jest.spyOn(SharedService.prototype.grid, 'registerPlugin'); + const onRegisteredSpy = jest.spyOn(SharedService.prototype.gridOptions.headerMenu, 'onExtensionRegistered'); + + const instance = extension.register(); + + expect(mockAddon).toHaveBeenCalledWith({ + autoAlignOffset: 12, + minWidth: 140, + hideColumnHideCommand: false, + hideForceFitButton: false, + hideSyncResizeButton: true, + hideSortCommands: false, + title: '', + onCommand: expect.anything(), + onBeforeMenuShow: expect.anything(), + onExtensionRegistered: expect.anything(), + }); + expect(onRegisteredSpy).toHaveBeenCalledWith(instance); + expect(pluginSpy).toHaveBeenCalledWith(instance); + }); + + it('should call internal event handler subscribe and expect the "onBeforeMenuShow" option to be called when addon notify is called', () => { + const handlerSpy = jest.spyOn(extension.eventHandler, 'subscribe'); + const onBeforeSpy = jest.spyOn(SharedService.prototype.gridOptions.headerMenu, 'onBeforeMenuShow'); + const onCommandSpy = jest.spyOn(SharedService.prototype.gridOptions.headerMenu, 'onCommand'); + + const instance = extension.register(); + instance.onBeforeMenuShow.notify({ grid: gridStub, menu: {} }, new Slick.EventData(), gridStub); + + expect(handlerSpy).toHaveBeenCalledTimes(2); + expect(handlerSpy).toHaveBeenCalledWith( + { notify: expect.anything(), subscribe: expect.anything(), unsubscribe: expect.anything(), }, + expect.anything() + ); + expect(onBeforeSpy).toHaveBeenCalledWith(expect.anything(), { grid: gridStub, menu: {} }); + expect(onCommandSpy).not.toHaveBeenCalled(); + }); + + it('should call internal event handler subscribe and expect the "onCommand" option to be called when addon notify is called', () => { + const handlerSpy = jest.spyOn(extension.eventHandler, 'subscribe'); + const onBeforeSpy = jest.spyOn(SharedService.prototype.gridOptions.headerMenu, 'onBeforeMenuShow'); + const onCommandSpy = jest.spyOn(SharedService.prototype.gridOptions.headerMenu, 'onCommand'); + + const instance = extension.register(); + instance.onCommand.notify({ grid: gridStub, command: 'help' }, new Slick.EventData(), gridStub); + + expect(handlerSpy).toHaveBeenCalledTimes(2); + expect(handlerSpy).toHaveBeenCalledWith( + { notify: expect.anything(), subscribe: expect.anything(), unsubscribe: expect.anything(), }, + expect.anything() + ); + expect(onCommandSpy).toHaveBeenCalledWith(expect.anything(), { grid: gridStub, command: 'help' }); + expect(onBeforeSpy).not.toHaveBeenCalled(); + }); + + it('should dispose of the addon', () => { + const instance = extension.register(); + const destroySpy = jest.spyOn(instance, 'destroy'); + + extension.dispose(); + + expect(destroySpy).toHaveBeenCalled(); + }); + }); + + describe('addHeaderMenuCustomCommands method', () => { + const mockColumn = { id: 'field1', field: 'field1', width: 100, headerKey: 'TITLE', sortable: true, filterable: true } as any; + + beforeEach(() => { + jest.spyOn(SharedService.prototype, 'columnDefinitions', 'get').mockReturnValue([mockColumn]); + }); + afterEach(() => { + mockColumn.header = undefined; + jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(gridOptionsMock); + }); + + it('should have the command "hide-column" in the header menu list', () => { + const copyGridOptionsMock = { ...gridOptionsMock, headerMenu: { hideColumnHideCommand: false } } as unknown as GridOption; + jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); + extension.register(); + + expect(mockColumn.header.menu.items).not.toBeNull(); + expect(mockColumn.header.menu.items).toEqual([ + { iconCssClass: 'fa fa-times', title: 'Cacher la colonne', command: 'hide', positionOrder: 55 } + ]); + }); + + it('should expect all menu related to Sorting when "enableSorting" is set', () => { + const copyGridOptionsMock = { ...gridOptionsMock, enableSorting: true, headerMenu: { hideColumnHideCommand: true } } as unknown as GridOption; + jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); + + extension.register(); + + const headerMenuExpected = [ + { iconCssClass: 'fa fa-sort-asc', title: 'Trier par ordre croissant', command: 'sort-asc', positionOrder: 50 }, + { iconCssClass: 'fa fa-sort-desc', title: 'Trier par ordre décroissant', command: 'sort-desc', positionOrder: 51 }, + { divider: true, command: '', positionOrder: 52 }, + { iconCssClass: 'fa fa-unsorted', title: 'Supprimer le tri', command: 'clear-sort', positionOrder: 54 } + ]; + expect(mockColumn.header.menu.items).not.toBeNull(); + expect(mockColumn.header.menu.items).toEqual(headerMenuExpected); + + // double-check that registering again won't add duplicate commands + extension.register(); + expect(mockColumn.header.menu.items).toEqual(headerMenuExpected); + }); + + it('should expect only the "hide-column" command in the menu when "enableSorting" and "hideSortCommands" are set', () => { + const copyGridOptionsMock = { ...gridOptionsMock, enableSorting: true } as unknown as GridOption; + copyGridOptionsMock.headerMenu.hideColumnHideCommand = false; + copyGridOptionsMock.headerMenu.hideSortCommands = true; + jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); + + extension.register(); + + expect(mockColumn.header.menu.items).not.toBeNull(); + expect(mockColumn.header.menu.items).toEqual([ + { iconCssClass: 'fa fa-times', title: 'Cacher la colonne', command: 'hide', positionOrder: 55 } + ]); + }); + + it('should expect all menu related to Filtering when "enableFiltering" is set', () => { + const copyGridOptionsMock = { ...gridOptionsMock, enableFiltering: true, headerMenu: { hideColumnHideCommand: true } } as unknown as GridOption; + jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); + + extension.register(); + + const headerMenuExpected = [{ iconCssClass: 'fa fa-filter', title: 'Supprimer le filtre', command: 'clear-filter', positionOrder: 53 }]; + expect(mockColumn.header.menu.items).not.toBeNull(); + expect(mockColumn.header.menu.items).toEqual(headerMenuExpected); + + // double-check that registering again won't add duplicate commands + extension.register(); + expect(mockColumn.header.menu.items).toEqual(headerMenuExpected); + }); + }); + + describe('hideColumn method', () => { + it('should call hideColumn and expect "visibleColumns" to be updated accordingly', () => { + jest.spyOn(SharedService.prototype, 'grid', 'get').mockReturnValue(gridStub); + jest.spyOn(gridStub, 'getColumnIndex').mockReturnValue(1); + jest.spyOn(gridStub, 'getColumns').mockReturnValue(columnsMock); + const setColumnsSpy = jest.spyOn(gridStub, 'setColumns'); + const visibleSpy = jest.spyOn(SharedService.prototype, 'visibleColumns', 'set'); + const updatedColumnsMock = [{ + id: 'field1', field: 'field1', headerKey: 'TITLE', width: 100, + header: { menu: { items: [{ command: 'hide', iconCssClass: 'fa fa-times', positionOrder: 55, title: 'Cacher la colonne' }] } } + }] as Column[]; + + extension.hideColumn(columnsMock[1]); + + expect(visibleSpy).toHaveBeenCalledWith(updatedColumnsMock); + expect(setColumnsSpy).toHaveBeenCalledWith(updatedColumnsMock); + }); + }); + + describe('translateHeaderMenu method', () => { + it('should call the resetHeaderMenuTranslations and have all header menu translated', () => { + const mockColumns: Column[] = [{ + id: 'field1', field: 'field1', width: 100, + header: { + menu: { + items: [ + { iconCssClass: 'fa fa-sort-asc', title: 'Trier par ordre croissant', command: 'sort-asc', positionOrder: 50 }, + { iconCssClass: 'fa fa-sort-desc', title: 'Trier par ordre décroissant', command: 'sort-desc', positionOrder: 51 }, + { divider: true, command: '', positionOrder: 52 }, + { iconCssClass: 'fa fa-filter', title: 'Supprimer le filtre', command: 'clear-filter', positionOrder: 53 }, + { iconCssClass: 'fa fa-unsorted', title: 'Supprimer le tri', command: 'clear-sort', positionOrder: 54 }, + { iconCssClass: 'fa fa-times', command: 'hide', positionOrder: 55, title: 'Cacher la colonne' }, + ] + } + } + }]; + jest.spyOn(SharedService.prototype, 'visibleColumns', 'get').mockReturnValue(mockColumns); + + i18n.setLocale('en'); + extension.translateHeaderMenu(); + + expect(mockColumns).toEqual([{ + id: 'field1', field: 'field1', width: 100, + header: { + menu: { + items: [ + { iconCssClass: 'fa fa-sort-asc', title: 'Sort Ascending', command: 'sort-asc', positionOrder: 50 }, + { iconCssClass: 'fa fa-sort-desc', title: 'Sort Descending', command: 'sort-desc', positionOrder: 51 }, + { divider: true, command: '', positionOrder: 52 }, + { iconCssClass: 'fa fa-filter', title: 'Remove Filter', command: 'clear-filter', positionOrder: 53 }, + { iconCssClass: 'fa fa-unsorted', title: 'Remove Sort', command: 'clear-sort', positionOrder: 54 }, + { iconCssClass: 'fa fa-times', command: 'hide', positionOrder: 55, title: 'Hide Column' }, + ] + } + } + }]); + }); + }); + + describe('executeHeaderMenuInternalCommands method', () => { + it('should trigger the command "hide" and expect the grid "autosizeColumns" method being called', () => { + const onCommandSpy = jest.spyOn(SharedService.prototype.gridOptions.headerMenu, 'onCommand'); + const autosizeSpy = jest.spyOn(SharedService.prototype.grid, 'autosizeColumns'); + + const instance = extension.register(); + instance.onCommand.notify({ column: columnsMock[0], grid: gridStub, command: 'hide' }, new Slick.EventData(), gridStub); + + expect(onCommandSpy).toHaveBeenCalled(); + expect(autosizeSpy).toHaveBeenCalled(); + }); + + it('should trigger the command "clear-filter" and expect "clearColumnFilter" method being called with dataview refresh', () => { + const filterSpy = jest.spyOn(filterServiceStub, 'clearFilterByColumnId'); + const onCommandSpy = jest.spyOn(SharedService.prototype.gridOptions.headerMenu, 'onCommand'); + + const instance = extension.register(); + instance.onCommand.notify({ column: columnsMock[0], grid: gridStub, command: 'clear-filter' }, new Slick.EventData(), gridStub); + + expect(onCommandSpy).toHaveBeenCalled(); + expect(filterSpy).toHaveBeenCalledWith(expect.anything(), columnsMock[0].id); + }); + + it('should trigger the command "clear-sort" and expect Sort Service to call "onBackendSortChanged" being called without the sorted column', () => { + const mockSortedCols: ColumnSort[] = [{ sortAsc: true, sortCol: { id: 'field1', field: 'field1' } }, { sortAsc: false, sortCol: { id: 'field2', field: 'field2' } }]; + const previousSortSpy = jest.spyOn(sortServiceStub, 'getPreviousColumnSorts').mockReturnValue([mockSortedCols[1]]).mockReturnValueOnce(mockSortedCols).mockRejectedValueOnce([mockSortedCols[1]]); + const backendSortSpy = jest.spyOn(sortServiceStub, 'onBackendSortChanged'); + const onCommandSpy = jest.spyOn(SharedService.prototype.gridOptions.headerMenu, 'onCommand'); + const setSortSpy = jest.spyOn(SharedService.prototype.grid, 'setSortColumns'); + + const instance = extension.register(); + instance.onCommand.notify({ column: columnsMock[0], grid: gridStub, command: 'clear-sort' }, new Slick.EventData(), gridStub); + + expect(previousSortSpy).toHaveBeenCalled(); + expect(backendSortSpy).toHaveBeenCalledWith(expect.anything(), { multiColumnSort: true, sortCols: [mockSortedCols[1]], grid: gridStub }); + expect(onCommandSpy).toHaveBeenCalled(); + expect(setSortSpy).toHaveBeenCalled(); + }); + + it('should trigger the command "clear-sort" and expect Sort Service to call "onLocalSortChanged" being called without the sorted column', () => { + const copyGridOptionsMock = { ...gridOptionsMock, backendServiceApi: undefined } as unknown as GridOption; + jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); + const mockSortedCols: ColumnSort[] = [{ sortAsc: true, sortCol: { id: 'field1', field: 'field1' } }, { sortAsc: false, sortCol: { id: 'field2', field: 'field2' } }]; + const previousSortSpy = jest.spyOn(sortServiceStub, 'getPreviousColumnSorts').mockReturnValue([mockSortedCols[1]]).mockReturnValueOnce(mockSortedCols).mockRejectedValueOnce([mockSortedCols[1]]); + const localSortSpy = jest.spyOn(sortServiceStub, 'onLocalSortChanged'); + const onCommandSpy = jest.spyOn(SharedService.prototype.gridOptions.headerMenu, 'onCommand'); + const setSortSpy = jest.spyOn(SharedService.prototype.grid, 'setSortColumns'); + + const instance = extension.register(); + instance.onCommand.notify({ column: columnsMock[0], grid: gridStub, command: 'clear-sort' }, new Slick.EventData(), gridStub); + + expect(previousSortSpy).toHaveBeenCalled(); + expect(localSortSpy).toHaveBeenCalledWith(gridStub, dataViewStub, [mockSortedCols[1]], true); + expect(onCommandSpy).toHaveBeenCalled(); + expect(setSortSpy).toHaveBeenCalled(); + }); + + it('should trigger the command "clear-sort" and expect "onSort" event triggered when no DataView is provided', () => { + const copyGridOptionsMock = { ...gridOptionsMock, backendServiceApi: undefined } as unknown as GridOption; + const mockSortedCols: ColumnSort[] = [{ sortAsc: true, sortCol: { id: 'field1', field: 'field1' } }, { sortAsc: false, sortCol: { id: 'field2', field: 'field2' } }]; + + jest.spyOn(SharedService.prototype, 'dataView', 'get').mockReturnValue(undefined); + jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); + const previousSortSpy = jest.spyOn(sortServiceStub, 'getPreviousColumnSorts').mockReturnValue([mockSortedCols[1]]).mockReturnValueOnce(mockSortedCols).mockRejectedValueOnce([mockSortedCols[1]]); + const onCommandSpy = jest.spyOn(SharedService.prototype.gridOptions.headerMenu, 'onCommand'); + const setSortSpy = jest.spyOn(SharedService.prototype.grid, 'setSortColumns'); + const gridSortSpy = jest.spyOn(gridStub.onSort, 'notify'); + + const instance = extension.register(); + instance.onCommand.notify({ column: columnsMock[0], grid: gridStub, command: 'clear-sort' }, new Slick.EventData(), gridStub); + + expect(previousSortSpy).toHaveBeenCalled(); + expect(onCommandSpy).toHaveBeenCalled(); + expect(setSortSpy).toHaveBeenCalled(); + expect(gridSortSpy).toHaveBeenCalledWith([mockSortedCols[1]]); + }); + + it('should trigger the command "sort-asc" and expect Sort Service to call "onBackendSortChanged" being called without the sorted column', () => { + jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(gridOptionsMock); + const mockSortedCols: ColumnSort[] = [{ sortAsc: true, sortCol: { id: 'field1', field: 'field1' } }, { sortAsc: false, sortCol: { id: 'field2', field: 'field2' } }]; + const mockSortedOuput: ColumnSort[] = [{ sortAsc: true, sortCol: { id: 'field1', field: 'field1' } }, { sortAsc: true, sortCol: { id: 'field2', field: 'field2' } }]; + const previousSortSpy = jest.spyOn(sortServiceStub, 'getPreviousColumnSorts').mockReturnValue([mockSortedCols[0]]); + const backendSortSpy = jest.spyOn(sortServiceStub, 'onBackendSortChanged'); + const onCommandSpy = jest.spyOn(SharedService.prototype.gridOptions.headerMenu, 'onCommand'); + const setSortSpy = jest.spyOn(SharedService.prototype.grid, 'setSortColumns'); + + const instance = extension.register(); + instance.onCommand.notify({ column: mockSortedCols[1].sortCol, grid: gridStub, command: 'sort-asc' }, new Slick.EventData(), gridStub); + + expect(previousSortSpy).toHaveBeenCalled(); + expect(backendSortSpy).toHaveBeenCalledWith(expect.anything(), { multiColumnSort: true, sortCols: mockSortedOuput, grid: gridStub }); + expect(onCommandSpy).toHaveBeenCalled(); + expect(setSortSpy).toHaveBeenCalled(); + }); + + it('should trigger the command "sort-desc" and expect Sort Service to call "onBackendSortChanged" being called without the sorted column', () => { + jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(gridOptionsMock); + const mockSortedCols: ColumnSort[] = [{ sortAsc: true, sortCol: { id: 'field1', field: 'field1' } }, { sortAsc: true, sortCol: { id: 'field2', field: 'field2' } }]; + const mockSortedOuput: ColumnSort[] = [{ sortAsc: true, sortCol: { id: 'field1', field: 'field1' } }, { sortAsc: false, sortCol: { id: 'field2', field: 'field2' } }]; + const previousSortSpy = jest.spyOn(sortServiceStub, 'getPreviousColumnSorts').mockReturnValue([mockSortedCols[0]]); + const backendSortSpy = jest.spyOn(sortServiceStub, 'onBackendSortChanged'); + const onCommandSpy = jest.spyOn(SharedService.prototype.gridOptions.headerMenu, 'onCommand'); + const setSortSpy = jest.spyOn(SharedService.prototype.grid, 'setSortColumns'); + + const instance = extension.register(); + instance.onCommand.notify({ column: mockSortedCols[1].sortCol, grid: gridStub, command: 'sort-desc' }, new Slick.EventData(), gridStub); + + expect(previousSortSpy).toHaveBeenCalled(); + expect(backendSortSpy).toHaveBeenCalledWith(expect.anything(), { multiColumnSort: true, sortCols: mockSortedOuput, grid: gridStub }); + expect(onCommandSpy).toHaveBeenCalled(); + expect(setSortSpy).toHaveBeenCalled(); + }); + + it('should trigger the command "sort-desc" and expect Sort Service to call "onLocalSortChanged" being called without the sorted column', () => { + const copyGridOptionsMock = { ...gridOptionsMock, backendServiceApi: undefined } as unknown as GridOption; + jest.spyOn(SharedService.prototype, 'dataView', 'get').mockReturnValue(dataViewStub); + jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); + const mockSortedCols: ColumnSort[] = [{ sortAsc: true, sortCol: { id: 'field1', field: 'field1' } }, { sortAsc: true, sortCol: { id: 'field2', field: 'field2' } }]; + const mockSortedOuput: ColumnSort[] = [{ sortAsc: true, sortCol: { id: 'field1', field: 'field1' } }, { sortAsc: false, sortCol: { id: 'field2', field: 'field2' } }]; + const previousSortSpy = jest.spyOn(sortServiceStub, 'getPreviousColumnSorts').mockReturnValue([mockSortedCols[0]]); + const localSortSpy = jest.spyOn(sortServiceStub, 'onLocalSortChanged'); + const onCommandSpy = jest.spyOn(SharedService.prototype.gridOptions.headerMenu, 'onCommand'); + const setSortSpy = jest.spyOn(SharedService.prototype.grid, 'setSortColumns'); + + const instance = extension.register(); + instance.onCommand.notify({ column: mockSortedCols[1].sortCol, grid: gridStub, command: 'sort-desc' }, new Slick.EventData(), gridStub); + + expect(previousSortSpy).toHaveBeenCalled(); + expect(localSortSpy).toHaveBeenCalledWith(gridStub, dataViewStub, mockSortedOuput); + expect(onCommandSpy).toHaveBeenCalled(); + expect(setSortSpy).toHaveBeenCalled(); + }); + + it('should trigger the command "sort-desc" and expect "onSort" event triggered when no DataView is provided', () => { + const copyGridOptionsMock = { ...gridOptionsMock, backendServiceApi: undefined } as unknown as GridOption; + jest.spyOn(SharedService.prototype, 'dataView', 'get').mockReturnValue(undefined); + jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); + const mockSortedCols: ColumnSort[] = [{ sortAsc: true, sortCol: { id: 'field1', field: 'field1' } }, { sortAsc: true, sortCol: { id: 'field2', field: 'field2' } }]; + const mockSortedOuput: ColumnSort[] = [{ sortAsc: true, sortCol: { id: 'field1', field: 'field1' } }, { sortAsc: false, sortCol: { id: 'field2', field: 'field2' } }]; + const previousSortSpy = jest.spyOn(sortServiceStub, 'getPreviousColumnSorts').mockReturnValue([mockSortedCols[0]]); + const onCommandSpy = jest.spyOn(SharedService.prototype.gridOptions.headerMenu, 'onCommand'); + const setSortSpy = jest.spyOn(SharedService.prototype.grid, 'setSortColumns'); + const gridSortSpy = jest.spyOn(gridStub.onSort, 'notify'); + + const instance = extension.register(); + instance.onCommand.notify({ column: mockSortedCols[1].sortCol, grid: gridStub, command: 'sort-desc' }, new Slick.EventData(), gridStub); + + expect(previousSortSpy).toHaveBeenCalled(); + expect(gridSortSpy).toHaveBeenCalledWith(mockSortedOuput); + expect(onCommandSpy).toHaveBeenCalled(); + expect(setSortSpy).toHaveBeenCalled(); + }); + }); +}); diff --git a/src/aurelia-slickgrid/extensions/__tests__/rowMoveManagerExtension.spec.ts b/src/aurelia-slickgrid/extensions/__tests__/rowMoveManagerExtension.spec.ts new file mode 100644 index 000000000..6e2c36610 --- /dev/null +++ b/src/aurelia-slickgrid/extensions/__tests__/rowMoveManagerExtension.spec.ts @@ -0,0 +1,136 @@ +import { I18N } from 'aurelia-i18n'; +import { GridOption } from '../../models/gridOption.interface'; +import { RowMoveManagerExtension } from '../rowMoveManagerExtension'; +import { ExtensionUtility } from '../extensionUtility'; +import { SharedService } from '../../services/shared.service'; + +declare var Slick: any; + +const gridStub = { + getOptions: jest.fn(), + getSelectionModel: jest.fn(), + registerPlugin: jest.fn(), + setSelectionModel: jest.fn(), +}; + +const mockAddon = jest.fn().mockImplementation(() => ({ + init: jest.fn(), + destroy: jest.fn(), + onBeforeMoveRows: new Slick.Event(), + onMoveRows: new Slick.Event(), +})); + +const mockSelectionModel = jest.fn().mockImplementation(() => ({ + init: jest.fn(), + destroy: jest.fn() +})); + +jest.mock('slickgrid/plugins/slick.rowmovemanager', () => mockAddon); +Slick.RowMoveManager = mockAddon; + +jest.mock('slickgrid/plugins/slick.rowselectionmodel', () => mockSelectionModel); +Slick.RowSelectionModel = mockSelectionModel; + +describe('rowMoveManagerExtension', () => { + let extensionUtility: ExtensionUtility; + let sharedService: SharedService; + let extension: RowMoveManagerExtension; + const gridOptionsMock = { + enableRowMoveManager: true, + rowMoveManager: { + onExtensionRegistered: jest.fn(), + onBeforeMoveRows: (e, args: { insertBefore: number; rows: number[]; }) => { }, + onMoveRows: (e, args: { insertBefore: number; rows: number[]; }) => { }, + }, + } as GridOption; + + beforeEach(() => { + sharedService = new SharedService(); + extensionUtility = new ExtensionUtility({} as I18N, sharedService); + extension = new RowMoveManagerExtension(extensionUtility, sharedService); + }); + + it('should return null when either the grid object or the grid options is missing', () => { + const output = extension.register(); + expect(output).toBeNull(); + }); + + describe('registered addon', () => { + beforeEach(() => { + jest.spyOn(SharedService.prototype, 'grid', 'get').mockReturnValue(gridStub); + jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(gridOptionsMock); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should register the addon', () => { + const onRegisteredSpy = jest.spyOn(SharedService.prototype.gridOptions.rowMoveManager, 'onExtensionRegistered'); + const pluginSpy = jest.spyOn(SharedService.prototype.grid, 'registerPlugin'); + + const instance = extension.register(); + + expect(mockAddon).toHaveBeenCalledWith({ + onExtensionRegistered: expect.anything(), + onBeforeMoveRows: expect.anything(), + onMoveRows: expect.anything(), + }); + expect(onRegisteredSpy).toHaveBeenCalledWith(instance); + expect(pluginSpy).toHaveBeenCalledWith(instance); + }); + + it('should dispose of the addon', () => { + const instance = extension.register(); + const destroySpy = jest.spyOn(instance, 'destroy'); + + extension.dispose(); + + expect(destroySpy).toHaveBeenCalled(); + }); + + it('should provide addon options and expect them to be called in the addon constructor', () => { + const optionMock = { cancelEditOnDrag: true }; + const addonOptions = { ...gridOptionsMock, rowMoveManager: optionMock }; + jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(addonOptions); + + extension.register(); + + expect(mockAddon).toHaveBeenCalledWith(optionMock); + }); + + it('should call internal event handler subscribe and expect the "onBeforeMoveRows" option to be called when addon notify is called', () => { + const handlerSpy = jest.spyOn(extension.eventHandler, 'subscribe'); + const onBeforeSpy = jest.spyOn(SharedService.prototype.gridOptions.rowMoveManager, 'onBeforeMoveRows'); + const onMoveSpy = jest.spyOn(SharedService.prototype.gridOptions.rowMoveManager, 'onMoveRows'); + + const instance = extension.register(); + instance.onBeforeMoveRows.notify({ insertBefore: 3, rows: [1] }, new Slick.EventData(), gridStub); + + expect(handlerSpy).toHaveBeenCalledTimes(2); + expect(handlerSpy).toHaveBeenCalledWith( + { notify: expect.anything(), subscribe: expect.anything(), unsubscribe: expect.anything(), }, + expect.anything() + ); + expect(onBeforeSpy).toHaveBeenCalledWith(expect.anything(), { insertBefore: 3, rows: [1] }); + expect(onMoveSpy).not.toHaveBeenCalled(); + }); + + it('should call internal event handler subscribe and expect the "onMoveRows" option to be called when addon notify is called', () => { + const handlerSpy = jest.spyOn(extension.eventHandler, 'subscribe'); + const onBeforeSpy = jest.spyOn(SharedService.prototype.gridOptions.rowMoveManager, 'onBeforeMoveRows'); + const onMoveSpy = jest.spyOn(SharedService.prototype.gridOptions.rowMoveManager, 'onMoveRows'); + + const instance = extension.register(); + instance.onMoveRows.notify({ insertBefore: 3, rows: [1] }, new Slick.EventData(), gridStub); + + expect(handlerSpy).toHaveBeenCalledTimes(2); + expect(handlerSpy).toHaveBeenCalledWith( + { notify: expect.anything(), subscribe: expect.anything(), unsubscribe: expect.anything(), }, + expect.anything() + ); + expect(onMoveSpy).toHaveBeenCalledWith(expect.anything(), { insertBefore: 3, rows: [1] }); + expect(onBeforeSpy).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/src/aurelia-slickgrid/extensions/columnPickerExtension.ts b/src/aurelia-slickgrid/extensions/columnPickerExtension.ts index 3e5df70a8..3b509ed14 100644 --- a/src/aurelia-slickgrid/extensions/columnPickerExtension.ts +++ b/src/aurelia-slickgrid/extensions/columnPickerExtension.ts @@ -1,5 +1,5 @@ import { singleton, inject } from 'aurelia-framework'; -import { CellArgs, Extension, ExtensionName } from '../models/index'; +import { CellArgs, Extension, ExtensionName, SlickEventHandler } from '../models/index'; import { ExtensionUtility } from './extensionUtility'; import { SharedService } from '../services/shared.service'; @@ -9,20 +9,26 @@ declare var Slick: any; @singleton(true) @inject(ExtensionUtility, SharedService) export class ColumnPickerExtension implements Extension { - private _eventHandler: any = new Slick.EventHandler(); - private _extension: any; + private _addon: any; + private _eventHandler: SlickEventHandler; constructor( private extensionUtility: ExtensionUtility, private sharedService: SharedService, - ) { } + ) { + this._eventHandler = new Slick.EventHandler(); + } + + get eventHandler(): SlickEventHandler { + return this._eventHandler; + } dispose() { // unsubscribe all SlickGrid events this._eventHandler.unsubscribeAll(); - if (this._extension && this._extension.destroy) { - this._extension.destroy(); + if (this._addon && this._addon.destroy) { + this._addon.destroy(); } } @@ -41,23 +47,27 @@ export class ColumnPickerExtension implements Extension { this.sharedService.gridOptions.columnPicker.forceFitTitle = this.sharedService.gridOptions.columnPicker.forceFitTitle || forceFitTitle; this.sharedService.gridOptions.columnPicker.syncResizeTitle = this.sharedService.gridOptions.columnPicker.syncResizeTitle || syncResizeTitle; - this._extension = new Slick.Controls.ColumnPicker(this.sharedService.columnDefinitions, this.sharedService.grid, this.sharedService.gridOptions); + this._addon = new Slick.Controls.ColumnPicker(this.sharedService.columnDefinitions, this.sharedService.grid, this.sharedService.gridOptions); if (this.sharedService.grid && this.sharedService.gridOptions.enableColumnPicker) { - this._eventHandler.subscribe(this._extension.onColumnsChanged, (e: any, args: CellArgs) => { + if (this.sharedService.gridOptions.columnPicker.onExtensionRegistered) { + this.sharedService.gridOptions.columnPicker.onExtensionRegistered(this._addon); + } + this._eventHandler.subscribe(this._addon.onColumnsChanged, (e: any, args: CellArgs) => { if (this.sharedService.gridOptions.columnPicker && typeof this.sharedService.gridOptions.columnPicker.onColumnsChanged === 'function') { this.sharedService.gridOptions.columnPicker.onColumnsChanged(e, args); } }); } - return this._extension; + return this._addon; } + return null; } - /** Translate the Column Picker and it's last 2 checkboxes */ + /** Translate the Column Picker headers and also the last 2 checkboxes */ translateColumnPicker() { if (this.sharedService && this.sharedService.grid && this.sharedService.gridOptions) { - // update the properties by pointers, that is the only way to get Grid Menu Control to see the new values + // update the properties by pointers, that is the only way to get Column Picker Control to see the new values if (this.sharedService.gridOptions.columnPicker) { this.emptyColumnPickerTitles(); @@ -66,13 +76,13 @@ export class ColumnPickerExtension implements Extension { this.sharedService.gridOptions.columnPicker.syncResizeTitle = this.extensionUtility.getPickerTitleOutputString('syncResizeTitle', 'columnPicker'); } - // translate all columns (including non-visible) + // translate all columns (including hidden columns) this.extensionUtility.translateItems(this.sharedService.allColumns, 'headerKey', 'name'); // re-initialize the Column Picker, that will recreate all the list // doing an "init()" won't drop any existing command attached - if (this._extension.init) { - this._extension.init(this.sharedService.grid); + if (this._addon.init) { + this._addon.init(this.sharedService.grid); } } } diff --git a/src/aurelia-slickgrid/extensions/draggableGroupingExtension.ts b/src/aurelia-slickgrid/extensions/draggableGroupingExtension.ts index 247e0e342..7e1371ad3 100644 --- a/src/aurelia-slickgrid/extensions/draggableGroupingExtension.ts +++ b/src/aurelia-slickgrid/extensions/draggableGroupingExtension.ts @@ -1,6 +1,6 @@ import { singleton, inject } from 'aurelia-framework'; import { SharedService } from '../services/shared.service'; -import { Extension, ExtensionName, GridOption, Grouping } from '../models/index'; +import { Extension, ExtensionName, GridOption, Grouping, SlickEventHandler } from '../models/index'; import { ExtensionUtility } from './extensionUtility'; // using external non-typed js libraries @@ -9,17 +9,23 @@ declare var Slick: any; @singleton(true) @inject(ExtensionUtility, SharedService) export class DraggableGroupingExtension implements Extension { - private _eventHandler: any = new Slick.EventHandler(); - private _extension: any; + private _addon: any; + private _eventHandler: SlickEventHandler; - constructor(private extensionUtility: ExtensionUtility, private sharedService: SharedService) { } + constructor(private extensionUtility: ExtensionUtility, private sharedService: SharedService) { + this._eventHandler = new Slick.EventHandler(); + } + + get eventHandler(): SlickEventHandler { + return this._eventHandler; + } dispose() { // unsubscribe all SlickGrid events this._eventHandler.unsubscribeAll(); - if (this._extension && this._extension.destroy) { - this._extension.destroy(); + if (this._addon && this._addon.destroy) { + this._addon.destroy(); } } @@ -31,29 +37,29 @@ export class DraggableGroupingExtension implements Extension { // dynamically import the SlickGrid plugin (addon) with RequireJS this.extensionUtility.loadExtensionDynamically(ExtensionName.draggableGrouping); - if (!this._extension && gridOptions) { - this._extension = new Slick.DraggableGrouping(gridOptions.draggableGrouping || {}); + if (!this._addon && gridOptions) { + this._addon = new Slick.DraggableGrouping(gridOptions.draggableGrouping || {}); } - return this._extension; + return this._addon; } register(): any { if (this.sharedService && this.sharedService.grid && this.sharedService.gridOptions) { - this.sharedService.grid.registerPlugin(this._extension); + this.sharedService.grid.registerPlugin(this._addon); // Events if (this.sharedService.grid && this.sharedService.gridOptions.draggableGrouping) { if (this.sharedService.gridOptions.draggableGrouping.onExtensionRegistered) { - this.sharedService.gridOptions.draggableGrouping.onExtensionRegistered(this._extension); + this.sharedService.gridOptions.draggableGrouping.onExtensionRegistered(this._addon); } - this._eventHandler.subscribe(this._extension.onGroupChanged, (e: any, args: { caller?: string; groupColumns: Grouping[] }) => { + this._eventHandler.subscribe(this._addon.onGroupChanged, (e: any, args: { caller?: string; groupColumns: Grouping[] }) => { if (this.sharedService.gridOptions.draggableGrouping && typeof this.sharedService.gridOptions.draggableGrouping.onGroupChanged === 'function') { this.sharedService.gridOptions.draggableGrouping.onGroupChanged(e, args); } }); } - return this._extension; + return this._addon; } return null; } diff --git a/src/aurelia-slickgrid/extensions/extensionUtility.ts b/src/aurelia-slickgrid/extensions/extensionUtility.ts index 82057f320..be81813b4 100644 --- a/src/aurelia-slickgrid/extensions/extensionUtility.ts +++ b/src/aurelia-slickgrid/extensions/extensionUtility.ts @@ -17,9 +17,7 @@ export class ExtensionUtility { * @param index */ arrayRemoveItemByIndex(array: any[], index: number) { - return array.filter((el: any, i: number) => { - return index !== i; - }); + return array.filter((el: any, i: number) => index !== i); } /** @@ -43,7 +41,7 @@ export class ExtensionUtility { require('slickgrid/controls/slick.columnpicker'); break; case ExtensionName.draggableGrouping: - require('slickgrid/plugins/slick.draggablegrouping.js'); + require('slickgrid/plugins/slick.draggablegrouping'); break; case ExtensionName.gridMenu: require('slickgrid/controls/slick.gridmenu'); @@ -61,10 +59,10 @@ export class ExtensionUtility { require('slickgrid/plugins/slick.rowselectionmodel'); break; case ExtensionName.rowDetailView: - require('slickgrid/plugins/slick.rowdetailview.js'); + require('slickgrid/plugins/slick.rowdetailview'); break; case ExtensionName.rowMoveManager: - require('slickgrid/plugins/slick.rowmovemanager.js'); + require('slickgrid/plugins/slick.rowmovemanager'); break; } } catch (e) { @@ -112,10 +110,9 @@ export class ExtensionUtility { } /** - * Sort items in an array by a property name + * Sort items (by pointers) in an array by a property name * @params items array * @param property name to sort with - * @return sorted array */ sortItems(items: any[], propertyName: string) { // sort the custom items by their position in the list @@ -129,9 +126,11 @@ export class ExtensionUtility { /** Translate the an array of items from an input key and assign to the output key */ translateItems(items: any[], inputKey: string, outputKey: string) { - for (const item of items) { - if (item[inputKey]) { - item[outputKey] = this.i18n.tr(item[inputKey]); + if (Array.isArray(items)) { + for (const item of items) { + if (item[inputKey]) { + item[outputKey] = this.i18n.tr(item[inputKey]); + } } } } diff --git a/src/aurelia-slickgrid/extensions/gridMenuExtension.ts b/src/aurelia-slickgrid/extensions/gridMenuExtension.ts index 52a87e75b..0d6f1d8fd 100644 --- a/src/aurelia-slickgrid/extensions/gridMenuExtension.ts +++ b/src/aurelia-slickgrid/extensions/gridMenuExtension.ts @@ -3,8 +3,6 @@ import { I18N } from 'aurelia-i18n'; import { Constants } from '../constants'; import { CellArgs, - Column, - ColumnSort, DelimiterType, ExtensionName, Extension, @@ -13,7 +11,7 @@ import { GridMenu, GridMenuItem, GridOption, - HeaderMenuOnCommandArgs, + SlickEventHandler, } from '../models/index'; import { ExportService } from '../services/export.service'; import { ExtensionUtility } from './extensionUtility'; @@ -35,8 +33,8 @@ declare var Slick: any; SortService, ) export class GridMenuExtension implements Extension { - private _eventHandler: any = new Slick.EventHandler(); - private _extension: any; + private _addon: any; + private _eventHandler: SlickEventHandler; areVisibleColumnDifferent = false; userOriginalGridMenu: GridMenu; @@ -47,20 +45,26 @@ export class GridMenuExtension implements Extension { private i18n: I18N, private sharedService: SharedService, private sortService: SortService, - ) { } + ) { + this._eventHandler = new Slick.EventHandler(); + } + + get eventHandler(): SlickEventHandler { + return this._eventHandler; + } dispose() { // unsubscribe all SlickGrid events this._eventHandler.unsubscribeAll(); - if (this._extension && this._extension.destroy) { - this._extension.destroy(); + if (this._addon && this._addon.destroy) { + this._addon.destroy(); } } /** Show the Grid Menu typically from a button click since we need to know the event */ showGridMenu(e: Event) { - this._extension.showGridMenu(e); + this._addon.showGridMenu(e); } /** Create the Header Menu and expose all the available hooks that user can subscribe (onCommand, onBeforeMenuShow, ...) */ @@ -80,31 +84,31 @@ export class GridMenuExtension implements Extension { this.extensionUtility.translateItems(this.sharedService.gridOptions.gridMenu.customItems, 'titleKey', 'title'); this.extensionUtility.sortItems(this.sharedService.gridOptions.gridMenu.customItems, 'positionOrder'); - this._extension = new Slick.Controls.GridMenu(this.sharedService.columnDefinitions, this.sharedService.grid, this.sharedService.gridOptions); + this._addon = new Slick.Controls.GridMenu(this.sharedService.columnDefinitions, this.sharedService.grid, this.sharedService.gridOptions); // hook all events if (this.sharedService.grid && this.sharedService.gridOptions.gridMenu) { if (this.sharedService.gridOptions.gridMenu.onExtensionRegistered) { - this.sharedService.gridOptions.gridMenu.onExtensionRegistered(this._extension); + this.sharedService.gridOptions.gridMenu.onExtensionRegistered(this._addon); } - this._eventHandler.subscribe(this._extension.onBeforeMenuShow, (e: any, args: CellArgs) => { + this._eventHandler.subscribe(this._addon.onBeforeMenuShow, (e: any, args: CellArgs) => { if (this.sharedService.gridOptions.gridMenu && typeof this.sharedService.gridOptions.gridMenu.onBeforeMenuShow === 'function') { this.sharedService.gridOptions.gridMenu.onBeforeMenuShow(e, args); } }); - this._eventHandler.subscribe(this._extension.onColumnsChanged, (e: any, args: CellArgs) => { + this._eventHandler.subscribe(this._addon.onColumnsChanged, (e: any, args: CellArgs) => { this.areVisibleColumnDifferent = true; if (this.sharedService.gridOptions.gridMenu && typeof this.sharedService.gridOptions.gridMenu.onColumnsChanged === 'function') { this.sharedService.gridOptions.gridMenu.onColumnsChanged(e, args); } }); - this._eventHandler.subscribe(this._extension.onCommand, (e: any, args: any) => { + this._eventHandler.subscribe(this._addon.onCommand, (e: any, args: any) => { this.executeGridMenuInternalCustomCommands(e, args); if (this.sharedService.gridOptions.gridMenu && typeof this.sharedService.gridOptions.gridMenu.onCommand === 'function') { this.sharedService.gridOptions.gridMenu.onCommand(e, args); } }); - this._eventHandler.subscribe(this._extension.onMenuClose, (e: any, args: CellArgs) => { + this._eventHandler.subscribe(this._addon.onMenuClose, (e: any, args: CellArgs) => { if (this.sharedService.gridOptions.gridMenu && typeof this.sharedService.gridOptions.gridMenu.onMenuClose === 'function') { this.sharedService.gridOptions.gridMenu.onMenuClose(e, args); } @@ -122,62 +126,11 @@ export class GridMenuExtension implements Extension { } }); } - return this._extension; + return this._addon; } return null; } - /** - * Execute the Grid Menu Custom command callback that was triggered by the onCommand subscribe - * These are the default internal custom commands - * @param event - * @param GridMenuItem args - */ - executeGridMenuInternalCustomCommands(e: Event, args: GridMenuItem) { - if (args && args.command) { - switch (args.command) { - case 'clear-filter': - this.filterService.clearFilters(); - this.sharedService.dataView.refresh(); - break; - case 'clear-sorting': - this.sortService.clearSorting(); - this.sharedService.dataView.refresh(); - break; - case 'export-csv': - this.exportService.exportToFile({ - delimiter: DelimiterType.comma, - filename: 'export', - format: FileType.csv, - useUtf8WithBom: true - }); - break; - case 'export-text-delimited': - this.exportService.exportToFile({ - delimiter: DelimiterType.tab, - filename: 'export', - format: FileType.txt, - useUtf8WithBom: true - }); - break; - case 'toggle-filter': - this.sharedService.grid.setHeaderRowVisibility(!this.sharedService.grid.getOptions().showHeaderRow); - break; - case 'toggle-toppanel': - this.sharedService.grid.setTopPanelVisibility(!this.sharedService.grid.getOptions().showTopPanel); - break; - case 'toggle-preheader': - this.sharedService.grid.setPreHeaderPanelVisibility(!this.sharedService.grid.getOptions().showPreHeaderPanel); - break; - case 'refresh-dataset': - this.refreshBackendDataset(); - break; - default: - break; - } - } - } - /** Refresh the dataset through the Backend Service */ refreshBackendDataset(gridOptions?: GridOption) { let query = ''; @@ -232,6 +185,39 @@ export class GridMenuExtension implements Extension { } } + /** Translate the Grid Menu titles and column picker */ + translateGridMenu() { + // update the properties by pointers, that is the only way to get Grid Menu Control to see the new values + // we also need to call the control init so that it takes the new Grid object with latest values + if (this.sharedService.gridOptions && this.sharedService.gridOptions.gridMenu) { + this.sharedService.gridOptions.gridMenu.customItems = []; + this.emptyGridMenuTitles(); + + // merge original user grid menu items with internal items + // then sort all Grid Menu Custom Items (sorted by pointer, no need to use the return) + this.sharedService.gridOptions.gridMenu.customItems = [...this.userOriginalGridMenu.customItems || [], ...this.addGridMenuCustomCommands()]; + this.extensionUtility.translateItems(this.sharedService.gridOptions.gridMenu.customItems, 'titleKey', 'title'); + this.extensionUtility.sortItems(this.sharedService.gridOptions.gridMenu.customItems, 'positionOrder'); + + this.sharedService.gridOptions.gridMenu.columnTitle = this.extensionUtility.getPickerTitleOutputString('columnTitle', 'gridMenu'); + this.sharedService.gridOptions.gridMenu.forceFitTitle = this.extensionUtility.getPickerTitleOutputString('forceFitTitle', 'gridMenu'); + this.sharedService.gridOptions.gridMenu.syncResizeTitle = this.extensionUtility.getPickerTitleOutputString('syncResizeTitle', 'gridMenu'); + + // translate all columns (including non-visible) + this.extensionUtility.translateItems(this.sharedService.allColumns, 'headerKey', 'name'); + + // re-initialize the Grid Menu, that will recreate all the menus & list + // doing an "init()" won't drop any existing command attached + if (this._addon.init) { + this._addon.init(this.sharedService.grid); + } + } + } + + // -- + // private functions + // ------------------ + /** Create Grid Menu with Custom Commands if user has enabled Filters and/or uses a Backend Service (OData, GraphQL) */ private addGridMenuCustomCommands() { const backendApi = this.sharedService.gridOptions.backendServiceApi || null; @@ -341,37 +327,53 @@ export class GridMenuExtension implements Extension { return gridMenuCustomItems; } - /** Execute the Header Menu Commands that was triggered by the onCommand subscribe */ - executeHeaderMenuInternalCommands(e: Event, args: HeaderMenuOnCommandArgs) { + /** + * Execute the Grid Menu Custom command callback that was triggered by the onCommand subscribe + * These are the default internal custom commands + * @param event + * @param GridMenuItem args + */ + private executeGridMenuInternalCustomCommands(e: Event, args: GridMenuItem) { if (args && args.command) { switch (args.command) { - case 'hide': - this.hideColumn(args.column); - if (this.sharedService.gridOptions && this.sharedService.gridOptions.enableAutoSizeColumns) { - this.sharedService.grid.autosizeColumns(); - } + case 'clear-filter': + this.filterService.clearFilters(); + this.sharedService.dataView.refresh(); break; - case 'sort-asc': - case 'sort-desc': - // get previously sorted columns - const cols: ColumnSort[] = this.sortService.getPreviousColumnSorts(args.column.id + ''); - - // add to the column array, the column sorted by the header menu - cols.push({ sortCol: args.column, sortAsc: (args.command === 'sort-asc') }); - if (this.sharedService.gridOptions.backendServiceApi) { - this.sortService.onBackendSortChanged(e, { multiColumnSort: true, sortCols: cols, grid: this.sharedService.grid }); - } else { - this.sortService.onLocalSortChanged(this.sharedService.grid, this.sharedService.dataView, cols); - } - - // update the this.sharedService.gridObj sortColumns array which will at the same add the visual sort icon(s) on the UI - const newSortColumns: ColumnSort[] = cols.map((col) => { - return { - columnId: col && col.sortCol && col.sortCol.id, - sortAsc: col && col.sortAsc - }; + case 'clear-sorting': + this.sortService.clearSorting(); + this.sharedService.dataView.refresh(); + break; + case 'export-csv': + this.exportService.exportToFile({ + delimiter: DelimiterType.comma, + filename: 'export', + format: FileType.csv, + useUtf8WithBom: true }); - this.sharedService.grid.setSortColumns(newSortColumns); // add sort icon in UI + break; + case 'export-text-delimited': + this.exportService.exportToFile({ + delimiter: DelimiterType.tab, + filename: 'export', + format: FileType.txt, + useUtf8WithBom: true + }); + break; + case 'toggle-filter': + const showHeaderRow = this.sharedService && this.sharedService.gridOptions && this.sharedService.gridOptions.showHeaderRow || false; + this.sharedService.grid.setHeaderRowVisibility(!showHeaderRow); + break; + case 'toggle-toppanel': + const showTopPanel = this.sharedService && this.sharedService.gridOptions && this.sharedService.gridOptions.showTopPanel || false; + this.sharedService.grid.setTopPanelVisibility(!showTopPanel); + break; + case 'toggle-preheader': + const showPreHeaderPanel = this.sharedService && this.sharedService.gridOptions && this.sharedService.gridOptions.showPreHeaderPanel || false; + this.sharedService.grid.setPreHeaderPanelVisibility(!showPreHeaderPanel); + break; + case 'refresh-dataset': + this.refreshBackendDataset(); break; default: break; @@ -379,44 +381,6 @@ export class GridMenuExtension implements Extension { } } - /** Hide a column from the grid */ - hideColumn(column: Column) { - if (this.sharedService.grid && this.sharedService.grid.getColumns && this.sharedService.grid.setColumns) { - const columnIndex = this.sharedService.grid.getColumnIndex(column.id); - this.sharedService.visibleColumns = this.extensionUtility.arrayRemoveItemByIndex(this.sharedService.grid.getColumns(), columnIndex); - this.sharedService.grid.setColumns(this.sharedService.visibleColumns); - } - } - - /** Translate the Grid Menu titles and column picker */ - translateGridMenu() { - // update the properties by pointers, that is the only way to get Grid Menu Control to see the new values - // we also need to call the control init so that it takes the new Grid object with latest values - if (this.sharedService.gridOptions && this.sharedService.gridOptions.gridMenu) { - this.sharedService.gridOptions.gridMenu.customItems = []; - this.emptyGridMenuTitles(); - - // merge original user grid menu items with internal items - // then sort all Grid Menu Custom Items (sorted by pointer, no need to use the return) - this.sharedService.gridOptions.gridMenu.customItems = [...this.userOriginalGridMenu.customItems || [], ...this.addGridMenuCustomCommands()]; - this.extensionUtility.translateItems(this.sharedService.gridOptions.gridMenu.customItems, 'titleKey', 'title'); - this.extensionUtility.sortItems(this.sharedService.gridOptions.gridMenu.customItems, 'positionOrder'); - - this.sharedService.gridOptions.gridMenu.columnTitle = this.extensionUtility.getPickerTitleOutputString('columnTitle', 'gridMenu'); - this.sharedService.gridOptions.gridMenu.forceFitTitle = this.extensionUtility.getPickerTitleOutputString('forceFitTitle', 'gridMenu'); - this.sharedService.gridOptions.gridMenu.syncResizeTitle = this.extensionUtility.getPickerTitleOutputString('syncResizeTitle', 'gridMenu'); - - // translate all columns (including non-visible) - this.extensionUtility.translateItems(this.sharedService.allColumns, 'headerKey', 'name'); - - // re-initialize the Grid Menu, that will recreate all the menus & list - // doing an "init()" won't drop any existing command attached - if (this._extension.init) { - this._extension.init(this.sharedService.grid); - } - } - } - private emptyGridMenuTitles() { if (this.sharedService && this.sharedService.gridOptions && this.sharedService.gridOptions.gridMenu) { this.sharedService.gridOptions.gridMenu.customTitle = ''; @@ -426,9 +390,7 @@ export class GridMenuExtension implements Extension { } } - /** - * @return default Grid Menu options - */ + /** @return default Grid Menu options */ private getDefaultGridMenuOptions(): GridMenu { return { customTitle: undefined, diff --git a/src/aurelia-slickgrid/extensions/headerMenuExtension.ts b/src/aurelia-slickgrid/extensions/headerMenuExtension.ts index 298740a04..fa48a1250 100644 --- a/src/aurelia-slickgrid/extensions/headerMenuExtension.ts +++ b/src/aurelia-slickgrid/extensions/headerMenuExtension.ts @@ -11,6 +11,7 @@ import { HeaderMenuItem, HeaderMenuOnCommandArgs, HeaderMenuOnBeforeMenuShowArgs, + SlickEventHandler, } from '../models/index'; import { FilterService } from '../services/filter.service'; import { SortService } from '../services/sort.service'; @@ -29,8 +30,8 @@ declare var Slick: any; SortService, ) export class HeaderMenuExtension implements Extension { - private _eventHandler: any = new Slick.EventHandler(); - private _extension: any; + private _addon: any; + private _eventHandler: SlickEventHandler; constructor( private extensionUtility: ExtensionUtility, @@ -38,14 +39,20 @@ export class HeaderMenuExtension implements Extension { private i18n: I18N, private sharedService: SharedService, private sortService: SortService, - ) { } + ) { + this._eventHandler = new Slick.EventHandler(); + } + + get eventHandler(): SlickEventHandler { + return this._eventHandler; + } dispose() { // unsubscribe all SlickGrid events this._eventHandler.unsubscribeAll(); - if (this._extension && this._extension.destroy) { - this._extension.destroy(); + if (this._addon && this._addon.destroy) { + this._addon.destroy(); } } @@ -64,27 +71,27 @@ export class HeaderMenuExtension implements Extension { if (this.sharedService.gridOptions.enableHeaderMenu) { this.sharedService.gridOptions.headerMenu = this.addHeaderMenuCustomCommands(this.sharedService.gridOptions, this.sharedService.columnDefinitions); } - this._extension = new Slick.Plugins.HeaderMenu(this.sharedService.gridOptions.headerMenu); - this.sharedService.grid.registerPlugin(this._extension); + this._addon = new Slick.Plugins.HeaderMenu(this.sharedService.gridOptions.headerMenu); + this.sharedService.grid.registerPlugin(this._addon); // hook all events if (this.sharedService.grid && this.sharedService.gridOptions.headerMenu) { if (this.sharedService.gridOptions.headerMenu.onExtensionRegistered) { - this.sharedService.gridOptions.headerMenu.onExtensionRegistered(this._extension); + this.sharedService.gridOptions.headerMenu.onExtensionRegistered(this._addon); } - this._eventHandler.subscribe(this._extension.onCommand, (event: Event, args: HeaderMenuOnCommandArgs) => { + this._eventHandler.subscribe(this._addon.onCommand, (event: Event, args: HeaderMenuOnCommandArgs) => { this.executeHeaderMenuInternalCommands(event, args); if (this.sharedService.gridOptions.headerMenu && typeof this.sharedService.gridOptions.headerMenu.onCommand === 'function') { this.sharedService.gridOptions.headerMenu.onCommand(event, args); } }); - this._eventHandler.subscribe(this._extension.onBeforeMenuShow, (event: Event, args: HeaderMenuOnBeforeMenuShowArgs) => { + this._eventHandler.subscribe(this._addon.onBeforeMenuShow, (event: Event, args: HeaderMenuOnBeforeMenuShowArgs) => { if (this.sharedService.gridOptions.headerMenu && typeof this.sharedService.gridOptions.headerMenu.onBeforeMenuShow === 'function') { this.sharedService.gridOptions.headerMenu.onBeforeMenuShow(event, args); } }); } - return this._extension; + return this._addon; } return null; } @@ -130,7 +137,7 @@ export class HeaderMenuExtension implements Extension { } // add a divider (separator) between the top sort commands and the other clear commands - if (!headerMenuOptions.hideSortCommandsDivider) { + if (columnHeaderMenuItems.filter((item: HeaderMenuItem) => item.positionOrder === 52).length === 0) { columnHeaderMenuItems.push({ divider: true, command: '', positionOrder: 52 }); } @@ -139,7 +146,7 @@ export class HeaderMenuExtension implements Extension { iconCssClass: headerMenuOptions.iconClearSortCommand || 'fa fa-unsorted', title: options.enableTranslate ? this.i18n.tr('REMOVE_SORT') : Constants.TEXT_REMOVE_SORT, command: 'clear-sort', - positionOrder: 53 + positionOrder: 54 }); } } @@ -151,7 +158,7 @@ export class HeaderMenuExtension implements Extension { iconCssClass: headerMenuOptions.iconClearFilterCommand || 'fa fa-filter', title: options.enableTranslate ? this.i18n.tr('REMOVE_FILTER') : Constants.TEXT_REMOVE_FILTER, command: 'clear-filter', - positionOrder: 52 + positionOrder: 53 }); } } @@ -162,19 +169,12 @@ export class HeaderMenuExtension implements Extension { iconCssClass: headerMenuOptions.iconColumnHideCommand || 'fa fa-times', title: options.enableTranslate ? this.i18n.tr('HIDE_COLUMN') : Constants.TEXT_HIDE_COLUMN, command: 'hide', - positionOrder: 54 + positionOrder: 55 }); } this.extensionUtility.translateItems(columnHeaderMenuItems, 'titleKey', 'title'); - - // sort the custom items by their position in the list - columnHeaderMenuItems.sort((itemA: any, itemB: any) => { - if (itemA && itemB && itemA.hasOwnProperty('positionOrder') && itemB.hasOwnProperty('positionOrder')) { - return itemA.positionOrder - itemB.positionOrder; - } - return 0; - }); + this.extensionUtility.sortItems(columnHeaderMenuItems, 'positionOrder'); } }); @@ -183,47 +183,42 @@ export class HeaderMenuExtension implements Extension { return headerMenuOptions; } - /** Execute the Header Menu Commands that was triggered by the onCommand subscribe */ - executeHeaderMenuInternalCommands(event: Event, args: HeaderMenuOnCommandArgs) { - if (args && args.command) { - switch (args.command) { - case 'hide': - this.hideColumn(args.column); - if (this.sharedService.gridOptions && this.sharedService.gridOptions.enableAutoSizeColumns) { - this.sharedService.grid.autosizeColumns(); - } - break; - case 'clear-filter': - this.clearColumnFilter(event, args); - break; - case 'clear-sort': - this.clearColumnSort(event, args); - break; - case 'sort-asc': - case 'sort-desc': - const isSortingAsc = (args.command === 'sort-asc'); - this.sortColumn(event, args, isSortingAsc); - break; - default: - break; - } - } - } - /** Hide a column from the grid */ hideColumn(column: Column) { - if (this.sharedService.grid && this.sharedService.grid.getColumns && this.sharedService.grid.setColumns) { + if (this.sharedService.grid && this.sharedService.grid.getColumns && this.sharedService.grid.setColumns && this.sharedService.grid.getColumnIndex) { const columnIndex = this.sharedService.grid.getColumnIndex(column.id); - this.sharedService.visibleColumns = this.extensionUtility.arrayRemoveItemByIndex(this.sharedService.grid.getColumns(), columnIndex); - this.sharedService.grid.setColumns(this.sharedService.visibleColumns); + const currentColumns = this.sharedService.grid.getColumns(); + const visibleColumns = this.extensionUtility.arrayRemoveItemByIndex(currentColumns, columnIndex); + this.sharedService.visibleColumns = visibleColumns; + this.sharedService.grid.setColumns(visibleColumns); + } + } + + /** Translate the Header Menu titles, we need to loop through all column definition to re-translate them */ + translateHeaderMenu() { + if (this.sharedService.gridOptions && this.sharedService.gridOptions.headerMenu) { + this.resetHeaderMenuTranslations(this.sharedService.visibleColumns); } } + /** + * @return default Header Menu options + */ + private getDefaultHeaderMenuOptions(): HeaderMenu { + return { + autoAlignOffset: 12, + minWidth: 140, + hideColumnHideCommand: false, + hideSortCommands: false, + title: '' + }; + } + /** * Reset all the Grid Menu options which have text to translate * @param grid menu object */ - resetHeaderMenuTranslations(columnDefinitions: Column[]) { + private resetHeaderMenuTranslations(columnDefinitions: Column[]) { columnDefinitions.forEach((columnDef: Column) => { if (columnDef && columnDef.header && columnDef.header && columnDef.header.menu && columnDef.header.menu.items) { if (!columnDef.excludeFromHeaderMenu) { @@ -258,62 +253,14 @@ export class HeaderMenuExtension implements Extension { }); } - /** - * Translate the Header Menu titles, we need to loop through all column definition to re-translate them - */ - translateHeaderMenu() { - if (this.sharedService.gridOptions && this.sharedService.gridOptions.headerMenu) { - this.resetHeaderMenuTranslations(this.sharedService.visibleColumns); - } - } - - /** - * @return default Header Menu options - */ - private getDefaultHeaderMenuOptions(): HeaderMenu { - return { - autoAlignOffset: 12, - minWidth: 140, - hideColumnHideCommand: false, - hideSortCommands: false, - title: '' - }; - } - - /** Sort the current column */ - private sortColumn(event: Event, args: HeaderMenuOnCommandArgs, isSortingAsc = true) { - if (args && args.column) { - // get previously sorted columns - const sortedColsWithoutCurrent: ColumnSort[] = this.sortService.getPreviousColumnSorts(args.column.id + ''); - - // add to the column array, the column sorted by the header menu - sortedColsWithoutCurrent.push({ sortCol: args.column, sortAsc: isSortingAsc }); - if (this.sharedService.gridOptions.backendServiceApi) { - this.sortService.onBackendSortChanged(event, { multiColumnSort: true, sortCols: sortedColsWithoutCurrent, grid: this.sharedService.grid }); - } else if (this.sharedService.dataView) { - this.sortService.onLocalSortChanged(this.sharedService.grid, this.sharedService.dataView, sortedColsWithoutCurrent); - } else { - // when using customDataView, we will simply send it as a onSort event with notify - const isMultiSort = this.sharedService && this.sharedService.gridOptions && this.sharedService.gridOptions.multiColumnSort || false; - const sortOutput = isMultiSort ? sortedColsWithoutCurrent : sortedColsWithoutCurrent[0]; - args.grid.onSort.notify(sortOutput); - } - - // update the this.sharedService.gridObj sortColumns array which will at the same add the visual sort icon(s) on the UI - const newSortColumns: ColumnSort[] = sortedColsWithoutCurrent.map((col) => { - return { - columnId: col && col.sortCol && col.sortCol.id, - sortAsc: col && col.sortAsc - }; - }); - this.sharedService.grid.setSortColumns(newSortColumns); // add sort icon in UI - } - } + // -- + // private functions + // ------------------ /** Clear the Filter on the current column (if it's actually filtered) */ - private clearColumnFilter(e: Event, args: HeaderMenuOnCommandArgs) { + private clearColumnFilter(event: Event, args: HeaderMenuOnCommandArgs) { if (args && args.column) { - this.filterService.clearFilterByColumnId(args.column.id); + this.filterService.clearFilterByColumnId(event, args.column.id); } } @@ -324,7 +271,7 @@ export class HeaderMenuExtension implements Extension { const allSortedCols: ColumnSort[] = this.sortService.getPreviousColumnSorts(); const sortedColsWithoutCurrent: ColumnSort[] = this.sortService.getPreviousColumnSorts(args.column.id + ''); - if (allSortedCols.length !== sortedColsWithoutCurrent.length) { + if (Array.isArray(allSortedCols) && Array.isArray(sortedColsWithoutCurrent) && allSortedCols.length !== sortedColsWithoutCurrent.length) { if (this.sharedService.gridOptions.backendServiceApi) { this.sortService.onBackendSortChanged(e, { multiColumnSort: true, sortCols: sortedColsWithoutCurrent, grid: this.sharedService.grid }); } else if (this.sharedService.dataView) { @@ -347,4 +294,61 @@ export class HeaderMenuExtension implements Extension { } } } + + /** Execute the Header Menu Commands that was triggered by the onCommand subscribe */ + private executeHeaderMenuInternalCommands(event: Event, args: HeaderMenuOnCommandArgs) { + if (args && args.command) { + switch (args.command) { + case 'hide': + this.hideColumn(args.column); + if (this.sharedService.gridOptions && this.sharedService.gridOptions.enableAutoSizeColumns) { + this.sharedService.grid.autosizeColumns(); + } + break; + case 'clear-filter': + this.clearColumnFilter(event, args); + break; + case 'clear-sort': + this.clearColumnSort(event, args); + break; + case 'sort-asc': + case 'sort-desc': + const isSortingAsc = (args.command === 'sort-asc'); + this.sortColumn(event, args, isSortingAsc); + break; + default: + break; + } + } + } + + /** Sort the current column */ + private sortColumn(event: Event, args: HeaderMenuOnCommandArgs, isSortingAsc = true) { + if (args && args.column) { + // get previously sorted columns + const sortedColsWithoutCurrent: ColumnSort[] = this.sortService.getPreviousColumnSorts(args.column.id + ''); + + // add to the column array, the column sorted by the header menu + sortedColsWithoutCurrent.push({ sortCol: args.column, sortAsc: isSortingAsc }); + if (this.sharedService.gridOptions.backendServiceApi) { + this.sortService.onBackendSortChanged(event, { multiColumnSort: true, sortCols: sortedColsWithoutCurrent, grid: this.sharedService.grid }); + } else if (this.sharedService.dataView) { + this.sortService.onLocalSortChanged(this.sharedService.grid, this.sharedService.dataView, sortedColsWithoutCurrent); + } else { + // when using customDataView, we will simply send it as a onSort event with notify + const isMultiSort = this.sharedService && this.sharedService.gridOptions && this.sharedService.gridOptions.multiColumnSort || false; + const sortOutput = isMultiSort ? sortedColsWithoutCurrent : sortedColsWithoutCurrent[0]; + args.grid.onSort.notify(sortOutput); + } + + // update the this.sharedService.gridObj sortColumns array which will at the same add the visual sort icon(s) on the UI + const newSortColumns: ColumnSort[] = sortedColsWithoutCurrent.map((col) => { + return { + columnId: col && col.sortCol && col.sortCol.id, + sortAsc: col && col.sortAsc + }; + }); + this.sharedService.grid.setSortColumns(newSortColumns); // add sort icon in UI + } + } } diff --git a/src/aurelia-slickgrid/extensions/rowMoveManagerExtension.ts b/src/aurelia-slickgrid/extensions/rowMoveManagerExtension.ts index ad37cfd2c..0b682e8e7 100644 --- a/src/aurelia-slickgrid/extensions/rowMoveManagerExtension.ts +++ b/src/aurelia-slickgrid/extensions/rowMoveManagerExtension.ts @@ -1,5 +1,5 @@ import { singleton, inject } from 'aurelia-framework'; -import { CellArgs, Extension, ExtensionName } from '../models/index'; +import { CellArgs, Extension, ExtensionName, SlickEventHandler } from '../models/index'; import { ExtensionUtility } from './extensionUtility'; import { SharedService } from '../services/shared.service'; @@ -9,17 +9,23 @@ declare var Slick: any; @singleton(true) @inject(ExtensionUtility, SharedService) export class RowMoveManagerExtension implements Extension { - private _eventHandler: any = new Slick.EventHandler(); - private _extension: any; + private _addon: any; + private _eventHandler: SlickEventHandler; - constructor(private extensionUtility: ExtensionUtility, private sharedService: SharedService) { } + constructor(private extensionUtility: ExtensionUtility, private sharedService: SharedService) { + this._eventHandler = new Slick.EventHandler(); + } + + get eventHandler(): SlickEventHandler { + return this._eventHandler; + } dispose() { // unsubscribe all SlickGrid events this._eventHandler.unsubscribeAll(); - if (this._extension && this._extension.destroy) { - this._extension.destroy(); + if (this._addon && this._addon.destroy) { + this._addon.destroy(); } } @@ -35,26 +41,26 @@ export class RowMoveManagerExtension implements Extension { this.sharedService.grid.setSelectionModel(rowSelectionPlugin); } - this._extension = new Slick.RowMoveManager(this.sharedService.gridOptions.rowMoveManager || { cancelEditOnDrag: true }); - this.sharedService.grid.registerPlugin(this._extension); + this._addon = new Slick.RowMoveManager(this.sharedService.gridOptions.rowMoveManager || { cancelEditOnDrag: true }); + this.sharedService.grid.registerPlugin(this._addon); // hook all events if (this.sharedService.grid && this.sharedService.gridOptions.rowMoveManager) { if (this.sharedService.gridOptions.rowMoveManager.onExtensionRegistered) { - this.sharedService.gridOptions.rowMoveManager.onExtensionRegistered(this._extension); + this.sharedService.gridOptions.rowMoveManager.onExtensionRegistered(this._addon); } - this._eventHandler.subscribe(this._extension.onBeforeMoveRows, (e: any, args: CellArgs) => { + this._eventHandler.subscribe(this._addon.onBeforeMoveRows, (e: any, args: CellArgs) => { if (this.sharedService.gridOptions.rowMoveManager && typeof this.sharedService.gridOptions.rowMoveManager.onBeforeMoveRows === 'function') { this.sharedService.gridOptions.rowMoveManager.onBeforeMoveRows(e, args); } }); - this._eventHandler.subscribe(this._extension.onMoveRows, (e: any, args: CellArgs) => { + this._eventHandler.subscribe(this._addon.onMoveRows, (e: any, args: CellArgs) => { if (this.sharedService.gridOptions.rowMoveManager && typeof this.sharedService.gridOptions.rowMoveManager.onMoveRows === 'function') { this.sharedService.gridOptions.rowMoveManager.onMoveRows(e, args); } }); } - return this._extension; + return this._addon; } return null; } diff --git a/src/aurelia-slickgrid/models/columnPicker.interface.ts b/src/aurelia-slickgrid/models/columnPicker.interface.ts index 4d28ea002..92032a6a1 100644 --- a/src/aurelia-slickgrid/models/columnPicker.interface.ts +++ b/src/aurelia-slickgrid/models/columnPicker.interface.ts @@ -14,6 +14,12 @@ export interface ColumnPicker { /** Defaults to "Synchronous resize" which is 1 of the last 2 checkbox title shown at the end of the picker list */ syncResizeTitle?: string; + // -- + // Events + + /** Fired after extension (control) is registered by SlickGrid */ + onExtensionRegistered?: (addon: any) => void; + /** SlickGrid Event fired when any of the columns checkbox selection changes. */ onColumnsChanged?: (e: Event, args: any) => void; } diff --git a/src/aurelia-slickgrid/services/filter.service.ts b/src/aurelia-slickgrid/services/filter.service.ts index 5acddd871..8f475d95b 100644 --- a/src/aurelia-slickgrid/services/filter.service.ts +++ b/src/aurelia-slickgrid/services/filter.service.ts @@ -178,7 +178,7 @@ export class FilterService { }); } - clearFilterByColumnId(columnId: number | string) { + clearFilterByColumnId(event: Event, columnId: number | string) { const colFilter: Filter = this._filters.find((filter: Filter) => filter.columnDef.id === columnId); if (colFilter && colFilter.clear) { colFilter.clear(true); diff --git a/src/aurelia-slickgrid/services/grid.service.ts b/src/aurelia-slickgrid/services/grid.service.ts index 0b3eecb87..06e429c09 100644 --- a/src/aurelia-slickgrid/services/grid.service.ts +++ b/src/aurelia-slickgrid/services/grid.service.ts @@ -6,15 +6,13 @@ import { FilterService } from './filter.service'; import { GridStateService } from './gridState.service'; import { SortService } from './sort.service'; -const GridServiceDeleteOptionDefaults: GridServiceDeleteOption = { triggerEvent: true }; -const GridServiceInsertOptionDefaults: GridServiceInsertOption = { highlightRow: true, resortGrid: false, selectRow: false, triggerEvent: true }; -const GridServiceUpdateOptionDefaults: GridServiceUpdateOption = { highlightRow: true, selectRow: false, triggerEvent: true }; - // using external non-typed js libraries declare var Slick: any; let highlightTimerEnd: any; - const DEFAULT_AURELIA_EVENT_PREFIX = 'asg'; +const GridServiceDeleteOptionDefaults: GridServiceDeleteOption = { triggerEvent: true }; +const GridServiceInsertOptionDefaults: GridServiceInsertOption = { highlightRow: true, resortGrid: false, selectRow: false, triggerEvent: true }; +const GridServiceUpdateOptionDefaults: GridServiceUpdateOption = { highlightRow: true, selectRow: false, triggerEvent: true }; @singleton(true) @inject(EventAggregator, ExtensionService, FilterService, GridStateService, SortService)