Skip to content

Commit c311758

Browse files
committed
FilterBuilder: fix invalid date formatting
1 parent 2c70828 commit c311758

File tree

5 files changed

+240
-113
lines changed

5 files changed

+240
-113
lines changed
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import { describe, expect, it } from '@jest/globals';
2+
import type { CustomOperation, Field } from '@js/ui/filter_builder';
3+
4+
import { getCurrentValueText } from '../m_utils';
5+
6+
describe('Formatting', () => {
7+
it('empty string', () => {
8+
const field = {};
9+
const value = '';
10+
11+
expect(getCurrentValueText(field, value, null)).toBe('');
12+
});
13+
14+
it('string', () => {
15+
const field = {};
16+
const value = 'Text';
17+
18+
expect(getCurrentValueText(field, value, null)).toBe('Text');
19+
});
20+
21+
it('shortDate', () => {
22+
const field = { format: 'shortDate' };
23+
const value = new Date(2017, 8, 5);
24+
25+
expect(getCurrentValueText(field, value, null)).toBe('9/5/2017');
26+
});
27+
28+
it('invalid date string (T1319193)', () => {
29+
const field = { format: 'shortDate' };
30+
const dateString = 'Weekend';
31+
32+
expect(getCurrentValueText(field, dateString, null)).toBe(dateString);
33+
});
34+
35+
it('boolean', () => {
36+
const field: Field = { dataType: 'boolean' };
37+
let value = true;
38+
39+
expect(getCurrentValueText(field, value, null)).toBe('true');
40+
41+
value = false;
42+
expect(getCurrentValueText(field, value, null)).toBe('false');
43+
44+
field.falseText = 'False Text';
45+
expect(getCurrentValueText(field, value, null)).toBe('False Text');
46+
});
47+
48+
it('field.customizeText', () => {
49+
const field: Field = {
50+
customizeText(conditionInfo) {
51+
return `${conditionInfo.valueText}Test`;
52+
},
53+
};
54+
const value = 'MyValue';
55+
56+
expect(getCurrentValueText(field, value, null)).toBe('MyValueTest');
57+
});
58+
59+
it('customOperation.customizeText', () => {
60+
const field: Field = {
61+
customizeText(conditionInfo) {
62+
return `${conditionInfo.valueText}Test`;
63+
},
64+
};
65+
const value = 'MyValue';
66+
const customOperation: CustomOperation = {
67+
customizeText(conditionInfo) {
68+
return `${conditionInfo.valueText}CustomOperation`;
69+
},
70+
};
71+
72+
expect(getCurrentValueText(field, value, customOperation)).toBe('MyValueTestCustomOperation');
73+
});
74+
75+
it('customOperation.customizeText for array', async () => {
76+
const field: Field = { dataType: 'string' };
77+
78+
const customOperation = { customizeText: (): string => '(Blanks)' };
79+
let text = await getCurrentValueText(field, '', customOperation);
80+
81+
expect(text).toBe('');
82+
83+
text = await getCurrentValueText(field, [null], customOperation);
84+
expect(text).toEqual(['(Blanks)']);
85+
86+
const field2: Field = { dataType: 'number' };
87+
88+
text = await getCurrentValueText(field2, null, customOperation);
89+
90+
expect(text).toBe('');
91+
92+
text = await getCurrentValueText(field, [null], customOperation);
93+
expect(text).toEqual(['(Blanks)']);
94+
});
95+
96+
it('default format for date', () => {
97+
const field: Field = { dataType: 'date' };
98+
const value = new Date(2017, 8, 5, 12, 30, 0);
99+
100+
expect(getCurrentValueText(field, value, null)).toBe('9/5/2017');
101+
});
102+
103+
it('default format for datetime', () => {
104+
const field: Field = { dataType: 'datetime' };
105+
const value = new Date(2017, 8, 5, 12, 30, 0);
106+
107+
expect(getCurrentValueText(field, value, null)).toBe('9/5/2017, 12:30 PM');
108+
});
109+
});

packages/devextreme/js/__internal/filter_builder/m_utils.ts

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,26 @@
1+
import type { Format } from '@js/common/core/localization';
12
import messageLocalization from '@js/common/core/localization/message';
23
import { DataSource } from '@js/common/data/data_source/data_source';
34
import { errors as dataErrors } from '@js/common/data/errors';
45
import $ from '@js/core/renderer';
56
import { compileGetter } from '@js/core/utils/data';
7+
import type { DeferredObj } from '@js/core/utils/deferred';
68
import { Deferred, when } from '@js/core/utils/deferred';
79
import { extend } from '@js/core/utils/extend';
810
import { captionize } from '@js/core/utils/inflector';
9-
import { isBoolean, isDefined, isFunction } from '@js/core/utils/type';
11+
import {
12+
isBoolean, isDefined, isFunction, isNumeric, isString,
13+
} from '@js/core/utils/type';
1014
import formatHelper from '@js/format_helper';
15+
import type { CustomOperation, DataType, Field } from '@js/ui/filter_builder';
1116
import filterUtils from '@js/ui/shared/filtering';
1217
import errors from '@js/ui/widget/ui.errors';
1318

1419
import { getConfig } from './m_between';
1520
import filterOperationsDictionary from './m_filter_operations_dictionary';
1621

22+
type FieldValue = string | number | boolean | Date | null | undefined;
23+
1724
const DEFAULT_DATA_TYPE = 'string';
1825
const EMPTY_MENU_ICON = 'icon-none';
1926
const AND_GROUP_OPERATION = 'and';
@@ -27,7 +34,7 @@ const DATATYPE_OPERATIONS = {
2734
boolean: ['=', '<>', 'isblank', 'isnotblank'],
2835
object: ['isblank', 'isnotblank'],
2936
};
30-
const DEFAULT_FORMAT = {
37+
const DEFAULT_FORMAT: Partial<Record<DataType, Format>> = {
3138
date: 'shortDate',
3239
datetime: 'shortDateShortTime',
3340
};
@@ -54,19 +61,24 @@ const FILTER_BUILDER_ITEM_TEXT_PART_CLASS = `${FILTER_BUILDER_ITEM_TEXT_CLASS}-p
5461
const FILTER_BUILDER_ITEM_TEXT_SEPARATOR_CLASS = `${FILTER_BUILDER_ITEM_TEXT_CLASS}-separator`;
5562
const FILTER_BUILDER_ITEM_TEXT_SEPARATOR_EMPTY_CLASS = `${FILTER_BUILDER_ITEM_TEXT_SEPARATOR_CLASS}-empty`;
5663

57-
function getFormattedValueText(field, value): string {
58-
const fieldFormat = field.format || DEFAULT_FORMAT[field.dataType];
64+
function getDateFormat(dataType: DataType | undefined): Format | undefined {
65+
return dataType ? DEFAULT_FORMAT[dataType] : undefined;
66+
}
67+
68+
function getFormattedValueText(field: Field, value: FieldValue): string {
69+
const fieldFormat = field.format ?? getDateFormat(field.dataType);
5970

6071
if (isBoolean(value)) {
61-
const trueText: string = field.trueText || messageLocalization.format('dxDataGrid-trueText');
62-
const falseText: string = field.falseText || messageLocalization.format('dxDataGrid-falseText');
72+
const trueText = field.trueText ?? messageLocalization.format('dxDataGrid-trueText');
73+
const falseText = field.falseText ?? messageLocalization.format('dxDataGrid-falseText');
6374

6475
return value ? trueText : falseText;
6576
}
6677

6778
if (field.dataType === 'date' || field.dataType === 'datetime') {
68-
// value can be string or number, we need to convert it to Date object
69-
return formatHelper.format(new Date(value), fieldFormat);
79+
if (isString(value) || isNumeric(value)) {
80+
return formatHelper.format(new Date(value), fieldFormat);
81+
}
7082
}
7183

7284
return formatHelper.format(value, fieldFormat);
@@ -574,11 +586,18 @@ export function getCurrentLookupValueText(field, value, handler) {
574586
}
575587
}
576588

577-
function getPrimitiveValueText(field, value, customOperation, target, options?) {
589+
function getPrimitiveValueText(
590+
field: Field,
591+
value: FieldValue,
592+
customOperation: CustomOperation | null,
593+
target: string,
594+
options?,
595+
): string {
578596
let valueText = getFormattedValueText(field, value);
579597

580598
if (field.customizeText) {
581599
valueText = field.customizeText.call(field, {
600+
// @ts-expect-error
582601
value,
583602
valueText,
584603
target,
@@ -591,32 +610,43 @@ function getPrimitiveValueText(field, value, customOperation, target, options?)
591610
valueText,
592611
field,
593612
target,
613+
// @ts-expect-error
594614
}, options);
595615
}
596616

597617
return valueText;
598618
}
599619

600-
function getArrayValueText(field, value, customOperation, target) {
620+
function getArrayValueText(
621+
field: Field,
622+
value: FieldValue[],
623+
customOperation: CustomOperation | null,
624+
target: string,
625+
): string[] {
601626
const options = { values: value };
602627
return value.map((v) => getPrimitiveValueText(field, v, customOperation, target, options));
603628
}
604629

605-
function checkDefaultValue(value) {
630+
function checkDefaultValue(value: FieldValue | FieldValue[]): value is '' | null {
606631
return value === '' || value === null;
607632
}
608633

609-
export function getCurrentValueText(field, value, customOperation, target = 'filterBuilder') {
634+
export function getCurrentValueText(
635+
field: Field,
636+
value: FieldValue | FieldValue[],
637+
customOperation: CustomOperation | null,
638+
target = 'filterBuilder',
639+
): string | DeferredObj<string | string[]> {
610640
if (checkDefaultValue(value)) {
611641
return '';
612642
}
613643

614644
if (Array.isArray(value)) {
615645
// @ts-expect-error Deferred has badly typed ctor function
616-
const result = new Deferred();
646+
const result: DeferredObj<string | string[]> = new Deferred();
617647
when.apply(this, getArrayValueText(field, value, customOperation, target)).done((...args) => {
618-
const text = args.some((item) => !checkDefaultValue(item))
619-
? args.map((item) => (!checkDefaultValue(item) ? item : '?'))
648+
const text: string | string[] = (args as string[]).some((item) => !checkDefaultValue(item))
649+
? (args as string[]).map((item) => (!checkDefaultValue(item) ? item : '?'))
620650
: '';
621651
result.resolve(text);
622652
});

packages/devextreme/js/format_helper.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@ import './common/core/localization/currency';
1616
export default dependencyInjector({
1717
format: function(value, format) {
1818
const formatIsValid = isString(format) && format !== '' || isPlainObject(format) || isFunction(format);
19-
const valueIsValid = isNumeric(value) || isDate(value);
20-
19+
const valueIsValid = isNumeric(value) || (isDate(value) && !isNaN(value.getTime()));
2120

2221
if(!formatIsValid || !valueIsValid) {
2322
return isDefined(value) ? value.toString() : '';

packages/devextreme/testing/tests/DevExpress.ui.widgets/filterBuilderParts/utilsTests.js

Lines changed: 0 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1506,101 +1506,6 @@ QUnit.module('Custom filter expressions', {
15061506
});
15071507
});
15081508

1509-
QUnit.module('Formatting', function() {
1510-
QUnit.test('empty string', function(assert) {
1511-
const field = {};
1512-
const value = '';
1513-
1514-
assert.equal(utils.getCurrentValueText(field, value), '');
1515-
});
1516-
1517-
QUnit.test('string', function(assert) {
1518-
const field = {};
1519-
const value = 'Text';
1520-
assert.equal(utils.getCurrentValueText(field, value), 'Text');
1521-
});
1522-
1523-
QUnit.test('shortDate', function(assert) {
1524-
const field = { format: 'shortDate' };
1525-
const value = new Date(2017, 8, 5);
1526-
assert.equal(utils.getCurrentValueText(field, value), '9/5/2017');
1527-
});
1528-
1529-
QUnit.test('boolean', function(assert) {
1530-
const field = { dataType: 'boolean' };
1531-
let value = true;
1532-
assert.equal(utils.getCurrentValueText(field, value), 'true');
1533-
1534-
value = false;
1535-
assert.equal(utils.getCurrentValueText(field, value), 'false');
1536-
1537-
field.falseText = 'False Text';
1538-
assert.equal(utils.getCurrentValueText(field, value), 'False Text');
1539-
});
1540-
1541-
QUnit.test('field.customizeText', function(assert) {
1542-
const field = {
1543-
customizeText: function(conditionInfo) {
1544-
return conditionInfo.valueText + 'Test';
1545-
}
1546-
};
1547-
const value = 'MyValue';
1548-
assert.equal(utils.getCurrentValueText(field, value), 'MyValueTest');
1549-
});
1550-
1551-
QUnit.test('customOperation.customizeText', function(assert) {
1552-
const field = {
1553-
customizeText: function(conditionInfo) {
1554-
return conditionInfo.valueText + 'Test';
1555-
}
1556-
};
1557-
const value = 'MyValue';
1558-
const customOperation = {
1559-
customizeText: function(conditionInfo) {
1560-
return conditionInfo.valueText + 'CustomOperation';
1561-
}
1562-
};
1563-
assert.equal(utils.getCurrentValueText(field, value, customOperation), 'MyValueTestCustomOperation');
1564-
});
1565-
1566-
QUnit.test('customOperation.customizeText for array', function(assert) {
1567-
const field = { dataType: 'string' };
1568-
1569-
const customOperation = { customizeText: (fieldInfo) => '(Blanks)' };
1570-
let text = utils.getCurrentValueText(field, '', customOperation);
1571-
1572-
assert.equal(text, '');
1573-
1574-
text = utils.getCurrentValueText(field, [null], customOperation).done(text => {
1575-
assert.equal(text, '(Blanks)');
1576-
});
1577-
1578-
const field2 = { dataType: 'number' };
1579-
1580-
text = utils.getCurrentValueText(field2, null, customOperation);
1581-
1582-
assert.equal(text, '');
1583-
1584-
text = utils.getCurrentValueText(field, [null], customOperation).done(text => {
1585-
assert.equal(text, '(Blanks)');
1586-
});
1587-
});
1588-
1589-
QUnit.test('default format for date', function(assert) {
1590-
const field = { dataType: 'date' };
1591-
const value = new Date(2017, 8, 5, 12, 30, 0);
1592-
1593-
assert.equal(utils.getCurrentValueText(field, value), '9/5/2017');
1594-
});
1595-
1596-
QUnit.test('default format for datetime', function(assert) {
1597-
const field = { dataType: 'datetime' };
1598-
const value = new Date(2017, 8, 5, 12, 30, 0);
1599-
1600-
assert.equal(utils.getCurrentValueText(field, value), '9/5/2017, 12:30 PM');
1601-
});
1602-
});
1603-
16041509
QUnit.module('Lookup Value', function() {
16051510
QUnit.test('array of strings & value=empty', function(assert) {
16061511
const field = {

0 commit comments

Comments
 (0)