From 91c48a2c550db4c94d0a349777efdd071170ee3f Mon Sep 17 00:00:00 2001 From: ghiscoding Date: Mon, 14 Jun 2021 12:15:42 -0400 Subject: [PATCH] fix(tree): using `initiallyCollapsed` change internal toggled state - if we defined `initiallyCollapsed` as true, we should expect the internal toggle state change to be updated accordingly - also add extra arguments to the `applyToggledItemStateChanges()` method to skip a full toggle and also another argument to optionally trigger an item toggled event --- .../__tests__/treeData.service.spec.ts | 63 ++++++++++++++++--- .../services/treeData.service.ts | 42 ++++++++++--- 2 files changed, 88 insertions(+), 17 deletions(-) diff --git a/src/app/modules/angular-slickgrid/services/__tests__/treeData.service.spec.ts b/src/app/modules/angular-slickgrid/services/__tests__/treeData.service.spec.ts index f28398e44..b4aae6698 100644 --- a/src/app/modules/angular-slickgrid/services/__tests__/treeData.service.spec.ts +++ b/src/app/modules/angular-slickgrid/services/__tests__/treeData.service.spec.ts @@ -332,13 +332,14 @@ describe('SortService', () => { const beginUpdateSpy = jest.spyOn(dataViewStub, 'beginUpdate'); const endUpdateSpy = jest.spyOn(dataViewStub, 'endUpdate'); const updateItemSpy = jest.spyOn(dataViewStub, 'updateItem'); - // const pubSubSpy = jest.spyOn(pubSubServiceStub, 'publish'); + const rxStartSpy = jest.spyOn(sharedService.onTreeFullToggleStart, 'next'); + const rxEndSpy = jest.spyOn(sharedService.onTreeFullToggleEnd, 'next'); service.init(gridStub); service.toggleTreeDataCollapse(true); - // expect(pubSubSpy).toHaveBeenCalledWith(`onTreeFullToggleStart`, { collapsing: true }); - // expect(pubSubSpy).toHaveBeenCalledWith(`onTreeFullToggleEnd`, { type: 'full-collapse', previousFullToggleType: 'full-collapse', toggledItems: null, }); + expect(rxStartSpy).toHaveBeenCalledWith({ collapsing: true }); + expect(rxEndSpy).toHaveBeenCalledWith({ type: 'full-collapse', previousFullToggleType: 'full-collapse', toggledItems: null, }); expect(dataGetItemsSpy).toHaveBeenCalled(); expect(beginUpdateSpy).toHaveBeenCalled(); expect(updateItemSpy).toHaveBeenNthCalledWith(1, 0, { __collapsed: true, __hasChildren: true, id: 0, file: 'TXT', size: 5.8 }); @@ -356,13 +357,14 @@ describe('SortService', () => { const beginUpdateSpy = jest.spyOn(dataViewStub, 'beginUpdate'); const endUpdateSpy = jest.spyOn(dataViewStub, 'endUpdate'); const updateItemSpy = jest.spyOn(dataViewStub, 'updateItem'); - // const pubSubSpy = jest.spyOn(pubSubServiceStub, 'publish'); + const rxStartSpy = jest.spyOn(sharedService.onTreeFullToggleStart, 'next'); + const rxEndSpy = jest.spyOn(sharedService.onTreeFullToggleEnd, 'next'); service.init(gridStub); service.toggleTreeDataCollapse(true); - // expect(pubSubSpy).toHaveBeenCalledWith(`onTreeFullToggleStart`, { collapsing: true }); - // expect(pubSubSpy).toHaveBeenCalledWith(`onTreeFullToggleEnd`, { type: 'full-collapse', previousFullToggleType: 'full-collapse', toggledItems: null, }); + expect(rxStartSpy).toHaveBeenCalledWith({ collapsing: true }); + expect(rxEndSpy).toHaveBeenCalledWith({ type: 'full-collapse', previousFullToggleType: 'full-collapse', toggledItems: null, }); expect(dataGetItemsSpy).toHaveBeenCalled(); expect(dataGetItemsSpy).toHaveBeenCalled(); expect(beginUpdateSpy).toHaveBeenCalled(); @@ -376,13 +378,14 @@ describe('SortService', () => { const beginUpdateSpy = jest.spyOn(dataViewStub, 'beginUpdate'); const endUpdateSpy = jest.spyOn(dataViewStub, 'endUpdate'); const updateItemSpy = jest.spyOn(dataViewStub, 'updateItem'); - // const pubSubSpy = jest.spyOn(pubSubServiceStub, 'publish'); + const rxStartSpy = jest.spyOn(sharedService.onTreeFullToggleStart, 'next'); + const rxEndSpy = jest.spyOn(sharedService.onTreeFullToggleEnd, 'next'); service.init(gridStub); service.toggleTreeDataCollapse(false); - // expect(pubSubSpy).toHaveBeenCalledWith(`onTreeFullToggleStart`, { collapsing: false }); - // expect(pubSubSpy).toHaveBeenCalledWith(`onTreeFullToggleEnd`, { type: 'full-expand', previousFullToggleType: 'full-expand', toggledItems: null, }); + expect(rxStartSpy).toHaveBeenCalledWith({ collapsing: false }); + expect(rxEndSpy).toHaveBeenCalledWith({ type: 'full-expand', previousFullToggleType: 'full-expand', toggledItems: null, }); expect(dataGetItemsSpy).toHaveBeenCalled(); expect(beginUpdateSpy).toHaveBeenCalled(); expect(updateItemSpy).toHaveBeenNthCalledWith(1, 0, { __collapsed: false, __hasChildren: true, id: 0, file: 'TXT', size: 5.8 }); @@ -391,13 +394,14 @@ describe('SortService', () => { }); describe('applyToggledItemStateChanges method', () => { - it('should execute the method', () => { + it('should execute the method and expect a full toggle or items', () => { const dataGetItemsSpy = jest.spyOn(dataViewStub, 'getItems').mockReturnValue(mockFlatDataset); jest.spyOn(dataViewStub, 'getItemById').mockReturnValue(mockFlatDataset[3]); jest.spyOn(SharedService.prototype, 'hierarchicalDataset', 'get').mockReturnValue(mockHierarchical); const beginUpdateSpy = jest.spyOn(dataViewStub, 'beginUpdate'); const endUpdateSpy = jest.spyOn(dataViewStub, 'endUpdate'); const updateItemSpy = jest.spyOn(dataViewStub, 'updateItem'); + const rxToggleSpy = jest.spyOn(sharedService.onTreeItemToggled, 'next'); service.init(gridStub); service.applyToggledItemStateChanges([{ itemId: 4, isCollapsed: true }]); @@ -405,6 +409,45 @@ describe('SortService', () => { expect(dataGetItemsSpy).toHaveBeenCalled(); expect(beginUpdateSpy).toHaveBeenCalled(); expect(updateItemSpy).toHaveBeenNthCalledWith(1, 4, { __collapsed: true, __hasChildren: true, id: 4, file: 'MP3', size: 3.4 }); + expect(rxToggleSpy).not.toHaveBeenCalled(); + expect(endUpdateSpy).toHaveBeenCalled(); + }); + + it('should execute the method but without calling "getItems" to skip doing a full toggle of items', () => { + const dataGetItemsSpy = jest.spyOn(dataViewStub, 'getItems').mockReturnValue(mockFlatDataset); + jest.spyOn(dataViewStub, 'getItemById').mockReturnValue(mockFlatDataset[3]); + jest.spyOn(SharedService.prototype, 'hierarchicalDataset', 'get').mockReturnValue(mockHierarchical); + const beginUpdateSpy = jest.spyOn(dataViewStub, 'beginUpdate'); + const endUpdateSpy = jest.spyOn(dataViewStub, 'endUpdate'); + const updateItemSpy = jest.spyOn(dataViewStub, 'updateItem'); + const rxToggleSpy = jest.spyOn(sharedService.onTreeItemToggled, 'next'); + + service.init(gridStub); + service.applyToggledItemStateChanges([{ itemId: 4, isCollapsed: true }], 'full-collapse', false, true); + + expect(dataGetItemsSpy).not.toHaveBeenCalled(); + expect(beginUpdateSpy).toHaveBeenCalled(); + expect(updateItemSpy).toHaveBeenNthCalledWith(1, 4, { __collapsed: true, __hasChildren: true, id: 4, file: 'MP3', size: 3.4 }); + expect(rxToggleSpy).toHaveBeenCalledWith({ fromItemId: 4, previousFullToggleType: 'full-collapse', toggledItems: [{ itemId: 4, isCollapsed: true }], type: 'toggle-collapse' }); + expect(endUpdateSpy).toHaveBeenCalled(); + }); + + it('should execute the method and also trigger an event when specified', () => { + const dataGetItemsSpy = jest.spyOn(dataViewStub, 'getItems').mockReturnValue(mockFlatDataset); + jest.spyOn(dataViewStub, 'getItemById').mockReturnValue(mockFlatDataset[3]); + jest.spyOn(SharedService.prototype, 'hierarchicalDataset', 'get').mockReturnValue(mockHierarchical); + const beginUpdateSpy = jest.spyOn(dataViewStub, 'beginUpdate'); + const endUpdateSpy = jest.spyOn(dataViewStub, 'endUpdate'); + const updateItemSpy = jest.spyOn(dataViewStub, 'updateItem'); + const rxToggleSpy = jest.spyOn(sharedService.onTreeItemToggled, 'next'); + + service.init(gridStub); + service.applyToggledItemStateChanges([{ itemId: 4, isCollapsed: true }], 'full-collapse', true, true); + + expect(dataGetItemsSpy).toHaveBeenCalled(); + expect(beginUpdateSpy).toHaveBeenCalled(); + expect(updateItemSpy).toHaveBeenNthCalledWith(1, 4, { __collapsed: true, __hasChildren: true, id: 4, file: 'MP3', size: 3.4 }); + expect(rxToggleSpy).toHaveBeenCalledWith({ fromItemId: 4, previousFullToggleType: 'full-collapse', toggledItems: [{ itemId: 4, isCollapsed: true }], type: 'toggle-collapse' }); expect(endUpdateSpy).toHaveBeenCalled(); }); }); diff --git a/src/app/modules/angular-slickgrid/services/treeData.service.ts b/src/app/modules/angular-slickgrid/services/treeData.service.ts index acd4fa939..350fc74dc 100644 --- a/src/app/modules/angular-slickgrid/services/treeData.service.ts +++ b/src/app/modules/angular-slickgrid/services/treeData.service.ts @@ -94,8 +94,11 @@ export class TreeDataService { * Apply different tree toggle state changes by providing an array of parentIds that are designated as collapsed (or not). * User will have to provide an array of `parentId` and `isCollapsed` boolean and the code will only apply the ones that are tagged as collapsed, everything else will be expanded * @param {Array} treeToggledItems - array of parentId which are tagged as changed + * @param {ToggleStateChangeType} previousFullToggleType - optionally provide the previous full toggle type ('full-expand' or 'full-collapse') + * @param {Boolean} shouldPreProcessFullToggle - should we pre-process a full toggle on all items? defaults to True + * @param {Boolean} shouldTriggerEvent - should we trigger a toggled item event? defaults to False */ - applyToggledItemStateChanges(treeToggledItems: TreeToggledItem[], previousFullToggleType?: Exclude | Exclude) { + applyToggledItemStateChanges(treeToggledItems: TreeToggledItem[], previousFullToggleType?: Exclude | Exclude, shouldPreProcessFullToggle = true, shouldTriggerEvent = false) { if (Array.isArray(treeToggledItems)) { const collapsedPropName = this.getTreeDataOptionPropName('collapsedPropName'); const hasChildrenPropName = this.getTreeDataOptionPropName('hasChildrenPropName'); @@ -107,17 +110,42 @@ export class TreeDataService { // we first need to put back the previous full toggle state (whether it was a full collapse or expand) by collapsing/expanding everything depending on the last toggled that was called `isLastFullToggleCollapsed` const previousFullToggle = previousFullToggleType ?? this._lastToggleStateChange.previousFullToggleType; const shouldCollapseAll = previousFullToggle === 'full-collapse'; - (this.dataView.getItems() || []).forEach((item: any) => { - // collapse/expand the item but only when it's a parent item with children - if (item[hasChildrenPropName]) { - item[collapsedPropName] = shouldCollapseAll; - } - }); + + // when full toggle type is provided, we also need to update our internal reference of our current toggle state + if (previousFullToggleType) { + this._lastToggleStateChange.previousFullToggleType = previousFullToggleType; + } + + // typically (optionally and defaults to true) if we want to reapply some toggled items we probably want to be in the full toggled state as it was at the start + // collapse/expand from the last full toggle state, all the items which are parent items with children + if (shouldPreProcessFullToggle) { + (this.dataView.getItems() || []).forEach((item: any) => { + if (item[hasChildrenPropName]) { + item[collapsedPropName] = shouldCollapseAll; + } + }); + } // then we reapply only the ones that changed (provided as argument to the function) for (const collapsedItem of treeToggledItems) { const item = this.dataView.getItemById(collapsedItem.itemId); this.updateToggledItem(item, collapsedItem.isCollapsed); + + if (shouldTriggerEvent) { + const parentFoundIdx = this._currentToggledItems.findIndex(treeChange => treeChange.itemId === collapsedItem.itemId); + if (parentFoundIdx >= 0) { + this._currentToggledItems[parentFoundIdx].isCollapsed = collapsedItem.isCollapsed; + } else { + this._currentToggledItems.push({ itemId: collapsedItem.itemId, isCollapsed: collapsedItem.isCollapsed }); + } + + this.sharedService.onTreeItemToggled.next({ + ...this._lastToggleStateChange, + fromItemId: collapsedItem.itemId, + toggledItems: this._currentToggledItems, + type: collapsedItem.isCollapsed ? ToggleStateChangeType.toggleCollapse : ToggleStateChangeType.toggleExpand + } as TreeToggleStateChange); + } } // close the update transaction & call a refresh which will trigger a re-render with filters applied (including expand/collapse)