diff --git a/packages/composite-editor-component/src/slick-composite-editor.component.spec.ts b/packages/composite-editor-component/src/slick-composite-editor.component.spec.ts index 794fccf46..9e1eb353a 100644 --- a/packages/composite-editor-component/src/slick-composite-editor.component.spec.ts +++ b/packages/composite-editor-component/src/slick-composite-editor.component.spec.ts @@ -1004,7 +1004,10 @@ describe('CompositeEditorService', () => { jest.spyOn(dataViewStub, 'getItemCount').mockReturnValue(1); jest.spyOn(dataViewStub, 'getItems').mockReturnValue([mockProduct1]); const gridSrvAddItemSpy = jest.spyOn(gridServiceStub, 'addItem'); - const saveSpy = jest.spyOn(gridStub.getEditController() as any, 'commitCurrentEdit'); + (getEditControllerMock as any).commitCurrentEdit = () => { + gridStub.onAddNewRow.notify({ grid: gridStub, item: mockProduct2, column: columnsMock[0] }); + return true; + }; const mockModalOptions = { headerTitle: 'Details', modalType: 'create' } as CompositeEditorOpenDetailOption; component = new SlickCompositeEditorComponent(); @@ -1025,7 +1028,6 @@ describe('CompositeEditorService', () => { gridStub.onCompositeEditorChange.notify({ row: 0, cell: 0, column: columnsMock[0], item: mockProduct2, formValues: { productName: 'test' }, editors: {}, grid: gridStub }); compositeFooterSaveBtnElm.click(); - gridStub.onAddNewRow.notify({ grid: gridStub, item: mockProduct2, column: columnsMock[0] }); setTimeout(() => { expect(component).toBeTruthy(); @@ -1037,7 +1039,6 @@ describe('CompositeEditorService', () => { expect(productNameLabelElm.textContent).toBe('Product'); expect(productNameDetailCellElm.classList.contains('modified')).toBe(true); expect(gridSrvAddItemSpy).toHaveBeenCalledWith({ ...mockProduct2, id: 2 }, undefined); - expect(saveSpy).toHaveBeenCalled(); done(); }); }); @@ -1148,10 +1149,13 @@ describe('CompositeEditorService', () => { jest.spyOn(gridStub, 'getDataItem').mockReturnValue(mockNewProduct2); jest.spyOn(dataViewStub, 'getItems').mockReturnValue([mockProduct1]); const gridSrvAddItemSpy = jest.spyOn(gridServiceStub, 'addItem'); - const saveSpy = jest.spyOn(gridStub.getEditController() as any, 'commitCurrentEdit'); - + (getEditControllerMock as any).commitCurrentEdit = () => { + gridStub.onAddNewRow.notify({ grid: gridStub, item: mockNewProduct2, column: columnsMock[0] }); + return true; + }; const mockCreateOnSave = jest.fn(); mockCreateOnSave.mockResolvedValue(Promise.resolve(true)); + const mockModalOptions = { headerTitle: 'Details', modalType: 'create', insertNewId: 3, onSave: mockCreateOnSave } as CompositeEditorOpenDetailOption; component = new SlickCompositeEditorComponent(); component.init(gridStub, container); @@ -1171,7 +1175,6 @@ describe('CompositeEditorService', () => { gridStub.onCompositeEditorChange.notify({ row: 0, cell: 0, column: columnsMock[0], item: mockNewProduct2, formValues: { productName: 'test' }, editors: {}, grid: gridStub }); compositeFooterSaveBtnElm.click(); - gridStub.onAddNewRow.notify({ grid: gridStub, item: mockNewProduct2, column: columnsMock[0] }); setTimeout(() => { expect(component).toBeTruthy(); @@ -1183,7 +1186,6 @@ describe('CompositeEditorService', () => { expect(productNameLabelElm.textContent).toBe('Product'); expect(productNameDetailCellElm.classList.contains('modified')).toBe(true); expect(gridSrvAddItemSpy).toHaveBeenCalledWith({ ...mockNewProduct2, id: 3 }, undefined); - expect(saveSpy).toHaveBeenCalled(); expect(mockCreateOnSave).toHaveBeenCalledWith({ productName: 'test' }, { gridRowIndexes: [], dataContextIds: [] }, { ...mockNewProduct2, id: 3 }); done(); }); diff --git a/packages/composite-editor-component/src/slick-composite-editor.component.ts b/packages/composite-editor-component/src/slick-composite-editor.component.ts index f51f69841..bf1dadde7 100644 --- a/packages/composite-editor-component/src/slick-composite-editor.component.ts +++ b/packages/composite-editor-component/src/slick-composite-editor.component.ts @@ -500,9 +500,8 @@ export class SlickCompositeEditorComponent implements ExternalResource { // when adding a new row to the grid, we need to invalidate that row and re-render the grid this._eventHandler.subscribe(this.grid.onAddNewRow, (_e, args) => { - this.insertNewItemInDataView(args.item); - this._originalDataContext = args.item; // this becomes the new data context - this.dispose(); + this._originalDataContext = this.insertNewItemInDataView(args.item); // this becomes the new data context + // this.disposeComponent(); }); } return this; @@ -917,7 +916,7 @@ export class SlickCompositeEditorComponent implements ExternalResource { } /** switch case handler to determine which code to execute depending on the modal type */ - protected handleSaveClicked() { + protected async handleSaveClicked() { const modalType = this._options?.modalType; switch (modalType) { case 'mass-update': @@ -962,14 +961,18 @@ export class SlickCompositeEditorComponent implements ExternalResource { // commit the changes into the grid // if it's a "create" then it will triggered the "onAddNewRow" event which will in term push it to the grid // while an "edit" will simply applies the changes directly on the same row - this.grid.getEditController()?.commitCurrentEdit(); + let isFormValid = this.grid.getEditController()?.commitCurrentEdit(); // if the user provided the "onSave" callback, let's execute it with the item data context - if (typeof this._options?.onSave === 'function') { - const itemDataContext = this.grid.getDataItem(this._lastActiveRowNumber); // we can get item data context directly from DataView - this._options?.onSave(this.formValues, this.getCurrentRowSelections(), itemDataContext); + if (isFormValid && typeof this._options?.onSave === 'function') { + const itemDataContext = (modalType === 'create') + ? this._originalDataContext // the inserted item was copied to our ref by the "onAddNewRow" event + : this.grid.getDataItem(this._lastActiveRowNumber); // for clone, we can get item data context directly from DataView + isFormValid = await this._options?.onSave(this.formValues, this.getCurrentRowSelections(), itemDataContext); + } + if (isFormValid) { + this.dispose(); // when the form is valid, we can close the modal } - break; } } @@ -985,6 +988,7 @@ export class SlickCompositeEditorComponent implements ExternalResource { } else { this.executeOnError({ type: 'error', code: 'ITEM_ALREADY_EXIST', message: `The item object which you are trying to add already exist with the same Id:: ${newId}` }); } + return item; } protected parseText(inputText: string, mappedArgs: any): string { diff --git a/test/cypress/e2e/example12.cy.ts b/test/cypress/e2e/example12.cy.ts index 24aa06a67..74e9ac1ae 100644 --- a/test/cypress/e2e/example12.cy.ts +++ b/test/cypress/e2e/example12.cy.ts @@ -241,17 +241,64 @@ describe('Example 12 - Composite Editor Modal', () => { cy.get('.item-details-container.editor-title .item-details-validation').contains('* This is a required field.'); }); - it('should fill in the (Create Item) form inputs and expect a new row in the grid', () => { + it('should fill in the (Create Item) as Task 7777 and expect a new row in the grid', () => { + cy.get('textarea').type('Task 7777'); + cy.get('.item-details-container.editor-title .item-details-validation').should('be.empty'); + cy.get('.item-details-container.editor-title .modified').should('have.length', 1); + + cy.get('.item-details-editor-container .slider-editor-input.editor-percentComplete').as('range').invoke('val', 44).trigger('change', { force: true }); + cy.get('.item-details-editor-container .input-group-text').contains('44'); + cy.get('.item-details-container.editor-percentComplete .modified').should('have.length', 1); + + cy.get('.item-details-container.editor-product .autocomplete').type('sleek'); + cy.get('.slick-autocomplete.autocomplete-custom-four-corners').should('be.visible'); + cy.get('.slick-autocomplete.autocomplete-custom-four-corners').find('div:nth(0)').click(); + cy.get('.item-details-container.editor-product .modified').should('have.length', 1); + + cy.get('.item-details-container.editor-duration .editor-text').type('33'); + cy.get('.item-details-container.editor-duration .modified').should('have.length', 1); + + cy.get('.item-details-container.editor-origin .autocomplete').type('au'); + cy.get('.slick-autocomplete:visible').find('div:nth(1)').click(); + cy.get('.item-details-container.editor-origin .autocomplete').invoke('val').then(text => expect(text).to.eq('Austria')); + cy.get('.item-details-container.editor-origin .modified').should('have.length', 1); + + cy.get('.btn-save').contains('Save').click(); + cy.get('.slick-editor-modal').should('not.exist'); + + cy.window().then((win) => { + expect(win.console.log).to.be.calledWithMatch('create item data context', { + id: 501, title: 'Task 7777', completed: false, complexity: '', duration: 33, finish: '', + percentComplete: 44, start: '', origin: { name: 'Austria', code: 'AT' }, + product: { id: 0, icon: 'mdi-arrow-collapse', itemName: 'Sleek Metal Computer', itemTypeName: 'I', listPrice: 2100.23 }, + }); + }); + }); + + it('should have new TASK 7777 displayed on first row', () => { + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).contains('TASK 7777', { matchCase: false }); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(2)`).should('contain', '33 days'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(4)`).should('contain', '44'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(5)`).find('.editing-field').should('have.length', 1); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(7)`).find('.mdi.mdi-check.checkmark-icon').should('have.length', 0); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(8)`).should('be.empty'); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(9)`).should('contain', 'Sleek Metal Computer'); + + // next few rows Title should be unchanged + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).contains('TASK 0', { matchCase: false }); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(1)`).contains('TASK 1111', { matchCase: false }); + }); + + it('should fill in the (Create Item) form inputs with Task 8888 and expect a new row in the grid', () => { + cy.get('[data-test="open-modal-create-btn"]').click(); + cy.get('.slick-editor-modal-title').contains('Inserting New Task'); + cy.get('textarea').type('Task'); cy.get('.item-details-container.editor-title .item-details-validation').contains('* Your title is invalid, it must start with "Task" followed by a number.'); cy.get('textarea').type(' 8888'); cy.get('.item-details-container.editor-title .item-details-validation').should('be.empty'); cy.get('.item-details-container.editor-title .modified').should('have.length', 1); - // cy.get('.slick-large-editor-text.editor-title') - // .should('have.css', 'border') - // .and('contain', `solid ${UNSAVED_RGB_COLOR}`); - cy.get('.item-details-editor-container .slider-editor-input.editor-percentComplete').as('range').invoke('val', 5).trigger('change', { force: true }); cy.get('.item-details-editor-container .input-group-text').contains('5'); cy.get('.item-details-container.editor-percentComplete .modified').should('have.length', 1); @@ -279,6 +326,14 @@ describe('Example 12 - Composite Editor Modal', () => { cy.get('.btn-save').contains('Save').click(); cy.get('.slick-editor-modal').should('not.exist'); + + cy.window().then((win) => { + expect(win.console.log).to.be.calledWithMatch('create item data context', { + id: 502, title: 'Task 8888', completed: true, complexity: '', duration: 22, percentComplete: 5, + start: '', origin: { name: 'Antarctica', code: 'AQ' }, + product: { id: 1, icon: 'mdi-arrow-expand', itemName: 'Tasty Granite Table', itemTypeName: 'I', listPrice: 3200.12 }, + }); + }); }); it('should have new TASK 8888 displayed on first row', () => { @@ -291,14 +346,16 @@ describe('Example 12 - Composite Editor Modal', () => { cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(9)`).should('contain', 'Tasty Granite Table'); // next few rows Title should be unchanged - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).contains('TASK 0', { matchCase: false }); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(1)`).contains('TASK 1111', { matchCase: false }); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).contains('TASK 8888', { matchCase: false }); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).contains('TASK 7777', { matchCase: false }); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(1)`).contains('TASK 0', { matchCase: false }); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(1)`).contains('TASK 1111', { matchCase: false }); }); - it('should open the Composite Editor (Edit Item) and expect all form inputs to be filled with TASK 8888 data of previous create item', () => { + it('should open the Composite Editor (Edit Item) and expect all form inputs to be filled with TASK 8888 data of previously created item', () => { cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(3)`).click({ force: true }); cy.get('[data-test="open-modal-edit-btn"]').click(); - cy.get('.slick-editor-modal-title').contains('Editing - Task 8888 (id: 501)'); + cy.get('.slick-editor-modal-title').contains('Editing - Task 8888 (id: 502)'); cy.get('textarea').contains('Task 8888').type('Task 8899'); cy.get('.item-details-editor-container .slider-editor-input.editor-percentComplete').as('range').invoke('val', 7).trigger('change', { force: true }); @@ -325,15 +382,16 @@ describe('Example 12 - Composite Editor Modal', () => { cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(9)`).should('contain', 'Tasty Granite Table'); // next few rows Title should be unchanged - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).contains('TASK 0', { matchCase: false }); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(1)`).contains('TASK 1111', { matchCase: false }); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).contains('TASK 7777', { matchCase: false }); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(1)`).contains('TASK 0', { matchCase: false }); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(1)`).contains('TASK 1111', { matchCase: false }); }); it('should open the Composite Editor (Mass Update) and be able to change some of the inputs in the form', () => { cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(3)`).click(); cy.get('[data-test="open-modal-mass-update-btn"]').wait(200).click(); cy.get('.slick-editor-modal-title').should('contain', 'Mass Update All Records'); - cy.get('.footer-status-text').should('contain', 'All 501 records selected'); + cy.get('.footer-status-text').should('contain', 'All 502 records selected'); cy.get('.item-details-editor-container .editor-checkbox').check(); cy.get('.item-details-container.editor-completed .modified').should('have.length', 1); @@ -393,7 +451,7 @@ describe('Example 12 - Composite Editor Modal', () => { cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(3)`).click(); cy.get('[data-test="open-modal-mass-update-btn"]').wait(200).click(); cy.get('.slick-editor-modal-title').should('contain', 'Mass Update All Records'); - cy.get('.footer-status-text').should('contain', 'All 501 records selected'); + cy.get('.footer-status-text').should('contain', 'All 502 records selected'); cy.get('.item-details-editor-container .editor-checkbox').check(); cy.get('.item-details-container.editor-completed .modified').should('have.length', 1); @@ -449,7 +507,7 @@ describe('Example 12 - Composite Editor Modal', () => { it('should be able to open the Composite Editor (Mass Selection) and be able to change some of the inputs in the form', () => { cy.get('.slick-editor-modal-title').should('contain', 'Update Selected Records'); - cy.get('.footer-status-text').should('contain', '2 of 501 selected'); + cy.get('.footer-status-text').should('contain', '2 of 502 selected'); cy.get('.item-details-editor-container .editor-checkbox').check(); cy.get('.item-details-container.editor-completed .modified').should('have.length', 1);