From b9f4cec14b017d21d278cdc94f6795f020e0a7f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marker=20dao=20=C2=AE?= Date: Mon, 21 Oct 2024 14:47:38 +0200 Subject: [PATCH 01/21] Chat: Implement onTyping events --- packages/devextreme/playground/jquery.html | 45 +++++++++++++++++++--- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/packages/devextreme/playground/jquery.html b/packages/devextreme/playground/jquery.html index fde766f17218..fb718970d18e 100644 --- a/packages/devextreme/playground/jquery.html +++ b/packages/devextreme/playground/jquery.html @@ -49,13 +49,48 @@

Te
-
+
From 594dd3779306744e9cf016bb1e3cd16bcfa65609 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marker=20dao=20=C2=AE?= Date: Mon, 21 Oct 2024 15:42:15 +0200 Subject: [PATCH 02/21] feat(utils): Move throttle to utils --- packages/devextreme/js/core/utils/throttle.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 packages/devextreme/js/core/utils/throttle.ts diff --git a/packages/devextreme/js/core/utils/throttle.ts b/packages/devextreme/js/core/utils/throttle.ts new file mode 100644 index 000000000000..f22c8c0304c2 --- /dev/null +++ b/packages/devextreme/js/core/utils/throttle.ts @@ -0,0 +1,17 @@ +export function throttle( + func: (this: This, ...args: Args) => void, + delay: number, +): (this: This, ...args: Args) => void { + let timestamp = 0; + + // eslint-disable-next-line func-names + return function (this: This, ...args: Args) { + const now = Date.now(); + + if (now - timestamp >= delay) { + func.apply(this, args); + + timestamp = now; + } + }; +} From 24b84c175ace5618b0b701e456880f5aff35e381 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marker=20dao=20=C2=AE?= Date: Mon, 21 Oct 2024 16:23:57 +0200 Subject: [PATCH 03/21] feat(throttle): Add tests --- packages/devextreme/js/core/utils/throttle.js | 13 +++ packages/devextreme/js/core/utils/throttle.ts | 17 ---- .../DevExpress.core/utils.throttle.tests.js | 99 +++++++++++++++++++ 3 files changed, 112 insertions(+), 17 deletions(-) create mode 100644 packages/devextreme/js/core/utils/throttle.js delete mode 100644 packages/devextreme/js/core/utils/throttle.ts create mode 100644 packages/devextreme/testing/tests/DevExpress.core/utils.throttle.tests.js diff --git a/packages/devextreme/js/core/utils/throttle.js b/packages/devextreme/js/core/utils/throttle.js new file mode 100644 index 000000000000..c9bd69bc745b --- /dev/null +++ b/packages/devextreme/js/core/utils/throttle.js @@ -0,0 +1,13 @@ +export function throttle(func, delay) { + let timestamp = 0; + + return function(...args) { + const now = Date.now(); + + if(now - timestamp >= delay) { + func.apply(this, args); + + timestamp = now; + } + }; +} diff --git a/packages/devextreme/js/core/utils/throttle.ts b/packages/devextreme/js/core/utils/throttle.ts deleted file mode 100644 index f22c8c0304c2..000000000000 --- a/packages/devextreme/js/core/utils/throttle.ts +++ /dev/null @@ -1,17 +0,0 @@ -export function throttle( - func: (this: This, ...args: Args) => void, - delay: number, -): (this: This, ...args: Args) => void { - let timestamp = 0; - - // eslint-disable-next-line func-names - return function (this: This, ...args: Args) { - const now = Date.now(); - - if (now - timestamp >= delay) { - func.apply(this, args); - - timestamp = now; - } - }; -} diff --git a/packages/devextreme/testing/tests/DevExpress.core/utils.throttle.tests.js b/packages/devextreme/testing/tests/DevExpress.core/utils.throttle.tests.js new file mode 100644 index 000000000000..ef170492591f --- /dev/null +++ b/packages/devextreme/testing/tests/DevExpress.core/utils.throttle.tests.js @@ -0,0 +1,99 @@ +import { throttle } from 'core/utils/throttle'; + +QUnit.module('throttle', + () => { + QUnit.test('Must call the function immediately on the first call', function(assert) { + let callCount = 0; + + function func() { + callCount++; + } + + const throttledFunc = throttle(func, 100); + + throttledFunc(); + + assert.strictEqual(callCount, 1, 'Must call the function immediately on the first call'); + }); + + QUnit.test('The function is called immediately on the first call', function(assert) { + const clock = sinon.useFakeTimers({ now: new Date().getTime() }); + + let callCount = 0; + + function func() { + callCount++; + } + + try { + const throttledFunc = throttle(func, 100); + + throttledFunc(); + throttledFunc(); + + assert.strictEqual(callCount, 1, 'The function is called only once'); + + clock.tick(50); + + throttledFunc(); + + assert.strictEqual(callCount, 1, 'The function is not called again until the delay expires'); + } finally { + clock.restore(); + } + + }); + + QUnit.test('Must call the function again after the delay has expired', function(assert) { + const clock = sinon.useFakeTimers({ now: new Date().getTime() }); + + let callCount = 0; + + function func() { + callCount++; + } + + try { + const throttledFunc = throttle(func, 100); + + throttledFunc(); + + assert.strictEqual(callCount, 1, 'Function called for the first time'); + + clock.tick(100); + + throttledFunc(); + + assert.strictEqual(callCount, 2, 'The function is called again after a delay'); + } finally { + clock.restore(); + } + }); + + QUnit.test('The function must preserve the correct context', function(assert) { + assert.expect(1); + + const context = { value: 42 }; + + function func() { + assert.strictEqual(this.value, 42, 'The context is saved correctly'); + } + + const throttledFunc = throttle(func, 100); + + throttledFunc.call(context); + }); + + QUnit.test('The function must pass the correct arguments to the function', function(assert) { + assert.expect(1); + + function func(a, b) { + assert.deepEqual([a, b], [1, 2], 'The arguments are properly conveyed'); + } + + const throttledFunc = throttle(func, 100); + + throttledFunc(1, 2); + }); + }); + From 0773fe03a54249db8cb8f4481ad88af4ed20c82c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marker=20dao=20=C2=AE?= Date: Mon, 21 Oct 2024 18:14:35 +0200 Subject: [PATCH 04/21] feat(chat): Add typification --- packages/devextreme/js/__internal/ui/chat/messagebox.ts | 2 +- packages/devextreme/playground/jquery.html | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/devextreme/js/__internal/ui/chat/messagebox.ts b/packages/devextreme/js/__internal/ui/chat/messagebox.ts index f6e292abac77..79bcd2d412ee 100644 --- a/packages/devextreme/js/__internal/ui/chat/messagebox.ts +++ b/packages/devextreme/js/__internal/ui/chat/messagebox.ts @@ -32,7 +32,7 @@ export interface Properties extends DOMComponentProperties { onMessageSend?: (e: MessageSendEvent) => void; - onTypingStart?: (e: TypingStartEvent) => void; + onTypingStart?: (e: NativeEventInfo) => void; onTypingEnd?: (e: NativeEventInfo) => void; } diff --git a/packages/devextreme/playground/jquery.html b/packages/devextreme/playground/jquery.html index fb718970d18e..36388267f813 100644 --- a/packages/devextreme/playground/jquery.html +++ b/packages/devextreme/playground/jquery.html @@ -83,12 +83,12 @@

Te e.component.renderMessage(e.message); }, onTypingStart: (e) => { + // debugger console.log('onTypingStart'); - console.log(e); }, onTypingEnd: (e) => { + // debugger console.log('onTypingEnd'); - console.log(e); }, }).dxChat('instance'); }); From d3df760461680c800ce8de171bd1098c68e98d9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marker=20dao=20=C2=AE?= Date: Mon, 21 Oct 2024 19:49:36 +0200 Subject: [PATCH 05/21] dirty --- .../devextreme/js/__internal/ui/chat/chat.ts | 41 ++++++++++--------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/packages/devextreme/js/__internal/ui/chat/chat.ts b/packages/devextreme/js/__internal/ui/chat/chat.ts index ecedebc3cdaa..7b759dabee1d 100644 --- a/packages/devextreme/js/__internal/ui/chat/chat.ts +++ b/packages/devextreme/js/__internal/ui/chat/chat.ts @@ -5,13 +5,13 @@ import $ from '@js/core/renderer'; import { isDefined } from '@js/core/utils/type'; import type { Options as DataSourceOptions } from '@js/data/data_source'; import DataHelperMixin from '@js/data_helper'; -import type { NativeEventInfo } from '@js/events'; +// import type { NativeEventInfo } from '@js/events'; import messageLocalization from '@js/localization/message'; import type { Message, MessageSendEvent, Properties as ChatProperties, - User, + // User, } from '@js/ui/chat'; import type { OptionChanged } from '@ts/core/widget/types'; import Widget from '@ts/core/widget/widget'; @@ -29,8 +29,8 @@ import MessageList from './messagelist'; const CHAT_CLASS = 'dx-chat'; const TEXTEDITOR_INPUT_CLASS = 'dx-texteditor-input'; -type TypingStartEvent = NativeEventInfo & { user?: User }; -type TypingEndEvent = NativeEventInfo & { user?: User }; +// type TypingStartEvent = NativeEventInfo & { user?: User }; +// type TypingEndEvent = NativeEventInfo & { user?: User }; type Properties = ChatProperties & { title: string; @@ -50,9 +50,9 @@ class Chat extends Widget { _messageSendAction?: (e: Partial) => void; - _typingStartAction?: (e: Partial) => void; + // _typingStartAction?: (e: Partial) => void; - _typingEndAction?: (e: Partial) => void; + // _typingEndAction?: (e: Partial) => void; _getDefaultOptions(): Properties { return { @@ -67,8 +67,8 @@ class Chat extends Widget { user: { id: new Guid().toString() }, errors: [], onMessageSend: undefined, - onTypingStart: undefined, - onTypingEnd: undefined, + // onTypingStart: undefined, + // onTypingEnd: undefined, }; } @@ -81,8 +81,8 @@ class Chat extends Widget { this._refreshDataSource(); this._createMessageSendAction(); - this._createTypingStartAction(); - this._createTypingEndAction(); + // this._createTypingStartAction(); + // this._createTypingEndAction(); } _dataSourceLoadErrorHandler(): void { @@ -243,11 +243,11 @@ class Chat extends Widget { this._typingStartAction?.({ user, event }); } - _typingEndHandler(): void { - const { user } = this.option(); + // _typingEndHandler(): void { + // const { user } = this.option(); - this._typingEndAction?.({ user }); - } + // this._typingEndAction?.({ user }); + // } _focusTarget(): dxElementWrapper { const $input = $(this.element()).find(`.${TEXTEDITOR_INPUT_CLASS}`); @@ -287,6 +287,7 @@ class Chat extends Widget { this._messageList.option(name, value); this._updateMessageBoxAria(); break; + case 'dataSource': // @ts-expect-error this._refreshDataSource(); @@ -297,12 +298,12 @@ class Chat extends Widget { case 'onMessageSend': this._createMessageSendAction(); break; - case 'onTypingStart': - this._createTypingStartAction(); - break; - case 'onTypingEnd': - this._createTypingEndAction(); - break; + // case 'onTypingStart': + // this._createTypingStartAction(); + // break; + // case 'onTypingEnd': + // this._createTypingEndAction(); + // break; case 'showDayHeaders': this._messageList.option(name, value); break; From 061a3dbb560db58b94cc60fc9ee6836c09bea2b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marker=20dao=20=C2=AE?= Date: Mon, 21 Oct 2024 19:58:31 +0200 Subject: [PATCH 06/21] fix(types) --- .../devextreme/js/__internal/ui/chat/chat.ts | 41 +++++++++---------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/packages/devextreme/js/__internal/ui/chat/chat.ts b/packages/devextreme/js/__internal/ui/chat/chat.ts index 7b759dabee1d..ecedebc3cdaa 100644 --- a/packages/devextreme/js/__internal/ui/chat/chat.ts +++ b/packages/devextreme/js/__internal/ui/chat/chat.ts @@ -5,13 +5,13 @@ import $ from '@js/core/renderer'; import { isDefined } from '@js/core/utils/type'; import type { Options as DataSourceOptions } from '@js/data/data_source'; import DataHelperMixin from '@js/data_helper'; -// import type { NativeEventInfo } from '@js/events'; +import type { NativeEventInfo } from '@js/events'; import messageLocalization from '@js/localization/message'; import type { Message, MessageSendEvent, Properties as ChatProperties, - // User, + User, } from '@js/ui/chat'; import type { OptionChanged } from '@ts/core/widget/types'; import Widget from '@ts/core/widget/widget'; @@ -29,8 +29,8 @@ import MessageList from './messagelist'; const CHAT_CLASS = 'dx-chat'; const TEXTEDITOR_INPUT_CLASS = 'dx-texteditor-input'; -// type TypingStartEvent = NativeEventInfo & { user?: User }; -// type TypingEndEvent = NativeEventInfo & { user?: User }; +type TypingStartEvent = NativeEventInfo & { user?: User }; +type TypingEndEvent = NativeEventInfo & { user?: User }; type Properties = ChatProperties & { title: string; @@ -50,9 +50,9 @@ class Chat extends Widget { _messageSendAction?: (e: Partial) => void; - // _typingStartAction?: (e: Partial) => void; + _typingStartAction?: (e: Partial) => void; - // _typingEndAction?: (e: Partial) => void; + _typingEndAction?: (e: Partial) => void; _getDefaultOptions(): Properties { return { @@ -67,8 +67,8 @@ class Chat extends Widget { user: { id: new Guid().toString() }, errors: [], onMessageSend: undefined, - // onTypingStart: undefined, - // onTypingEnd: undefined, + onTypingStart: undefined, + onTypingEnd: undefined, }; } @@ -81,8 +81,8 @@ class Chat extends Widget { this._refreshDataSource(); this._createMessageSendAction(); - // this._createTypingStartAction(); - // this._createTypingEndAction(); + this._createTypingStartAction(); + this._createTypingEndAction(); } _dataSourceLoadErrorHandler(): void { @@ -243,11 +243,11 @@ class Chat extends Widget { this._typingStartAction?.({ user, event }); } - // _typingEndHandler(): void { - // const { user } = this.option(); + _typingEndHandler(): void { + const { user } = this.option(); - // this._typingEndAction?.({ user }); - // } + this._typingEndAction?.({ user }); + } _focusTarget(): dxElementWrapper { const $input = $(this.element()).find(`.${TEXTEDITOR_INPUT_CLASS}`); @@ -287,7 +287,6 @@ class Chat extends Widget { this._messageList.option(name, value); this._updateMessageBoxAria(); break; - case 'dataSource': // @ts-expect-error this._refreshDataSource(); @@ -298,12 +297,12 @@ class Chat extends Widget { case 'onMessageSend': this._createMessageSendAction(); break; - // case 'onTypingStart': - // this._createTypingStartAction(); - // break; - // case 'onTypingEnd': - // this._createTypingEndAction(); - // break; + case 'onTypingStart': + this._createTypingStartAction(); + break; + case 'onTypingEnd': + this._createTypingEndAction(); + break; case 'showDayHeaders': this._messageList.option(name, value); break; From b99397c012741894abdd20d02ed41357feaf8e97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marker=20dao=20=C2=AE?= Date: Tue, 22 Oct 2024 12:03:41 +0200 Subject: [PATCH 07/21] feat(mb): Add onTypingStart tests --- .../chatParts/messageBox.tests.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets/chatParts/messageBox.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets/chatParts/messageBox.tests.js index fbae23055004..f354834534a4 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets/chatParts/messageBox.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets/chatParts/messageBox.tests.js @@ -545,6 +545,24 @@ QUnit.module('MessageBox', moduleConfig, () => { assert.strictEqual(onTypingEndStub.callCount, 1, 'called immediately'); }); + + QUnit.test('should be triggered with correct arguments', function(assert) { + assert.expect(3); + + this.reinit({ + onTypingStart: (e) => { + const { component, element } = e; + + assert.strictEqual(component, this.instance, 'component field is correct'); + assert.strictEqual(isRenderer(element), !!config().useJQuery, 'element is correct'); + assert.strictEqual($(element).is(this.$element), true, 'element field is correct'); + }, + }); + + keyboardMock(this.$input) + .focus() + .type('n'); + }); }); QUnit.module('Proxy state options', () => { From f5838828aae382b388ca8edc05ebd9d1f6df4454 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marker=20dao=20=C2=AE?= Date: Tue, 22 Oct 2024 12:31:19 +0200 Subject: [PATCH 08/21] feat(mb): Add onTypingEnd tests --- .../chatParts/messageBox.tests.js | 124 ++++++++++++++++++ 1 file changed, 124 insertions(+) diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets/chatParts/messageBox.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets/chatParts/messageBox.tests.js index f354834534a4..b4f2dd39209f 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets/chatParts/messageBox.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets/chatParts/messageBox.tests.js @@ -565,6 +565,130 @@ QUnit.module('MessageBox', moduleConfig, () => { }); }); + QUnit.module('onTypingEnd event', () => { + QUnit.test('should be triggered once if a character is entered in the input', function(assert) { + const clock = sinon.useFakeTimers({ now: new Date().getTime() }); + const onTypingEndStub = sinon.stub(); + + this.reinit({ onTypingEnd: onTypingEndStub }); + + try { + const keyboard = keyboardMock(this.$input); + + keyboard + .focus() + .type('n'); + + assert.strictEqual(onTypingEndStub.callCount, 0, 'is not called immediately after entering'); + + clock.tick(TYPING_END_DELAY - 500); + + assert.strictEqual(onTypingEndStub.callCount, 0, 'is not called still'); + + clock.tick(500); + + assert.strictEqual(onTypingEndStub.callCount, 1, 'is called once after delay'); + } finally { + clock.restore(); + } + }); + + QUnit.test('should not be called if the user continues to enter text during the delay', function(assert) { + const clock = sinon.useFakeTimers({ now: new Date().getTime() }); + const onTypingEndStub = sinon.stub(); + + this.reinit({ onTypingEnd: onTypingEndStub }); + + try { + const keyboard = keyboardMock(this.$input); + + keyboard + .focus() + .type('n'); + + clock.tick(TYPING_END_DELAY - 1); + + keyboard.type('n'); + + clock.tick(2); + + assert.strictEqual(onTypingEndStub.callCount, 0, 'is not called'); + + clock.tick(TYPING_END_DELAY); + + assert.strictEqual(onTypingEndStub.callCount, 1, 'is called once after delay'); + } finally { + clock.restore(); + } + }); + + ['', ' '].forEach(value => { + QUnit.test(`should not be triggered if an empty character is entered in the input, value is '${value}'`, function(assert) { + const clock = sinon.useFakeTimers({ now: new Date().getTime() }); + const onTypingEndStub = sinon.stub(); + + this.reinit({ onTypingEnd: onTypingEndStub }); + + try { + keyboardMock(this.$input) + .focus() + .type(value); + + clock.tick(TYPING_END_DELAY); + + assert.strictEqual(onTypingEndStub.callCount, 0); + } finally { + clock.restore(); + } + }); + }); + + QUnit.test('should be triggered with correct arguments', function(assert) { + assert.expect(3); + + const clock = sinon.useFakeTimers({ now: new Date().getTime() }); + + try { + this.reinit({ + onTypingEnd: (e) => { + const { component, element } = e; + + assert.strictEqual(component, this.instance, 'component field is correct'); + assert.strictEqual(isRenderer(element), !!config().useJQuery, 'element is correct'); + assert.strictEqual($(element).is(this.$element), true, 'element field is correct'); + }, + }); + + keyboardMock(this.$input) + .focus() + .type('n'); + + clock.tick(TYPING_END_DELAY); + } finally { + clock.restore(); + } + }); + + QUnit.test('should be called after sending a message', function(assert) { + const clock = sinon.useFakeTimers({ now: new Date().getTime() }); + const onTypingEndStub = sinon.stub(); + + this.reinit({ onTypingEnd: onTypingEndStub }); + + try { + keyboardMock(this.$input) + .focus() + .type('new text message'); + + this.$sendButton.trigger('dxclick'); + + assert.strictEqual(onTypingEndStub.callCount, 1, 'called immediately'); + } finally { + clock.restore(); + } + }); + }); + QUnit.module('Proxy state options', () => { [true, false].forEach(value => { QUnit.test('passed state options should be equal message box state options', function(assert) { From 94dd726d492062da527e534af3c84d9ec8ce96f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marker=20dao=20=C2=AE?= Date: Tue, 22 Oct 2024 13:08:12 +0200 Subject: [PATCH 09/21] revert(pg) --- packages/devextreme/playground/jquery.html | 45 +++------------------- 1 file changed, 5 insertions(+), 40 deletions(-) diff --git a/packages/devextreme/playground/jquery.html b/packages/devextreme/playground/jquery.html index 36388267f813..fde766f17218 100644 --- a/packages/devextreme/playground/jquery.html +++ b/packages/devextreme/playground/jquery.html @@ -49,48 +49,13 @@

Te
-
+
From 6fa4bf73188980aded80779b70fc1553ccc2823e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marker=20dao=20=C2=AE?= Date: Tue, 22 Oct 2024 14:02:34 +0200 Subject: [PATCH 10/21] feat(caht && mb): Add runtime tests --- .../chatParts/messageBox.tests.js | 142 ------------------ 1 file changed, 142 deletions(-) diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets/chatParts/messageBox.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets/chatParts/messageBox.tests.js index b4f2dd39209f..fbae23055004 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets/chatParts/messageBox.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets/chatParts/messageBox.tests.js @@ -545,148 +545,6 @@ QUnit.module('MessageBox', moduleConfig, () => { assert.strictEqual(onTypingEndStub.callCount, 1, 'called immediately'); }); - - QUnit.test('should be triggered with correct arguments', function(assert) { - assert.expect(3); - - this.reinit({ - onTypingStart: (e) => { - const { component, element } = e; - - assert.strictEqual(component, this.instance, 'component field is correct'); - assert.strictEqual(isRenderer(element), !!config().useJQuery, 'element is correct'); - assert.strictEqual($(element).is(this.$element), true, 'element field is correct'); - }, - }); - - keyboardMock(this.$input) - .focus() - .type('n'); - }); - }); - - QUnit.module('onTypingEnd event', () => { - QUnit.test('should be triggered once if a character is entered in the input', function(assert) { - const clock = sinon.useFakeTimers({ now: new Date().getTime() }); - const onTypingEndStub = sinon.stub(); - - this.reinit({ onTypingEnd: onTypingEndStub }); - - try { - const keyboard = keyboardMock(this.$input); - - keyboard - .focus() - .type('n'); - - assert.strictEqual(onTypingEndStub.callCount, 0, 'is not called immediately after entering'); - - clock.tick(TYPING_END_DELAY - 500); - - assert.strictEqual(onTypingEndStub.callCount, 0, 'is not called still'); - - clock.tick(500); - - assert.strictEqual(onTypingEndStub.callCount, 1, 'is called once after delay'); - } finally { - clock.restore(); - } - }); - - QUnit.test('should not be called if the user continues to enter text during the delay', function(assert) { - const clock = sinon.useFakeTimers({ now: new Date().getTime() }); - const onTypingEndStub = sinon.stub(); - - this.reinit({ onTypingEnd: onTypingEndStub }); - - try { - const keyboard = keyboardMock(this.$input); - - keyboard - .focus() - .type('n'); - - clock.tick(TYPING_END_DELAY - 1); - - keyboard.type('n'); - - clock.tick(2); - - assert.strictEqual(onTypingEndStub.callCount, 0, 'is not called'); - - clock.tick(TYPING_END_DELAY); - - assert.strictEqual(onTypingEndStub.callCount, 1, 'is called once after delay'); - } finally { - clock.restore(); - } - }); - - ['', ' '].forEach(value => { - QUnit.test(`should not be triggered if an empty character is entered in the input, value is '${value}'`, function(assert) { - const clock = sinon.useFakeTimers({ now: new Date().getTime() }); - const onTypingEndStub = sinon.stub(); - - this.reinit({ onTypingEnd: onTypingEndStub }); - - try { - keyboardMock(this.$input) - .focus() - .type(value); - - clock.tick(TYPING_END_DELAY); - - assert.strictEqual(onTypingEndStub.callCount, 0); - } finally { - clock.restore(); - } - }); - }); - - QUnit.test('should be triggered with correct arguments', function(assert) { - assert.expect(3); - - const clock = sinon.useFakeTimers({ now: new Date().getTime() }); - - try { - this.reinit({ - onTypingEnd: (e) => { - const { component, element } = e; - - assert.strictEqual(component, this.instance, 'component field is correct'); - assert.strictEqual(isRenderer(element), !!config().useJQuery, 'element is correct'); - assert.strictEqual($(element).is(this.$element), true, 'element field is correct'); - }, - }); - - keyboardMock(this.$input) - .focus() - .type('n'); - - clock.tick(TYPING_END_DELAY); - } finally { - clock.restore(); - } - }); - - QUnit.test('should be called after sending a message', function(assert) { - const clock = sinon.useFakeTimers({ now: new Date().getTime() }); - const onTypingEndStub = sinon.stub(); - - this.reinit({ onTypingEnd: onTypingEndStub }); - - try { - keyboardMock(this.$input) - .focus() - .type('new text message'); - - this.$sendButton.trigger('dxclick'); - - assert.strictEqual(onTypingEndStub.callCount, 1, 'called immediately'); - } finally { - clock.restore(); - } - }); }); QUnit.module('Proxy state options', () => { From c9c140598f42b2c8313e16b9195347644ea3e673 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marker=20dao=20=C2=AE?= Date: Tue, 22 Oct 2024 14:49:28 +0200 Subject: [PATCH 11/21] feat(chat): Add event into args --- packages/devextreme/js/__internal/ui/chat/messagebox.ts | 2 +- .../tests/DevExpress.ui.widgets/chatParts/messageBox.tests.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/devextreme/js/__internal/ui/chat/messagebox.ts b/packages/devextreme/js/__internal/ui/chat/messagebox.ts index 79bcd2d412ee..f6e292abac77 100644 --- a/packages/devextreme/js/__internal/ui/chat/messagebox.ts +++ b/packages/devextreme/js/__internal/ui/chat/messagebox.ts @@ -32,7 +32,7 @@ export interface Properties extends DOMComponentProperties { onMessageSend?: (e: MessageSendEvent) => void; - onTypingStart?: (e: NativeEventInfo) => void; + onTypingStart?: (e: TypingStartEvent) => void; onTypingEnd?: (e: NativeEventInfo) => void; } diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets/chatParts/messageBox.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets/chatParts/messageBox.tests.js index fbae23055004..cf68637a38cc 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets/chatParts/messageBox.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets/chatParts/messageBox.tests.js @@ -513,7 +513,7 @@ QUnit.module('MessageBox', moduleConfig, () => { }); QUnit.test('should be triggered with correct arguments', function(assert) { - assert.expect(3); + assert.expect(4); this.reinit({ onTypingEnd: (e) => { @@ -522,6 +522,7 @@ QUnit.module('MessageBox', moduleConfig, () => { assert.strictEqual(component, this.instance, 'component field is correct'); assert.strictEqual(isRenderer(element), !!config().useJQuery, 'element is correct'); assert.strictEqual($(element).is(this.$element), true, 'element field is correct'); + assert.strictEqual(event.type, 'input', 'e.event.type is correct'); }, }); From 1a58f9ef327e460a9a84905d44e36a17dc718547 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marker=20dao=20=C2=AE?= Date: Tue, 22 Oct 2024 17:20:38 +0200 Subject: [PATCH 12/21] Chat: Implement typingUsers --- .../devextreme/js/__internal/ui/chat/chat.ts | 21 +++++++++++++++---- .../js/__internal/ui/chat/messagelist.ts | 3 ++- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/packages/devextreme/js/__internal/ui/chat/chat.ts b/packages/devextreme/js/__internal/ui/chat/chat.ts index ecedebc3cdaa..ec30f79b1979 100644 --- a/packages/devextreme/js/__internal/ui/chat/chat.ts +++ b/packages/devextreme/js/__internal/ui/chat/chat.ts @@ -35,6 +35,7 @@ type TypingEndEvent = NativeEventInfo & { user?: User }; type Properties = ChatProperties & { title: string; showDayHeaders: boolean; + typingUsers: User[]; onTypingStart?: ((e: TypingStartEvent) => void); onTypingEnd?: ((e: TypingEndEvent) => void); }; @@ -66,6 +67,7 @@ class Chat extends Widget { dataSource: null, user: { id: new Guid().toString() }, errors: [], + typingUsers: [], onMessageSend: undefined, onTypingStart: undefined, onTypingEnd: undefined, @@ -130,19 +132,27 @@ class Chat extends Widget { } _renderMessageList(): void { - const { items = [], user, showDayHeaders } = this.option(); + const { + items = [], + user, + showDayHeaders, + typingUsers, + } = this.option(); - const currentUserId = user?.id; const $messageList = $('
'); this.$element().append($messageList); + // @ts-expect-error + const isLoading = this._dataController.isLoading(); + const currentUserId = user?.id; + this._messageList = this._createComponent($messageList, MessageList, { items, currentUserId, showDayHeaders, - // @ts-expect-error - isLoading: this._dataController.isLoading(), + typingUsers, + isLoading, }); } @@ -306,6 +316,9 @@ class Chat extends Widget { case 'showDayHeaders': this._messageList.option(name, value); break; + case 'typingUsers': + this._messageList.option(name, value); + break; default: super._optionChanged(args); } diff --git a/packages/devextreme/js/__internal/ui/chat/messagelist.ts b/packages/devextreme/js/__internal/ui/chat/messagelist.ts index ab4c72f33e92..2a7a4da15cd1 100644 --- a/packages/devextreme/js/__internal/ui/chat/messagelist.ts +++ b/packages/devextreme/js/__internal/ui/chat/messagelist.ts @@ -9,7 +9,7 @@ import { isElementInDom } from '@js/core/utils/dom'; import { isDate, isDefined } from '@js/core/utils/type'; import messageLocalization from '@js/localization/message'; import { getScrollTopMax } from '@js/renovation/ui/scroll_view/utils/get_scroll_top_max'; -import type { Message } from '@js/ui/chat'; +import type { Message, User } from '@js/ui/chat'; import ScrollView from '@js/ui/scroll_view'; import type { WidgetOptions } from '@js/ui/widget/ui.widget'; import type { OptionChanged } from '@ts/core/widget/types'; @@ -35,6 +35,7 @@ export interface Properties extends WidgetOptions { items: Message[]; currentUserId: number | string | undefined; showDayHeaders: boolean; + typingUsers: User[]; isLoading?: boolean; } From 512093b2ecc859492b75dca781e6c548ca11824a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marker=20dao=20=C2=AE?= Date: Tue, 22 Oct 2024 17:23:19 +0200 Subject: [PATCH 13/21] Revert "Chat: Implement typingUsers" This reverts commit 425a608d849d71914627187e7d87f0fb2ff2e784. --- .../devextreme/js/__internal/ui/chat/chat.ts | 21 ++++--------------- .../js/__internal/ui/chat/messagelist.ts | 3 +-- 2 files changed, 5 insertions(+), 19 deletions(-) diff --git a/packages/devextreme/js/__internal/ui/chat/chat.ts b/packages/devextreme/js/__internal/ui/chat/chat.ts index ec30f79b1979..ecedebc3cdaa 100644 --- a/packages/devextreme/js/__internal/ui/chat/chat.ts +++ b/packages/devextreme/js/__internal/ui/chat/chat.ts @@ -35,7 +35,6 @@ type TypingEndEvent = NativeEventInfo & { user?: User }; type Properties = ChatProperties & { title: string; showDayHeaders: boolean; - typingUsers: User[]; onTypingStart?: ((e: TypingStartEvent) => void); onTypingEnd?: ((e: TypingEndEvent) => void); }; @@ -67,7 +66,6 @@ class Chat extends Widget { dataSource: null, user: { id: new Guid().toString() }, errors: [], - typingUsers: [], onMessageSend: undefined, onTypingStart: undefined, onTypingEnd: undefined, @@ -132,27 +130,19 @@ class Chat extends Widget { } _renderMessageList(): void { - const { - items = [], - user, - showDayHeaders, - typingUsers, - } = this.option(); + const { items = [], user, showDayHeaders } = this.option(); + const currentUserId = user?.id; const $messageList = $('
'); this.$element().append($messageList); - // @ts-expect-error - const isLoading = this._dataController.isLoading(); - const currentUserId = user?.id; - this._messageList = this._createComponent($messageList, MessageList, { items, currentUserId, showDayHeaders, - typingUsers, - isLoading, + // @ts-expect-error + isLoading: this._dataController.isLoading(), }); } @@ -316,9 +306,6 @@ class Chat extends Widget { case 'showDayHeaders': this._messageList.option(name, value); break; - case 'typingUsers': - this._messageList.option(name, value); - break; default: super._optionChanged(args); } diff --git a/packages/devextreme/js/__internal/ui/chat/messagelist.ts b/packages/devextreme/js/__internal/ui/chat/messagelist.ts index 2a7a4da15cd1..ab4c72f33e92 100644 --- a/packages/devextreme/js/__internal/ui/chat/messagelist.ts +++ b/packages/devextreme/js/__internal/ui/chat/messagelist.ts @@ -9,7 +9,7 @@ import { isElementInDom } from '@js/core/utils/dom'; import { isDate, isDefined } from '@js/core/utils/type'; import messageLocalization from '@js/localization/message'; import { getScrollTopMax } from '@js/renovation/ui/scroll_view/utils/get_scroll_top_max'; -import type { Message, User } from '@js/ui/chat'; +import type { Message } from '@js/ui/chat'; import ScrollView from '@js/ui/scroll_view'; import type { WidgetOptions } from '@js/ui/widget/ui.widget'; import type { OptionChanged } from '@ts/core/widget/types'; @@ -35,7 +35,6 @@ export interface Properties extends WidgetOptions { items: Message[]; currentUserId: number | string | undefined; showDayHeaders: boolean; - typingUsers: User[]; isLoading?: boolean; } From 72840e24f0304c2ae5707ac48dfb182bf77975bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marker=20dao=20=C2=AE?= Date: Thu, 24 Oct 2024 12:12:10 +0200 Subject: [PATCH 14/21] refactor(chat): Get rid of readOnly --- packages/devextreme/playground/jquery.html | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/devextreme/playground/jquery.html b/packages/devextreme/playground/jquery.html index fde766f17218..f28a06634a6a 100644 --- a/packages/devextreme/playground/jquery.html +++ b/packages/devextreme/playground/jquery.html @@ -52,9 +52,8 @@

Te
From 6eb83b7041853c26b7775c4452e4e042794a8051 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marker=20dao=20=C2=AE?= Date: Thu, 24 Oct 2024 12:46:14 +0200 Subject: [PATCH 15/21] feat(mb): Update typing start event behavior --- packages/devextreme/js/core/utils/throttle.js | 13 --- packages/devextreme/playground/jquery.html | 7 +- .../DevExpress.core/utils.throttle.tests.js | 99 ------------------- 3 files changed, 6 insertions(+), 113 deletions(-) delete mode 100644 packages/devextreme/js/core/utils/throttle.js delete mode 100644 packages/devextreme/testing/tests/DevExpress.core/utils.throttle.tests.js diff --git a/packages/devextreme/js/core/utils/throttle.js b/packages/devextreme/js/core/utils/throttle.js deleted file mode 100644 index c9bd69bc745b..000000000000 --- a/packages/devextreme/js/core/utils/throttle.js +++ /dev/null @@ -1,13 +0,0 @@ -export function throttle(func, delay) { - let timestamp = 0; - - return function(...args) { - const now = Date.now(); - - if(now - timestamp >= delay) { - func.apply(this, args); - - timestamp = now; - } - }; -} diff --git a/packages/devextreme/playground/jquery.html b/packages/devextreme/playground/jquery.html index f28a06634a6a..f659a98c5146 100644 --- a/packages/devextreme/playground/jquery.html +++ b/packages/devextreme/playground/jquery.html @@ -53,7 +53,12 @@

Te diff --git a/packages/devextreme/testing/tests/DevExpress.core/utils.throttle.tests.js b/packages/devextreme/testing/tests/DevExpress.core/utils.throttle.tests.js deleted file mode 100644 index ef170492591f..000000000000 --- a/packages/devextreme/testing/tests/DevExpress.core/utils.throttle.tests.js +++ /dev/null @@ -1,99 +0,0 @@ -import { throttle } from 'core/utils/throttle'; - -QUnit.module('throttle', - () => { - QUnit.test('Must call the function immediately on the first call', function(assert) { - let callCount = 0; - - function func() { - callCount++; - } - - const throttledFunc = throttle(func, 100); - - throttledFunc(); - - assert.strictEqual(callCount, 1, 'Must call the function immediately on the first call'); - }); - - QUnit.test('The function is called immediately on the first call', function(assert) { - const clock = sinon.useFakeTimers({ now: new Date().getTime() }); - - let callCount = 0; - - function func() { - callCount++; - } - - try { - const throttledFunc = throttle(func, 100); - - throttledFunc(); - throttledFunc(); - - assert.strictEqual(callCount, 1, 'The function is called only once'); - - clock.tick(50); - - throttledFunc(); - - assert.strictEqual(callCount, 1, 'The function is not called again until the delay expires'); - } finally { - clock.restore(); - } - - }); - - QUnit.test('Must call the function again after the delay has expired', function(assert) { - const clock = sinon.useFakeTimers({ now: new Date().getTime() }); - - let callCount = 0; - - function func() { - callCount++; - } - - try { - const throttledFunc = throttle(func, 100); - - throttledFunc(); - - assert.strictEqual(callCount, 1, 'Function called for the first time'); - - clock.tick(100); - - throttledFunc(); - - assert.strictEqual(callCount, 2, 'The function is called again after a delay'); - } finally { - clock.restore(); - } - }); - - QUnit.test('The function must preserve the correct context', function(assert) { - assert.expect(1); - - const context = { value: 42 }; - - function func() { - assert.strictEqual(this.value, 42, 'The context is saved correctly'); - } - - const throttledFunc = throttle(func, 100); - - throttledFunc.call(context); - }); - - QUnit.test('The function must pass the correct arguments to the function', function(assert) { - assert.expect(1); - - function func(a, b) { - assert.deepEqual([a, b], [1, 2], 'The arguments are properly conveyed'); - } - - const throttledFunc = throttle(func, 100); - - throttledFunc(1, 2); - }); - }); - From c80f4e40ab2429fa193b2c763ce6f193577dd52f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marker=20dao=20=C2=AE?= Date: Thu, 24 Oct 2024 12:57:53 +0200 Subject: [PATCH 16/21] revert(pg) --- packages/devextreme/playground/jquery.html | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/packages/devextreme/playground/jquery.html b/packages/devextreme/playground/jquery.html index f659a98c5146..fde766f17218 100644 --- a/packages/devextreme/playground/jquery.html +++ b/packages/devextreme/playground/jquery.html @@ -52,13 +52,9 @@

Te
From 5174f2424e1347796f9f58b7ebf3eeca9f806027 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marker=20dao=20=C2=AE?= Date: Tue, 22 Oct 2024 15:55:50 +0200 Subject: [PATCH 17/21] Chat: Publish onTyping events --- .../devextreme-angular/src/ui/chat/index.ts | 20 +++++++++- packages/devextreme-react/src/chat.ts | 6 ++- packages/devextreme-vue/src/chat.ts | 6 +++ .../devextreme/js/__internal/ui/chat/chat.ts | 9 +---- packages/devextreme/js/ui/chat.d.ts | 40 ++++++++++++++++++- packages/devextreme/js/ui/chat_types.d.ts | 2 + packages/devextreme/ts/dx.all.d.ts | 29 ++++++++++++++ 7 files changed, 101 insertions(+), 11 deletions(-) diff --git a/packages/devextreme-angular/src/ui/chat/index.ts b/packages/devextreme-angular/src/ui/chat/index.ts index 33611204a5e4..923e1b0dfc8c 100644 --- a/packages/devextreme-angular/src/ui/chat/index.ts +++ b/packages/devextreme-angular/src/ui/chat/index.ts @@ -24,7 +24,7 @@ import { import { Store } from 'devextreme/data'; import DataSource, { Options as DataSourceOptions } from 'devextreme/data/data_source'; -import { ChatError, DisposingEvent, InitializedEvent, Message, MessageSendEvent, OptionChangedEvent, User } from 'devextreme/ui/chat'; +import { ChatError, DisposingEvent, InitializedEvent, Message, MessageSendEvent, OptionChangedEvent, TypingEndEvent, TypingStartEvent, User } from 'devextreme/ui/chat'; import DxChat from 'devextreme/ui/chat'; @@ -299,6 +299,22 @@ export class DxChatComponent extends DxComponent implements OnDestroy, OnChanges */ @Output() onOptionChanged: EventEmitter; + /** + + * [descr:dxChatOptions.onTypingEnd] + + + */ + @Output() onTypingEnd: EventEmitter; + + /** + + * [descr:dxChatOptions.onTypingStart] + + + */ + @Output() onTypingStart: EventEmitter; + /** * This member supports the internal infrastructure and is not intended to be used directly from your code. @@ -463,6 +479,8 @@ export class DxChatComponent extends DxComponent implements OnDestroy, OnChanges { subscribe: 'initialized', emit: 'onInitialized' }, { subscribe: 'messageSend', emit: 'onMessageSend' }, { subscribe: 'optionChanged', emit: 'onOptionChanged' }, + { subscribe: 'typingEnd', emit: 'onTypingEnd' }, + { subscribe: 'typingStart', emit: 'onTypingStart' }, { emit: 'accessKeyChange' }, { emit: 'activeStateEnabledChange' }, { emit: 'dataSourceChange' }, diff --git a/packages/devextreme-react/src/chat.ts b/packages/devextreme-react/src/chat.ts index 22572ba2b9ac..a9e5f68284c8 100644 --- a/packages/devextreme-react/src/chat.ts +++ b/packages/devextreme-react/src/chat.ts @@ -8,7 +8,7 @@ import dxChat, { import { Component as BaseComponent, IHtmlOptions, ComponentRef, NestedComponentMeta } from "./core/component"; import NestedOption from "./core/nested-option"; -import type { Message, DisposingEvent, InitializedEvent, MessageSendEvent, User as ChatUser } from "devextreme/ui/chat"; +import type { Message, DisposingEvent, InitializedEvent, MessageSendEvent, TypingEndEvent, TypingStartEvent, User as ChatUser } from "devextreme/ui/chat"; type ReplaceFieldTypes = { [P in keyof TSource]: P extends keyof TReplacement ? TReplacement[P] : TSource[P]; @@ -18,6 +18,8 @@ type IChatOptionsNarrowedEvents = { onDisposing?: ((e: DisposingEvent) => void); onInitialized?: ((e: InitializedEvent) => void); onMessageSend?: ((e: MessageSendEvent) => void); + onTypingEnd?: ((e: TypingEndEvent) => void); + onTypingStart?: ((e: TypingStartEvent) => void); } type IChatOptions = React.PropsWithChildren & IHtmlOptions & { @@ -43,7 +45,7 @@ const Chat = memo( ), [baseRef.current]); const subscribableOptions = useMemo(() => (["items"]), []); - const independentEvents = useMemo(() => (["onDisposing","onInitialized","onMessageSend"]), []); + const independentEvents = useMemo(() => (["onDisposing","onInitialized","onMessageSend","onTypingEnd","onTypingStart"]), []); const defaults = useMemo(() => ({ defaultItems: "items", diff --git a/packages/devextreme-vue/src/chat.ts b/packages/devextreme-vue/src/chat.ts index 03d7e8a84f0d..40806bb420ba 100644 --- a/packages/devextreme-vue/src/chat.ts +++ b/packages/devextreme-vue/src/chat.ts @@ -19,6 +19,8 @@ type AccessibleOptions = Pick & { user?: User }; -type TypingEndEvent = NativeEventInfo & { user?: User }; - type Properties = ChatProperties & { title: string; showDayHeaders: boolean; - onTypingStart?: ((e: TypingStartEvent) => void); - onTypingEnd?: ((e: TypingEndEvent) => void); }; class Chat extends Widget { diff --git a/packages/devextreme/js/ui/chat.d.ts b/packages/devextreme/js/ui/chat.d.ts index 3c1d3f414f1c..16e8576bd7e0 100644 --- a/packages/devextreme/js/ui/chat.d.ts +++ b/packages/devextreme/js/ui/chat.d.ts @@ -42,6 +42,28 @@ export type MessageSendEvent = NativeEventInfo & { + /** @docid _ui_chat_TypingStartEvent.user */ + readonly user?: User; +}; + +/** + * @docid _ui_chat_TypingStartEvent + * @public + * @type object + * @inherits NativeEventInfo + */ +export type TypingEndEvent = NativeEventInfo & { + /** @docid _ui_chat_TypingEndEvent.user */ + readonly user?: User; +}; + /** * @docid * @namespace DevExpress.ui.dxChat @@ -170,12 +192,28 @@ export interface dxChatOptions extends WidgetOptions { errors?: Array; /** * @docid - * @default null + * @default undefined * @type_function_param1 e:{ui/chat:MessageSendEvent} * @action * @public */ onMessageSend?: ((e: MessageSendEvent) => void); + /** + * @docid + * @default undefined + * @type_function_param1 e:{ui/chat:TypingStartEvent} + * @action + * @public + */ + onTypingStart?: ((e: TypingEndEvent) => void); + /** + * @docid + * @default undefined + * @type_function_param1 e:{ui/chat:TypingEndEvent} + * @action + * @public + */ + onTypingEnd?: ((e: TypingEndEvent) => void); } /** diff --git a/packages/devextreme/js/ui/chat_types.d.ts b/packages/devextreme/js/ui/chat_types.d.ts index 4a4fa625a730..ed8ffc19bcaf 100644 --- a/packages/devextreme/js/ui/chat_types.d.ts +++ b/packages/devextreme/js/ui/chat_types.d.ts @@ -3,6 +3,8 @@ export { InitializedEvent, OptionChangedEvent, MessageSendEvent, + TypingStartEvent, + TypingEndEvent, User, ChatError, Message, diff --git a/packages/devextreme/ts/dx.all.d.ts b/packages/devextreme/ts/dx.all.d.ts index 145e80d9389f..defb7d7b081b 100644 --- a/packages/devextreme/ts/dx.all.d.ts +++ b/packages/devextreme/ts/dx.all.d.ts @@ -9527,6 +9527,27 @@ declare module DevExpress.ui { export type OptionChangedEvent = DevExpress.events.EventInfo & DevExpress.events.ChangedOptionInfo; export type Properties = dxChatOptions; + /** + * [descr:_ui_chat_TypingStartEvent] + */ + export type TypingEndEvent = DevExpress.events.NativeEventInfo & { + /** + * [descr:_ui_chat_TypingEndEvent.user] + */ + readonly user?: User; + }; + /** + * [descr:_ui_chat_TypingStartEvent] + */ + export type TypingStartEvent = DevExpress.events.NativeEventInfo< + dxChat, + UIEvent & { target: HTMLInputElement } + > & { + /** + * [descr:_ui_chat_TypingStartEvent.user] + */ + readonly user?: User; + }; } /** * [descr:dxChatOptions] @@ -9565,6 +9586,14 @@ declare module DevExpress.ui { * [descr:dxChatOptions.onMessageSend] */ onMessageSend?: (e: DevExpress.ui.dxChat.MessageSendEvent) => void; + /** + * [descr:dxChatOptions.onTypingStart] + */ + onTypingStart?: (e: DevExpress.ui.dxChat.TypingEndEvent) => void; + /** + * [descr:dxChatOptions.onTypingEnd] + */ + onTypingEnd?: (e: DevExpress.ui.dxChat.TypingEndEvent) => void; } /** * [descr:dxCheckBox] From 1294dbcd5da7be4e9b29dcb480a9cb0fa91cc673 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marker=20dao=20=C2=AE?= Date: Thu, 24 Oct 2024 20:51:28 +0200 Subject: [PATCH 18/21] fix(mb): Fix after rebase --- .../tests/DevExpress.ui.widgets/chatParts/messageBox.tests.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets/chatParts/messageBox.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets/chatParts/messageBox.tests.js index cf68637a38cc..fbae23055004 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets/chatParts/messageBox.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets/chatParts/messageBox.tests.js @@ -513,7 +513,7 @@ QUnit.module('MessageBox', moduleConfig, () => { }); QUnit.test('should be triggered with correct arguments', function(assert) { - assert.expect(4); + assert.expect(3); this.reinit({ onTypingEnd: (e) => { @@ -522,7 +522,6 @@ QUnit.module('MessageBox', moduleConfig, () => { assert.strictEqual(component, this.instance, 'component field is correct'); assert.strictEqual(isRenderer(element), !!config().useJQuery, 'element is correct'); assert.strictEqual($(element).is(this.$element), true, 'element field is correct'); - assert.strictEqual(event.type, 'input', 'e.event.type is correct'); }, }); From d8d8802ffda860b3d5fdf6ea4e2347a72e885a8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marker=20dao=20=C2=AE?= Date: Thu, 24 Oct 2024 20:58:43 +0200 Subject: [PATCH 19/21] fix(chat) --- packages/devextreme/js/ui/chat.d.ts | 2 +- packages/devextreme/ts/dx.all.d.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/devextreme/js/ui/chat.d.ts b/packages/devextreme/js/ui/chat.d.ts index 16e8576bd7e0..6c4011bfdf96 100644 --- a/packages/devextreme/js/ui/chat.d.ts +++ b/packages/devextreme/js/ui/chat.d.ts @@ -54,7 +54,7 @@ export type TypingStartEvent = NativeEventInfo & { /** From b9fa0f44600f0ef4a93dd081dad835354b6e1768 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marker=20dao=20=C2=AE?= Date: Thu, 24 Oct 2024 21:12:00 +0200 Subject: [PATCH 20/21] fix(.d.ts): Add requered events --- packages/devextreme/js/ui/chat.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/devextreme/js/ui/chat.d.ts b/packages/devextreme/js/ui/chat.d.ts index 6c4011bfdf96..36bc321ce863 100644 --- a/packages/devextreme/js/ui/chat.d.ts +++ b/packages/devextreme/js/ui/chat.d.ts @@ -250,7 +250,7 @@ import { CheckedEvents } from '../core'; type FilterOutHidden = Omit; -type EventsIntegrityCheckingHelper = CheckedEvents, Required, 'onMessageSend'>; +type EventsIntegrityCheckingHelper = CheckedEvents, Required, 'onMessageSend' | 'onTypingStart' | 'onTypingEnd'>; /** * @hidden From d95033024037f05793fbc6fb7af74e956a1337ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marker=20dao=20=C2=AE?= Date: Mon, 28 Oct 2024 11:21:54 +0100 Subject: [PATCH 21/21] fix(chat.d.ts): Replace NativeEventInfo with EventInfo --- packages/devextreme/js/ui/chat.d.ts | 4 ++-- packages/devextreme/ts/dx.all.d.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/devextreme/js/ui/chat.d.ts b/packages/devextreme/js/ui/chat.d.ts index 36bc321ce863..d219ae695c7e 100644 --- a/packages/devextreme/js/ui/chat.d.ts +++ b/packages/devextreme/js/ui/chat.d.ts @@ -57,9 +57,9 @@ export type TypingStartEvent = NativeEventInfo & { +export type TypingEndEvent = EventInfo & { /** @docid _ui_chat_TypingEndEvent.user */ readonly user?: User; }; diff --git a/packages/devextreme/ts/dx.all.d.ts b/packages/devextreme/ts/dx.all.d.ts index 8e02edd637f9..337e77a1b6ab 100644 --- a/packages/devextreme/ts/dx.all.d.ts +++ b/packages/devextreme/ts/dx.all.d.ts @@ -9530,7 +9530,7 @@ declare module DevExpress.ui { /** * [descr:_ui_chat_TypingEndEvent] */ - export type TypingEndEvent = DevExpress.events.NativeEventInfo & { + export type TypingEndEvent = DevExpress.events.EventInfo & { /** * [descr:_ui_chat_TypingEndEvent.user] */