diff --git a/app/actions/views/emoji.js b/app/actions/views/emoji.js index 6692fe113..bc2d75825 100644 --- a/app/actions/views/emoji.js +++ b/app/actions/views/emoji.js @@ -2,16 +2,16 @@ // See License.txt for license information. import {addReaction} from 'mattermost-redux/actions/posts'; -import {getPostsInCurrentChannel, makeGetPostsForThread} from 'mattermost-redux/selectors/entities/posts'; +import {getPostIdsInCurrentChannel, makeGetPostIdsForThread} from 'mattermost-redux/selectors/entities/posts'; -const getPostsForThread = makeGetPostsForThread(); +const getPostIdsForThread = makeGetPostIdsForThread(); export function addReactionToLatestPost(emoji, rootId) { return async (dispatch, getState) => { const state = getState(); - const posts = rootId ? getPostsForThread(state, {rootId}) : getPostsInCurrentChannel(state); - const lastPost = posts[0]; + const postIds = rootId ? getPostIdsForThread(state, rootId) : getPostIdsInCurrentChannel(state); + const lastPostId = postIds[0]; - dispatch(addReaction(lastPost.id, emoji)); + dispatch(addReaction(lastPostId, emoji)); }; } diff --git a/app/components/post/index.js b/app/components/post/index.js index a564abf45..a9f60fc2e 100644 --- a/app/components/post/index.js +++ b/app/components/post/index.js @@ -18,11 +18,38 @@ function mapStateToProps(state, ownProps) { const {config, license} = state.entities.general; const roles = getCurrentUserId(state) ? getCurrentUserRoles(state) : ''; + let isFirstReply = true; + let isLastReply = true; + let commentedOnPost = null; + if (ownProps.renderReplies && post && post.root_id) { + if (ownProps.previousPostId) { + const previousPost = getPost(state, ownProps.previousPostId); + + if (previousPost && (previousPost.id === post.root_id || previousPost.root_id === post.root_id)) { + // Previous post is root post or previous post is in same thread + isFirstReply = false; + } else { + // Last post is not a comment on the same message + commentedOnPost = getPost(state, post.root_id); + } + } + + if (ownProps.nextPostId) { + const nextPost = getPost(state, ownProps.nextPostId); + + if (nextPost && nextPost.root_id === post.root_id) { + isLastReply = false; + } + } + } + return { - post, config, currentUserId: getCurrentUserId(state), - highlight: ownProps.highlight, + post, + isFirstReply, + isLastReply, + commentedOnPost, license, roles, theme: getTheme(state) diff --git a/app/components/post/post.js b/app/components/post/post.js index 3ab5ea62d..0fb39cdd0 100644 --- a/app/components/post/post.js +++ b/app/components/post/post.js @@ -41,6 +41,7 @@ class Post extends PureComponent { intl: intlShape.isRequired, style: ViewPropTypes.style, post: PropTypes.object, + postId: PropTypes.string.isRequired, // Used by container // eslint-disable-line no-unused-prop-types renderReplies: PropTypes.bool, isFirstReply: PropTypes.bool, isLastReply: PropTypes.bool, @@ -65,15 +66,27 @@ class Post extends PureComponent { const {config, license, currentUserId, roles, post} = props; this.editDisableAction = new DelayedAction(this.handleEditDisable); - this.state = { - canEdit: canEditPost(config, license, currentUserId, post, this.editDisableAction), - canDelete: canDeletePost(config, license, currentUserId, post, isAdmin(roles), isSystemAdmin(roles)) - }; + if (post) { + this.state = { + canEdit: canEditPost(config, license, currentUserId, post, this.editDisableAction), + canDelete: canDeletePost(config, license, currentUserId, post, isAdmin(roles), isSystemAdmin(roles)) + }; + } else { + this.state = { + canEdit: false, + canDelete: false + }; + } } componentWillReceiveProps(nextProps) { - const {config, license, currentUserId, roles, post} = nextProps; - if (post) { + if (nextProps.config !== this.props.config || + nextProps.license !== this.props.license || + nextProps.currentUserId !== this.props.currentUserId || + nextProps.post !== this.props.post || + nextProps.roles !== this.props.roles) { + const {config, license, currentUserId, roles, post} = nextProps; + this.setState({ canEdit: canEditPost(config, license, currentUserId, post, this.editDisableAction), canDelete: canDeletePost(config, license, currentUserId, post, isAdmin(roles), isSystemAdmin(roles)) diff --git a/app/components/post_list/index.js b/app/components/post_list/index.js index 61db323c5..96c09e38e 100644 --- a/app/components/post_list/index.js +++ b/app/components/post_list/index.js @@ -5,13 +5,20 @@ import {bindActionCreators} from 'redux'; import {connect} from 'react-redux'; import {refreshChannelWithRetry} from 'app/actions/views/channel'; +import {makePreparePostIdsForPostList} from 'app/selectors/post_list'; + import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; import PostList from './post_list'; -function mapStateToProps(state) { - return { - theme: getTheme(state) +function makeMapStateToProps() { + const preparePostIds = makePreparePostIdsForPostList(); + + return (state, ownProps) => { + return { + postIds: preparePostIds(state, ownProps), + theme: getTheme(state) + }; }; } @@ -23,4 +30,4 @@ function mapDispatchToProps(dispatch) { }; } -export default connect(mapStateToProps, mapDispatchToProps)(PostList); +export default connect(makeMapStateToProps, mapDispatchToProps)(PostList); diff --git a/app/components/post_list/post_list.js b/app/components/post_list/post_list.js index e068db993..d8a9a928c 100644 --- a/app/components/post_list/post_list.js +++ b/app/components/post_list/post_list.js @@ -7,19 +7,16 @@ import { StyleSheet, View } from 'react-native'; -import FlatList from 'app/components/inverted_flat_list'; - -import {General} from 'mattermost-redux/constants'; -import {addDatesToPostList} from 'mattermost-redux/utils/post_utils'; import ChannelIntro from 'app/components/channel_intro'; +import FlatList from 'app/components/inverted_flat_list'; import Post from 'app/components/post'; +import {DATE_LINE, START_OF_NEW_MESSAGES} from 'app/selectors/post_list'; + import DateHeader from './date_header'; import LoadMorePosts from './load_more_posts'; import NewMessagesDivider from './new_messages_divider'; -const LOAD_MORE_POSTS = 'load-more-posts'; - export default class PostList extends PureComponent { static propTypes = { actions: PropTypes.shape({ @@ -27,14 +24,15 @@ export default class PostList extends PureComponent { }).isRequired, channelId: PropTypes.string, currentUserId: PropTypes.string, + highlightPostId: PropTypes.string, indicateNewMessages: PropTypes.bool, isSearchResult: PropTypes.bool, - lastViewedAt: PropTypes.number, + lastViewedAt: PropTypes.number, // Used by container // eslint-disable-line no-unused-prop-types loadMore: PropTypes.func, navigator: PropTypes.object, onPostPress: PropTypes.func, onRefresh: PropTypes.func, - posts: PropTypes.array.isRequired, + postIds: PropTypes.array.isRequired, renderReplies: PropTypes.bool, showLoadMore: PropTypes.bool, shouldRenderReplyButton: PropTypes.bool, @@ -52,22 +50,13 @@ export default class PostList extends PureComponent { } } - getPostsWithDates = () => { - const {posts, indicateNewMessages, currentUserId, lastViewedAt, showLoadMore} = this.props; - const list = addDatesToPostList(posts, {indicateNewMessages, currentUserId, lastViewedAt}); - if (showLoadMore) { - return [...list, LOAD_MORE_POSTS]; - } + getItem = (data, index) => data[index]; - return list; - }; + getItemCount = (data) => data.length; keyExtractor = (item) => { - if (item instanceof Date) { - return item.getTime(); - } - - return item.id || item; + // All keys are strings (either post IDs or special keys) + return item; }; onRefresh = () => { @@ -86,19 +75,26 @@ export default class PostList extends PureComponent { } }; - renderChannelIntro = () => { - const {channelId, navigator, showLoadMore} = this.props; - - // FIXME: Only show the channel intro when we are at the very start of the channel - if (channelId && !showLoadMore) { + renderItem = ({item, index}) => { + if (item === START_OF_NEW_MESSAGES) { return ( - - - + ); + } else if (item.indexOf(DATE_LINE) === 0) { + const date = item.substring(DATE_LINE.length); + return this.renderDateHeader(new Date(date)); } - return null; + const postId = item; + + // Remember that the list is rendered with item 0 at the bottom so the "previous" post + // comes after this one in the list + const previousPostId = index < this.props.postIds.length - 1 ? this.props.postIds[index + 1] : null; + const nextPostId = index > 0 ? this.props.postIds[index - 1] : null; + + return this.renderPost(postId, previousPostId, nextPostId); }; renderDateHeader = (date) => { @@ -110,32 +106,9 @@ export default class PostList extends PureComponent { ); }; - renderItem = ({item}) => { - if (item instanceof Date) { - return this.renderDateHeader(item); - } - if (item === General.START_OF_NEW_MESSAGES) { - return ( - - ); - } - if (item === LOAD_MORE_POSTS && this.props.showLoadMore) { - return ( - - ); - } - - return this.renderPost(item); - }; - - getItem = (data, index) => data[index]; - - getItemCount = (data) => data.length; - - renderPost = (post) => { + renderPost = (postId, previousPostId, nextPostId) => { const { + highlightPostId, isSearchResult, navigator, onPostPress, @@ -145,22 +118,42 @@ export default class PostList extends PureComponent { return ( ); }; + renderFooter = () => { + if (this.props.showLoadMore) { + return ; + } else if (this.props.channelId) { + // FIXME: Only show the channel intro when we are at the very start of the channel + return ( + + + + ); + } + + return null; + }; + render() { - const {channelId, loadMore, theme} = this.props; + const { + channelId, + highlightPostId, + loadMore, + postIds, + theme + } = this.props; const refreshControl = { refreshing: false @@ -170,15 +163,15 @@ export default class PostList extends PureComponent { refreshControl.onRefresh = this.onRefresh; } - const data = this.getPostsWithDates(); return ( p.id === ownProps.focusedPostId) : -1; - let posts = []; - - if (focusedPostIndex !== -1) { - const desiredPostIndexBefore = focusedPostIndex - 5; - const minPostIndex = desiredPostIndexBefore < 0 ? 0 : desiredPostIndexBefore; - posts = [...postsAroundPost].splice(minPostIndex, 10); - } + const postIds = getPostIdsAroundPost(state, post.id, post.channel_id, {postsBeforeCount: 5, postsAfterCount: 5}); return { - ...ownProps, channelId: post.channel_id, currentUserId: getCurrentUserId(state), - posts + postIds }; }; } diff --git a/app/components/search_preview/search_preview.js b/app/components/search_preview/search_preview.js index 96c2b53d4..69d35117f 100644 --- a/app/components/search_preview/search_preview.js +++ b/app/components/search_preview/search_preview.js @@ -39,15 +39,16 @@ export default class SearchPreview extends PureComponent { channelId: PropTypes.string, channelName: PropTypes.string, currentUserId: PropTypes.string.isRequired, + focusedPostId: PropTypes.string.isRequired, navigator: PropTypes.object, onClose: PropTypes.func, onPress: PropTypes.func, - posts: PropTypes.array, + postIds: PropTypes.array, theme: PropTypes.object.isRequired }; static defaultProps = { - posts: [] + postIds: [] }; state = { @@ -57,7 +58,7 @@ export default class SearchPreview extends PureComponent { componentWillReceiveProps(nextProps) { const {animationEnded, showPosts} = this.state; - if (animationEnded && !showPosts && nextProps.posts.length) { + if (animationEnded && !showPosts && nextProps.postIds.length) { this.setState({showPosts: true}); } } @@ -82,24 +83,31 @@ export default class SearchPreview extends PureComponent { showPostList = () => { this.setState({animationEnded: true}); - if (!this.state.showPosts && this.props.posts.length) { + if (!this.state.showPosts && this.props.postIds.length) { this.setState({showPosts: true}); } }; render() { - const {channelName, currentUserId, posts, theme} = this.props; + const { + channelName, + currentUserId, + focusedPostId, + postIds, + theme + } = this.props; const style = getStyleSheet(theme); let postList; if (this.state.showPosts) { postList = ( = props.postVisibility + visiblePostIds: this.getVisiblePostIds(props), + showLoadMore: props.postIds.length >= props.postVisibility }; } componentWillReceiveProps(nextProps) { - const {posts: nextPosts} = nextProps; + const {postIds: nextPostIds} = nextProps; - const showLoadMore = nextPosts.length >= nextProps.postVisibility; - let visiblePosts = this.state.visiblePosts; + const showLoadMore = nextPostIds.length >= nextProps.postVisibility; + let visiblePostIds = this.state.visiblePostIds; - if (nextPosts !== this.props.posts || nextProps.postVisibility !== this.props.postVisibility) { - visiblePosts = this.getVisiblePosts(nextProps); + if (nextPostIds !== this.props.postIds || nextProps.postVisibility !== this.props.postVisibility) { + visiblePostIds = this.getVisiblePostIds(nextProps); } this.setState({ showLoadMore, - visiblePosts + visiblePostIds }); } - getVisiblePosts = (props) => { - return props.posts.slice(0, props.postVisibility); + getVisiblePostIds = (props) => { + return props.postIds.slice(0, props.postVisibility); }; goToThread = (post) => { @@ -130,17 +130,17 @@ class ChannelPostList extends PureComponent { currentUserId, lastViewedAt, navigator, - posts, + postIds, theme } = this.props; const { showLoadMore, - visiblePosts + visiblePostIds } = this.state; let component; - if (!posts.length && channelRefreshingFailed) { + if (!postIds.length && channelRefreshingFailed) { component = ( getMyPosts(state, props.postIds), + (state, props) => props.lastViewedAt, + getCurrentUserId, + shouldShowJoinLeaveMessages, + (posts, lastViewedAt, currentUserId, showJoinLeave) => { + const out = []; + + let lastDate = null; + let addedNewMessagesIndicator = false; + + const filterOptions = {showJoinLeave}; + + // Remember that we're iterating through the posts from newest to oldest + for (const post of posts) { + if (post.state === Posts.POST_DELETED && post.user_id === currentUserId) { + continue; + } + + // Filter out join/leave messages if necessary + if (shouldFilterPost(post, filterOptions)) { + continue; + } + + // Only add the new messages line if a lastViewedAt time is set + if (lastViewedAt && !addedNewMessagesIndicator) { + const postIsUnread = post.create_at > lastViewedAt; + + if (postIsUnread) { + out.push(START_OF_NEW_MESSAGES); + addedNewMessagesIndicator = true; + } + } + + // Push on a date header if the last post was on a different day than the current one + const postDate = new Date(post.create_at); + + if (lastDate && lastDate.toDateString() !== postDate.toDateString()) { + out.push(DATE_LINE + lastDate.toDateString()); + } + + lastDate = postDate; + + out.push(post.id); + } + + // Push on the date header for the oldest post + if (lastDate) { + out.push(DATE_LINE + lastDate.toDateString()); + } + + return out; + } + ); +} diff --git a/test/app/selectors/post_list.test.js b/test/app/selectors/post_list.test.js new file mode 100644 index 000000000..01b527513 --- /dev/null +++ b/test/app/selectors/post_list.test.js @@ -0,0 +1,288 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import assert from 'assert'; + +import { + DATE_LINE, + makePreparePostIdsForPostList, + START_OF_NEW_MESSAGES +} from 'app/selectors/post_list'; + +import {Posts, Preferences} from 'mattermost-redux/constants'; +import {getPreferenceKey} from 'mattermost-redux/utils/preference_utils'; + +describe('Selectors.PostList', () => { + describe('makePreparePostIdsForPostList', () => { + it('filter join/leave posts', () => { + const preparePostIdsForPostList = makePreparePostIdsForPostList(); + + let state = { + entities: { + posts: { + posts: { + 1001: {id: '1001', create_at: 0}, + 1002: {id: '1002', create_at: 1, type: Posts.POST_TYPES.JOIN_CHANNEL} + } + }, + preferences: { + myPreferences: {} + }, + users: { + currentUserId: '1234' + } + } + }; + const lastViewedAt = Number.POSITIVE_INFINITY; + const postIds = ['1001', '1002']; + + // Defaults to show post + let now = preparePostIdsForPostList(state, {postIds, lastViewedAt}); + assert.ok(now.indexOf('1001') !== -1); + assert.ok(now.indexOf('1002') !== -1); + + // Show join/leave posts + state = { + ...state, + entities: { + ...state.entities, + preferences: { + ...state.entities.preferences, + myPreferences: { + ...state.entities.preferences.myPreferences, + [getPreferenceKey(Preferences.CATEGORY_ADVANCED_SETTINGS, Preferences.ADVANCED_FILTER_JOIN_LEAVE)]: { + category: Preferences.CATEGORY_ADVANCED_SETTINGS, + name: Preferences.ADVANCED_FILTER_JOIN_LEAVE, + value: 'true' + } + } + } + } + }; + + now = preparePostIdsForPostList(state, {postIds, lastViewedAt}); + assert.ok(now.indexOf('1001') !== -1); + assert.ok(now.indexOf('1002') !== -1); + + // Hide join/leave posts + state = { + ...state, + entities: { + ...state.entities, + preferences: { + ...state.entities.preferences, + myPreferences: { + ...state.entities.preferences.myPreferences, + [getPreferenceKey(Preferences.CATEGORY_ADVANCED_SETTINGS, Preferences.ADVANCED_FILTER_JOIN_LEAVE)]: { + category: Preferences.CATEGORY_ADVANCED_SETTINGS, + name: Preferences.ADVANCED_FILTER_JOIN_LEAVE, + value: 'false' + } + } + } + } + }; + + now = preparePostIdsForPostList(state, {postIds, lastViewedAt}); + assert.ok(now.indexOf('1001') !== -1); + assert.ok(now.indexOf('1002') === -1); + }); + + it('memoization', () => { + const preparePostIdsForPostList = makePreparePostIdsForPostList(); + + // Posts 7 hours apart so they should appear on multiple days + const initialPosts = { + 1001: {id: '1001', create_at: 1 * 60 * 60 * 1000}, + 1002: {id: '1002', create_at: (1 * 60 * 60 * 1000) + 5}, + 1003: {id: '1003', create_at: (1 * 60 * 60 * 1000) + 10}, + 1004: {id: '1004', create_at: 25 * 60 * 60 * 1000}, + 1005: {id: '1005', create_at: (25 * 60 * 60 * 1000) + 5}, + 1006: {id: '1006', create_at: (25 * 60 * 60 * 1000) + 10, type: Posts.POST_TYPES.JOIN_CHANNEL} + }; + let state = { + entities: { + posts: { + posts: initialPosts + }, + preferences: { + myPreferences: { + [getPreferenceKey(Preferences.CATEGORY_ADVANCED_SETTINGS, Preferences.ADVANCED_FILTER_JOIN_LEAVE)]: { + category: Preferences.CATEGORY_ADVANCED_SETTINGS, + name: Preferences.ADVANCED_FILTER_JOIN_LEAVE, + value: 'true' + } + } + }, + users: { + currentUserId: '1234' + } + } + }; + + let postIds = ['1001', '1003', '1004', '1006']; + let lastViewedAt = initialPosts['1001'].create_at + 1; + + let now = preparePostIdsForPostList(state, {postIds, lastViewedAt}); + assert.ok(now.indexOf('1001') !== -1); + assert.ok(now.indexOf('1002') === -1); + assert.ok(now.indexOf('1003') !== -1); + assert.ok(now.indexOf('1004') !== -1); + assert.ok(now.indexOf('1005') === -1); + assert.ok(now.indexOf('1006') !== -1); + assert.ok(now.findIndex((id) => id.startsWith(DATE_LINE)) !== -1); + assert.ok(now.indexOf(START_OF_NEW_MESSAGES) !== -1); + + // No changes + let prev = now; + now = preparePostIdsForPostList(state, {postIds, lastViewedAt}); + assert.equal(now, prev); + assert.ok(now.indexOf('1001') !== -1); + assert.ok(now.indexOf('1002') === -1); + assert.ok(now.indexOf('1003') !== -1); + assert.ok(now.indexOf('1004') !== -1); + assert.ok(now.indexOf('1005') === -1); + assert.ok(now.indexOf('1006') !== -1); + assert.ok(now.findIndex((id) => id.startsWith(DATE_LINE)) !== -1); + assert.ok(now.indexOf(START_OF_NEW_MESSAGES) !== -1); + + // lastViewedAt changed slightly + lastViewedAt = initialPosts['1001'].create_at + 2; + + prev = now; + now = preparePostIdsForPostList(state, {postIds, lastViewedAt}); + assert.equal(now, prev); + assert.ok(now.indexOf('1001') !== -1); + assert.ok(now.indexOf('1002') === -1); + assert.ok(now.indexOf('1003') !== -1); + assert.ok(now.indexOf('1004') !== -1); + assert.ok(now.indexOf('1005') === -1); + assert.ok(now.indexOf('1006') !== -1); + assert.ok(now.findIndex((id) => id.startsWith(DATE_LINE)) !== -1); + assert.ok(now.indexOf(START_OF_NEW_MESSAGES) !== -1); + + // lastViewedAt changed a lot + lastViewedAt += initialPosts['1003'].create_at + 1; + + prev = now; + now = preparePostIdsForPostList(state, {postIds, lastViewedAt}); + assert.notEqual(now, prev); + assert.ok(now.indexOf('1001') !== -1); + assert.ok(now.indexOf('1002') === -1); + assert.ok(now.indexOf('1003') !== -1); + assert.ok(now.indexOf('1004') !== -1); + assert.ok(now.indexOf('1005') === -1); + assert.ok(now.indexOf('1006') !== -1); + assert.ok(now.findIndex((id) => id.startsWith(DATE_LINE)) !== -1); + assert.ok(now.indexOf(START_OF_NEW_MESSAGES) !== -1); + + prev = now; + now = preparePostIdsForPostList(state, {postIds, lastViewedAt}); + assert.equal(now, prev); + + // postIds changed, but still shallowly equal + postIds = [...postIds]; + + prev = now; + now = preparePostIdsForPostList(state, {postIds, lastViewedAt}); + assert.equal(now, prev); + assert.ok(now.indexOf('1001') !== -1); + assert.ok(now.indexOf('1002') === -1); + assert.ok(now.indexOf('1003') !== -1); + assert.ok(now.indexOf('1004') !== -1); + assert.ok(now.indexOf('1005') === -1); + assert.ok(now.indexOf('1006') !== -1); + assert.ok(now.findIndex((id) => id.startsWith(DATE_LINE)) !== -1); + assert.ok(now.indexOf(START_OF_NEW_MESSAGES) !== -1); + + // Post changed, not in postIds + state = { + ...state, + entities: { + ...state.entities, + posts: { + ...state.entities.posts, + posts: { + ...state.entities.posts.posts, + 1007: {id: '1007', create_at: 7 * 60 * 60 * 7 * 1000} + } + } + } + }; + + prev = now; + now = preparePostIdsForPostList(state, {postIds, lastViewedAt}); + assert.equal(now, prev); + assert.ok(now.indexOf('1001') !== -1); + assert.ok(now.indexOf('1002') === -1); + assert.ok(now.indexOf('1003') !== -1); + assert.ok(now.indexOf('1004') !== -1); + assert.ok(now.indexOf('1005') === -1); + assert.ok(now.indexOf('1006') !== -1); + assert.ok(now.findIndex((id) => id.startsWith(DATE_LINE)) !== -1); + assert.ok(now.indexOf(START_OF_NEW_MESSAGES) !== -1); + + // Post changed, in postIds + state = { + ...state, + entities: { + ...state.entities, + posts: { + ...state.entities.posts, + posts: { + ...state.entities.posts.posts, + 1006: {...state.entities.posts.posts['1006'], message: 'abcd'} + } + } + } + }; + + prev = now; + now = preparePostIdsForPostList(state, {postIds, lastViewedAt}); + assert.equal(now, prev); + assert.ok(now.indexOf('1001') !== -1); + assert.ok(now.indexOf('1002') === -1); + assert.ok(now.indexOf('1003') !== -1); + assert.ok(now.indexOf('1004') !== -1); + assert.ok(now.indexOf('1005') === -1); + assert.ok(now.indexOf('1006') !== -1); + assert.ok(now.findIndex((id) => id.startsWith(DATE_LINE)) !== -1); + assert.ok(now.indexOf(START_OF_NEW_MESSAGES) !== -1); + + // Filter changed + state = { + ...state, + entities: { + ...state.entities, + preferences: { + ...state.entities.preferences, + myPreferences: { + ...state.entities.preferences.myPreferences, + [getPreferenceKey(Preferences.CATEGORY_ADVANCED_SETTINGS, Preferences.ADVANCED_FILTER_JOIN_LEAVE)]: { + category: Preferences.CATEGORY_ADVANCED_SETTINGS, + name: Preferences.ADVANCED_FILTER_JOIN_LEAVE, + value: 'false' + } + } + } + } + }; + + prev = now; + now = preparePostIdsForPostList(state, {postIds, lastViewedAt}); + assert.notEqual(now, prev); + assert.ok(now.indexOf('1001') !== -1); + assert.ok(now.indexOf('1002') === -1); + assert.ok(now.indexOf('1003') !== -1); + assert.ok(now.indexOf('1004') !== -1); + assert.ok(now.indexOf('1005') === -1); + assert.ok(now.indexOf('1006') === -1); + assert.ok(now.findIndex((id) => id.startsWith(DATE_LINE)) !== -1); + assert.ok(now.indexOf(START_OF_NEW_MESSAGES) !== -1); + + prev = now; + now = preparePostIdsForPostList(state, {postIds, lastViewedAt}); + assert.equal(now, prev); + }); + }); +}); diff --git a/yarn.lock b/yarn.lock index c9a49d530..a7863aace 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3878,7 +3878,7 @@ makeerror@1.0.x: mattermost-redux@mattermost/mattermost-redux#master: version "0.0.1" - resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/18fa0a2dc2d0648d0346e01f71f096aa835096ac" + resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/f6d97c8a49e9402fcb5d72604751c9a5ae6d7e5a" dependencies: deep-equal "1.0.1" harmony-reflect "1.5.1"