Skip to content
2 changes: 2 additions & 0 deletions packages/devextreme/js/__internal/grids/new/grid_core/di.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { HeaderFilterViewController } from './filtering/header_filter/view_contr
import * as FilterControllerModule from './filtering/index';
import { ItemsController } from './items_controller/items_controller';
import { KeyboardNavigationController } from './keyboard_navigation/index';
import { OptionsValidationController } from './options_validation/controller';
import { PagerView } from './pager/view';
import { SearchController } from './search/controller';
import { SearchView } from './search/view';
Expand Down Expand Up @@ -57,4 +58,5 @@ export function register(diContext: DIContext): void {
diContext.register(ClearFilterVisitor);
diContext.register(GetAppliedFilterVisitor);
diContext.register(KeyboardNavigationController);
diContext.register(OptionsValidationController);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { isDefined } from '@js/core/utils/type';
import errors from '@js/ui/widget/ui.errors';
import { DataController } from '@ts/grids/new/grid_core/data_controller/index';

export class OptionsValidationController {
public static dependencies = [
DataController,
] as const;

constructor(
private readonly dataController: DataController,
) {
}

public validateKeyExpr(): void {
const keyExpr = this.dataController.dataSource.peek().key();

if (!isDefined(keyExpr)) {
throw errors.Error('E1042', 'CardView');
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,21 @@ const setup = (config: Options = {}) => {
};
};

const checkKeyExprError = (
action: () => unknown,
selectionController?: SelectionController,
): void => {
try {
action();
} catch (err) {
expect((err as { __id: string }).__id).toEqual('E1042');

if (selectionController) {
expect(selectionController.getSelectedCardKeys()).toEqual([]);
}
}
};

describe('SelectionController', () => {
// Public methods

Expand Down Expand Up @@ -762,6 +777,169 @@ describe('SelectionController', () => {
});
});

describe('when selection changes via selectionChanged callback', () => {
it('should update both selectionController and itemsController selection states when selecting single card', () => {
const cardData = { id: 1, value: 'test' };
const {
selectionController,
itemsController,
} = setup({
keyExpr: 'id',
dataSource: [cardData],
selection: {
mode: 'multiple',
},
});

// Set up the spy before calling the method
const setSelectionStateSpy = jest.spyOn(itemsController, 'setSelectionState');

// Mock the selectionChanged private method call with test data
const selectionChangedEvent = {
addedItemKeys: [1],
removedItemKeys: [],
selectedItemKeys: [1],
selectedItems: [cardData],
};

// Call the private method directly
// @ts-expect-error - accessing private method
selectionController.selectionChanged(selectionChangedEvent);

// Verify that both controllers were updated
expect(selectionController.getSelectedCardKeys()).toEqual([1]);

// Check that the itemsController was updated with the same keys
expect(setSelectionStateSpy).toHaveBeenCalledWith([1]);

// Verify the UI would show correct selection state
const items = itemsController.items.peek();
expect(items[0].isSelected).toBe(true);
});

it('should update both selectionController and itemsController when selecting multiple cards', () => {
const cardsData = [
{ id: 1, value: 'test1' },
{ id: 2, value: 'test2' },
{ id: 3, value: 'test3' },
];
const {
selectionController,
itemsController,
} = setup({
keyExpr: 'id',
dataSource: cardsData,
selection: {
mode: 'multiple',
},
});

// Set up the spy before calling the method
const setSelectionStateSpy = jest.spyOn(itemsController, 'setSelectionState');

// Mock the selectionChanged private method call with test data
const selectionChangedEvent = {
addedItemKeys: [1, 3],
removedItemKeys: [],
selectedItemKeys: [1, 3],
selectedItems: [cardsData[0], cardsData[2]],
};

// Call the private method directly
// @ts-expect-error - accessing private method
selectionController.selectionChanged(selectionChangedEvent);

// Verify that both controllers were updated
expect(selectionController.getSelectedCardKeys()).toEqual([1, 3]);

// Check that the itemsController was updated with the same keys
expect(setSelectionStateSpy).toHaveBeenCalledWith([1, 3]);

// Verify the UI would show correct selection state
const items = itemsController.items.peek();
expect(items[0].isSelected).toBe(true);
expect(items[1].isSelected).toBe(false);
expect(items[2].isSelected).toBe(true);
});

it('should update both selectionController and itemsController when deselecting cards', () => {
const cardsData = [
{ id: 1, value: 'test1' },
{ id: 2, value: 'test2' },
{ id: 3, value: 'test3' },
];
const {
selectionController,
itemsController,
} = setup({
keyExpr: 'id',
dataSource: cardsData,
selection: {
mode: 'multiple',
},
selectedCardKeys: [1, 2, 3],
});

// Initially all cards should be selected
expect(itemsController.items.peek()[0].isSelected).toBe(true);
expect(itemsController.items.peek()[1].isSelected).toBe(true);
expect(itemsController.items.peek()[2].isSelected).toBe(true);

// Set up the spy before calling the method
const setSelectionStateSpy = jest.spyOn(itemsController, 'setSelectionState');

// Mock the selectionChanged event when deselecting card #2
const selectionChangedEvent = {
addedItemKeys: [],
removedItemKeys: [2],
selectedItemKeys: [1, 3],
selectedItems: [cardsData[0], cardsData[2]],
};

// Call the private method directly
// @ts-expect-error - accessing private method
selectionController.selectionChanged(selectionChangedEvent);

// Verify that both controllers were updated
expect(selectionController.getSelectedCardKeys()).toEqual([1, 3]);

// Check that the itemsController was updated with the same keys
expect(setSelectionStateSpy).toHaveBeenCalledWith([1, 3]);

// Verify the UI would show correct selection state
const items = itemsController.items.peek();
expect(items[0].isSelected).toBe(true);
expect(items[1].isSelected).toBe(false);
expect(items[2].isSelected).toBe(true);
});

it('should throw error E1042 if keyExpr is missing and selectionChanged', () => {
const cardData = { id: 1, value: 'test' };
const {
selectionController,
} = setup({
dataSource: [cardData],
selection: {
mode: 'multiple',
},
});

// Mock the selectionChanged private method call with test data
const selectionChangedEvent = {
addedItemKeys: [1],
removedItemKeys: [],
selectedItemKeys: [1],
selectedItems: [cardData],
};

checkKeyExprError(
// @ts-expect-error - accessing private method
() => selectionController.selectionChanged(selectionChangedEvent),
selectionController,
);
});
});

describe('when selecting all cards', () => {
it('should be called', () => {
const selectionChangedMockFn = jest.fn();
Expand Down Expand Up @@ -823,4 +1001,132 @@ describe('SelectionController', () => {
});
});
});

describe('when keyExpr is missing', () => {
describe('selection mode single', () => {
it('should throw E1042 error if keyExpr is missing and selection ', () => {
checkKeyExprError(() => setup({
dataSource: [{ value: 'test1' }, { value: 'test2' }],
selection: {
mode: 'single',
},
}));
});

it('should throw E1042 error on card click selection', () => {
const { selectionController } = setup({
dataSource: [{ value: 'test1' }, { value: 'test2' }],
selection: {
mode: 'single',
},
});

checkKeyExprError(
() => selectionController.changeCardSelection(0),
selectionController,
);
});

it('should throw E1042 error on initial selectedCardKeys', () => {
checkKeyExprError(() => setup({
dataSource: [{ value: 'test1' }, { value: 'test2' }],
selection: {
mode: 'single',
},
selectedCardKeys: [0],
}));
});

it('should throw E1042 error on runtime selectedCardKeys update', () => {
const { optionsController } = setup({
dataSource: [{ value: 'test1' }, { value: 'test2' }],
selection: {
mode: 'single',
},
});

checkKeyExprError(() => optionsController.option('selectedCardKeys', [1]));
});
});

describe('selection mode multiple', () => {
it('should throw E1042 error if keyExpr is missing and selection ', () => {
checkKeyExprError(() => setup({
dataSource: [{ value: 'test1' }, { value: 'test2' }],
selection: {
mode: 'single',
},
}));
});

it('should throw E1042 error on card click selection', () => {
const { selectionController } = setup({
dataSource: [{ value: 'test1' }, { value: 'test2' }],
selection: {
mode: 'multiple',
showCheckBoxesMode: 'always',
},
});

checkKeyExprError(
() => selectionController.changeCardSelection(0),
selectionController,
);
});

it('should throw E1042 error on checkbox click selection', () => {
const { selectionController } = setup({
dataSource: [{ value: 'test1' }, { value: 'test2' }],
selection: {
mode: 'multiple',
showCheckBoxesMode: 'always',
},
});

checkKeyExprError(
() => selectionController.changeCardSelection(0, { control: true }),
selectionController,
);
});

it('should throw E1042 error on selectAll toolbar button click', () => {
const { selectionController } = setup({
dataSource: [{ value: 'test1' }, { value: 'test2' }],
selection: {
mode: 'multiple',
showCheckBoxesMode: 'always',
allowSelectAll: true,
},
});

checkKeyExprError(
() => selectionController.selectAll(),
selectionController,
);
});

it('should throw E1042 error on initial selectedCardKeys', () => {
checkKeyExprError(() => setup({
dataSource: [{ value: 'test1' }, { value: 'test2' }],
selection: {
mode: 'multiple',
showCheckBoxesMode: 'always',
},
selectedCardKeys: [0, 1],
}));
});

it('should throw E1042 error on runtime selectedCardKeys update', () => {
const { optionsController } = setup({
dataSource: [{ value: 'test1' }, { value: 'test2' }],
selection: {
mode: 'multiple',
showCheckBoxesMode: 'always',
},
});

checkKeyExprError(() => optionsController.option('selectedCardKeys', [0, 1]));
});
});
});
});
Loading
Loading