mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
MM-53360 : Scroll to the bottom of channel banner (#24682)
* feat(channel-chat): MM-53360 Scroll to the bottom of channel chat This commit adds new toast to the channel chat, which allows users to scroll to the bottom quickly. As it is needed to be along side with the `Search hint toast`, I needed to adjust the code inside `hint_toast, toast_wrapper` more than expected. - [x] Updated tests/snapshots * refactor(scroll-to-bottom): MM-53360 Replace if block => separated func * style: MM-53360 Fix order of imports * refactor(scroll-to-bottom): MM-53360 Simplify hideScrollToBottonToast * test(scroll-to-bottom): MM-53360 Migrate test from enzyme to react test This commit migrates unit tests from `enzyme` to `react-testing-library`. Besides, it adds new test ids for testing purposes. * fixup! test(scroll-to-bottom): MM-53360 Migrate test from enzyme to react test * style: MM-53360 Fix eslint error * style(hint_toast): MM-53360 Update style to match the design Decrease the size, and increase the font-weight of the shortcut key. See more at: https://www.figma.com/file/gbnx8ydTX0bTFIbJ8NWGfR/MM-53360-Scroll-to-bottom-of-chat?type=design&node-id=1101-21615&mode=dev * feat(scroll-to-bottom): MM-53360 Change condition to show toast - Hide the toast after clicking "Jump to recents". - Do not show the toast if the user dismissed it before. * fixup! feat(scroll-to-bottom): MM-53360 Change condition to show toast --------- Co-authored-by: Mattermost Build <build@mattermost.com>
This commit is contained in:
parent
2c82ff85a5
commit
808c6ec6dc
@ -2,26 +2,23 @@
|
||||
|
||||
exports[`components/HintToast should match snapshot 1`] = `
|
||||
<div
|
||||
className="hint-toast__wrapper"
|
||||
className="hint-toast"
|
||||
data-testid="hint-toast"
|
||||
>
|
||||
<div
|
||||
className="hint-toast"
|
||||
className="hint-toast__message"
|
||||
>
|
||||
<div
|
||||
className="hint-toast__message"
|
||||
>
|
||||
A hint
|
||||
</div>
|
||||
<div
|
||||
className="hint-toast__dismiss"
|
||||
data-testid="dismissHintToast"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<CloseIcon
|
||||
className="close-btn"
|
||||
id="dismissHintToast"
|
||||
/>
|
||||
</div>
|
||||
A hint
|
||||
</div>
|
||||
<div
|
||||
className="hint-toast__dismiss"
|
||||
data-testid="dismissHintToast"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<CloseIcon
|
||||
className="close-btn"
|
||||
id="dismissHintToast"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
@ -1,10 +1,7 @@
|
||||
.hint-toast {
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
padding: 4px 4px 4px 12px;
|
||||
border: 1px solid rgba(var(--center-channel-color-rgb), 0.16);
|
||||
margin-top: 16px;
|
||||
margin-right: 0;
|
||||
margin-left: 0;
|
||||
margin: 0;
|
||||
background-color: var(--center-channel-bg);
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 6px 14px rgba(0, 0, 0, 0.12);
|
||||
@ -14,15 +11,19 @@
|
||||
|
||||
.hint-toast__message {
|
||||
display: inline-block;
|
||||
padding-left: 12px;
|
||||
line-height: 20px;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
|
||||
.shortcut-key {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.hint-toast__dismiss {
|
||||
display: inline-block;
|
||||
padding: 0 8px;
|
||||
padding: 4px;
|
||||
margin-left: 16px;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--center-channel-bg);
|
||||
@ -33,6 +34,7 @@
|
||||
}
|
||||
|
||||
svg {
|
||||
padding-top: 2px; /* Vertical alignment fix */
|
||||
fill: rgba(var(--center-channel-color-rgb), 0.56);
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
@ -7,6 +7,8 @@ import CloseIcon from 'components/widgets/icons/close_icon';
|
||||
|
||||
import './hint_toast.scss';
|
||||
|
||||
export const HINT_TOAST_TESTID = 'hint-toast';
|
||||
|
||||
type Props = {
|
||||
children: React.ReactNode;
|
||||
onDismiss: () => void;
|
||||
@ -20,23 +22,24 @@ export const HintToast: React.FC<Props> = ({children, onDismiss}: Props) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='hint-toast__wrapper'>
|
||||
<div className='hint-toast'>
|
||||
<div
|
||||
className='hint-toast__message'
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
<div
|
||||
className='hint-toast__dismiss'
|
||||
onClick={handleDismiss}
|
||||
data-testid='dismissHintToast'
|
||||
>
|
||||
<CloseIcon
|
||||
className='close-btn'
|
||||
id='dismissHintToast'
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
data-testid={HINT_TOAST_TESTID}
|
||||
className='hint-toast'
|
||||
>
|
||||
<div
|
||||
className='hint-toast__message'
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
<div
|
||||
className='hint-toast__dismiss'
|
||||
onClick={handleDismiss}
|
||||
data-testid='dismissHintToast'
|
||||
>
|
||||
<CloseIcon
|
||||
className='close-btn'
|
||||
id='dismissHintToast'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -141,6 +141,8 @@ type State = {
|
||||
isSearchHintDismissed: boolean;
|
||||
isMobileView?: boolean;
|
||||
isNewMessageLineReached: boolean;
|
||||
showScrollToBottomToast: boolean;
|
||||
isScrollToBottomDismissed: boolean;
|
||||
}
|
||||
|
||||
export default class PostList extends React.PureComponent<Props, State> {
|
||||
@ -175,6 +177,8 @@ export default class PostList extends React.PureComponent<Props, State> {
|
||||
showSearchHint: false,
|
||||
isSearchHintDismissed: false,
|
||||
isNewMessageLineReached: false,
|
||||
showScrollToBottomToast: false,
|
||||
isScrollToBottomDismissed: false,
|
||||
};
|
||||
|
||||
this.listRef = React.createRef();
|
||||
@ -408,7 +412,7 @@ export default class PostList extends React.PureComponent<Props, State> {
|
||||
const didUserScrollBackwards = scrollDirection === 'backward' && !scrollUpdateWasRequested;
|
||||
const didUserScrollForwards = scrollDirection === 'forward' && !scrollUpdateWasRequested;
|
||||
const isOffsetWithInRange = scrollOffset < HEIGHT_TRIGGER_FOR_MORE_POSTS;
|
||||
const offsetFromBottom = (scrollHeight - clientHeight) - scrollOffset;
|
||||
const offsetFromBottom = this.getOffsetFromBottom(scrollOffset, scrollHeight, clientHeight);
|
||||
const shouldLoadNewPosts = offsetFromBottom < HEIGHT_TRIGGER_FOR_MORE_POSTS;
|
||||
|
||||
if (didUserScrollBackwards && isOffsetWithInRange && !this.props.atOldestPost) {
|
||||
@ -458,6 +462,8 @@ export default class PostList extends React.PureComponent<Props, State> {
|
||||
showSearchHint: offsetFromBottom > this.showSearchHintThreshold,
|
||||
});
|
||||
}
|
||||
|
||||
this.updateScrollToBottomToastVisibility(scrollOffset, scrollHeight, clientHeight);
|
||||
};
|
||||
|
||||
getShowSearchHintThreshold = () => {
|
||||
@ -468,9 +474,11 @@ export default class PostList extends React.PureComponent<Props, State> {
|
||||
this.updateAtBottom(this.isAtBottom(scrollOffset, scrollHeight, clientHeight));
|
||||
};
|
||||
|
||||
// Calculate how far the post list is from being scrolled to the bottom
|
||||
getOffsetFromBottom = (scrollOffset: number, scrollHeight: number, clientHeight: number) => scrollHeight - clientHeight - scrollOffset;
|
||||
|
||||
isAtBottom = (scrollOffset: number, scrollHeight: number, clientHeight: number) => {
|
||||
// Calculate how far the post list is from being scrolled to the bottom
|
||||
const offsetFromBottom = scrollHeight - clientHeight - scrollOffset;
|
||||
const offsetFromBottom = this.getOffsetFromBottom(scrollOffset, scrollHeight, clientHeight);
|
||||
|
||||
return offsetFromBottom <= BUFFER_TO_BE_CONSIDERED_BOTTOM && scrollHeight > 0;
|
||||
};
|
||||
@ -512,6 +520,40 @@ export default class PostList extends React.PureComponent<Props, State> {
|
||||
});
|
||||
};
|
||||
|
||||
handleScrollToBottomToastDismiss = () => {
|
||||
this.setState({
|
||||
showScrollToBottomToast: false,
|
||||
isScrollToBottomDismissed: true,
|
||||
});
|
||||
};
|
||||
|
||||
hideScrollToBottomToast = () => {
|
||||
this.setState({
|
||||
showScrollToBottomToast: false,
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
* - Show the scroll-to-bottom toast at the same time as the search-hint toast.
|
||||
* - Only show if the user hasn't dismissed it before, within a session.
|
||||
* - Hide it if the user is at the bottom of the list.
|
||||
*/
|
||||
updateScrollToBottomToastVisibility = (scrollOffset: number, scrollHeight: number, clientHeight: number) => {
|
||||
if (this.state.showScrollToBottomToast && this.state.atBottom) {
|
||||
this.setState({
|
||||
showScrollToBottomToast: false,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.state.isScrollToBottomDismissed) {
|
||||
const offsetFromBottom = this.getOffsetFromBottom(scrollOffset, scrollHeight, clientHeight);
|
||||
this.setState({
|
||||
showScrollToBottomToast: offsetFromBottom > this.showSearchHintThreshold,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
updateFloatingTimestamp = (visibleTopItem: number) => {
|
||||
if (!this.props.isMobileView) {
|
||||
return;
|
||||
@ -627,6 +669,9 @@ export default class PostList extends React.PureComponent<Props, State> {
|
||||
initScrollOffsetFromBottom={this.state.initScrollOffsetFromBottom}
|
||||
onSearchHintDismiss={this.handleSearchHintDismiss}
|
||||
showSearchHintToast={this.state.showSearchHint}
|
||||
showScrollToBottomToast={this.state.showScrollToBottomToast}
|
||||
onScrollToBottomToastDismiss={this.handleScrollToBottomToastDismiss}
|
||||
hideScrollToBottomToast={this.hideScrollToBottomToast}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -0,0 +1,6 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import ScrollToBottomToast from './scroll_to_bottom_toast';
|
||||
|
||||
export default ScrollToBottomToast;
|
@ -0,0 +1,63 @@
|
||||
.scroll-to-bottom-toast {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 4px;
|
||||
padding-left: 12px;
|
||||
|
||||
// background-color: var(--center-channel-color--88);
|
||||
background: linear-gradient(0deg, rgba(63, 67, 80, 0.9) 0%, rgba(63, 67, 80, 0.9) 100%), #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 4px 6px 0 rgba(0, 0, 0, 0.12);
|
||||
color: var(--sidebar-text);
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
gap: 8px;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--center-channel-color);
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: var(--sidebar-text);
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
body.enable-animations & {
|
||||
transition-duration: 0s;
|
||||
transition-property: opacity, visibility;
|
||||
transition-timing-function: ease-in-out;
|
||||
}
|
||||
|
||||
.scroll-to-bottom-toast__message {
|
||||
display: inline-block;
|
||||
line-height: 20px;
|
||||
vertical-align: middle;
|
||||
|
||||
> svg {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.scroll-to-bottom-toast__dismiss {
|
||||
display: inline-block;
|
||||
padding: 4px;
|
||||
border-radius: 4px;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--center-channel-bg-08);
|
||||
}
|
||||
|
||||
svg {
|
||||
padding-top: 1px; // To align vertically
|
||||
fill: rgba(var(--sidebar-text-rgb), 0.56);
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
cursor: pointer;
|
||||
line-height: normal;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import ScrollToBottomToast from './scroll_to_bottom_toast';
|
||||
|
||||
describe('ScrollToBottomToast Component', () => {
|
||||
const mockOnDismiss = jest.fn();
|
||||
const mockOnClick = jest.fn();
|
||||
const mockOnClickEvent = {
|
||||
preventDefault: jest.fn(),
|
||||
stopPropagation: jest.fn(),
|
||||
};
|
||||
|
||||
it('should render ScrollToBottomToast component', () => {
|
||||
const wrapper = shallow(
|
||||
<ScrollToBottomToast
|
||||
onDismiss={mockOnDismiss}
|
||||
onClick={mockOnClick}
|
||||
/>,
|
||||
);
|
||||
|
||||
// Assertions
|
||||
expect(wrapper.find('.scroll-to-bottom-toast')).toHaveLength(1);
|
||||
expect(wrapper.find('UnreadBelowIcon')).toHaveLength(1);
|
||||
expect(wrapper.text()).toContain('Jump to recents');
|
||||
expect(wrapper.find('.scroll-to-bottom-toast__dismiss')).toHaveLength(1);
|
||||
expect(wrapper.find('.close-btn')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should call onClick when clicked', () => {
|
||||
const wrapper = shallow(
|
||||
<ScrollToBottomToast
|
||||
onDismiss={mockOnDismiss}
|
||||
onClick={mockOnClick}
|
||||
/>,
|
||||
);
|
||||
|
||||
// Simulate click
|
||||
wrapper.simulate('click', mockOnClickEvent);
|
||||
|
||||
// Expect the onClick function to be called
|
||||
expect(mockOnClick).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call onDismiss when close button is clicked', () => {
|
||||
const wrapper = shallow(
|
||||
<ScrollToBottomToast
|
||||
onDismiss={mockOnDismiss}
|
||||
onClick={mockOnClick}
|
||||
/>,
|
||||
);
|
||||
|
||||
// Simulate click on the close button
|
||||
wrapper.find('.scroll-to-bottom-toast__dismiss').simulate('click', mockOnClickEvent);
|
||||
|
||||
// Expect the onDismiss function to be called
|
||||
expect(mockOnDismiss).toHaveBeenCalled();
|
||||
|
||||
// Expect to stop propagation to avoid scrolling down on dismissing
|
||||
expect(mockOnClickEvent.stopPropagation).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
@ -0,0 +1,61 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
|
||||
import CloseIcon from 'components/widgets/icons/close_icon';
|
||||
import UnreadBelowIcon from 'components/widgets/icons/unread_below_icon';
|
||||
|
||||
import './scroll_to_bottom_toast.scss';
|
||||
|
||||
export const SCROLL_TO_BOTTOM_TOAST_TESTID = 'scroll-to-bottom-toast';
|
||||
export const SCROLL_TO_BOTTOM_DISMISS_BUTTON_TESTID = 'scroll-to-bottom-toast--dismiss-button';
|
||||
|
||||
type ScrollToBottomToastProps = {
|
||||
onDismiss: () => void;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
export const ScrollToBottomToast = ({onDismiss, onClick}: ScrollToBottomToastProps) => {
|
||||
const {formatMessage} = useIntl();
|
||||
|
||||
const jumpToRecentsMessage = formatMessage({
|
||||
id: 'postlist.toast.scrollToBottom',
|
||||
defaultMessage: 'Jump to recents',
|
||||
});
|
||||
|
||||
const handleScrollToBottom: React.MouseEventHandler<HTMLDivElement> = (e) => {
|
||||
e.preventDefault();
|
||||
onClick();
|
||||
};
|
||||
|
||||
const handleDismiss: React.MouseEventHandler<HTMLDivElement> = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
onDismiss();
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
data-testid={SCROLL_TO_BOTTOM_TOAST_TESTID}
|
||||
className='scroll-to-bottom-toast'
|
||||
onClick={handleScrollToBottom}
|
||||
>
|
||||
<UnreadBelowIcon/>
|
||||
{jumpToRecentsMessage}
|
||||
<div
|
||||
className='scroll-to-bottom-toast__dismiss'
|
||||
onClick={handleDismiss}
|
||||
data-testid={SCROLL_TO_BOTTOM_DISMISS_BUTTON_TESTID}
|
||||
>
|
||||
<CloseIcon
|
||||
className='close-btn'
|
||||
id='dismissScrollToBottomToast'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ScrollToBottomToast;
|
@ -0,0 +1,10 @@
|
||||
.toasts-wrapper {
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 16px;
|
||||
gap: 7px;
|
||||
}
|
@ -1,13 +1,18 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {fireEvent, screen} from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import type {ComponentProps} from 'react';
|
||||
|
||||
import Preferences from 'mattermost-redux/constants/preferences';
|
||||
import {DATE_LINE} from 'mattermost-redux/utils/post_list';
|
||||
|
||||
import {HINT_TOAST_TESTID} from 'components/hint-toast/hint_toast';
|
||||
import {SCROLL_TO_BOTTOM_DISMISS_BUTTON_TESTID, SCROLL_TO_BOTTOM_TOAST_TESTID} from 'components/scroll_to_bottom_toast/scroll_to_bottom_toast';
|
||||
|
||||
import {shallowWithIntl} from 'tests/helpers/intl-test-helper';
|
||||
import {renderWithIntlAndStore} from 'tests/react_testing_utils';
|
||||
import {getHistory} from 'utils/browser_history';
|
||||
import {PostListRowListIds} from 'utils/constants';
|
||||
|
||||
@ -45,6 +50,9 @@ describe('components/ToastWrapper', () => {
|
||||
scrollToUnreadMessages: jest.fn(),
|
||||
showSearchHintToast: true,
|
||||
onSearchHintDismiss: jest.fn(),
|
||||
showScrollToBottomToast: false,
|
||||
onScrollToBottomToastDismiss: jest.fn(),
|
||||
hideScrollToBottomToast: jest.fn(),
|
||||
actions: {
|
||||
updateToastStatus: jest.fn(),
|
||||
},
|
||||
@ -613,4 +621,105 @@ describe('components/ToastWrapper', () => {
|
||||
expect(dismissHandler).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Scroll-to-bottom toast', () => {
|
||||
test('should not be shown when unread toast should be shown', () => {
|
||||
const props = {
|
||||
...baseProps,
|
||||
unreadCountInChannel: 10,
|
||||
newRecentMessagesCount: 5,
|
||||
showScrollToBottomToast: true,
|
||||
};
|
||||
renderWithIntlAndStore(<ToastWrapper {...props}/>);
|
||||
const scrollToBottomToast = screen.queryByTestId(SCROLL_TO_BOTTOM_TOAST_TESTID);
|
||||
expect(scrollToBottomToast).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should not be shown when history toast should be shown', () => {
|
||||
const props = {
|
||||
...baseProps,
|
||||
focusedPostId: 'asdasd',
|
||||
atLatestPost: false,
|
||||
atBottom: false,
|
||||
showScrollToBottomToast: true,
|
||||
};
|
||||
|
||||
renderWithIntlAndStore(<ToastWrapper {...props}/>);
|
||||
|
||||
const scrollToBottomToast = screen.queryByTestId(SCROLL_TO_BOTTOM_TOAST_TESTID);
|
||||
expect(scrollToBottomToast).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should NOT be shown if showScrollToBottomToast is false', () => {
|
||||
const props = {
|
||||
...baseProps,
|
||||
showScrollToBottomToast: false,
|
||||
};
|
||||
|
||||
renderWithIntlAndStore(<ToastWrapper {...props}/>);
|
||||
|
||||
const scrollToBottomToast = screen.queryByTestId(SCROLL_TO_BOTTOM_TOAST_TESTID);
|
||||
expect(scrollToBottomToast).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should be shown when no other toasts are shown', () => {
|
||||
const props = {
|
||||
...baseProps,
|
||||
showSearchHintToast: false,
|
||||
showScrollToBottomToast: true,
|
||||
};
|
||||
|
||||
renderWithIntlAndStore(<ToastWrapper {...props}/>);
|
||||
|
||||
const scrollToBottomToast = screen.queryByTestId(SCROLL_TO_BOTTOM_TOAST_TESTID);
|
||||
expect(scrollToBottomToast).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should be shown along side with Search hint toast', () => {
|
||||
const props = {
|
||||
...baseProps,
|
||||
showSearchHintToast: true,
|
||||
showScrollToBottomToast: true,
|
||||
};
|
||||
|
||||
renderWithIntlAndStore(<ToastWrapper {...props}/>);
|
||||
|
||||
const scrollToBottomToast = screen.queryByTestId(SCROLL_TO_BOTTOM_TOAST_TESTID);
|
||||
const hintToast = screen.queryByTestId(HINT_TOAST_TESTID);
|
||||
|
||||
// Assert that both components exist
|
||||
expect(scrollToBottomToast).toBeInTheDocument();
|
||||
expect(hintToast).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should call scrollToLatestMessages on click, and hide this toast (do not call dismiss function)', () => {
|
||||
const props = {
|
||||
...baseProps,
|
||||
showScrollToBottomToast: true,
|
||||
};
|
||||
|
||||
renderWithIntlAndStore(<ToastWrapper {...props}/>);
|
||||
const scrollToBottomToast = screen.getByTestId(SCROLL_TO_BOTTOM_TOAST_TESTID);
|
||||
fireEvent.click(scrollToBottomToast);
|
||||
|
||||
expect(baseProps.scrollToLatestMessages).toHaveBeenCalledTimes(1);
|
||||
|
||||
// * Do not dismiss the toast, hide it only
|
||||
expect(baseProps.onScrollToBottomToastDismiss).toHaveBeenCalledTimes(0);
|
||||
expect(baseProps.hideScrollToBottomToast).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('should call the dismiss callback', () => {
|
||||
const props = {
|
||||
...baseProps,
|
||||
showScrollToBottomToast: true,
|
||||
};
|
||||
|
||||
renderWithIntlAndStore(<ToastWrapper {...props}/>);
|
||||
const scrollToBottomToastDismiss = screen.getByTestId(SCROLL_TO_BOTTOM_DISMISS_BUTTON_TESTID);
|
||||
fireEvent.click(scrollToBottomToastDismiss);
|
||||
|
||||
expect(baseProps.onScrollToBottomToastDismiss).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -9,6 +9,7 @@ import type {RouteComponentProps} from 'react-router-dom';
|
||||
import {Preferences} from 'mattermost-redux/constants';
|
||||
|
||||
import {HintToast} from 'components/hint-toast/hint_toast';
|
||||
import ScrollToBottomToast from 'components/scroll_to_bottom_toast';
|
||||
import {SearchShortcut} from 'components/search_shortcut';
|
||||
import Timestamp, {RelativeRanges} from 'components/timestamp';
|
||||
import Toast from 'components/toast/toast';
|
||||
@ -20,6 +21,8 @@ import {isKeyPressed} from 'utils/keyboard';
|
||||
import {isIdNotPost, getNewMessageIndex} from 'utils/post_utils';
|
||||
import {localizeMessage} from 'utils/utils';
|
||||
|
||||
import './toast__wrapper.scss';
|
||||
|
||||
const TOAST_TEXT_COLLAPSE_WIDTH = 500;
|
||||
|
||||
const TOAST_REL_RANGES = [
|
||||
@ -42,6 +45,9 @@ export type Props = WrappedComponentProps & RouteComponentProps<{team: string}>
|
||||
updateLastViewedBottomAt: (lastViewedBottom?: number) => void;
|
||||
showSearchHintToast: boolean;
|
||||
onSearchHintDismiss: () => void;
|
||||
showScrollToBottomToast: boolean;
|
||||
onScrollToBottomToastDismiss: () => void;
|
||||
hideScrollToBottomToast: () => void;
|
||||
shouldStartFromBottomWhenUnread: boolean;
|
||||
isNewMessageLineReached: boolean;
|
||||
rootPosts: Record<string, boolean>;
|
||||
@ -67,6 +73,7 @@ type State = {
|
||||
showNewMessagesToast?: boolean;
|
||||
showMessageHistoryToast?: boolean;
|
||||
showUnreadWithBottomStartToast?: boolean;
|
||||
showScrollToBottomToast?: boolean;
|
||||
};
|
||||
|
||||
export class ToastWrapperClass extends React.PureComponent<Props, State> {
|
||||
@ -368,6 +375,7 @@ export class ToastWrapperClass extends React.PureComponent<Props, State> {
|
||||
|
||||
scrollToLatestMessages();
|
||||
this.hideUnreadToast();
|
||||
this.props.hideScrollToBottomToast?.();
|
||||
};
|
||||
|
||||
scrollToUnreadMessages = () => {
|
||||
@ -376,7 +384,7 @@ export class ToastWrapperClass extends React.PureComponent<Props, State> {
|
||||
};
|
||||
|
||||
getToastToRender() {
|
||||
const {atLatestPost, atBottom, width, lastViewedAt, showSearchHintToast} = this.props;
|
||||
const {atLatestPost, atBottom, width, lastViewedAt, showSearchHintToast, showScrollToBottomToast} = this.props;
|
||||
const {showUnreadToast, showNewMessagesToast, showMessageHistoryToast, showUnreadWithBottomStartToast, unreadCount} = this.state;
|
||||
|
||||
const unreadToastProps = {
|
||||
@ -449,13 +457,33 @@ export class ToastWrapperClass extends React.PureComponent<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
const toasts = [];
|
||||
if (showScrollToBottomToast) {
|
||||
toasts.push(
|
||||
<ScrollToBottomToast
|
||||
key='scroll-to-bottom-toast'
|
||||
onClick={this.scrollToLatestMessages}
|
||||
onDismiss={this.props.onScrollToBottomToastDismiss}
|
||||
/>,
|
||||
);
|
||||
}
|
||||
|
||||
if (showSearchHintToast) {
|
||||
return (
|
||||
toasts.push(
|
||||
<HintToast
|
||||
key='search-hint-toast'
|
||||
onDismiss={this.hideSearchHintToast}
|
||||
>
|
||||
{this.getSearchHintToastText()}
|
||||
</HintToast>
|
||||
</HintToast>,
|
||||
);
|
||||
}
|
||||
|
||||
if (toasts.length > 0) {
|
||||
return (
|
||||
<div className='toasts-wrapper'>
|
||||
{toasts}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user