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 @@ -271,28 +271,44 @@ describe('selectByIndexesCommand', () => {
afterEach(() => afterTest());

describe('schema', () => {
it('accepts an array of positive integers', () => {
expect(selectByIndexesCommand.schema.safeParse({ indexes: [1, 2, 3] }).success).toBe(true);
it('accepts an array of positive integers with deselect', () => {
expect(selectByIndexesCommand.schema.safeParse({
indexes: [1, 2, 3], deselect: true,
}).success).toBe(true);
});

it('rejects when indexes is missing', () => {
expect(selectByIndexesCommand.schema.safeParse({}).success).toBe(false);
});

it('accepts when deselect is omitted (optional)', () => {
expect(selectByIndexesCommand.schema.safeParse({
indexes: [1],
}).success).toBe(true);
});

it('rejects when indexes is an empty array', () => {
expect(selectByIndexesCommand.schema.safeParse({ indexes: [] }).success).toBe(false);
expect(selectByIndexesCommand.schema.safeParse({
indexes: [],
}).success).toBe(false);
});

it('rejects zero (indexes are 1-based)', () => {
expect(selectByIndexesCommand.schema.safeParse({ indexes: [0] }).success).toBe(false);
expect(selectByIndexesCommand.schema.safeParse({
indexes: [0],
}).success).toBe(false);
});

it('rejects negative indexes', () => {
expect(selectByIndexesCommand.schema.safeParse({ indexes: [-1] }).success).toBe(false);
expect(selectByIndexesCommand.schema.safeParse({
indexes: [-1],
}).success).toBe(false);
});

it('rejects non-integer indexes', () => {
expect(selectByIndexesCommand.schema.safeParse({ indexes: [1.5] }).success).toBe(false);
expect(selectByIndexesCommand.schema.safeParse({
indexes: [1.5],
}).success).toBe(false);
});

it('rejects unknown properties', () => {
Expand All @@ -309,7 +325,9 @@ describe('selectByIndexesCommand', () => {
const selectSpy = jest.spyOn(instance, 'selectRowsByIndexes');
const callbacks = createCallbacks();

const result = await selectByIndexesCommand.execute(instance, callbacks)({ indexes: [0] });
const result = await selectByIndexesCommand.execute(instance, callbacks)({
indexes: [1],
});

expect(result.status).toBe('failure');
expect(selectSpy).not.toHaveBeenCalled();
Expand Down Expand Up @@ -341,7 +359,9 @@ describe('selectByIndexesCommand', () => {
const selectSpy = jest.spyOn(instance, 'selectRowsByIndexes');
const callbacks = createCallbacks();

const result = await selectByIndexesCommand.execute(instance, callbacks)({ indexes: [1] });
const result = await selectByIndexesCommand.execute(instance, callbacks)({
indexes: [1],
});

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

const result = await selectByIndexesCommand.execute(instance, callbacks)({ indexes: [1, 3] });
const result = await selectByIndexesCommand.execute(instance, callbacks)({
indexes: [1, 3],
});

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

it('selects when deselect is omitted (defaults to selecting)', async () => {
const instance = await createGrid();
const selectSpy = jest.spyOn(instance, 'selectRowsByIndexes').mockReturnValue(Promise.resolve([]) as never);
const deselectSpy = jest.spyOn(instance, 'deselectRows');
const callbacks = createCallbacks();

const result = await selectByIndexesCommand.execute(instance, callbacks)({
indexes: [1, 3],
});

expect(selectSpy).toHaveBeenCalledWith([0, 2]);
expect(deselectSpy).not.toHaveBeenCalled();
expect(result.status).toBe('success');
});

it('resolves indexes to row keys and calls deselectRows when deselecting', async () => {
const instance = await createGrid();
const deselectSpy = jest.spyOn(instance, 'deselectRows').mockReturnValue(Promise.resolve([]) as never);
const selectSpy = jest.spyOn(instance, 'selectRowsByIndexes');
const callbacks = createCallbacks();

const result = await selectByIndexesCommand.execute(instance, callbacks)({
indexes: [1], deselect: true,
});

expect(deselectSpy).toHaveBeenCalledWith([1]);
expect(selectSpy).not.toHaveBeenCalled();
expect(result.status).toBe('success');
});

it('returns failure when selectRowsByIndexes throws', async () => {
const instance = await createGrid();
jest.spyOn(instance, 'selectRowsByIndexes').mockImplementation(() => {
throw new Error('Error');
});
const callbacks = createCallbacks();

const result = await selectByIndexesCommand.execute(instance, callbacks)({ indexes: [1] });
const result = await selectByIndexesCommand.execute(instance, callbacks)({
indexes: [1],
});

expect(result.status).toBe('failure');
});
Expand All @@ -376,28 +430,59 @@ describe('selectByIndexesCommand', () => {
.mockReturnValue(Promise.reject(new Error('Error')) as never);
const callbacks = createCallbacks();

const result = await selectByIndexesCommand.execute(instance, callbacks)({ indexes: [1] });
const result = await selectByIndexesCommand.execute(instance, callbacks)({
indexes: [1],
});

expect(result.status).toBe('failure');
});

it('returns failure when deselectRows rejects', async () => {
const instance = await createGrid();
jest.spyOn(instance, 'deselectRows')
.mockReturnValue(Promise.reject(new Error('Error')) as never);
const callbacks = createCallbacks();

const result = await selectByIndexesCommand.execute(instance, callbacks)({
indexes: [1], deselect: true,
});

expect(result.status).toBe('failure');
});
});

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

await selectByIndexesCommand.execute(instance, callbacks)({ indexes: [1, 3] });
await selectByIndexesCommand.execute(instance, callbacks)({
indexes: [1, 3],
});

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

it('reports the 1-based row numbers on the current page on deselect', async () => {
const instance = await createGrid();
jest.spyOn(instance, 'deselectRows').mockReturnValue(Promise.resolve([]) as never);
const callbacks = createCallbacks();

await selectByIndexesCommand.execute(instance, callbacks)({
indexes: [1], deselect: true,
});

expect(callbacks.success).toHaveBeenCalledWith('Deselect row(s) number 1 on the current page.');
});

it('passes the same default message to failure', async () => {
const instance = await createGrid({ selection: { mode: 'none' } });
const callbacks = createCallbacks();

await selectByIndexesCommand.execute(instance, callbacks)({ indexes: [1] });
await selectByIndexesCommand.execute(instance, callbacks)({
indexes: [1],
});

expect(callbacks.failure).toHaveBeenCalledWith('Select row(s) number 1 on the current page.');
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,22 @@ export const selectByKeysCommand = defineGridCommand({

const selectByIndexesCommandSchema = z.object({
indexes: z.array(z.number().int().min(1)).min(1),
deselect: z.boolean().optional(),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use optionalNullish instead of optional - it allows null values and sanitize them on parse, which is necessary for proper work in strict mode

}).strict();
Comment thread
dmirgaev marked this conversation as resolved.

export const selectByIndexesCommand = defineGridCommand({
name: 'selectByIndexes',
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.',
description: 'Select or deselect specific 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 addressable. '
+ 'Set deselect to true to remove the listed rows from the current selection (e.g. "unselect row 1"); omit it or set it to false to select them. '
+ 'When deselect is false or omitted, the listed rows replace the current selection. '
+ 'To target rows that are not on the current page, use selectByKeys, or call pageIndex first to switch the page. '
+ 'To clear selection only within the current selectAll scope, use deselectAll; to clear selection across all pages regardless of selectAllMode, use clearSelection.',
schema: selectByIndexesCommandSchema,
execute: (component, { success, failure }) => async (args): Promise<CommandResult> => {
const rowIndexes = args.indexes.join(', ');
const defaultMessage = `Select row(s) number ${rowIndexes} on the current page.`;
const action = args.deselect ? 'Deselect' : 'Select';
const defaultMessage = `${action} row(s) number ${rowIndexes} on the current page.`;

if (component.option('selection.mode') === 'none') {
return failure(defaultMessage);
Expand All @@ -70,7 +77,12 @@ export const selectByIndexesCommand = defineGridCommand({
}

try {
await component.selectRowsByIndexes(normalizedRowIndexes);
if (args.deselect) {
const itemKeys = normalizedRowIndexes.map((index) => items[index].key);
await component.deselectRows(itemKeys);
} else {
await component.selectRowsByIndexes(normalizedRowIndexes);
}

return success(defaultMessage);
} catch {
Expand Down
3 changes: 2 additions & 1 deletion packages/devextreme/js/common/grids.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,8 @@ export type PredefinedCommands = {
preserve: boolean;
};
selectByIndexes: {
indexes: number[]
indexes: number[];
deselect?: boolean;
};
selectAll: {};
deselectAll: {};
Expand Down
1 change: 1 addition & 0 deletions packages/devextreme/ts/dx.all.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6611,6 +6611,7 @@ declare module DevExpress.common.grids {
};
selectByIndexes: {
indexes: number[];
deselect?: boolean;
};
selectAll: {};
deselectAll: {};
Expand Down
Loading