diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ca9b846ab..1932885b01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Fixes [#3565](https://github.com/microsoft/BotFramework-WebChat/issues/3565). Allow strikethrough `` on sanitize markdown, by [@corinagum](https://github.com/corinagum) in PR [#3646](https://github.com/microsoft/BotFramework-WebChat/pull/3646) - Fixes [#3672](https://github.com/microsoft/BotFramework-WebChat/issues/3672). Center the icon of send box buttons vertically and horizontally, by [@compulim](https://github.com/compulim) in PR [#3673](https://github.com/microsoft/BotFramework-WebChat/pull/3673) - Fixes [#3683](https://github.com/microsoft/BotFramework-WebChat/issues/3683). Activities should be acknowledged when user scrolls to bottom, by [@compulim](https://github.com/compulim) in PR [#3684](https://github.com/microsoft/BotFramework-WebChat/pull/3684) +- Fixes [#3431](https://github.com/microsoft/BotFramework-WebChat/issues/3431). Race condition between the first bot activity and first user activity, which should not cause the first bot activity to be delayed, by [@compulim](https://github.com/compulim) in PR [#3705](https://github.com/microsoft/BotFramework-WebChat/pull/3705) - Fixes [#3676](https://github.com/microsoft/BotFramework-WebChat/issues/3676). Activities without text should not generate bogus `aria-labelledby`, by [@compulim](https://github.com/compulim) in PR [#3697](https://github.com/microsoft/BotFramework-WebChat/pull/3697) - Fixes [#3625](https://github.com/microsoft/BotFramework-WebChat/issues/3625). Update 'no screen reader for custom activity middleware' warning and add screen reader renderer documentation to `ACCESSIBILITY.md`, by [@corinagum](https://github.com/corinagum) in PR [#3689](https://github.com/microsoft/BotFramework-WebChat/pull/3689) diff --git a/__tests__/html/accessibility.delayActivity.raceCondition.html b/__tests__/html/accessibility.delayActivity.raceCondition.html new file mode 100644 index 0000000000..b9f4cc7763 --- /dev/null +++ b/__tests__/html/accessibility.delayActivity.raceCondition.html @@ -0,0 +1,113 @@ + + + + + + + +
+ + + diff --git a/__tests__/html/accessibility.delayActivity.raceCondition.js b/__tests__/html/accessibility.delayActivity.raceCondition.js new file mode 100644 index 0000000000..9148cdef6b --- /dev/null +++ b/__tests__/html/accessibility.delayActivity.raceCondition.js @@ -0,0 +1,8 @@ +/** + * @jest-environment ./__tests__/html/__jest__/WebChatEnvironment.js + */ + +describe('accessibility requirement', () => { + test('race conditions between first bot activity and first user activity should not cause any delay to the first bot activity', () => + runHTMLTest('accessibility.delayActivity.raceCondition.html')); +}); diff --git a/__tests__/html/accessibility.delayActivity.withReplyToId.html b/__tests__/html/accessibility.delayActivity.withReplyToId.html index a47e54b6e8..f1f5ae2a08 100644 --- a/__tests__/html/accessibility.delayActivity.withReplyToId.html +++ b/__tests__/html/accessibility.delayActivity.withReplyToId.html @@ -13,7 +13,7 @@ WebChatTest: { conditions, createStore, host, pageObjects, timeouts } } = window; - (async function() { + (async function () { const clock = lolex.install(); const directLine = createDirectLineForTest({ withReplyToId: true }); @@ -30,6 +30,9 @@ // Wait for "Connecting..." message to dismiss clock.tick(600); + // TODO: [P1] #3707 This line should be removed when the SDK removes the bogus replyToId. + directLine.botSendMessage({ replyToId: 'bogus', type: 'event' }); + await pageObjects.sendMessageViaSendBox('Hello, World!', { waitForSend: false }); // This screenshot should show only user-initiated message "Hello, World!" with "Sending" status. diff --git a/__tests__/html/assets/accessibility.delayActivity.js b/__tests__/html/assets/accessibility.delayActivity.js index b2a7d9fdb0..ea2d7765ad 100644 --- a/__tests__/html/assets/accessibility.delayActivity.js +++ b/__tests__/html/assets/accessibility.delayActivity.js @@ -19,10 +19,19 @@ window.TestAsset = { activity$: shareObservable(activityDeferred$.observable), connectionStatus$: shareObservable(connectionStatusDeferred$.observable), numActivities: 0, + botSendMessage: activity => { + activityDeferred$.next({ + from: { + role: 'bot' + }, + id: Math.random().toString(36).substr(2, 10), + timestamp: new Date().toISOString(), + type: 'message', + ...activity + }); + }, postActivity: activity => { - const id = Math.random() - .toString(36) - .substr(2, 10); + const id = Math.random().toString(36).substr(2, 10); const now = Date.now(); const timestamp = new Date(now).toISOString(); const postActivityDeferred$ = createDeferredObservable(); @@ -31,9 +40,7 @@ window.TestAsset = { from: { role: 'bot' }, - id: Math.random() - .toString(36) - .substr(2, 10), + id: Math.random().toString(36).substr(2, 10), text: `You said: ${activity.text}`, timestamp: new Date(now + 1).toISOString(), type: 'message', diff --git a/packages/core/src/sagas/queueIncomingActivitySaga.js b/packages/core/src/sagas/queueIncomingActivitySaga.js index bd9db9781a..ad62c5623f 100644 --- a/packages/core/src/sagas/queueIncomingActivitySaga.js +++ b/packages/core/src/sagas/queueIncomingActivitySaga.js @@ -37,7 +37,13 @@ function* waitForActivityId(replyToId, initialActivities) { break; } - yield take(INCOMING_ACTIVITY); + const { + payload: { activity } + } = yield take(INCOMING_ACTIVITY); + + if (activity.id === replyToId) { + break; + } activities = yield select(activitiesSelector); } @@ -48,14 +54,15 @@ function* queueIncomingActivity({ userID }) { { payload: { activity } }, initialActivities ) { - // This is for accessibility issue. + // This is for resolving an accessibility issue. // If the incoming activity has "replyToId" field, hold on it until the activity replied to is in the transcript, then release this one. const { replyToId } = activity; + const initialBotActivities = initialActivities.filter(({ from: { role } }) => role === 'bot'); - // To speed up the first activity render time, we do not delay the first activity. + // To speed up the first activity render time, we do not delay the first activity from the bot. // Even if it is the first activity from the bot, the bot might be "replying" to the "conversationUpdate" event. // Thus, the "replyToId" will always be there even it is the first activity in the conversation. - if (replyToId && initialActivities.length) { + if (replyToId && initialBotActivities.length) { // Either the activity replied to is in the transcript or after timeout. const result = yield race({ _: waitForActivityId(replyToId, initialActivities), diff --git a/packages/testharness/src/index.js b/packages/testharness/src/index.js index dc58752b3e..3bc97ec9b0 100644 --- a/packages/testharness/src/index.js +++ b/packages/testharness/src/index.js @@ -10,6 +10,7 @@ import classNames from 'classnames'; import createDeferred from 'p-defer-es5'; import expect from 'expect'; import lolex from 'lolex'; +import Observable from 'core-js/features/observable'; import updateIn from 'simple-update-in'; import { EventIterator } from './external/event-iterator'; @@ -174,6 +175,7 @@ export { jobs, loadTranscriptAsset, MockAudioContext, + Observable, pageObjects, parseURLParams, pcmWaveArrayBufferToRiffWaveArrayBuffer, diff --git a/packages/testharness/src/utils/createDirectLineWithTranscript.js b/packages/testharness/src/utils/createDirectLineWithTranscript.js index 98685ac602..daa77d3f94 100644 --- a/packages/testharness/src/utils/createDirectLineWithTranscript.js +++ b/packages/testharness/src/utils/createDirectLineWithTranscript.js @@ -25,7 +25,7 @@ function updateRelativeTimestamp(now, activity) { }; } -export default function createDirectLineWithTranscript(activitiesOrFilename) { +export default function createDirectLineWithTranscript(activitiesOrFilename, { overridePostActivity } = {}) { const now = Date.now(); const patchActivity = updateRelativeTimestamp.bind(null, now); const connectionStatusDeferredObservable = createDeferredObservable(() => { @@ -61,6 +61,10 @@ export default function createDirectLineWithTranscript(activitiesOrFilename) { connectionStatusDeferredObservable, end: () => {}, postActivity: activity => { + if (overridePostActivity) { + return overridePostActivity(activity); + } + const id = Math.random().toString(36).substr(2, 5); activityDeferredObservable.next(