diff --git a/packages/devextreme/js/__internal/grids/data_grid/grouping/__tests__/utils.test.ts b/packages/devextreme/js/__internal/grids/data_grid/grouping/__tests__/utils.test.ts index ff48341edb38..4bf12e9d36b8 100644 --- a/packages/devextreme/js/__internal/grids/data_grid/grouping/__tests__/utils.test.ts +++ b/packages/devextreme/js/__internal/grids/data_grid/grouping/__tests__/utils.test.ts @@ -5,7 +5,7 @@ import type { ProcessedItem } from '@ts/grids/grid_core/data_controller/types'; import type { RawItemData } from '@ts/grids/grid_core/data_source_adapter/types'; import { - isGroupNode, isGroupRow, isSameContinuationState, isSameExpandedState, + getGroupColumnIndices, isGroupNode, isGroupRow, isSameContinuationState, isSameExpandedState, } from '../utils'; const groupRow = (partial: Partial = {}): ProcessedItem => ({ @@ -136,3 +136,39 @@ describe('isSameContinuationState', () => { )).toBe(true); }); }); + +describe('getGroupColumnIndices', () => { + const expandedGroupRow = (partial: Partial = {}): ProcessedItem => groupRow({ + isExpanded: true, + data: { isContinuation: false, isContinuationOnNextPage: false }, + ...partial, + }); + + it('should skip the group expand cell', () => { + const oldItem = expandedGroupRow({ + cells: [{ column: { type: 'groupExpand' } }, {}, { column: { dataField: 'name' } }], + }); + + expect(getGroupColumnIndices(oldItem, expandedGroupRow())).toEqual([1, 2]); + }); + + it('should return undefined when the old row has no cells', () => { + expect(getGroupColumnIndices(expandedGroupRow(), expandedGroupRow())).toBeUndefined(); + }); + + it('should return undefined when the expanded state has changed', () => { + const oldItem = expandedGroupRow({ cells: [{}] }); + + expect(getGroupColumnIndices(oldItem, expandedGroupRow({ isExpanded: false }))) + .toBeUndefined(); + }); + + it('should return undefined when the continuation state has changed', () => { + const oldItem = expandedGroupRow({ cells: [{}] }); + const newItem = expandedGroupRow({ + data: { isContinuation: true, isContinuationOnNextPage: false }, + }); + + expect(getGroupColumnIndices(oldItem, newItem)).toBeUndefined(); + }); +}); diff --git a/packages/devextreme/js/__internal/grids/data_grid/grouping/extenders/__tests__/grouping_data_controller.data_row_index.test.ts b/packages/devextreme/js/__internal/grids/data_grid/grouping/extenders/__tests__/grouping_data_controller.data_row_index.test.ts new file mode 100644 index 000000000000..6e1d64269b75 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/data_grid/grouping/extenders/__tests__/grouping_data_controller.data_row_index.test.ts @@ -0,0 +1,81 @@ +import { + afterEach, beforeEach, describe, expect, it, +} from '@jest/globals'; +import { + afterTest, + beforeTest, + createDataGrid, +} from '@ts/grids/grid_core/__tests__/__mock__/helpers/utils'; +import type { DataController } from '@ts/grids/grid_core/data_controller/data_controller'; +import type { ProcessedItem } from '@ts/grids/grid_core/data_controller/types'; + +declare class ExposedDataController extends DataController { + public _items: ProcessedItem[]; + + public adjustInsertRowIndex: (visibleRowIndex: number) => number; +} + +const row = (rowType: ProcessedItem['rowType']): ProcessedItem => ({ + rowType, + key: rowType, + data: {}, + values: [], +}); + +const withVisibleRows = async ( + rows: ProcessedItem[], +): Promise<(visibleRowIndex: number) => number> => { + const { instance } = await createDataGrid({ + dataSource: [], + columns: ['name', 'age'], + }); + const dataController = instance.getController('data') as unknown as ExposedDataController; + + dataController._items = rows; + + return (visibleRowIndex: number): number => dataController.adjustInsertRowIndex(visibleRowIndex); +}; + +describe('Grouping data controller data row index', () => { + beforeEach(beforeTest); + afterEach(afterTest); + + it('should count the group rows along with the data rows', async () => { + const dataRowIndex = await withVisibleRows([ + row('data'), row('group'), row('detail'), row('data'), + ]); + + expect(dataRowIndex(3)).toBe(2); + expect(dataRowIndex(4)).toBe(3); + }); + + it('should not count the group footer rows', async () => { + const dataRowIndex = await withVisibleRows([row('data'), row('groupFooter'), row('data')]); + + expect(dataRowIndex(3)).toBe(2); + }); + + it('should not count the adaptive detail rows', async () => { + const dataRowIndex = await withVisibleRows([row('data'), row('detailAdaptive'), row('data')]); + + expect(dataRowIndex(3)).toBe(2); + }); + + it('should count only the data rows when there are no group rows', async () => { + const dataRowIndex = await withVisibleRows([row('data'), row('detail'), row('data')]); + + expect(dataRowIndex(3)).toBe(2); + }); + + it('should return zero for the first visible index', async () => { + const dataRowIndex = await withVisibleRows([row('group'), row('data')]); + + expect(dataRowIndex(0)).toBe(0); + }); + + it('should count the rows that are there when the index is out of range', async () => { + const dataRowIndex = await withVisibleRows([row('group'), row('data')]); + + expect(dataRowIndex(10)).toBe(2); + }); +}); diff --git a/packages/devextreme/js/__internal/grids/data_grid/grouping/extenders/__tests__/grouping_data_controller.row_changes.test.ts b/packages/devextreme/js/__internal/grids/data_grid/grouping/extenders/__tests__/grouping_data_controller.row_changes.test.ts index 4a0559e43a5c..3c7bea04f2e4 100644 --- a/packages/devextreme/js/__internal/grids/data_grid/grouping/extenders/__tests__/grouping_data_controller.row_changes.test.ts +++ b/packages/devextreme/js/__internal/grids/data_grid/grouping/extenders/__tests__/grouping_data_controller.row_changes.test.ts @@ -12,20 +12,31 @@ interface GroupRowState { isExpanded?: boolean; isContinuation?: boolean; isContinuationOnNextPage?: boolean; + values?: unknown[]; + cells?: ProcessedItem['cells']; } const groupRow = ({ isExpanded = true, isContinuation = false, isContinuationOnNextPage = false, + values = [], + cells, }: GroupRowState = {}): ProcessedItem => ({ rowType: 'group', key: [1], data: { isContinuation, isContinuationOnNextPage }, - values: [], + values, isExpanded, + cells, }); +const renderedCells: ProcessedItem['cells'] = [ + { column: { type: 'groupExpand' } }, + {}, + { column: { dataField: 'name' } }, +]; + const dataRow = (partial: Partial = {}): ProcessedItem => ({ rowType: 'data', key: 1, @@ -46,12 +57,13 @@ describe('Grouping data controller row changes', () => { it('should report a group row when isExpanded changed', async () => { const change = await refreshRow( - groupRow({ isExpanded: true }), + groupRow({ isExpanded: true, cells: renderedCells }), groupRow({ isExpanded: false }), ); expect(change.rowIndices).toEqual([0]); expect(change.changeTypes).toEqual(['update']); + expect(change.columnIndices).toEqual([undefined]); }); it('should report a group row when isContinuation changed', async () => { @@ -68,6 +80,26 @@ describe('Grouping data controller row changes', () => { expect(change.changeTypes).toEqual(['update']); }); + it('should diff every group cell but the expand one when values changed', async () => { + const change = await refreshRow( + groupRow({ values: ['Alex'], cells: renderedCells }), + groupRow({ values: ['Bob'] }), + ); + + expect(change.rowIndices).toEqual([0]); + expect(change.columnIndices).toEqual([[1, 2]]); + }); + + it('should repaint the whole group row when it was never rendered', async () => { + const change = await refreshRow( + groupRow({ values: ['Alex'] }), + groupRow({ values: ['Bob'] }), + ); + + expect(change.rowIndices).toEqual([0]); + expect(change.columnIndices).toEqual([undefined]); + }); + it('should not report a data row when isExpanded changed (master detail row)', async () => { const change = await refreshRow( dataRow({ isExpanded: false }), diff --git a/packages/devextreme/js/__internal/grids/data_grid/grouping/extenders/grouping_data_controller.ts b/packages/devextreme/js/__internal/grids/data_grid/grouping/extenders/grouping_data_controller.ts index 440fb56af6d1..769d0aa1590f 100644 --- a/packages/devextreme/js/__internal/grids/data_grid/grouping/extenders/grouping_data_controller.ts +++ b/packages/devextreme/js/__internal/grids/data_grid/grouping/extenders/grouping_data_controller.ts @@ -3,6 +3,7 @@ import { Deferred, when } from '@js/core/utils/deferred'; import type { Properties } from '@js/ui/data_grid'; import type { DataController } from '@ts/grids/grid_core/data_controller/data_controller'; import type { ItemProcessingOptions, ProcessedItem } from '@ts/grids/grid_core/data_controller/types'; +import { countRowsBefore } from '@ts/grids/grid_core/data_controller/utils/row_changes'; import type { RawItemData } from '@ts/grids/grid_core/data_source_adapter/types'; import type { ModuleType, @@ -16,7 +17,7 @@ import type { ChangeRowExpandArgs, GroupItem, ProcessGroupItemsOptions, } from '../types'; import { - isGroupNode, isGroupRow, isSameContinuationState, isSameExpandedState, + getGroupColumnIndices, isGroupNode, isGroupRow, isSameContinuationState, isSameExpandedState, } from '../utils'; export const groupingDataControllerExtender = ( @@ -134,6 +135,25 @@ export const groupingDataControllerExtender = ( return resultItems; } + protected adjustInsertRowIndex(visibleRowIndex: number): number { + const groupRowCount = countRowsBefore(this.getVisibleRows(), visibleRowIndex, 'group'); + + return super.adjustInsertRowIndex(visibleRowIndex) + groupRowCount; + } + + protected getChangedColumnIndices( + oldItem: ProcessedItem, + newItem: ProcessedItem, + visibleRowIndex: number, + isLiveUpdate?: boolean, + ): number[] | undefined { + if (oldItem.rowType === 'group' && newItem.rowType === 'group') { + return getGroupColumnIndices(oldItem, newItem); + } + + return super.getChangedColumnIndices(oldItem, newItem, visibleRowIndex, isLiveUpdate); + } + protected isSameRowState(item1: ProcessedItem, item2: ProcessedItem): boolean { if (item1.rowType === 'group' && (!isSameExpandedState(item1, item2) || !isSameContinuationState(item1, item2))) { diff --git a/packages/devextreme/js/__internal/grids/data_grid/grouping/utils.ts b/packages/devextreme/js/__internal/grids/data_grid/grouping/utils.ts index 52a05945ae26..e7e8a71a566e 100644 --- a/packages/devextreme/js/__internal/grids/data_grid/grouping/utils.ts +++ b/packages/devextreme/js/__internal/grids/data_grid/grouping/utils.ts @@ -25,3 +25,19 @@ export function isSameContinuationState(item1: ProcessedItem, item2: ProcessedIt return item1.data?.isContinuation === item2.data?.isContinuation && item1.data?.isContinuationOnNextPage === item2.data?.isContinuationOnNextPage; } + +export function getGroupColumnIndices( + oldItem: ProcessedItem, + newItem: ProcessedItem, +): number[] | undefined { + const isSameState = isSameExpandedState(oldItem, newItem) + && isSameContinuationState(oldItem, newItem); + + if (!oldItem.cells || !isSameState) { + return undefined; + } + + return oldItem.cells + .map((cell, index) => (cell.column?.type !== 'groupExpand' ? index : -1)) + .filter((index) => index >= 0); +} diff --git a/packages/devextreme/js/__internal/grids/data_grid/summary/extenders/__tests__/summary_data_controller.row_changes.test.ts b/packages/devextreme/js/__internal/grids/data_grid/summary/extenders/__tests__/summary_data_controller.row_changes.test.ts index 08455315c79f..fbfd87bbc4ea 100644 --- a/packages/devextreme/js/__internal/grids/data_grid/summary/extenders/__tests__/summary_data_controller.row_changes.test.ts +++ b/packages/devextreme/js/__internal/grids/data_grid/summary/extenders/__tests__/summary_data_controller.row_changes.test.ts @@ -55,6 +55,7 @@ describe('Summary data controller row changes', () => { expect(change.rowIndices).toEqual([0]); expect(change.changeTypes).toEqual(['update']); + expect(change.columnIndices).toEqual([undefined]); }); it('should report a group footer when isContinuation changed', async () => { diff --git a/packages/devextreme/js/__internal/grids/data_grid/summary/extenders/summary_data_controller.ts b/packages/devextreme/js/__internal/grids/data_grid/summary/extenders/summary_data_controller.ts index a32233d8861f..937c110c9ef3 100644 --- a/packages/devextreme/js/__internal/grids/data_grid/summary/extenders/summary_data_controller.ts +++ b/packages/devextreme/js/__internal/grids/data_grid/summary/extenders/summary_data_controller.ts @@ -288,6 +288,19 @@ export const summaryDataControllerExtender = ( return super.isSameRowState(item1, item2); } + protected getChangedColumnIndices( + oldItem: ProcessedItem, + newItem: ProcessedItem, + visibleRowIndex: number, + isLiveUpdate?: boolean, + ): number[] | undefined { + if (newItem.rowType === DATAGRID_GROUP_FOOTER_ROW_TYPE) { + return undefined; + } + + return super.getChangedColumnIndices(oldItem, newItem, visibleRowIndex, isLiveUpdate); + } + protected _updateItemsCore(change: DataChange): void { const dataSource = this._dataSource; const summaryTotalItems = this.option('summary.totalItems'); diff --git a/packages/devextreme/js/__internal/grids/grid_core/__tests__/__mock__/helpers/row_changes.ts b/packages/devextreme/js/__internal/grids/grid_core/__tests__/__mock__/helpers/row_changes.ts index 67df1f4eda7f..a93f657a28d5 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/__tests__/__mock__/helpers/row_changes.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/__tests__/__mock__/helpers/row_changes.ts @@ -1,3 +1,4 @@ +import type { Properties as DataGridProperties } from '@js/ui/data_grid'; import { createDataGrid } from '@ts/grids/grid_core/__tests__/__mock__/helpers/utils'; import type { DataController } from '@ts/grids/grid_core/data_controller/data_controller'; import type { @@ -10,26 +11,37 @@ declare class ExposedDataController extends DataController { public applyChangesOnly: (change: DataChange) => void; } -const createRefreshChange = (items: ProcessedItem[]): DataChange => ({ +export interface RefreshRowOptions { + gridOptions?: DataGridProperties; + isLiveUpdate?: boolean; +} + +const createRefreshChange = ( + items: ProcessedItem[], + isLiveUpdate?: boolean, +): DataChange => ({ changeType: 'refresh', repaintChangesOnly: true, items, + isLiveUpdate, }); export const refreshRow = async ( oldItem: ProcessedItem, newItem: ProcessedItem, + { gridOptions, isLiveUpdate }: RefreshRowOptions = {}, ): Promise => { const { instance } = await createDataGrid({ dataSource: [], columns: ['name', 'age'], repaintChangesOnly: true, + ...gridOptions, }); const dataController = instance.getController('data') as unknown as ExposedDataController; dataController._items = [oldItem]; - const change = createRefreshChange([newItem]); + const change = createRefreshChange([newItem], isLiveUpdate); dataController.applyChangesOnly(change); diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts index 592fa7c6acab..f4f545ea0a49 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts @@ -54,11 +54,9 @@ import { resolvePaginate, syncPaging } from './utils/paging'; import { getRefreshOptions } from './utils/refresh'; import { attachChangedItems, - canDiffColumns, convertToUpdateChange, + countRowsBefore, getChangedRowIndices, - getDataRowIndex, - getGroupColumnIndices, getItemChange, getRowKey, indexRowsByKey, @@ -759,6 +757,16 @@ export class DataController extends modules.Controller { // eslint-disable-next-line @typescript-eslint/no-unused-vars protected getDataIndex(change: DataChange): number { return 0; } + /** + * A store change is indexed by data rows, while an insert index coming from the grid counts + * every visible row. Each module that puts its own countable rows into the stream adds their + * count on top of this one. + * @extended: grouping (DataGrid) + */ + protected adjustInsertRowIndex(visibleRowIndex: number): number { + return countRowsBefore(this.getVisibleRows(), visibleRowIndex, 'data'); + } + /** * @extended: adaptivity, editing, master_detail, virtual_scrolling */ @@ -978,7 +986,8 @@ export class DataController extends modules.Controller { } /** - * @extended: editing_row_based, editing, editing_form_based + * @extended: editing_row_based, editing, editing_form_based, grouping (DataGrid), + * summary (DataGrid) */ protected getChangedColumnIndices( oldItem: ProcessedItem, @@ -986,18 +995,17 @@ export class DataController extends modules.Controller { visibleRowIndex: number, isLiveUpdate?: boolean, ): number[] | undefined { - if (!canDiffColumns(oldItem, newItem)) { + if (oldItem.rowType !== newItem.rowType) { return undefined; } - - switch (newItem.rowType) { - case 'group': - return getGroupColumnIndices(oldItem, newItem); - case 'detail': - return []; - default: - return this.getChangedColumnIndicesCore(oldItem, newItem, visibleRowIndex, isLiveUpdate); + // The detail type is not owned by a single module: Master-Detail creates these rows, + // while form-based editing reuses the same type for its edit row. Therefore, the check + // remains in the base module for now. + if (newItem.rowType === 'detail') { + return []; } + + return this.getChangedColumnIndicesCore(oldItem, newItem, visibleRowIndex, isLiveUpdate); } private getChangedColumnIndicesCore( @@ -1160,7 +1168,6 @@ export class DataController extends modules.Controller { } private readonly changingHandler = (e: ChangingEvent): void => { - const rows = this.getVisibleRows(); const dataSource = this.dataSource(); if (!dataSource) { @@ -1169,7 +1176,7 @@ export class DataController extends modules.Controller { e.changes.forEach((change) => { if (change.type === 'insert' && change.index !== undefined && change.index >= 0) { - change.index = getDataRowIndex(rows, change.index); + change.index = this.adjustInsertRowIndex(change.index); } }); }; diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_controller/utils/__tests__/row_changes.test.ts b/packages/devextreme/js/__internal/grids/grid_core/data_controller/utils/__tests__/row_changes.test.ts index 6a214c39d3a1..3f75575829b7 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_controller/utils/__tests__/row_changes.test.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_controller/utils/__tests__/row_changes.test.ts @@ -8,16 +8,13 @@ import type { } from '../../types'; import { attachChangedItems, - canDiffColumns, convertToUpdateChange, + countRowsBefore, getChangedRowIndices, - getDataRowIndex, - getGroupColumnIndices, getItemChange, getRowKey, getRowOperation, indexRowsByKey, - isSameGroupRowState, isSameItem, partialUpdateItem, resolveRepaintChangesOnly, @@ -96,84 +93,6 @@ describe('isSameItem', () => { }); }); -describe('isSameGroupRowState', () => { - const groupRow = (partial: Partial): ProcessedItem => row({ - rowType: 'group', - isExpanded: true, - data: { isContinuation: false, isContinuationOnNextPage: false }, - ...partial, - }); - - it('should return true for the same state', () => { - expect(isSameGroupRowState(groupRow({}), groupRow({}))).toBe(true); - }); - - it('should compare the expanded state', () => { - expect(isSameGroupRowState(groupRow({}), groupRow({ isExpanded: false }))).toBe(false); - }); - - it('should compare the continuation flags', () => { - expect(isSameGroupRowState( - groupRow({}), - groupRow({ data: { isContinuation: true, isContinuationOnNextPage: false } }), - )).toBe(false); - - expect(isSameGroupRowState( - groupRow({}), - groupRow({ data: { isContinuation: false, isContinuationOnNextPage: true } }), - )).toBe(false); - }); - - it('should not compare the data beyond the continuation flags', () => { - expect(isSameGroupRowState( - groupRow({ data: { key: 1, isContinuation: false, isContinuationOnNextPage: false } }), - groupRow({ data: { key: 2, isContinuation: false, isContinuationOnNextPage: false } }), - )).toBe(true); - }); -}); - -describe('canDiffColumns', () => { - it('should allow the diff for the rows of the same type', () => { - expect(canDiffColumns(row({ rowType: 'data' }), row({ rowType: 'data' }))).toBe(true); - }); - - it('should forbid the diff for the rows of different types', () => { - expect(canDiffColumns(row({ rowType: 'data' }), row({ rowType: 'detail' }))).toBe(false); - }); - - it('should forbid the diff for group footers', () => { - expect(canDiffColumns(row({ rowType: 'groupFooter' }), row({ rowType: 'groupFooter' }))) - .toBe(false); - }); -}); - -describe('getGroupColumnIndices', () => { - const groupRow = (partial: Partial): ProcessedItem => row({ - rowType: 'group', - isExpanded: true, - data: { isContinuation: false, isContinuationOnNextPage: false }, - ...partial, - }); - - it('should skip the group expand cell', () => { - const oldItem = groupRow({ - cells: [{ column: { type: 'groupExpand' } }, {}, { column: { dataField: 'name' } }], - }); - - expect(getGroupColumnIndices(oldItem, groupRow({}))).toEqual([1, 2]); - }); - - it('should return undefined when the old row has no cells', () => { - expect(getGroupColumnIndices(groupRow({}), groupRow({}))).toBeUndefined(); - }); - - it('should return undefined when the group state has changed', () => { - const oldItem = groupRow({ cells: [{}] }); - - expect(getGroupColumnIndices(oldItem, groupRow({ isExpanded: false }))).toBeUndefined(); - }); -}); - describe('getRowKey', () => { it('should tell apart the rows of different types with the same key', () => { expect(getRowKey(row({ key: 1, rowType: 'data' }))) @@ -307,7 +226,7 @@ describe('updateKeptRows', () => { }); }); -describe('getDataRowIndex', () => { +describe('countRowsBefore', () => { const rows = [ row({ rowType: 'data' }), row({ rowType: 'group' }), @@ -315,14 +234,24 @@ describe('getDataRowIndex', () => { row({ rowType: 'data' }), ]; - it('should count the data and group rows before the visible index', () => { - expect(getDataRowIndex(rows, 0)).toBe(0); - expect(getDataRowIndex(rows, 3)).toBe(2); - expect(getDataRowIndex(rows, rows.length)).toBe(3); + it('should count the rows of the requested type before the visible index', () => { + expect(countRowsBefore(rows, 0, 'data')).toBe(0); + expect(countRowsBefore(rows, 3, 'data')).toBe(1); + expect(countRowsBefore(rows, rows.length, 'data')).toBe(2); + }); + + it('should not count the rows of any other type', () => { + expect(countRowsBefore(rows, rows.length, 'group')).toBe(1); + expect(countRowsBefore(rows, rows.length, 'detail')).toBe(1); + expect(countRowsBefore(rows, rows.length, 'groupFooter')).toBe(0); }); it('should count the rows that are there when the index is out of range', () => { - expect(getDataRowIndex(rows, 10)).toBe(3); + expect(countRowsBefore(rows, 10, 'data')).toBe(2); + }); + + it('should return zero for an empty row set', () => { + expect(countRowsBefore([], 3, 'data')).toBe(0); }); }); diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_controller/utils/row_changes.ts b/packages/devextreme/js/__internal/grids/grid_core/data_controller/utils/row_changes.ts index c4ae25369d6e..b713669479e5 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_controller/utils/row_changes.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_controller/utils/row_changes.ts @@ -26,30 +26,6 @@ export function isSameItem( return isSameRowType && (!isDetailRow || isSameEditingState); } -// TODO remove after related checks moved to extenders (duplicated in grouping) -export function isSameGroupRowState(item1: ProcessedItem, item2: ProcessedItem): boolean { - return item1.isExpanded === item2.isExpanded - && item1.data?.isContinuation === item2.data?.isContinuation - && item1.data?.isContinuationOnNextPage === item2.data?.isContinuationOnNextPage; -} - -export function canDiffColumns(oldItem: ProcessedItem, newItem: ProcessedItem): boolean { - return oldItem.rowType === newItem.rowType && newItem.rowType !== 'groupFooter'; -} - -export function getGroupColumnIndices( - oldItem: ProcessedItem, - newItem: ProcessedItem, -): number[] | undefined { - if (!oldItem.cells || !isSameGroupRowState(oldItem, newItem)) { - return undefined; - } - - return oldItem.cells - .map((cell, index) => (cell.column?.type !== 'groupExpand' ? index : -1)) - .filter((index) => index >= 0); -} - /** * Rows of different types may share a key, so the row type is a part of the key * the diff is built on. @@ -122,14 +98,14 @@ export function updateKeptRows( }); } -/** - * A store change is indexed by data rows, while an insert index coming from the - * grid counts every visible row — group rows included. - */ -export function getDataRowIndex(rows: ProcessedItem[], visibleRowIndex: number): number { +export function countRowsBefore( + rows: ProcessedItem[], + visibleRowIndex: number, + rowType: ProcessedItem['rowType'], +): number { const previousRows = rows.slice(0, visibleRowIndex); - return previousRows.filter((row) => row?.rowType === 'data' || row?.rowType === 'group').length; + return previousRows.filter((row) => row?.rowType === rowType).length; } export function getChangedRowIndices( diff --git a/packages/devextreme/js/__internal/grids/grid_core/editing/extenders/__tests__/editing_data_controller.row_changes.test.ts b/packages/devextreme/js/__internal/grids/grid_core/editing/extenders/__tests__/editing_data_controller.row_changes.test.ts index 77a44b18000c..efddd940e9f5 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/editing/extenders/__tests__/editing_data_controller.row_changes.test.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/editing/extenders/__tests__/editing_data_controller.row_changes.test.ts @@ -16,6 +16,11 @@ const dataRow = (partial: Partial = {}): ProcessedItem => ({ ...partial, }); +const editFormRow = (partial: Partial = {}): ProcessedItem => dataRow({ + rowType: 'detail', + ...partial, +}); + describe('Editing data controller row changes', () => { beforeEach(beforeTest); afterEach(afterTest); @@ -38,4 +43,15 @@ describe('Editing data controller row changes', () => { expect(change.changeTypes).toEqual(['update']); }, ); + + it('should repaint the whole edit form row instead of diffing its columns', async () => { + const change = await refreshRow( + editFormRow({ isEditing: true, values: ['Alex', 15] }), + editFormRow({ isEditing: true, values: ['Bob', 15] }), + { gridOptions: { editing: { mode: 'form' } }, isLiveUpdate: false }, + ); + + expect(change.rowIndices).toEqual([0]); + expect(change.columnIndices).toEqual([undefined]); + }); });