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
1 change: 1 addition & 0 deletions packages/devextreme-angular/src/common/data/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export type {
LoadResult,
LoadResultObject,
LocalStoreOptions,
MultiValueSearchOperation,
ODataContextOptions,
ODataStoreOptions,
Query,
Expand Down
2 changes: 2 additions & 0 deletions packages/devextreme-angular/src/common/grids/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export type {
FilterPanelTexts,
FilterRow,
FilterRowOperationDescriptions,
FilterScalarValue,
FilterType,
FixedPosition,
GridBase,
Expand All @@ -57,6 +58,7 @@ export type {
KeyboardNavigation,
KeyDownInfo,
LoadPanel,
MultiValueFilterExpr,
NegatedFilterExpr,
NewRowInfo,
NewRowPosition,
Expand Down
3 changes: 3 additions & 0 deletions packages/devextreme-angular/src/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ export namespace Data {
export const LocalStore = DataModule.LocalStore;
export type LocalStore<TItem = any, TKey = any> = DataModule.LocalStore<TItem, TKey>;
export type LocalStoreOptions<TItem = any, TKey = any> = DataModule.LocalStoreOptions<TItem, TKey>;
export type MultiValueSearchOperation = DataModule.MultiValueSearchOperation;
export const ODataContext = DataModule.ODataContext;
export type ODataContext = DataModule.ODataContext;
export type ODataContextOptions = DataModule.ODataContextOptions;
Expand Down Expand Up @@ -317,6 +318,7 @@ export namespace Grids {
export type FilterPanelTexts = GridsModule.FilterPanelTexts;
export type FilterRow = GridsModule.FilterRow;
export type FilterRowOperationDescriptions = GridsModule.FilterRowOperationDescriptions;
export type FilterScalarValue = GridsModule.FilterScalarValue;
export type FilterType = GridsModule.FilterType;
export type FixedPosition = GridsModule.FixedPosition;
export type GridBase<TRowData = any, TKey = any> = GridsModule.GridBase<TRowData, TKey>;
Expand All @@ -332,6 +334,7 @@ export namespace Grids {
export type KeyboardNavigation = GridsModule.KeyboardNavigation;
export type KeyDownInfo = GridsModule.KeyDownInfo;
export type LoadPanel = GridsModule.LoadPanel;
export type MultiValueFilterExpr = GridsModule.MultiValueFilterExpr;
export type NegatedFilterExpr = GridsModule.NegatedFilterExpr;
export type NewRowInfo<TRowData = any> = GridsModule.NewRowInfo<TRowData>;
export type NewRowPosition = GridsModule.NewRowPosition;
Expand Down
1 change: 1 addition & 0 deletions packages/devextreme-angular/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,7 @@ export namespace Common {
export const LocalStore = (DataModule as any).LocalStore as typeof import('devextreme/common/data').LocalStore;
export type LocalStore<TItem = any, TKey = any> = import('devextreme/common/data').LocalStore<TItem, TKey>;
export type LocalStoreOptions<TItem = any, TKey = any> = import('devextreme/common/data').LocalStoreOptions<TItem, TKey>;
export type MultiValueSearchOperation = import('devextreme/common/data').MultiValueSearchOperation;
export const ODataContext = (DataModule as any).ODataContext as typeof import('devextreme/common/data').ODataContext;
export type ODataContext = import('devextreme/common/data').ODataContext;
export type ODataContextOptions = import('devextreme/common/data').ODataContextOptions;
Expand Down
1 change: 1 addition & 0 deletions packages/devextreme-react/src/common/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export type {
LoadResult,
LoadResultObject,
LocalStoreOptions,
MultiValueSearchOperation,
ODataContextOptions,
ODataStoreOptions,
Query,
Expand Down
2 changes: 2 additions & 0 deletions packages/devextreme-react/src/common/grids.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export type {
FilterPanelTexts,
FilterRow,
FilterRowOperationDescriptions,
FilterScalarValue,
FilterType,
FixedPosition,
GridBase,
Expand All @@ -57,6 +58,7 @@ export type {
KeyboardNavigation,
KeyDownInfo,
LoadPanel,
MultiValueFilterExpr,
NegatedFilterExpr,
NewRowInfo,
NewRowPosition,
Expand Down
3 changes: 3 additions & 0 deletions packages/devextreme-react/src/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ export namespace Data {
export const LocalStore = DataModule.LocalStore;
export type LocalStore<TItem = any, TKey = any> = DataModule.LocalStore<TItem, TKey>;
export type LocalStoreOptions<TItem = any, TKey = any> = DataModule.LocalStoreOptions<TItem, TKey>;
export type MultiValueSearchOperation = DataModule.MultiValueSearchOperation;
export const ODataContext = DataModule.ODataContext;
export type ODataContext = DataModule.ODataContext;
export type ODataContextOptions = DataModule.ODataContextOptions;
Expand Down Expand Up @@ -317,6 +318,7 @@ export namespace Grids {
export type FilterPanelTexts = GridsModule.FilterPanelTexts;
export type FilterRow = GridsModule.FilterRow;
export type FilterRowOperationDescriptions = GridsModule.FilterRowOperationDescriptions;
export type FilterScalarValue = GridsModule.FilterScalarValue;
export type FilterType = GridsModule.FilterType;
export type FixedPosition = GridsModule.FixedPosition;
export type GridBase<TRowData = any, TKey = any> = GridsModule.GridBase<TRowData, TKey>;
Expand All @@ -332,6 +334,7 @@ export namespace Grids {
export type KeyboardNavigation = GridsModule.KeyboardNavigation;
export type KeyDownInfo = GridsModule.KeyDownInfo;
export type LoadPanel = GridsModule.LoadPanel;
export type MultiValueFilterExpr = GridsModule.MultiValueFilterExpr;
export type NegatedFilterExpr = GridsModule.NegatedFilterExpr;
export type NewRowInfo<TRowData = any> = GridsModule.NewRowInfo<TRowData>;
export type NewRowPosition = GridsModule.NewRowPosition;
Expand Down
1 change: 1 addition & 0 deletions packages/devextreme-vue/src/common/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export type {
LoadResult,
LoadResultObject,
LocalStoreOptions,
MultiValueSearchOperation,
ODataContextOptions,
ODataStoreOptions,
Query,
Expand Down
2 changes: 2 additions & 0 deletions packages/devextreme-vue/src/common/grids.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export type {
FilterPanelTexts,
FilterRow,
FilterRowOperationDescriptions,
FilterScalarValue,
FilterType,
FixedPosition,
GridBase,
Expand All @@ -57,6 +58,7 @@ export type {
KeyboardNavigation,
KeyDownInfo,
LoadPanel,
MultiValueFilterExpr,
NegatedFilterExpr,
NewRowInfo,
NewRowPosition,
Expand Down
3 changes: 3 additions & 0 deletions packages/devextreme-vue/src/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ export namespace Data {
export const LocalStore = DataModule.LocalStore;
export type LocalStore<TItem = any, TKey = any> = DataModule.LocalStore<TItem, TKey>;
export type LocalStoreOptions<TItem = any, TKey = any> = DataModule.LocalStoreOptions<TItem, TKey>;
export type MultiValueSearchOperation = DataModule.MultiValueSearchOperation;
export const ODataContext = DataModule.ODataContext;
export type ODataContext = DataModule.ODataContext;
export type ODataContextOptions = DataModule.ODataContextOptions;
Expand Down Expand Up @@ -317,6 +318,7 @@ export namespace Grids {
export type FilterPanelTexts = GridsModule.FilterPanelTexts;
export type FilterRow = GridsModule.FilterRow;
export type FilterRowOperationDescriptions = GridsModule.FilterRowOperationDescriptions;
export type FilterScalarValue = GridsModule.FilterScalarValue;
export type FilterType = GridsModule.FilterType;
export type FixedPosition = GridsModule.FixedPosition;
export type GridBase<TRowData = any, TKey = any> = GridsModule.GridBase<TRowData, TKey>;
Expand All @@ -332,6 +334,7 @@ export namespace Grids {
export type KeyboardNavigation = GridsModule.KeyboardNavigation;
export type KeyDownInfo = GridsModule.KeyDownInfo;
export type LoadPanel = GridsModule.LoadPanel;
export type MultiValueFilterExpr = GridsModule.MultiValueFilterExpr;
export type NegatedFilterExpr = GridsModule.NegatedFilterExpr;
export type NewRowInfo<TRowData = any> = GridsModule.NewRowInfo<TRowData>;
export type NewRowPosition = GridsModule.NewRowPosition;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ const singleBasic = (field: string, operator: string, value: unknown): any => tr
basicNode('n1', field, operator, value),
]);

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const singleMultiValue = (field: string, operator: string, values: unknown[]): any => tree('n1', [
basicNode('n1', field, operator, values),
]);

const createCallbacks = (): {
success: jest.Mock<(message?: string) => CommandResult>;
failure: jest.Mock<(message?: string) => CommandResult>;
Expand Down Expand Up @@ -117,6 +122,44 @@ describe('filterValueCommand', () => {
expect(filterValueCommand.schema.safeParse({ expression }).success).toBe(true);
});

it.each([
['anyof'], ['noneof'],
])('accepts multi-value op "%s"', (op) => {
expect(filterValueCommand.schema.safeParse({
expression: singleMultiValue('name', op, ['Alpha', 'Beta']),
}).success).toBe(true);
});

it('accepts an anyof expression with mixed scalar types in array', () => {
expect(filterValueCommand.schema.safeParse({
expression: singleMultiValue('name', 'anyof', ['Alpha', 1, true, null]),
}).success).toBe(true);
});

it('accepts an anyof expression with an empty array', () => {
expect(filterValueCommand.schema.safeParse({
expression: singleMultiValue('name', 'anyof', []),
}).success).toBe(true);
});

it('rejects anyof with a scalar value instead of array', () => {
expect(filterValueCommand.schema.safeParse({
expression: singleBasic('name', 'anyof', 'Alpha'),
}).success).toBe(false);
});

it('rejects noneof with a scalar value instead of array', () => {
expect(filterValueCommand.schema.safeParse({
expression: singleBasic('name', 'noneof', 'Alpha'),
}).success).toBe(false);
});

it('rejects basic op with an array value', () => {
expect(filterValueCommand.schema.safeParse({
expression: singleMultiValue('name', '=', ['Alpha', 'Beta']),
}).success).toBe(false);
});

it('accepts a combined expression with "and"', () => {
expect(filterValueCommand.schema.safeParse({
expression: tree('n3', [
Expand Down Expand Up @@ -288,6 +331,55 @@ describe('filterValueCommand', () => {
expect(result.status).toBe('success');
});

it('converts anyof expression to the legacy array form', async () => {
const instance = await createGrid();
const spy = jest.spyOn(instance, 'option');
const callbacks = createCallbacks();

const result = await filterValueCommand.execute(instance, callbacks)({
expression: singleMultiValue('name', 'anyof', ['Alpha', 'Beta']),
});

expect(spy).toHaveBeenCalledWith('filterValue', ['name', 'anyof', ['Alpha', 'Beta']]);
expect(result.status).toBe('success');
});

it('converts noneof expression to the legacy array form', async () => {
const instance = await createGrid();
const spy = jest.spyOn(instance, 'option');
const callbacks = createCallbacks();

const result = await filterValueCommand.execute(instance, callbacks)({
expression: singleMultiValue('name', 'noneof', ['Alpha', 'Beta']),
});

expect(spy).toHaveBeenCalledWith('filterValue', ['name', 'noneof', ['Alpha', 'Beta']]);
expect(result.status).toBe('success');
});

it('resolves date values inside anyof array for date columns', async () => {
const instance = await createGrid({
dataSource: [
{ id: 1, SaleDate: new Date(2024, 4, 10) },
],
columns: [
{ dataField: 'id', dataType: 'number' },
{ dataField: 'SaleDate', dataType: 'date' },
],
});
const spy = jest.spyOn(instance, 'option');
const callbacks = createCallbacks();

const result = await filterValueCommand.execute(instance, callbacks)({
expression: singleMultiValue('SaleDate', 'anyof', ['2024-05-10T00:00:00', '2024-06-01T00:00:00']),
});

expect(spy).toHaveBeenCalledWith('filterValue', [
'SaleDate', 'anyof', [new Date('2024-05-10T00:00:00'), new Date('2024-06-01T00:00:00')],
]);
expect(result.status).toBe('success');
});

it('converts a combined node into the legacy array form', async () => {
const instance = await createGrid();
const spy = jest.spyOn(instance, 'option');
Expand Down Expand Up @@ -449,6 +541,19 @@ describe('filterValueCommand', () => {
expect(result.status).toBe('failure');
});

it('returns failure when anyof field has no corresponding column', async () => {
const instance = await createGrid();
const spy = jest.spyOn(instance, 'option');
const callbacks = createCallbacks();

const result = await filterValueCommand.execute(instance, callbacks)({
expression: singleMultiValue('nonexistent', 'anyof', ['Alpha']),
});

expect(spy).not.toHaveBeenCalled();
expect(result.status).toBe('failure');
});

it('succeeds when a field maps to a hidden but existing column', async () => {
const instance = await createGrid({
columns: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { z } from 'zod';

import {
// eslint-disable-next-line spellcheck/spell-checker
isKeyShapeValid, normalizeKey, optionalNullish, resolveFilterValue,
isKeyShapeValid, isMultiValueExpr, normalizeKey, optionalNullish, resolveFilterValue,
} from '../utils';

describe('normalizeKey', () => {
Expand Down Expand Up @@ -168,3 +168,71 @@ describe('resolveFilterValue', () => {
expect(resolveFilterValue('date', true)).toBe(true);
});
});

describe('isMultiValueExpr', () => {
it('returns true for an expression with an array value', () => {
const expr = {
type: 'basic' as const,
field: 'status',
operator: 'anyof' as const,
value: ['open', 'closed'],
};

expect(isMultiValueExpr(expr)).toBe(true);
});

it('returns true for an expression with an empty array value', () => {
const expr = {
type: 'basic' as const,
field: 'status',
operator: 'noneof' as const,
value: [] as string[],
};

expect(isMultiValueExpr(expr)).toBe(true);
});

it('returns false for an expression with a string value', () => {
const expr = {
type: 'basic' as const,
field: 'name',
operator: '=' as const,
value: 'Alice',
};

expect(isMultiValueExpr(expr)).toBe(false);
});

it('returns false for an expression with a number value', () => {
const expr = {
type: 'basic' as const,
field: 'age',
operator: '>' as const,
value: 18,
};

expect(isMultiValueExpr(expr)).toBe(false);
});

it('returns false for an expression with a null value', () => {
const expr = {
type: 'basic' as const,
field: 'name',
operator: '=' as const,
value: null,
};

expect(isMultiValueExpr(expr)).toBe(false);
});

it('returns false for an expression with a boolean value', () => {
const expr = {
type: 'basic' as const,
field: 'active',
operator: '=' as const,
value: true,
};

expect(isMultiValueExpr(expr)).toBe(false);
});
});
Loading
Loading