Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2149,6 +2149,99 @@ test('The onEditorPreparing event should be called once after clicking on a cell
})();
});

test('Adding rows to a second page should work correctly when initial row values ​​are specified in the onInitNewRow method (T1274123)', async (t) => {
// arrange
const dataGrid = new DataGrid('#container');
const addAndSaveData = async () => {
// act
await dataGrid.apiAddRow();

const editPopup = dataGrid.getEditPopup();

// assert
await t
.expect(editPopup.isVisible())
.ok();

// act
await dataGrid.apiSaveEditData();

// assert
await t
.expect(dataGrid.isReady())
.ok()
.expect(editPopup.isVisible())
.notOk();
};

await t.expect(dataGrid.isReady()).ok();

// act
await dataGrid.apiPageIndex(1);
await t.expect(dataGrid.isReady()).ok();

// assert
let visibleRows = await dataGrid.apiGetVisibleRows();

await t.expect(visibleRows.length)
.eql(10)
.expect(dataGrid.getDataCell(20, 0).element.textContent)
.eql('21');

// act
await addAndSaveData();
await addAndSaveData();

// assert
visibleRows = await dataGrid.apiGetVisibleRows();

await t
.expect(visibleRows.length)
.eql(12)
.expect(visibleRows[10].key)
.eql(31)
.expect(visibleRows[11].key)
.eql(32);
}).before(async () => {
await ClientFunction(() => {
(window as any).myData = new Array(30).fill(null).map((_, index) => ({ id: index + 1, text: `item ${index + 1}` }));
(window as any).myStore = new (window as any).DevExpress.data.ArrayStore({
key: 'id',
data: (window as any).myData,
});
})();

return createWidget('dxDataGrid', {
dataSource: {
key: 'id',
load(loadOptions) {
return (window as any).myStore.load(loadOptions);
},
totalCount() {
return (window as any).myStore.totalCount();
},
insert(values) {
if (values.id === 0) {
values.id = (window as any).myData.length + 1;
}

return (window as any).myStore.insert(values);
},
} as any,
columns: ['id', 'text'],
showBorders: true,
editing: {
mode: 'popup',
allowAdding: true,
},
onInitNewRow(e) {
e.data.id = 0;
e.data.text = 'test';
},
height: 300,
});
});

fixture`Editing - ShowEditorAlways`
.page(url(__dirname, '../../../container.html'));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,24 @@ import $ from '@js/core/renderer';
import type { Properties as DataGridProperties } from '@js/ui/data_grid';
import DataGrid from '@js/ui/data_grid';
import { DataGridModel } from '@ts/grids/data_grid/__tests__/__mock__/model/data_grid';
import type { Controllers } from '@ts/grids/grid_core/m_types';

export interface DataGridInstance extends DataGrid {
getController: <T extends keyof Controllers>(name: T) => Controllers[T];
}

export const SELECTORS = {
gridContainer: '#gridContainer',
};

export const GRID_CONTAINER_ID = 'gridContainer';

export const createDataGrid = async (
export const createDataGrid = (
options: DataGridProperties = {},
): Promise<{
$container: dxElementWrapper;
component: DataGridModel;
instance: DataGrid;
instance: DataGridInstance;
}> => new Promise((resolve) => {
const $container = $('<div>')
.attr('id', GRID_CONTAINER_ID)
Expand All @@ -28,7 +33,10 @@ export const createDataGrid = async (
...options,
};

const instance = new DataGrid($container.get(0) as HTMLDivElement, dataGridOptions);
const instance = new DataGrid(
$container.get(0) as HTMLDivElement,
dataGridOptions,
) as DataGridInstance;
const component = new DataGridModel($container.get(0) as HTMLElement);

jest.runAllTimers();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
afterEach, beforeEach, describe, expect, it,
} from '@jest/globals';
import CustomStore from '@ts/data/m_custom_store';

import {
afterTest,
Expand All @@ -9,40 +10,43 @@ import {
flushAsync,
} from '../../__tests__/__mock__/helpers/utils';

const dataSource = [{
ID: 1,
FirstName: 'John',
LastName: 'Heart',
Prefix: 'Mr.',
Position: 'CEO',
BirthDate: '1964/03/16',
HireDate: '1995/01/15',
Notes: 'John has been in the Audio/Video industry since 1990. He has led DevAv as its CEO since 2003.\r\n\r\nWhen not working hard as the CEO, John loves to golf and bowl. He once bowled a perfect game of 300.',
Address: '351 S Hill St.',
}, {
ID: 2,
FirstName: 'Olivia',
LastName: 'Peyton',
Prefix: 'Mrs.',
Position: 'Sales Assistant',
BirthDate: '1981/06/03',
HireDate: '2012/05/14',
Notes: 'Olivia loves to sell. She has been selling DevAV products since 2012. \r\n\r\nOlivia was homecoming queen in high school. She is expecting her first child in 6 months. Good Luck Olivia.',
Address: '807 W Paseo Del Mar',
}, {
ID: 3,
FirstName: 'Robert',
LastName: 'Reagan',
Prefix: 'Mr.',
Position: 'CMO',
BirthDate: '1974/09/07',
HireDate: '2002/11/08',
Notes: 'Robert was recently voted the CMO of the year by CMO Magazine. He is a proud member of the DevAV Management Team.\r\n\r\nRobert is a championship BBQ chef, so when you get the chance ask him for his secret recipe.',
Address: '4 Westmoreland Pl.',
}];

describe('DataGrid editing', () => {
beforeEach(beforeTest);
let dataSource: Record<string, string | number>[] = [];

beforeEach(() => {
beforeTest();
dataSource = [{
ID: 1,
FirstName: 'John',
LastName: 'Heart',
Prefix: 'Mr.',
Position: 'CEO',
BirthDate: '1964/03/16',
HireDate: '1995/01/15',
Notes: 'John has been in the Audio/Video industry since 1990. He has led DevAv as its CEO since 2003.\r\n\r\nWhen not working hard as the CEO, John loves to golf and bowl. He once bowled a perfect game of 300.',
Address: '351 S Hill St.',
}, {
ID: 2,
FirstName: 'Olivia',
LastName: 'Peyton',
Prefix: 'Mrs.',
Position: 'Sales Assistant',
BirthDate: '1981/06/03',
HireDate: '2012/05/14',
Notes: 'Olivia loves to sell. She has been selling DevAV products since 2012. \r\n\r\nOlivia was homecoming queen in high school. She is expecting her first child in 6 months. Good Luck Olivia.',
Address: '807 W Paseo Del Mar',
}, {
ID: 3,
FirstName: 'Robert',
LastName: 'Reagan',
Prefix: 'Mr.',
Position: 'CMO',
BirthDate: '1974/09/07',
HireDate: '2002/11/08',
Notes: 'Robert was recently voted the CMO of the year by CMO Magazine. He is a proud member of the DevAV Management Team.\r\n\r\nRobert is a championship BBQ chef, so when you get the chance ask him for his secret recipe.',
Address: '4 Westmoreland Pl.',
}];
});
afterEach(afterTest);

// T1293181
Expand Down Expand Up @@ -97,4 +101,164 @@ describe('DataGrid editing', () => {
expect(rows[recoveringRowIndex].data).toEqual(dataSource[recoveringRowIndex]);
});
});

describe('Internal state cleanup after save', () => {
it('should clear internal state after updating and saving row', async () => {
const { instance } = await createDataGrid({
keyExpr: 'ID',
dataSource,
editing: {
mode: 'batch',
allowUpdating: true,
},
});

// Edit row using cellValue
instance.cellValue(0, 'FirstName', 'Updated');
const editingController = instance.getController('editing');

// Verify internal state has entries before save
expect(editingController.getInternalStateSize()).toBe(1);

// Save changes
instance.saveEditData();
await flushAsync();

// Check internal state is cleared after save
expect(editingController.getInternalStateSize()).toBe(0);
});

it('should clear internal state after adding and saving row', async () => {
const { instance } = await createDataGrid({
keyExpr: 'ID',
dataSource,
editing: {
mode: 'batch',
allowAdding: true,
},
});

// Add new row
instance.addRow();
await flushAsync();

const editingController = instance.getController('editing');
expect(editingController.getInternalStateSize()).toBe(1);

// Save changes
instance.saveEditData();
await flushAsync();

// Check internal state is cleared
expect(editingController.getInternalStateSize()).toBe(0);
});

it('should clear internal state after deleting and saving row', async () => {
const { instance } = await createDataGrid({
keyExpr: 'ID',
dataSource,
editing: {
mode: 'batch',
allowDeleting: true,
},
});

// Delete row
instance.deleteRow(1);

const editingController = instance.getController('editing');
expect(editingController.getInternalStateSize()).toBe(1);

// Save changes
instance.saveEditData();
await flushAsync();

// Check internal state is cleared
expect(editingController.getInternalStateSize()).toBe(0);
});

it('should clear internal state for multiple operations after save', async () => {
const { instance } = await createDataGrid({
keyExpr: 'ID',
dataSource,
editing: {
mode: 'batch',
allowUpdating: true,
allowAdding: true,
allowDeleting: true,
},
});

// Multiple operations
instance.cellValue(0, 'FirstName', 'Updated');
instance.deleteRow(1);
instance.addRow();
await flushAsync();

const editingController = instance.getController('editing');
expect(editingController.getInternalStateSize()).toBe(3);

// Save all changes
instance.saveEditData();
await flushAsync();

// Check internal state is completely cleared
expect(editingController.getInternalStateSize()).toBe(0);
});

it('should clear internal state when canceling changes', async () => {
const { instance } = await createDataGrid({
keyExpr: 'ID',
dataSource,
editing: {
mode: 'batch',
allowUpdating: true,
allowAdding: true,
},
});

// Make some changes
instance.cellValue(0, 'FirstName', 'Updated');
instance.addRow();
await flushAsync();

const editingController = instance.getController('editing');
const stateBeforeCancel = editingController.getInternalStateSize();
expect(stateBeforeCancel).toBe(2);

// Cancel changes
instance.cancelEditData();

// Check internal state is cleared
expect(editingController.getInternalStateSize()).toBe(0);
});

it('should preserve internal state when save fails', async () => {
const failingDataSource = new CustomStore({
key: 'ID',
load: () => Promise.resolve([...dataSource]),
update: () => Promise.reject(new Error('Save failed')),
});

const { instance } = await createDataGrid({
keyExpr: 'ID',
dataSource: failingDataSource,
editing: {
mode: 'batch',
allowUpdating: true,
},
});

instance.cellValue(0, 'FirstName', 'Updated');

const editingController = instance.getController('editing');

expect(editingController.getInternalStateSize()).toBe(1);

instance.saveEditData();
await flushAsync();

expect(editingController.getInternalStateSize()).toBe(1);
});
});
});
Loading
Loading