Skip to content

Commit

Permalink
New message indicator only show up for new messages (#2915)
Browse files Browse the repository at this point in the history
* Show new message only when items arrive

* Flush

* Add tests

* Update entry

* Fix ESLint

* Fix tests

* Fix tests

* Fix tests

* Apply PR suggestions

* Fix ESLint
  • Loading branch information
compulim authored Feb 19, 2020
1 parent 6474886 commit e75021f
Show file tree
Hide file tree
Showing 21 changed files with 187 additions and 12 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Fixes [#2838](https://github.com/microsoft/BotFramework-WebChat/issues/2838). Fixed `concatMiddleware` should allow any middleware to call its downstream middleware twice, by [@compulim](https://github.com/compulim) in PR [#2839](https://github.com/microsoft/BotFramework-WebChat/pull/2839)
- Fixes [#2864](https://github.com/microsoft/BotFramework-WebChat/issues/2864). Replaced `DownloadAttachment` and `UploadAttachment` with `FileAttachment`, which shows the download link and icon if the attachment contains the `contentUrl`, by [@compulim](https://github.com/compulim) in PR [#2868](https://github.com/microsoft/BotFramework-WebChat/pull/2868)
- Fixes [#2877](https://github.com/microsoft/BotFramework-WebChat/issues/2877). Updated Cognitive Services Speech Services samples to use both pre-4.8 and 4.8 API signature, by [@compulim](https://github.com/compulim) in PR [#2916](https://github.com/microsoft/BotFramework-WebChat/pull/2916)
- Fixes [#2757](https://github.com/microsoft/BotFramework-WebChat/issues/2757). New message indicator should only show up for new messages, by [@compulim](https://github.com/compulim) in PR [#2915](https://github.com/microsoft/BotFramework-WebChat/pull/2915)

### Changed

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
35 changes: 34 additions & 1 deletion __tests__/hooks/useScrollToEnd.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,29 @@ import uiConnected from '../setup/conditions/uiConnected';
jest.setTimeout(timeouts.test);

test('calling scrollToEnd should scroll to end', async () => {
const { driver, pageObjects } = await setupWebDriver();
const { driver, pageObjects } = await setupWebDriver({
createDirectLine: options => {
const workingDirectLine = window.WebChat.createDirectLine(options);

return {
activity$: new Observable(activityObserver => {
window.WebChatTest.activityObserver = activityObserver;

const subscription = workingDirectLine.activity$.subscribe({
complete: () => activityObserver.complete(),
error: value => activityObserver.error(value),
next: value => activityObserver.next(value)
});

return () => subscription.unsubscribe();
}),
connectionStatus$: workingDirectLine.connectionStatus$,
postActivity: workingDirectLine.postActivity.bind(workingDirectLine),
token: workingDirectLine.token
};
},
setup: () => window.WebChatTest.loadScript('https://unpkg.com/[email protected]/client/core.min.js')
});

await driver.wait(uiConnected(), timeouts.directLine);

Expand All @@ -21,6 +43,17 @@ test('calling scrollToEnd should scroll to end', async () => {

await pageObjects.scrollToTop();

await driver.executeScript(() => {
window.WebChatTest.activityObserver.next({
from: {
id: 'bot',
role: 'bot'
},
text: 'Hello, World!',
type: 'message'
});
});

await driver.wait(scrollToBottomButtonVisible(), timeouts.ui);

expect(await driver.takeScreenshot()).toMatchImageSnapshot(imageSnapshotOptions);
Expand Down
1 change: 0 additions & 1 deletion __tests__/hooks/useTextBox.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ test('calling submit should scroll to end', async () => {

await pageObjects.scrollToTop();

await driver.wait(scrollToBottomButtonVisible(), timeouts.ui);
expect(await driver.takeScreenshot()).toMatchImageSnapshot(imageSnapshotOptions);

await pageObjects.runHook('useTextBoxValue', [], textBoxValue => textBoxValue[1]('Hello, World!'));
Expand Down
88 changes: 85 additions & 3 deletions __tests__/scrollToBottom.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { By } from 'selenium-webdriver';
import { imageSnapshotOptions, timeouts } from './constants.json';

import minNumActivitiesShown from './setup/conditions/minNumActivitiesShown';
import negationOf from './setup/conditions/negationOf';
import scrollToBottomButtonVisible from './setup/conditions/scrollToBottomButtonVisible';
import scrollToBottomCompleted from './setup/conditions/scrollToBottomCompleted';
import suggestedActionsShown from './setup/conditions/suggestedActionsShown';
Expand Down Expand Up @@ -36,7 +37,7 @@ test('should stick to bottom if submitting an Adaptive Card while suggested acti
expect(base64PNG).toMatchImageSnapshot(imageSnapshotOptions);
});

test('clicking "New messages" button should scroll to end and stick to bottom', async () => {
test('should scroll to bottom on send', async () => {
const { driver, pageObjects } = await setupWebDriver();

await driver.wait(uiConnected(), timeouts.directLine);
Expand All @@ -47,17 +48,98 @@ test('clicking "New messages" button should scroll to end and stick to bottom',

await pageObjects.scrollToTop();

await driver.wait(negationOf(scrollToBottomButtonVisible()), timeouts.ui);

// Should scroll to bottom on send

await pageObjects.sendMessageViaSendBox('Hello, World!');
await driver.wait(scrollToBottomCompleted(), timeouts.scrollToBottom);

await driver.wait(minNumActivitiesShown(4), timeouts.directLine);

expect(await driver.takeScreenshot()).toMatchImageSnapshot(imageSnapshotOptions);
});

test('show "New messages" button only when new message come', async () => {
const { driver, pageObjects } = await setupWebDriver({
createDirectLine: options => {
const workingDirectLine = window.WebChat.createDirectLine(options);

return {
activity$: new Observable(activityObserver => {
window.WebChatTest.activityObserver = activityObserver;

const subscription = workingDirectLine.activity$.subscribe({
complete: () => activityObserver.complete(),
error: value => activityObserver.error(value),
next: value => activityObserver.next(value)
});

return () => subscription.unsubscribe();
}),
connectionStatus$: workingDirectLine.connectionStatus$,
postActivity: workingDirectLine.postActivity.bind(workingDirectLine),
token: workingDirectLine.token
};
},
setup: () => window.WebChatTest.loadScript('https://unpkg.com/[email protected]/client/core.min.js')
});

await driver.wait(uiConnected(), timeouts.directLine);

await pageObjects.sendMessageViaSendBox('help');
await driver.wait(minNumActivitiesShown(2), timeouts.directLine);
await driver.wait(scrollToBottomCompleted(), timeouts.scrollToBottom);

// Should not show "New message" button because no new message coming in

await pageObjects.scrollToTop();

await driver.wait(negationOf(scrollToBottomButtonVisible()), timeouts.ui);

expect(await driver.takeScreenshot()).toMatchImageSnapshot(imageSnapshotOptions);

// Should show "New message" button when new message came in

await driver.executeScript(() => {
window.WebChatTest.activityObserver.next({
from: {
id: 'bot',
role: 'bot'
},
text: 'Hello, World!',
type: 'message'
});
});

await driver.wait(scrollToBottomButtonVisible(), timeouts.ui);

expect(await driver.takeScreenshot()).toMatchImageSnapshot(imageSnapshotOptions);

await pageObjects.clickScrollToBottomButton();
await driver.wait(scrollToBottomCompleted(), timeouts.scrollToBottom);
await driver.wait(negationOf(scrollToBottomButtonVisible()), timeouts.ui);

// Should stick to bottom

await driver.executeScript(() => {
window.WebChatTest.activityObserver.next({
from: {
id: 'bot',
role: 'bot'
},
text: 'Aloha!',
type: 'message'
});
});

await driver.wait(minNumActivitiesShown(4), timeouts.directLine);

expect(await driver.takeScreenshot()).toMatchImageSnapshot(imageSnapshotOptions);

await pageObjects.sendMessageViaSendBox('Hello, World!');
await driver.wait(scrollToBottomCompleted(), timeouts.scrollToBottom);
await pageObjects.scrollToTop();

await driver.wait(negationOf(scrollToBottomButtonVisible()), timeouts.ui);

expect(await driver.takeScreenshot()).toMatchImageSnapshot(imageSnapshotOptions);
});
35 changes: 34 additions & 1 deletion __tests__/styleOptions.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,29 @@ describe('style options', () => {
});

test('hide scroll to bottom button', async () => {
const { driver, pageObjects } = await setupWebDriver();
const { driver, pageObjects } = await setupWebDriver({
createDirectLine: options => {
const workingDirectLine = window.WebChat.createDirectLine(options);

return {
activity$: new Observable(activityObserver => {
window.WebChatTest.activityObserver = activityObserver;

const subscription = workingDirectLine.activity$.subscribe({
complete: () => activityObserver.complete(),
error: value => activityObserver.error(value),
next: value => activityObserver.next(value)
});

return () => subscription.unsubscribe();
}),
connectionStatus$: workingDirectLine.connectionStatus$,
postActivity: workingDirectLine.postActivity.bind(workingDirectLine),
token: workingDirectLine.token
};
},
setup: () => window.WebChatTest.loadScript('https://unpkg.com/[email protected]/client/core.min.js')
});

await driver.wait(uiConnected(), timeouts.directLine);
await pageObjects.sendMessageViaSendBox('markdown', { waitForSend: true });
Expand All @@ -50,6 +72,17 @@ describe('style options', () => {

await pageObjects.scrollToTop();

await driver.executeScript(() => {
window.WebChatTest.activityObserver.next({
from: {
id: 'bot',
role: 'bot'
},
text: 'Hello, World!',
type: 'message'
});
});

await driver.wait(scrollToBottomButtonVisible(), timeouts.ui);
expect(await driver.takeScreenshot()).toMatchImageSnapshot(imageSnapshotOptions);

Expand Down
39 changes: 33 additions & 6 deletions packages/component/src/Activity/ScrollToEndButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@ import { StateContext as ScrollToBottomStateContext } from 'react-scroll-to-bott

import classNames from 'classnames';
import PropTypes from 'prop-types';
import React, { useCallback } from 'react';
import React, { useCallback, useRef } from 'react';

import useActivities from '../hooks/useActivities';
import useDirection from '../hooks/useDirection';
import useFocusSendBox from '../hooks/useFocusSendBox';
import useLocalizer from '../hooks/useLocalizer';
import useScrollToEnd from '../hooks/useScrollToEnd';
import useStyleSet from '../hooks/useStyleSet';

const ScrollToEndButton = ({ className }) => {
const [direction] = useDirection();
const ScrollToEndButton = ({ animating, className, sticky }) => {
const [{ scrollToEndButton: scrollToEndButtonStyleSet }] = useStyleSet();
const [activities] = useActivities();
const [direction] = useDirection();
const focusSendBox = useFocusSendBox();
const localize = useLocalizer();
const scrollToEnd = useScrollToEnd();
Expand All @@ -22,6 +24,29 @@ const ScrollToEndButton = ({ className }) => {
focusSendBox();
}, [focusSendBox, scrollToEnd]);

const newMessageText = localize('TRANSCRIPT_NEW_MESSAGES');

// Ignore activity types other than "message"
const lastMessageActivity = [...activities].reverse().find(({ type }) => type === 'message');
const lastShownActivityId = (lastMessageActivity || {}).id;
const lastReadActivityIdRef = useRef(lastShownActivityId);

const { current: lastReadActivityId } = lastReadActivityIdRef;

if (sticky) {
// If it is sticky, mark the activity ID as read.
lastReadActivityIdRef.current = lastShownActivityId;
}

// Don't show the button if:
// - The scroll bar is animating
// - Otherwise, this will cause a flashy button when: 1. Scroll to top, 2. Send something, 3. The button flashes when it is scrolling down
// - It is already at the bottom (sticky)
// - The last activity ID has been read
if (animating || sticky || lastShownActivityId === lastReadActivityId) {
return false;
}

return (
<button
className={classNames(
Expand All @@ -33,7 +58,7 @@ const ScrollToEndButton = ({ className }) => {
onClick={handleClick}
type="button"
>
{localize('TRANSCRIPT_NEW_MESSAGES')}
{newMessageText}
</button>
);
};
Expand All @@ -43,12 +68,14 @@ ScrollToEndButton.defaultProps = {
};

ScrollToEndButton.propTypes = {
className: PropTypes.string
animating: PropTypes.bool.isRequired,
className: PropTypes.string,
sticky: PropTypes.bool.isRequired
};

const ConnectedScrollToEndButton = props => (
<ScrollToBottomStateContext.Consumer>
{({ sticky }) => !sticky && <ScrollToEndButton {...props} />}
{({ animating, sticky }) => <ScrollToEndButton animating={animating} sticky={sticky} {...props} />}
</ScrollToBottomStateContext.Consumer>
);

Expand Down

0 comments on commit e75021f

Please sign in to comment.