Skip to content

Commit 859ab4a

Browse files
Merge remote-tracking branch 'my/26_1_dx_scss_nx_infra_p' into 26_1_dx_scss_nx_infra_p
2 parents e2eae09 + 5d975bf commit 859ab4a

56 files changed

Lines changed: 2292 additions & 1110 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
This demo creates a new appointment from a multi-cell selection. Click and drag across two or more time cells to select a range. The Scheduler opens the appointment popup pre-filled with the corresponding start date, end date, and resource group.
2+
3+
<!--split-->
4+
5+
The Scheduler raises the [onSelectionEnd](/Documentation/ApiReference/UI_Components/dxScheduler/Configuration/#onSelectionEnd) event when a user finishes selecting cells. The event's `selectedCellData` array contains one object per selected cell, each with `startDate`, `endDate`, `allDay`, and group field values.
6+
7+
In this demo, the event handler ignores single-cell selection and passes a new appointment object built from the first and last cell in the selection to the [showAppointmentPopup](/Documentation/ApiReference/UI_Components/dxScheduler/Methods/#showAppointmentPopupappointmentData-createNewAppointment-appointmentElement) method.

e2e/testcafe-devextreme/tests/editors/dateBox/label.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const DATEBOX_CLASS = 'dx-datebox';
1414
const stylingModes = ['outlined', 'underlined', 'filled'];
1515
const visibleLabelModes = ['floating', 'static', 'outside'];
1616

17-
fixture.disablePageReloads`DateBox_Label`
17+
fixture`DateBox_Label`
1818
.page(url(__dirname, '../../container.html'));
1919

2020
test('Symbol parts in label should not be cropped', async (t) => {
@@ -48,12 +48,8 @@ test('Symbol parts in label should not be cropped', async (t) => {
4848
test('DateBox with buttons container', async (t) => {
4949
const { takeScreenshot, compareResults } = createScreenshotsComparer(t);
5050

51-
await insertStylesheetRulesToPage(`#container { display: flex; flex-wrap: wrap; } .${DATEBOX_CLASS} { width: 220px; margin: 2px; }`);
52-
5351
await testScreenshot(t, takeScreenshot, 'DateBox render with buttons container.png');
5452

55-
await removeStylesheetRulesFromPage();
56-
5753
await t
5854
.expect(compareResults.isValid())
5955
.ok(compareResults.errorMessages());
@@ -81,4 +77,8 @@ test('DateBox with buttons container', async (t) => {
8177
}
8278
}
8379
}
80+
81+
await insertStylesheetRulesToPage(`#container { display: flex; flex-wrap: wrap; } .${DATEBOX_CLASS} { width: 220px; margin: 2px; }`);
82+
}).after(async () => {
83+
await removeStylesheetRulesFromPage();
8484
});

e2e/testcafe-devextreme/tests/editors/lookup/common.ts

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,17 @@ import { isMaterial, isMaterialBased, testScreenshot } from '../../../helpers/th
77
import url from '../../../helpers/getPageUrl';
88
import { createWidget } from '../../../helpers/createWidget';
99
import {
10-
appendElementTo, insertStylesheetRulesToPage, setStyleAttribute,
10+
appendElementTo, insertStylesheetRulesToPage, removeStylesheetRulesFromPage, setStyleAttribute,
1111
} from '../../../helpers/domUtils';
1212

1313
const LOOKUP_FIELD_CLASS = 'dx-lookup-field';
1414
const OVERLAY_CLASS = 'dx-overlay-content';
1515

16+
const LOADINDICATOR_SEGMENT_CLASS = 'dx-loadindicator-segment';
17+
const LOADINDICATOR_CONTENT_CLASS = 'dx-loadindicator-content';
18+
const LOADINDICATOR_ICON_CLASS = 'dx-loadindicator-icon';
19+
const LOADINDICATOR_SEGMENT_INNER_CLASS = 'dx-loadindicator-segment-inner';
20+
1621
const stylingModes = ['outlined', 'underlined', 'filled'];
1722
const labelModes = ['static', 'floating', 'hidden', 'outside'];
1823

@@ -98,7 +103,7 @@ test.meta({ browserSize: [300, 400] })('Check popup height with no found data op
98103
.ok(compareResults.errorMessages());
99104
}).before(async () => createWidget('dxLookup', { dataSource: [], searchEnabled: true }));
100105

101-
test.meta({ browserSize: [300, 400], unstable: true })('Check popup height in loading state', async (t) => {
106+
test.meta({ browserSize: [300, 400] })('Check popup height in loading state', async (t) => {
102107
const { takeScreenshot, compareResults } = createScreenshotsComparer(t);
103108

104109
await t.click(Selector(`.${LOOKUP_FIELD_CLASS}`));
@@ -109,19 +114,30 @@ test.meta({ browserSize: [300, 400], unstable: true })('Check popup height in lo
109114
await t
110115
.expect(compareResults.isValid())
111116
.ok(compareResults.errorMessages());
112-
}).before(async () => createWidget('dxLookup', {
113-
dataSource: {
114-
load() {
115-
return new Promise((resolve) => {
116-
setTimeout(() => {
117-
resolve([1, 2, 3]);
118-
}, 5000);
119-
});
117+
}).before(async () => {
118+
await insertStylesheetRulesToPage(`
119+
.${LOADINDICATOR_SEGMENT_CLASS},
120+
.${LOADINDICATOR_CONTENT_CLASS},
121+
.${LOADINDICATOR_ICON_CLASS},
122+
.${LOADINDICATOR_SEGMENT_INNER_CLASS} {
123+
animation: none !important;
124+
}
125+
`);
126+
127+
return createWidget('dxLookup', {
128+
dataSource: {
129+
load() {
130+
return new Promise((resolve) => {
131+
setTimeout(() => {
132+
resolve([1, 2, 3]);
133+
}, 5000);
134+
});
135+
},
120136
},
121-
},
122-
valueExpr: 'id',
123-
displayExpr: 'text',
124-
}));
137+
valueExpr: 'id',
138+
displayExpr: 'text',
139+
});
140+
}).after(async () => removeStylesheetRulesFromPage());
125141

126142
test('Lookup appearance', async (t) => {
127143
const { takeScreenshot, compareResults } = createScreenshotsComparer(t);
Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
import {
2+
afterEach, beforeEach, describe, expect, it,
3+
} from '@jest/globals';
4+
import dateLocalization from '@js/common/core/localization/date';
5+
import config from '@js/core/config';
6+
7+
const GLOBAL_FORMAT_KEYS = ['dateFormat', 'timeFormat', 'dateTimeFormat', 'numberFormat', 'dateTimeFormatPresets'] as const;
8+
type GlobalFormatKey = typeof GLOBAL_FORMAT_KEYS[number];
9+
10+
const saveAndRestore = (): { save: () => void; restore: () => void } => {
11+
let savedValues: Partial<Record<GlobalFormatKey, unknown>> = {};
12+
13+
return {
14+
save() {
15+
const currentConfig = config();
16+
17+
savedValues = {};
18+
GLOBAL_FORMAT_KEYS.forEach((key) => {
19+
savedValues[key] = currentConfig[key];
20+
});
21+
},
22+
restore() {
23+
const currentConfig = config();
24+
25+
GLOBAL_FORMAT_KEYS.forEach((key) => {
26+
if (savedValues[key] === undefined) {
27+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
28+
delete currentConfig[key];
29+
} else {
30+
currentConfig[key] = savedValues[key] as never;
31+
}
32+
});
33+
},
34+
};
35+
};
36+
37+
describe('date localization - dateTimeFormatPresets', () => {
38+
const { save, restore } = saveAndRestore();
39+
40+
beforeEach(() => { save(); });
41+
afterEach(() => { restore(); });
42+
43+
describe('string preset override', () => {
44+
it('should override shortDate with custom LDML pattern', () => {
45+
config({
46+
...config(),
47+
dateTimeFormatPresets: {
48+
shortDate: 'dd/MM/yyyy',
49+
},
50+
});
51+
52+
const result = dateLocalization.format(new Date(2020, 0, 2), 'shortDate');
53+
54+
expect(result).toBe('02/01/2020');
55+
});
56+
57+
it('should override shortTime with custom LDML pattern', () => {
58+
config({
59+
...config(),
60+
dateTimeFormatPresets: {
61+
shortTime: 'HH:mm:ss',
62+
},
63+
});
64+
65+
const result = dateLocalization.format(new Date(2020, 0, 2, 14, 5, 30), 'shortTime');
66+
67+
expect(result).toBe('14:05:30');
68+
});
69+
70+
it('should override longDate with custom LDML pattern', () => {
71+
config({
72+
...config(),
73+
dateTimeFormatPresets: {
74+
longDate: 'dd MMMM yyyy',
75+
},
76+
});
77+
78+
const result = dateLocalization.format(new Date(2020, 0, 2), 'longDate');
79+
80+
expect(result).toBe('02 January 2020');
81+
});
82+
83+
it('should override shortDateShortTime with custom LDML pattern', () => {
84+
config({
85+
...config(),
86+
dateTimeFormatPresets: {
87+
shortDateShortTime: 'dd/MM/yyyy HH:mm',
88+
},
89+
});
90+
91+
const result = dateLocalization.format(new Date(2020, 0, 2, 14, 5), 'shortDateShortTime');
92+
93+
expect(result).toBe('02/01/2020 14:05');
94+
});
95+
});
96+
97+
describe('function preset override', () => {
98+
it('should use function override for shortDate', () => {
99+
config({
100+
...config(),
101+
dateTimeFormatPresets: {
102+
shortDate: (d: Date) => `${d.getDate()}-${d.getMonth() + 1}-${d.getFullYear()}`,
103+
},
104+
});
105+
106+
const result = dateLocalization.format(new Date(2020, 0, 2), 'shortDate');
107+
108+
expect(result).toBe('2-1-2020');
109+
});
110+
111+
it('should use function override for shortTime', () => {
112+
config({
113+
...config(),
114+
dateTimeFormatPresets: {
115+
shortTime: (d: Date) => `${d.getHours()}h${String(d.getMinutes()).padStart(2, '0')}`,
116+
},
117+
});
118+
119+
const result = dateLocalization.format(new Date(2020, 0, 2, 14, 5), 'shortTime');
120+
121+
expect(result).toBe('14h05');
122+
});
123+
});
124+
125+
describe('case insensitivity', () => {
126+
it('should apply override regardless of case in format name', () => {
127+
config({
128+
...config(),
129+
dateTimeFormatPresets: {
130+
shortDate: 'dd/MM/yyyy',
131+
},
132+
});
133+
134+
const date = new Date(2020, 0, 2);
135+
136+
expect(dateLocalization.format(date, 'shortdate')).toBe('02/01/2020');
137+
expect(dateLocalization.format(date, 'SHORTDATE')).toBe('02/01/2020');
138+
expect(dateLocalization.format(date, 'ShortDate')).toBe('02/01/2020');
139+
});
140+
});
141+
142+
describe('locale map in preset', () => {
143+
it('should resolve preset with default locale', () => {
144+
config({
145+
...config(),
146+
dateTimeFormatPresets: {
147+
shortDate: {
148+
default: 'dd/MM/yyyy',
149+
'de-DE': 'dd.MM.yyyy',
150+
},
151+
},
152+
});
153+
154+
const result = dateLocalization.format(new Date(2020, 0, 2), 'shortDate');
155+
156+
expect(result).toBe('02/01/2020');
157+
});
158+
});
159+
160+
describe('no override', () => {
161+
it('should use built-in format when no preset override is configured', () => {
162+
const result = dateLocalization.format(new Date(2020, 0, 2), 'shortDate');
163+
164+
// Built-in Intl format for en locale
165+
expect(result).toBeTruthy();
166+
expect(typeof result).toBe('string');
167+
});
168+
169+
it('should leave non-preset string formats unaffected', () => {
170+
config({
171+
...config(),
172+
dateTimeFormatPresets: {
173+
shortDate: 'dd/MM/yyyy',
174+
},
175+
});
176+
177+
const result = dateLocalization.format(new Date(2020, 0, 2), 'yyyy-MM-dd');
178+
179+
// LDML pattern should be used directly, not affected by preset overrides
180+
expect(result).toBe('2020-01-02');
181+
});
182+
183+
it('should leave FormatObject formats unaffected', () => {
184+
config({
185+
...config(),
186+
dateTimeFormatPresets: {
187+
shortDate: 'dd/MM/yyyy',
188+
},
189+
});
190+
191+
const customFormatter = (value: number | Date): string => {
192+
const d = value instanceof Date ? value : new Date(value);
193+
return `custom:${d.getFullYear()}`;
194+
};
195+
const result = dateLocalization.format(new Date(2020, 0, 2), { formatter: customFormatter });
196+
197+
expect(result).toBe('custom:2020');
198+
});
199+
200+
it('should not affect formatting when dateTimeFormatPresets is empty', () => {
201+
config({
202+
...config(),
203+
dateTimeFormatPresets: {},
204+
});
205+
206+
const result = dateLocalization.format(new Date(2020, 0, 2), 'shortDate');
207+
208+
expect(result).toBeTruthy();
209+
expect(typeof result).toBe('string');
210+
});
211+
});
212+
213+
describe('unknown preset key', () => {
214+
it('should safely ignore unknown preset keys', () => {
215+
config({
216+
...config(),
217+
dateTimeFormatPresets: {
218+
unknownFormat: 'dd/MM/yyyy',
219+
},
220+
});
221+
222+
// Known presets should still work normally
223+
const result = dateLocalization.format(new Date(2020, 0, 2), 'shortDate');
224+
225+
expect(result).toBeTruthy();
226+
expect(typeof result).toBe('string');
227+
});
228+
});
229+
230+
describe('preset override aliases another preset', () => {
231+
it('should support aliasing one preset to another', () => {
232+
config({
233+
...config(),
234+
dateTimeFormatPresets: {
235+
shortDate: 'longDate',
236+
},
237+
});
238+
239+
const dateLong = dateLocalization.format(new Date(2020, 0, 2), 'longDate');
240+
const dateShort = dateLocalization.format(new Date(2020, 0, 2), 'shortDate');
241+
242+
// shortDate should now format like longDate
243+
expect(dateShort).toBe(dateLong);
244+
});
245+
});
246+
});
247+
248+
describe('date localization - global *Format precedence', () => {
249+
const { save, restore } = saveAndRestore();
250+
251+
beforeEach(() => { save(); });
252+
afterEach(() => { restore(); });
253+
254+
it('should apply dateFormat for direct calls with the resolved format', () => {
255+
config({
256+
...config(),
257+
dateFormat: 'dd/MM/yyyy',
258+
});
259+
260+
const result = dateLocalization.format(new Date(2020, 0, 2), config().dateFormat);
261+
262+
expect(result).toBe('02/01/2020');
263+
});
264+
265+
it('should apply dateTimeFormat for direct calls with the resolved format', () => {
266+
config({
267+
...config(),
268+
dateTimeFormat: 'dd/MM/yyyy, HH:mm',
269+
});
270+
271+
const result = dateLocalization.format(new Date(2020, 0, 2, 14, 5), config().dateTimeFormat);
272+
273+
expect(result).toBe('02/01/2020, 14:05');
274+
});
275+
});

0 commit comments

Comments
 (0)