', { id: 'list' }).appendTo('#parentContainer');
+});
+
+const addTasksToList = ClientFunction((tasks) => {
+ tasks.forEach((task) => {
+ $('
', {
+ class: 'dx-card',
+ text: task.text,
+ }).appendTo('#list');
+ });
+});
+
+const createItemElement = async (task) => {
+ await createWidget('dxDraggable', {
+ group: draggingGroupName,
+ data: task,
+ clone: true,
+ onDragStart(e) {
+ e.itemData = e.fromData;
+ },
+ }, `.${DRAGGABLE_ITEM_CLASS}:contains(${task.text})`);
+};
+
+test('Scheduler - The \'Cannot read properties of undefined (reading \'getTime\')\' error is thrown on an attempt to drag an outside element if the previous drag operation was canceled', async (t) => {
+ const scheduler = new Scheduler('#container');
+ const draggableAppointment = scheduler.getAppointment('Book').element;
+ const targetCell = scheduler.getDateTableCell(5, 0);
+ const draggableItem = Selector(`.${DRAGGABLE_ITEM_CLASS}`).withText('Brochures');
+
+ await t.expect(scheduler.element.exists).ok();
+
+ await MouseUpEvents.disable(MouseAction.dragToElement);
+
+ await t
+ .dragToElement(draggableAppointment, targetCell)
+ .pressKey('esc');
+
+ await MouseUpEvents.enable(MouseAction.dragToElement);
+
+ await t
+ .expect(draggableItem.exists)
+ .ok()
+ .dragToElement(draggableItem, targetCell);
+
+ const newAppointment = scheduler.getAppointment('Brochures');
+
+ await t
+ .expect(newAppointment.element.exists)
+ .ok();
+}).before(async () => {
+ const tasks = [
+ { text: 'Brochures' },
+ ];
+
+ await initList();
+ await addTasksToList(tasks);
+ await Promise.all(tasks.map((task) => createItemElement(task)));
+ await createWidget('dxScheduler', {
+ timeZone: 'America/Los_Angeles',
+ dataSource: [
+ {
+ text: 'Book',
+ startDate: new Date('2021-04-26T19:00:00.000Z'),
+ endDate: new Date('2021-04-26T20:00:00.000Z'),
+ },
+ ],
+ currentDate: new Date(2021, 3, 26),
+ startDayHour: 9,
+ height: 600,
+ editing: true,
+ appointmentDragging: {
+ group: draggingGroupName,
+ onDragEnd(e) {
+ e.cancel = e.event.ctrlKey;
+ },
+ onRemove(e) {
+ e.component.deleteAppointment(e.itemData);
+ },
+ onAdd(e) {
+ e.component.addAppointment(e.itemData);
+ },
+ },
+ });
+});
diff --git a/e2e/testcafe-devextreme/tests/treeList/editing/editing.ts b/e2e/testcafe-devextreme/tests/treeList/editing/editing.ts
new file mode 100644
index 000000000000..eaf773334b51
--- /dev/null
+++ b/e2e/testcafe-devextreme/tests/treeList/editing/editing.ts
@@ -0,0 +1,59 @@
+import TreeList from 'devextreme-testcafe-models/treeList';
+import url from '../../../helpers/getPageUrl';
+import { createWidget } from '../../../helpers/createWidget';
+
+fixture`Treelist - Editing`.page(url(__dirname, '../../container.html'));
+
+// T1247158
+test('TreeList - Insertafterkey doesn\'t work on children nodes', async (t) => {
+ const treeList = new TreeList('#container');
+ const expectedInsertedRowIndex = 2;
+
+ await t
+ .click(treeList.getDataCell(1, 0).element)
+ .pressKey('ctrl+enter')
+ .expect(treeList.getDataRow(expectedInsertedRowIndex).isInserted)
+ .ok();
+}).before(async () => createWidget('dxTreeList', {
+ dataSource: [
+ {
+ ID: 1,
+ Head_ID: -1,
+ Full_Name: 'John Heart',
+ },
+ {
+ ID: 2,
+ Head_ID: 1,
+ Full_Name: 'Samantha Bright',
+ },
+ ],
+ rootValue: -1,
+ keyExpr: 'ID',
+ parentIdExpr: 'Head_ID',
+ columns: ['Full_Name'],
+ editing: {
+ mode: 'batch',
+ allowAdding: true,
+ allowUpdating: true,
+ useIcons: true,
+ },
+ focusedRowEnabled: true,
+ expandedRowKeys: [1],
+ onKeyDown(e) {
+ if (e.event.ctrlKey && e.event.key === 'Enter') {
+ const currentSelectedParentTaskId = e.component.getNodeByKey(
+ e.component.option('focusedRowKey'),
+ )?.parent?.key;
+ const key = new (window as any).DevExpress.data.Guid().toString();
+ const data = { Head_ID: currentSelectedParentTaskId };
+ e.component.option('editing.changes', [
+ {
+ key,
+ type: 'insert',
+ insertAfterKey: e.component.option('focusedRowKey'),
+ data,
+ },
+ ]);
+ }
+ },
+}));
diff --git a/packages/devextreme/js/__internal/grids/tree_list/editing/m_editing.ts b/packages/devextreme/js/__internal/grids/tree_list/editing/m_editing.ts
index 3473089fbc3a..7a02a0424260 100644
--- a/packages/devextreme/js/__internal/grids/tree_list/editing/m_editing.ts
+++ b/packages/devextreme/js/__internal/grids/tree_list/editing/m_editing.ts
@@ -39,8 +39,11 @@ class EditingController extends editingModule.controllers.editing {
}
protected _setInsertAfterOrBeforeKey(change, parentKey) {
- if (parentKey !== undefined && parentKey !== this.option('rootValue')) {
- change.insertAfterKey = parentKey;
+ const dataSourceAdapter = this._dataController.dataSource();
+ const key = parentKey || dataSourceAdapter?.parentKeyOf(change.data);
+
+ if (key !== undefined && key !== this.option('rootValue')) {
+ change.insertAfterKey = key;
} else {
// @ts-expect-error
super._setInsertAfterOrBeforeKey.apply(this, arguments);
@@ -55,7 +58,8 @@ class EditingController extends editingModule.controllers.editing {
const rowIndex = gridCoreUtils.getIndexByKey(parentKey, items);
// @ts-expect-error
if (rowIndex >= 0 && this._dataController.isRowExpanded(parentKey)) {
- return rowIndex + 1;
+ // @ts-expect-error
+ return super._getLoadedRowIndex.apply(this, arguments);
}
return -1;
}
diff --git a/packages/devextreme/js/__internal/scheduler/m_appointment_drag_behavior.ts b/packages/devextreme/js/__internal/scheduler/m_appointment_drag_behavior.ts
index 718913429957..2e4643cb3adf 100644
--- a/packages/devextreme/js/__internal/scheduler/m_appointment_drag_behavior.ts
+++ b/packages/devextreme/js/__internal/scheduler/m_appointment_drag_behavior.ts
@@ -130,7 +130,7 @@ export default class AppointmentDragBehavior {
// NOTE: event.cancel may be promise or different type, so we need strict check here.
if (e.cancel === true) {
- this.removeDroppableClasses();
+ options.onDragCancel(e);
}
if (e.cancel !== true && isSchedulerComponent(e.toComponent)) {
diff --git a/packages/devextreme/js/__internal/scheduler/workspaces/m_work_space.ts b/packages/devextreme/js/__internal/scheduler/workspaces/m_work_space.ts
index 638f0589743d..61c249d1a754 100644
--- a/packages/devextreme/js/__internal/scheduler/workspaces/m_work_space.ts
+++ b/packages/devextreme/js/__internal/scheduler/workspaces/m_work_space.ts
@@ -51,6 +51,7 @@ import {
import WidgetObserver from '../base/m_widget_observer';
import AppointmentDragBehavior from '../m_appointment_drag_behavior';
import {
+ APPOINTMENT_DRAG_SOURCE_CLASS,
DATE_TABLE_CLASS,
DATE_TABLE_ROW_CLASS,
FIXED_CONTAINER_CLASS,
@@ -3383,6 +3384,15 @@ const createDragBehaviorConfig = (
removeDroppableCellClass();
};
+ const onDragCancel = (e) => {
+ if (!isDefaultDraggingMode) {
+ enableDefaultDragging();
+ }
+
+ removeDroppableCellClass();
+ e.itemElement?.removeClass?.(APPOINTMENT_DRAG_SOURCE_CLASS);
+ };
+
const cursorOffset = options.isSetCursorOffset
? () => {
const $dragElement = $(state.dragElement);
@@ -3399,6 +3409,7 @@ const createDragBehaviorConfig = (
onDragStart,
onDragMove,
onDragEnd,
+ onDragCancel,
cursorOffset,
filter: options.filter,
};
diff --git a/packages/devextreme/js/__internal/ui/form/m_form.layout_manager.utils.ts b/packages/devextreme/js/__internal/ui/form/m_form.layout_manager.utils.ts
index 1e529640c675..a39176229f65 100644
--- a/packages/devextreme/js/__internal/ui/form/m_form.layout_manager.utils.ts
+++ b/packages/devextreme/js/__internal/ui/form/m_form.layout_manager.utils.ts
@@ -1,14 +1,43 @@
import Guid from '@js/core/guid';
+import $ from '@js/core/renderer';
import { extend } from '@js/core/utils/extend';
import { captionize } from '@js/core/utils/inflector';
import { each } from '@js/core/utils/iterator';
-import { isDefined } from '@js/core/utils/type';
+import { isBoolean, isDefined, isFunction } from '@js/core/utils/type';
+import type { dxDropDownEditorOptions } from '@js/ui/drop_down_editor/ui.drop_down_editor';
+import type { FormItemComponent } from '@js/ui/form';
+import type { dxOverlayOptions } from '@js/ui/overlay';
+import type dxTextBox from '@js/ui/text_box';
import { SIMPLE_ITEM_TYPE } from './constants';
-const EDITORS_WITH_ARRAY_VALUE = ['dxTagBox', 'dxRangeSlider', 'dxDateRangeBox'];
-const EDITORS_WITH_SPECIFIC_LABELS = ['dxRangeSlider', 'dxSlider'];
-export const EDITORS_WITHOUT_LABELS = ['dxCalendar', 'dxCheckBox', 'dxHtmlEditor', 'dxRadioGroup', 'dxRangeSlider', 'dxSlider', 'dxSwitch'];
+const EDITORS_WITH_ARRAY_VALUE: FormItemComponent[] = [
+ 'dxTagBox',
+ 'dxRangeSlider',
+ 'dxDateRangeBox',
+];
+const EDITORS_WITH_SPECIFIC_LABELS: FormItemComponent[] = ['dxRangeSlider', 'dxSlider'];
+export const EDITORS_WITHOUT_LABELS: FormItemComponent[] = [
+ 'dxCalendar',
+ 'dxCheckBox',
+ 'dxHtmlEditor',
+ 'dxRadioGroup',
+ 'dxRangeSlider',
+ 'dxSlider',
+ 'dxSwitch',
+];
+const DROP_DOWN_EDITORS: FormItemComponent[] = [
+ 'dxSelectBox',
+ 'dxDropDownBox',
+ 'dxTagBox',
+ 'dxLookup',
+ 'dxAutocomplete',
+ 'dxColorBox',
+ 'dxDateBox',
+ 'dxDateRangeBox',
+];
+
+type DropDownOptions = dxDropDownEditorOptions
;
export function convertToRenderFieldItemOptions({
$parent,
@@ -33,7 +62,9 @@ export function convertToRenderFieldItemOptions({
labelMode,
onLabelTemplateRendered,
}) {
- const isRequired = isDefined(item.isRequired) ? item.isRequired : !!_hasRequiredRuleInSet(item.validationRules);
+ const isRequired = isDefined(item.isRequired)
+ ? item.isRequired
+ : !!_hasRequiredRuleInSet(item.validationRules);
const isSimpleItem = item.itemType === SIMPLE_ITEM_TYPE;
const helpID = item.helpText ? `dx-${new Guid()}` : null;
@@ -49,11 +80,16 @@ export function convertToRenderFieldItemOptions({
onLabelTemplateRendered,
});
- const needRenderLabel = labelOptions.visible && (labelOptions.text || (labelOptions.labelTemplate && isSimpleItem));
+ const needRenderLabel = labelOptions.visible
+ && (labelOptions.text || (labelOptions.labelTemplate && isSimpleItem));
const { location: labelLocation, labelID } = labelOptions;
- const labelNeedBaselineAlign = labelLocation !== 'top' && ['dxTextArea', 'dxRadioGroup', 'dxCalendar', 'dxHtmlEditor'].includes(item.editorType);
+ const labelNeedBaselineAlign = labelLocation !== 'top'
+ && ['dxTextArea', 'dxRadioGroup', 'dxCalendar', 'dxHtmlEditor'].includes(
+ item.editorType,
+ );
const editorOptions = _convertToEditorOptions({
+ $parent,
editorType: item.editorType,
editorValue,
defaultEditorName: item.dataField,
@@ -70,8 +106,9 @@ export function convertToRenderFieldItemOptions({
});
const needRenderOptionalMarkAsHelpText = labelOptions.markOptions.showOptionalMark
- && !labelOptions.visible && editorOptions.labelMode !== 'hidden'
- && !isDefined(item.helpText);
+ && !labelOptions.visible
+ && editorOptions.labelMode !== 'hidden'
+ && !isDefined(item.helpText);
const helpText = needRenderOptionalMarkAsHelpText
? labelOptions.markOptions.optionalMark
@@ -102,18 +139,26 @@ export function convertToRenderFieldItemOptions({
}
export function getLabelMarkText({
- showRequiredMark, requiredMark, showOptionalMark, optionalMark,
+ showRequiredMark,
+ requiredMark,
+ showOptionalMark,
+ optionalMark,
}) {
if (!showRequiredMark && !showOptionalMark) {
return '';
}
- return String.fromCharCode(160) + (showRequiredMark ? requiredMark : optionalMark);
+ return (
+ String.fromCharCode(160) + (showRequiredMark ? requiredMark : optionalMark)
+ );
}
-export function convertToLabelMarkOptions({
- showRequiredMark, requiredMark, showOptionalMark, optionalMark,
-}, isRequired?: boolean) {
+export function convertToLabelMarkOptions(
+ {
+ showRequiredMark, requiredMark, showOptionalMark, optionalMark,
+ },
+ isRequired?: boolean,
+) {
return {
showRequiredMark: showRequiredMark && isRequired,
requiredMark,
@@ -122,8 +167,55 @@ export function convertToLabelMarkOptions({
};
}
+// eslint-disable-next-line @typescript-eslint/naming-convention
+function _getDropDownEditorOptions(
+ $parent,
+ editorType: FormItemComponent,
+ editorInputId: string,
+ onContentReadyExternal?: DropDownOptions['onContentReady'],
+): DropDownOptions {
+ const isDropDownEditor = DROP_DOWN_EDITORS.includes(editorType);
+
+ if (!isDropDownEditor) {
+ return {};
+ }
+
+ return {
+ onContentReady: (e) => {
+ const { component } = e;
+ const openOnFieldClick = component.option('openOnFieldClick') as DropDownOptions['openOnFieldClick'];
+ const initialHideOnOutsideClick = component.option('dropDownOptions.hideOnOutsideClick') as dxOverlayOptions['hideOnOutsideClick'];
+
+ if (openOnFieldClick) {
+ component.option('dropDownOptions', {
+ hideOnOutsideClick: (e) => {
+ if (isBoolean(initialHideOnOutsideClick)) {
+ return initialHideOnOutsideClick;
+ }
+
+ const $target = $(e.target);
+ const $label = $parent.find(`label[for="${editorInputId}"]`);
+ const isLabelClicked = !!$target.closest($label).length;
+
+ if (!isFunction(initialHideOnOutsideClick)) {
+ return !isLabelClicked;
+ }
+
+ return !isLabelClicked && initialHideOnOutsideClick(e);
+ },
+ });
+ }
+
+ if (isFunction(onContentReadyExternal)) {
+ onContentReadyExternal(e);
+ }
+ },
+ };
+}
+
// eslint-disable-next-line @typescript-eslint/naming-convention
function _convertToEditorOptions({
+ $parent,
editorType,
defaultEditorName,
editorValue,
@@ -153,10 +245,13 @@ function _convertToEditorOptions({
const stylingMode = externalEditorOptions?.stylingMode || editorStylingMode;
const useSpecificLabelOptions = EDITORS_WITH_SPECIFIC_LABELS.includes(editorType);
+ const dropDownEditorOptions = _getDropDownEditorOptions($parent, editorType, editorInputId, externalEditorOptions?.onContentReady);
+
const result = extend(
true,
editorOptionsWithValue,
externalEditorOptions,
+ dropDownEditorOptions,
{
inputAttr: { id: editorInputId },
validationBoundary: editorValidationBoundary,
@@ -179,6 +274,7 @@ function _convertToEditorOptions({
if (defaultEditorName && !result.name) {
result.name = defaultEditorName;
}
+
return result;
}
@@ -201,15 +297,27 @@ function _hasRequiredRuleInSet(rules) {
// eslint-disable-next-line @typescript-eslint/naming-convention
function _convertToLabelOptions({
- item, id, isRequired, managerMarkOptions, showColonAfterLabel, labelLocation, labelTemplate, formLabelMode, onLabelTemplateRendered,
+ item,
+ id,
+ isRequired,
+ managerMarkOptions,
+ showColonAfterLabel,
+ labelLocation,
+ labelTemplate,
+ formLabelMode,
+ onLabelTemplateRendered,
}) {
- const isEditorWithoutLabels = EDITORS_WITHOUT_LABELS.includes(item.editorType);
+ const isEditorWithoutLabels = EDITORS_WITHOUT_LABELS.includes(
+ item.editorType,
+ );
const labelOptions = extend(
{
showColon: showColonAfterLabel,
location: labelLocation,
id,
- visible: formLabelMode === 'outside' || (isEditorWithoutLabels && formLabelMode !== 'hidden'),
+ visible:
+ formLabelMode === 'outside'
+ || (isEditorWithoutLabels && formLabelMode !== 'hidden'),
isRequired,
},
item ? item.label : {},
@@ -220,7 +328,16 @@ function _convertToLabelOptions({
},
);
- const editorsRequiringIdForLabel = ['dxRadioGroup', 'dxCheckBox', 'dxLookup', 'dxSlider', 'dxRangeSlider', 'dxSwitch', 'dxHtmlEditor', 'dxDateRangeBox']; // TODO: support "dxCalendar"
+ const editorsRequiringIdForLabel: FormItemComponent[] = [
+ 'dxRadioGroup',
+ 'dxCheckBox',
+ 'dxLookup',
+ 'dxSlider',
+ 'dxRangeSlider',
+ 'dxSwitch',
+ 'dxHtmlEditor',
+ 'dxDateRangeBox',
+ ]; // TODO: support "dxCalendar"
if (editorsRequiringIdForLabel.includes(item.editorType)) {
labelOptions.labelID = `dx-label-${new Guid()}`;
}
diff --git a/packages/devextreme/js/exporter/image_creator.js b/packages/devextreme/js/exporter/image_creator.js
index 1b37460bf4ec..098a79c0b52e 100644
--- a/packages/devextreme/js/exporter/image_creator.js
+++ b/packages/devextreme/js/exporter/image_creator.js
@@ -455,7 +455,7 @@ function applyGradient(context, options, { linearGradients, radialGradients }, e
});
if(type === 'linear') {
- const angle = gradients[id].transform?.replace(/\D/g, '') * Math.PI / 180 ?? 0;
+ const angle = (gradients[id].transform?.replace(/\D/g, '') || 0) * Math.PI / 180;
context.translate(horizontalCenter, verticalCenter);
context.rotate(angle);
context.translate(-horizontalCenter, -verticalCenter);
diff --git a/packages/devextreme/testing/tests/DevExpress.exporter/imageCreator.tests.js b/packages/devextreme/testing/tests/DevExpress.exporter/imageCreator.tests.js
index f40b30623aa4..a1af698c0299 100644
--- a/packages/devextreme/testing/tests/DevExpress.exporter/imageCreator.tests.js
+++ b/packages/devextreme/testing/tests/DevExpress.exporter/imageCreator.tests.js
@@ -1247,6 +1247,34 @@ QUnit.test('lineargradient with rotation angle', function(assert) {
});
});
+QUnit.test('lineargradient with undefined rotation angle', function(assert) {
+ const done = assert.async();
+ const markup = testingMarkupStart +
+ '' +
+
+ '' +
+ '' +
+ '' +
+ '' +
+
+ '' +
+ '' +
+ '' +
+ testingMarkupEnd;
+
+ const imageBlob = getData(markup);
+ const context = window.CanvasRenderingContext2D.prototype;
+
+ $.when(imageBlob).done(() => {
+ try {
+ assert.strictEqual(context.rotate.callCount, 1, 'rotate call count');
+ assert.strictEqual(context.rotate.getCall(0).args[0], 0);
+ } finally {
+ done();
+ }
+ });
+});
+
QUnit.test('radialgradient', function(assert) {
const done = assert.async();
const markup = testingMarkupStart +
diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.form/form.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.form/form.tests.js
index ce3da955d02c..0a28a0451ea2 100644
--- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.form/form.tests.js
+++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.form/form.tests.js
@@ -42,14 +42,11 @@ import {
renderLabel,
} from '__internal/ui/form/components/m_label';
-const EDITOR_LABEL_CLASS = 'dx-texteditor-label';
-const EDITOR_INPUT_CLASS = 'dx-texteditor-input';
-const FIELD_ITEM_HELP_TEXT_CLASS = 'dx-field-item-help-text';
-
import { TOOLBAR_CLASS } from '__internal/ui/toolbar/m_constants';
import 'ui/html_editor';
import '../../helpers/ignoreQuillTimers.js';
+import pointerMock from '../../helpers/pointerMock.js';
import 'ui/lookup';
import 'ui/radio_group';
import 'ui/tag_box';
@@ -66,6 +63,11 @@ const FORM_GROUP_CONTENT_CLASS = 'dx-form-group-content';
const MULTIVIEW_ITEM_CONTENT_CLASS = 'dx-multiview-item-content';
const LAST_COL_CLASS = 'dx-last-col';
const SLIDER_LABEL = 'dx-slider-label';
+const EDITOR_LABEL_CLASS = 'dx-texteditor-label';
+const EDITOR_INPUT_CLASS = 'dx-texteditor-input';
+const FIELD_ITEM_HELP_TEXT_CLASS = 'dx-field-item-help-text';
+const DROP_DOWN_EDITOR_BUTTON_CLASS = 'dx-dropdowneditor-button';
+const TEXTBOX_CLASS = 'dx-textbox';
QUnit.testStart(function() {
const markup =
@@ -644,7 +646,7 @@ QUnit.test('Check aria-labelledby attribute for editors label', function(assert)
});
QUnit.test('field1.required -> form.validate() -> form.option("onFieldDataChanged", "newHandler") -> check form is not re-rendered (T1014577)', function(assert) {
- const checkEditorIsInvalid = (form) => form.$element().find('.dx-textbox').hasClass(INVALID_CLASS);
+ const checkEditorIsInvalid = (form) => form.$element().find(`.${TEXTBOX_CLASS}`).hasClass(INVALID_CLASS);
const form = $('#form').dxForm({
formData: { field1: '' },
items: [ {
@@ -1855,8 +1857,8 @@ QUnit.test('Align with "" required mark, T1031458', function(assert) {
}]
});
- const $labelText = $testContainer.find('.dx-field-item-label-text');
- const $textBox = $testContainer.find('.dx-textbox');
+ const $labelText = $testContainer.find(`.${FIELD_ITEM_LABEL_TEXT_CLASS}`);
+ const $textBox = $testContainer.find(`.${TEXTBOX_CLASS}`);
assert.roughEqual(getWidth($labelText), 11, 3, 'labelsContent.width');
assert.roughEqual($textBox.offset().left, $labelText.offset().left + 25, 3, 'textBox.left');
@@ -1872,8 +1874,8 @@ QUnit.test('Align with " " required mark, T1031458', function(assert) {
}]
});
- const $labelText = $testContainer.find('.dx-field-item-label-text');
- const $textBox = $testContainer.find('.dx-textbox');
+ const $labelText = $testContainer.find(`.${FIELD_ITEM_LABEL_TEXT_CLASS}`);
+ const $textBox = $testContainer.find(`.${TEXTBOX_CLASS}`);
assert.roughEqual(getWidth($labelText), 11, 3, 'labelsContent.width');
assert.roughEqual($textBox.offset().left, $labelText.offset().left + 25, 3, 'textBox.left');
@@ -1889,8 +1891,8 @@ QUnit.test('Align with "!" required mark, T1031458', function(assert) {
}]
});
- const $labelText = $testContainer.find('.dx-field-item-label-text');
- const $textBox = $testContainer.find('.dx-textbox');
+ const $labelText = $testContainer.find(`.${FIELD_ITEM_LABEL_TEXT_CLASS}`);
+ const $textBox = $testContainer.find(`.${TEXTBOX_CLASS}`);
assert.roughEqual(getWidth($labelText), 11, 3, 'labelsContent.width');
assert.roughEqual($textBox.offset().left, $labelText.offset().left + 29, 3, 'textBox.left');
@@ -1906,8 +1908,8 @@ QUnit.test('Align with "×" required mark, T1031458', function(assert) {
}]
});
- const $labelText = $testContainer.find('.dx-field-item-label-text');
- const $textBox = $testContainer.find('.dx-textbox');
+ const $labelText = $testContainer.find(`.${FIELD_ITEM_LABEL_TEXT_CLASS}`);
+ const $textBox = $testContainer.find(`.${TEXTBOX_CLASS}`);
assert.roughEqual(getWidth($labelText), 11, 3, 'labelsContent.width');
assert.roughEqual($textBox.offset().left, $labelText.offset().left + 35, 3, 'textBox.left');
@@ -4540,6 +4542,52 @@ QUnit.test('form should be dirty when some editors are dirty', function(assert)
assert.strictEqual(form.option('isDirty'), false, 'form is not dirty when all editors are back to pristine');
});
+[true, false].forEach((openOnFieldClick) => {
+ [true, false, undefined].forEach((hideOnOutsideClick) => {
+ QUnit.test(`Opened DropDownList must hide on input label click, openOnFieldClick: ${openOnFieldClick}, hideOnOutsideClick: ${hideOnOutsideClick} (T1257945)`, function(assert) {
+ const dropDownOptions = hideOnOutsideClick === undefined ? {} : { hideOnOutsideClick };
+ const $form = $('#form').dxForm({
+ formData: { CustomerID: 'VINET' },
+ items: [{
+ itemType: 'group',
+ colCount: 2,
+ items: [{
+ dataField: 'CustomerID',
+ editorType: 'dxSelectBox',
+ editorOptions: {
+ items: ['VINET', 'VALUE', 'VINS'],
+ value: '',
+ openOnFieldClick,
+ dropDownOptions,
+ },
+ }],
+ }],
+ });
+
+ const $dropDownButton = $form.find(`.${DROP_DOWN_EDITOR_BUTTON_CLASS}`);
+
+ pointerMock($dropDownButton).click();
+
+ const editorInstance = $form.dxForm('instance').getEditor('CustomerID');
+
+ assert.true(editorInstance.option('opened'), 'drop down list is visible');
+
+ const $label = $form.find(`.${FIELD_ITEM_LABEL_TEXT_CLASS}`);
+
+ pointerMock($label).click();
+
+ // NOTE: In the real environment, clicking the label triggers a click on the editor,
+ // toggling the popup visibility if openOnFieldClick=true.
+ // This assertion only takes hideOnOutsideClick into account
+ if(hideOnOutsideClick === false) {
+ assert.true(editorInstance.option('opened'), `drop down list ${openOnFieldClick ? 'is hidden by triggered input click' : 'is visible'}`);
+ } else {
+ assert.strictEqual(editorInstance.option('opened'), openOnFieldClick, `drop down list is hidden by ${openOnFieldClick ? 'triggered input click' : 'outside click'}`);
+ }
+ });
+ });
+});
+
QUnit.module('reset', () => {
[
['dxCalendar', new Date(2019, 1, 2), { dxCalendar: new Date(2019, 1, 3) } ],