Skip to content

Commit f49b228

Browse files
Merge remote-tracking branch 'my/26_1_WIP_check_ng22' into 26_1_WIP_check_ng22
2 parents 5c6b598 + 4abeef0 commit f49b228

4 files changed

Lines changed: 141 additions & 21 deletions

File tree

packages/devextreme/js/__internal/grids/grid_core/ai_assistant/commands/__tests__/selection.test.ts

Lines changed: 114 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -271,33 +271,62 @@ describe('selectByIndexesCommand', () => {
271271
afterEach(() => afterTest());
272272

273273
describe('schema', () => {
274-
it('accepts an array of positive integers', () => {
275-
expect(selectByIndexesCommand.schema.safeParse({ indexes: [1, 2, 3] }).success).toBe(true);
274+
it('accepts an array of positive integers with mode deselect', () => {
275+
expect(selectByIndexesCommand.schema.safeParse({
276+
indexes: [1, 2, 3], mode: 'deselect',
277+
}).success).toBe(true);
278+
});
279+
280+
it('accepts mode select', () => {
281+
expect(selectByIndexesCommand.schema.safeParse({
282+
indexes: [1], mode: 'select',
283+
}).success).toBe(true);
276284
});
277285

278286
it('rejects when indexes is missing', () => {
279-
expect(selectByIndexesCommand.schema.safeParse({}).success).toBe(false);
287+
expect(selectByIndexesCommand.schema.safeParse({ mode: 'select' }).success).toBe(false);
288+
});
289+
290+
it('rejects when mode is missing', () => {
291+
expect(selectByIndexesCommand.schema.safeParse({
292+
indexes: [1],
293+
}).success).toBe(false);
294+
});
295+
296+
it('rejects an invalid mode value', () => {
297+
expect(selectByIndexesCommand.schema.safeParse({
298+
indexes: [1], mode: 'toggle',
299+
}).success).toBe(false);
280300
});
281301

282302
it('rejects when indexes is an empty array', () => {
283-
expect(selectByIndexesCommand.schema.safeParse({ indexes: [] }).success).toBe(false);
303+
expect(selectByIndexesCommand.schema.safeParse({
304+
indexes: [], mode: 'select',
305+
}).success).toBe(false);
284306
});
285307

286308
it('rejects zero (indexes are 1-based)', () => {
287-
expect(selectByIndexesCommand.schema.safeParse({ indexes: [0] }).success).toBe(false);
309+
expect(selectByIndexesCommand.schema.safeParse({
310+
indexes: [0], mode: 'select',
311+
}).success).toBe(false);
288312
});
289313

290314
it('rejects negative indexes', () => {
291-
expect(selectByIndexesCommand.schema.safeParse({ indexes: [-1] }).success).toBe(false);
315+
expect(selectByIndexesCommand.schema.safeParse({
316+
indexes: [-1], mode: 'select',
317+
}).success).toBe(false);
292318
});
293319

294320
it('rejects non-integer indexes', () => {
295-
expect(selectByIndexesCommand.schema.safeParse({ indexes: [1.5] }).success).toBe(false);
321+
expect(selectByIndexesCommand.schema.safeParse({
322+
indexes: [1.5], mode: 'select',
323+
}).success).toBe(false);
296324
});
297325

298326
it('rejects unknown properties', () => {
299327
expect(selectByIndexesCommand.schema.safeParse({
300328
indexes: [1],
329+
mode: 'select',
301330
extra: 1,
302331
}).success).toBe(false);
303332
});
@@ -309,7 +338,9 @@ describe('selectByIndexesCommand', () => {
309338
const selectSpy = jest.spyOn(instance, 'selectRowsByIndexes');
310339
const callbacks = createCallbacks();
311340

312-
const result = await selectByIndexesCommand.execute(instance, callbacks)({ indexes: [0] });
341+
const result = await selectByIndexesCommand.execute(instance, callbacks)({
342+
indexes: [1], mode: 'select',
343+
});
313344

314345
expect(result.status).toBe('failure');
315346
expect(selectSpy).not.toHaveBeenCalled();
@@ -322,7 +353,7 @@ describe('selectByIndexesCommand', () => {
322353

323354
// Three rows in createGrid; 1-based index 100 has no row on the current page.
324355
const result = await selectByIndexesCommand.execute(instance, callbacks)({
325-
indexes: [1, 100],
356+
indexes: [1, 100], mode: 'select',
326357
});
327358

328359
expect(result.status).toBe('failure');
@@ -341,7 +372,9 @@ describe('selectByIndexesCommand', () => {
341372
const selectSpy = jest.spyOn(instance, 'selectRowsByIndexes');
342373
const callbacks = createCallbacks();
343374

344-
const result = await selectByIndexesCommand.execute(instance, callbacks)({ indexes: [1] });
375+
const result = await selectByIndexesCommand.execute(instance, callbacks)({
376+
indexes: [1], mode: 'select',
377+
});
345378

346379
expect(result.status).toBe('failure');
347380
expect(selectSpy).not.toHaveBeenCalled();
@@ -352,20 +385,54 @@ describe('selectByIndexesCommand', () => {
352385
const selectSpy = jest.spyOn(instance, 'selectRowsByIndexes').mockReturnValue(Promise.resolve([]) as never);
353386
const callbacks = createCallbacks();
354387

355-
const result = await selectByIndexesCommand.execute(instance, callbacks)({ indexes: [1, 3] });
388+
const result = await selectByIndexesCommand.execute(instance, callbacks)({
389+
indexes: [1, 3], mode: 'select',
390+
});
356391

357392
expect(selectSpy).toHaveBeenCalledWith([0, 2]);
358393
expect(result.status).toBe('success');
359394
});
360395

396+
it('selects when mode is select', async () => {
397+
const instance = await createGrid();
398+
const selectSpy = jest.spyOn(instance, 'selectRowsByIndexes').mockReturnValue(Promise.resolve([]) as never);
399+
const deselectSpy = jest.spyOn(instance, 'deselectRows');
400+
const callbacks = createCallbacks();
401+
402+
const result = await selectByIndexesCommand.execute(instance, callbacks)({
403+
indexes: [1, 3], mode: 'select',
404+
});
405+
406+
expect(selectSpy).toHaveBeenCalledWith([0, 2]);
407+
expect(deselectSpy).not.toHaveBeenCalled();
408+
expect(result.status).toBe('success');
409+
});
410+
411+
it('resolves indexes to row keys and calls deselectRows when deselecting', async () => {
412+
const instance = await createGrid();
413+
const deselectSpy = jest.spyOn(instance, 'deselectRows').mockReturnValue(Promise.resolve([]) as never);
414+
const selectSpy = jest.spyOn(instance, 'selectRowsByIndexes');
415+
const callbacks = createCallbacks();
416+
417+
const result = await selectByIndexesCommand.execute(instance, callbacks)({
418+
indexes: [1], mode: 'deselect',
419+
});
420+
421+
expect(deselectSpy).toHaveBeenCalledWith([1]);
422+
expect(selectSpy).not.toHaveBeenCalled();
423+
expect(result.status).toBe('success');
424+
});
425+
361426
it('returns failure when selectRowsByIndexes throws', async () => {
362427
const instance = await createGrid();
363428
jest.spyOn(instance, 'selectRowsByIndexes').mockImplementation(() => {
364429
throw new Error('Error');
365430
});
366431
const callbacks = createCallbacks();
367432

368-
const result = await selectByIndexesCommand.execute(instance, callbacks)({ indexes: [1] });
433+
const result = await selectByIndexesCommand.execute(instance, callbacks)({
434+
indexes: [1], mode: 'select',
435+
});
369436

370437
expect(result.status).toBe('failure');
371438
});
@@ -376,28 +443,59 @@ describe('selectByIndexesCommand', () => {
376443
.mockReturnValue(Promise.reject(new Error('Error')) as never);
377444
const callbacks = createCallbacks();
378445

379-
const result = await selectByIndexesCommand.execute(instance, callbacks)({ indexes: [1] });
446+
const result = await selectByIndexesCommand.execute(instance, callbacks)({
447+
indexes: [1], mode: 'select',
448+
});
449+
450+
expect(result.status).toBe('failure');
451+
});
452+
453+
it('returns failure when deselectRows rejects', async () => {
454+
const instance = await createGrid();
455+
jest.spyOn(instance, 'deselectRows')
456+
.mockReturnValue(Promise.reject(new Error('Error')) as never);
457+
const callbacks = createCallbacks();
458+
459+
const result = await selectByIndexesCommand.execute(instance, callbacks)({
460+
indexes: [1], mode: 'deselect',
461+
});
380462

381463
expect(result.status).toBe('failure');
382464
});
383465
});
384466

385467
describe('default message', () => {
386-
it('reports the 1-based row numbers on the current page on success', async () => {
468+
it('reports the 1-based row numbers on the current page on select', async () => {
387469
const instance = await createGrid();
388470
jest.spyOn(instance, 'selectRowsByIndexes').mockReturnValue(Promise.resolve([]) as never);
389471
const callbacks = createCallbacks();
390472

391-
await selectByIndexesCommand.execute(instance, callbacks)({ indexes: [1, 3] });
473+
await selectByIndexesCommand.execute(instance, callbacks)({
474+
indexes: [1, 3], mode: 'select',
475+
});
392476

393477
expect(callbacks.success).toHaveBeenCalledWith('Select row(s) number 1, 3 on the current page.');
394478
});
395479

480+
it('reports the 1-based row numbers on the current page on deselect', async () => {
481+
const instance = await createGrid();
482+
jest.spyOn(instance, 'deselectRows').mockReturnValue(Promise.resolve([]) as never);
483+
const callbacks = createCallbacks();
484+
485+
await selectByIndexesCommand.execute(instance, callbacks)({
486+
indexes: [1], mode: 'deselect',
487+
});
488+
489+
expect(callbacks.success).toHaveBeenCalledWith('Deselect row(s) number 1 on the current page.');
490+
});
491+
396492
it('passes the same default message to failure', async () => {
397493
const instance = await createGrid({ selection: { mode: 'none' } });
398494
const callbacks = createCallbacks();
399495

400-
await selectByIndexesCommand.execute(instance, callbacks)({ indexes: [1] });
496+
await selectByIndexesCommand.execute(instance, callbacks)({
497+
indexes: [1], mode: 'select',
498+
});
401499

402500
expect(callbacks.failure).toHaveBeenCalledWith('Select row(s) number 1 on the current page.');
403501
});

packages/devextreme/js/__internal/grids/grid_core/ai_assistant/commands/selection.ts

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ import type { CommandResult } from '@ts/grids/grid_core/ai_assistant/types';
22
import { z } from 'zod';
33

44
import { defineGridCommand } from './defineGridCommand';
5-
import { compositeKeyPairSchema, isKeyShapeValid, normalizeKey } from './utils';
5+
import {
6+
compositeKeyPairSchema, isKeyShapeValid, normalizeKey,
7+
} from './utils';
68

79
const selectByKeysCommandSchema = z.object({
810
keys: z.array(z.union([
@@ -45,15 +47,22 @@ export const selectByKeysCommand = defineGridCommand({
4547

4648
const selectByIndexesCommandSchema = z.object({
4749
indexes: z.array(z.number().int().min(1)).min(1),
50+
mode: z.enum(['select', 'deselect']),
4851
}).strict();
4952

5053
export const selectByIndexesCommand = defineGridCommand({
5154
name: 'selectByIndexes',
52-
description: 'Select rows by their 1-based indexes within the current page. Index 1 is the first row on the visible page; group/header rows are not selectable. To select rows that are not on the current page, use selectByKeys, or call pageIndex first to switch the page.',
55+
description: 'Select or deselect specific rows by their 1-based indexes within the current page. '
56+
+ 'Index 1 is the first row on the visible page; group/header rows are not addressable. '
57+
+ 'Set mode to "deselect" to remove the listed rows from the current selection (e.g. "unselect row 1"); set it to "select" to select them. '
58+
+ 'When mode is "select", the listed rows replace the current selection. '
59+
+ 'To target rows that are not on the current page, use selectByKeys, or call pageIndex first to switch the page. '
60+
+ 'To clear selection only within the current selectAll scope, use deselectAll; to clear selection across all pages regardless of selectAllMode, use clearSelection.',
5361
schema: selectByIndexesCommandSchema,
5462
execute: (component, { success, failure }) => async (args): Promise<CommandResult> => {
5563
const rowIndexes = args.indexes.join(', ');
56-
const defaultMessage = `Select row(s) number ${rowIndexes} on the current page.`;
64+
const action = args.mode === 'deselect' ? 'Deselect' : 'Select';
65+
const defaultMessage = `${action} row(s) number ${rowIndexes} on the current page.`;
5766

5867
if (component.option('selection.mode') === 'none') {
5968
return failure(defaultMessage);
@@ -70,7 +79,18 @@ export const selectByIndexesCommand = defineGridCommand({
7079
}
7180

7281
try {
73-
await component.selectRowsByIndexes(normalizedRowIndexes);
82+
switch (args.mode) {
83+
case 'deselect': {
84+
const itemKeys = normalizedRowIndexes.map((index) => items[index].key);
85+
await component.deselectRows(itemKeys);
86+
break;
87+
}
88+
case 'select':
89+
await component.selectRowsByIndexes(normalizedRowIndexes);
90+
break;
91+
default:
92+
return failure(defaultMessage);
93+
}
7494

7595
return success(defaultMessage);
7696
} catch {

packages/devextreme/js/common/grids.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,8 @@ export type PredefinedCommands = {
221221
preserve: boolean;
222222
};
223223
selectByIndexes: {
224-
indexes: number[]
224+
indexes: number[];
225+
mode: 'select' | 'deselect';
225226
};
226227
selectAll: {};
227228
deselectAll: {};

packages/devextreme/ts/dx.all.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6611,6 +6611,7 @@ declare module DevExpress.common.grids {
66116611
};
66126612
selectByIndexes: {
66136613
indexes: number[];
6614+
mode: 'select' | 'deselect';
66146615
};
66156616
selectAll: {};
66166617
deselectAll: {};

0 commit comments

Comments
 (0)