From c78cb9ca927d73ee94957b953e8588e7c89a73ff Mon Sep 17 00:00:00 2001 From: ghiscoding Date: Wed, 7 May 2025 20:20:07 -0400 Subject: [PATCH] fix: Row Detail open/close multiple times should always re-render --- .../__tests__/slickRowDetailView.spec.ts | 56 ++++++++++--------- .../extensions/slickRowDetailView.ts | 11 ++-- test/cypress/e2e/example21.cy.ts | 17 ++++++ 3 files changed, 54 insertions(+), 30 deletions(-) diff --git a/src/app/modules/angular-slickgrid/extensions/__tests__/slickRowDetailView.spec.ts b/src/app/modules/angular-slickgrid/extensions/__tests__/slickRowDetailView.spec.ts index e5613116..45be4cf5 100644 --- a/src/app/modules/angular-slickgrid/extensions/__tests__/slickRowDetailView.spec.ts +++ b/src/app/modules/angular-slickgrid/extensions/__tests__/slickRowDetailView.spec.ts @@ -262,35 +262,39 @@ describe('SlickRowDetailView', () => { expect(onRowBackViewSpy).not.toHaveBeenCalled(); }); - it('should call internal event handler subscribe and expect the "onAsyncEndUpdate" option to be called when addon notify is called', () => { - // const handlerSpy = vi.spyOn(plugin.eventHandler, 'subscribe'); - const renderSpy = vi.spyOn(plugin, 'renderViewModel'); + it('should call internal event handler subscribe and expect the "onAsyncEndUpdate" option to be called when addon notify is called', () => + new Promise((done: any) => { + // const handlerSpy = vi.spyOn(plugin.eventHandler, 'subscribe'); + const renderSpy = vi.spyOn(plugin, 'renderViewModel'); - const onAsyncRespSpy = vi.spyOn(gridOptionsMock.rowDetailView as RowDetailView, 'onAsyncResponse'); - const onAsyncEndSpy = vi.spyOn(gridOptionsMock.rowDetailView as RowDetailView, 'onAsyncEndUpdate'); - const onAfterRowSpy = vi.spyOn(gridOptionsMock.rowDetailView as RowDetailView, 'onAfterRowDetailToggle'); - const onBeforeRowSpy = vi.spyOn(gridOptionsMock.rowDetailView as RowDetailView, 'onBeforeRowDetailToggle'); - const onRowOutViewSpy = vi.spyOn(gridOptionsMock.rowDetailView as RowDetailView, 'onRowOutOfViewportRange'); - const onRowBackViewSpy = vi.spyOn(gridOptionsMock.rowDetailView as RowDetailView, 'onRowBackToViewportRange'); + const onAsyncRespSpy = vi.spyOn(gridOptionsMock.rowDetailView as RowDetailView, 'onAsyncResponse'); + const onAsyncEndSpy = vi.spyOn(gridOptionsMock.rowDetailView as RowDetailView, 'onAsyncEndUpdate'); + const onAfterRowSpy = vi.spyOn(gridOptionsMock.rowDetailView as RowDetailView, 'onAfterRowDetailToggle'); + const onBeforeRowSpy = vi.spyOn(gridOptionsMock.rowDetailView as RowDetailView, 'onBeforeRowDetailToggle'); + const onRowOutViewSpy = vi.spyOn(gridOptionsMock.rowDetailView as RowDetailView, 'onRowOutOfViewportRange'); + const onRowBackViewSpy = vi.spyOn(gridOptionsMock.rowDetailView as RowDetailView, 'onRowBackToViewportRange'); - plugin.init(gridStub); - plugin.onAsyncEndUpdate = new SlickEvent(); - plugin.register(); - plugin.onAsyncEndUpdate.notify({ item: columnsMock[0], itemDetail: columnsMock[0], grid: gridStub }, new SlickEventData(), gridStub); + plugin.init(gridStub); + plugin.onAsyncEndUpdate = new SlickEvent(); + plugin.register(); + plugin.onAsyncEndUpdate.notify({ item: columnsMock[0], itemDetail: columnsMock[0], grid: gridStub }, new SlickEventData(), gridStub); - // expect(handlerSpy).toHaveBeenCalledTimes(8); // there are an extra 2x on the grid itself - // expect(handlerSpy).toHaveBeenCalledWith( - // { notify: expect.anything(), subscribe: expect.anything(), unsubscribe: expect.anything(), }, - // expect.anything() - // ); - expect(onAsyncRespSpy).not.toHaveBeenCalled(); - expect(onAsyncEndSpy).toHaveBeenCalledWith(expect.anything(), { item: columnsMock[0], itemDetail: columnsMock[0], grid: gridStub }); - expect(renderSpy).toHaveBeenCalledWith({ cssClass: 'red', field: 'field1', id: 'field1', width: 100 }); - expect(onAfterRowSpy).not.toHaveBeenCalled(); - expect(onBeforeRowSpy).not.toHaveBeenCalled(); - expect(onRowOutViewSpy).not.toHaveBeenCalled(); - expect(onRowBackViewSpy).not.toHaveBeenCalled(); - }); + // expect(handlerSpy).toHaveBeenCalledTimes(8); // there are an extra 2x on the grid itself + // expect(handlerSpy).toHaveBeenCalledWith( + // { notify: expect.anything(), subscribe: expect.anything(), unsubscribe: expect.anything(), }, + // expect.anything() + // ); + setTimeout(() => { + expect(onAsyncRespSpy).not.toHaveBeenCalled(); + expect(onAsyncEndSpy).toHaveBeenCalledWith(expect.anything(), { item: columnsMock[0], itemDetail: columnsMock[0], grid: gridStub }); + expect(renderSpy).toHaveBeenCalledWith({ cssClass: 'red', field: 'field1', id: 'field1', width: 100 }); + expect(onAfterRowSpy).not.toHaveBeenCalled(); + expect(onBeforeRowSpy).not.toHaveBeenCalled(); + expect(onRowOutViewSpy).not.toHaveBeenCalled(); + expect(onRowBackViewSpy).not.toHaveBeenCalled(); + done(); + }); + })); it('should call internal event handler subscribe and expect the "onAfterRowDetailToggle" option to be called when addon notify is called', () => { // const handlerSpy = vi.spyOn(plugin.eventHandler, 'subscribe'); diff --git a/src/app/modules/angular-slickgrid/extensions/slickRowDetailView.ts b/src/app/modules/angular-slickgrid/extensions/slickRowDetailView.ts index 084bf961..8761a7fd 100644 --- a/src/app/modules/angular-slickgrid/extensions/slickRowDetailView.ts +++ b/src/app/modules/angular-slickgrid/extensions/slickRowDetailView.ts @@ -151,11 +151,14 @@ export class SlickRowDetailView extends UniversalSlickRowDetailView { this._preloadCompRef?.destroy(); // triggers after backend called "onAsyncResponse.notify()" - this.renderViewModel(args?.item); + // because of the preload destroy above, we need a small delay to make sure the DOM element is ready to render the Row Detail + queueMicrotask(() => { + this.renderViewModel(args?.item); - if (this.rowDetailViewOptions && typeof this.rowDetailViewOptions.onAsyncEndUpdate === 'function') { - this.rowDetailViewOptions.onAsyncEndUpdate(e, args); - } + if (this.rowDetailViewOptions && typeof this.rowDetailViewOptions.onAsyncEndUpdate === 'function') { + this.rowDetailViewOptions.onAsyncEndUpdate(e, args); + } + }); }); this.eventHandler.subscribe( diff --git a/test/cypress/e2e/example21.cy.ts b/test/cypress/e2e/example21.cy.ts index f65bd328..aa6e3185 100644 --- a/test/cypress/e2e/example21.cy.ts +++ b/test/cypress/e2e/example21.cy.ts @@ -245,4 +245,21 @@ describe('Example 21 - Row Detail View', () => { cy.get('#grid21').find('.slick-cell + .dynamic-cell-detail .innerDetailView_101').should('not.exist'); }); + + it('should expect the Row Detail to be re-rendered after expanding/collapsing multiple times', () => { + cy.get('#grid21').find('.slick-row:nth(1) .slick-cell:nth(0)').as('toggle1'); + cy.get('@toggle1').click(); + cy.get('@toggle1').click(); + cy.get('@toggle1').click(); + + cy.get('#grid21').find('.slick-cell + .dynamic-cell-detail .innerDetailView_1').as('detailContainer'); + cy.get('@detailContainer').find('h3').contains('Task 1'); + + cy.get('@toggle1').click(); + cy.get('@detailContainer').should('not.exist'); + + cy.get('@toggle1').click(); + cy.get('#grid21').find('.slick-cell + .dynamic-cell-detail .innerDetailView_1').as('detailContainer'); + cy.get('@detailContainer').find('h3').contains('Task 1'); + }); });