UNPKG

mattermost-redux

Version:

Common code (API client, Redux stores, logic, utility functions) for building a Mattermost client

1,391 lines (1,200 loc) 142 kB
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. import assert from 'assert'; import expect from 'expect'; import { ChannelTypes, GeneralTypes, PostTypes, } from 'action_types'; import {Posts} from '../../constants'; import * as reducers from 'reducers/entities/posts'; import deepFreeze from 'utils/deep_freeze'; describe('posts', () => { for (const actionType of [ PostTypes.RECEIVED_POST, PostTypes.RECEIVED_NEW_POST, ]) { describe(`received a single post (${actionType})`, () => { it('should add a new post', () => { const state = deepFreeze({ post1: {id: 'post1'}, }); const nextState = reducers.handlePosts(state, { type: actionType, data: {id: 'post2'}, }); expect(nextState).not.toBe(state); expect(nextState.post1).toBe(state.post1); expect(nextState).toEqual({ post1: {id: 'post1'}, post2: {id: 'post2'}, }); }); it('should add a new pending post', () => { const state = deepFreeze({ post1: {id: 'post1'}, }); const nextState = reducers.handlePosts(state, { type: actionType, data: {id: 'post2', pending_post_id: 'post2'}, }); expect(nextState).not.toBe(state); expect(nextState.post1).toBe(state.post1); expect(nextState).toEqual({ post1: {id: 'post1'}, post2: {id: 'post2', pending_post_id: 'post2'}, }); }); it('should update an existing post', () => { const state = deepFreeze({ post1: {id: 'post1', message: '123'}, }); const nextState = reducers.handlePosts(state, { type: actionType, data: {id: 'post1', message: 'abc'}, }); expect(nextState).not.toBe(state); expect(nextState.post1).not.toBe(state.post1); expect(nextState).toEqual({ post1: {id: 'post1', message: 'abc'}, }); }); it('should remove any pending posts when receiving the actual post', () => { const state = deepFreeze({ pending: {id: 'pending'}, }); const nextState = reducers.handlePosts(state, { type: actionType, data: {id: 'post1', pending_post_id: 'pending'}, }); expect(nextState).not.toBe(state); expect(nextState).toEqual({ post1: {id: 'post1', pending_post_id: 'pending'}, }); }); }); } describe('received multiple posts', () => { it('should do nothing when post list is empty', () => { const state = deepFreeze({ post1: {id: 'post1'}, }); const nextState = reducers.handlePosts(state, { type: PostTypes.RECEIVED_POSTS, data: { order: [], posts: {}, }, }); expect(nextState).toBe(state); }); it('should add new posts', () => { const state = deepFreeze({ post1: {id: 'post1'}, }); const nextState = reducers.handlePosts(state, { type: PostTypes.RECEIVED_POSTS, data: { order: ['post2', 'post3'], posts: { post2: {id: 'post2'}, post3: {id: 'post3'}, }, }, }); expect(nextState).not.toBe(state); expect(nextState.post1).toBe(state.post1); expect(nextState).toEqual({ post1: {id: 'post1'}, post2: {id: 'post2'}, post3: {id: 'post3'}, }); }); it('should update existing posts unless we have a more recent version', () => { const state = deepFreeze({ post1: {id: 'post1', message: '123', update_at: 1000}, post2: {id: 'post2', message: '456', update_at: 1000}, }); const nextState = reducers.handlePosts(state, { type: PostTypes.RECEIVED_POSTS, data: { order: ['post1', 'post2'], posts: { post1: {id: 'post1', message: 'abc', update_at: 2000}, post2: {id: 'post2', message: 'def', update_at: 500}, }, }, }); expect(nextState).not.toBe(state); expect(nextState.post1).not.toBe(state.post1); expect(nextState.post2).toBe(state.post2); expect(nextState).toEqual({ post1: {id: 'post1', message: 'abc', update_at: 2000}, post2: {id: 'post2', message: '456', update_at: 1000}, }); }); it('should set state for deleted posts', () => { const state = deepFreeze({ post1: {id: 'post1', message: '123', delete_at: 0, file_ids: ['file']}, post2: {id: 'post2', message: '456', delete_at: 0, has_reactions: true}, }); const nextState = reducers.handlePosts(state, { type: PostTypes.RECEIVED_POSTS, data: { order: ['post1', 'post2'], posts: { post1: {id: 'post1', message: '123', delete_at: 2000, file_ids: ['file']}, post2: {id: 'post2', message: '456', delete_at: 500, has_reactions: true}, }, }, }); expect(nextState).not.toBe(state); expect(nextState.post1).not.toBe(state.post1); expect(nextState.post2).not.toBe(state.post2); expect(nextState).toEqual({ post1: {id: 'post1', message: '123', delete_at: 2000, file_ids: [], has_reactions: false, state: Posts.POST_DELETED}, post2: {id: 'post2', message: '456', delete_at: 500, file_ids: [], has_reactions: false, state: Posts.POST_DELETED}, }); }); it('should remove any pending posts when receiving the actual post', () => { const state = deepFreeze({ pending1: {id: 'pending1'}, pending2: {id: 'pending2'}, }); const nextState = reducers.handlePosts(state, { type: PostTypes.RECEIVED_POSTS, data: { order: ['post1', 'post2'], posts: { post1: {id: 'post1', pending_post_id: 'pending1'}, post2: {id: 'post2', pending_post_id: 'pending2'}, }, }, }); expect(nextState).not.toBe(state); expect(nextState).toEqual({ post1: {id: 'post1', pending_post_id: 'pending1'}, post2: {id: 'post2', pending_post_id: 'pending2'}, }); }); it('should not add channelId entity to postsInChannel if there were no posts in channel and it has receivedNewPosts on action', () => { const state = deepFreeze({ posts: {}, postsInChannel: {}, }); const action = { type: PostTypes.RECEIVED_POSTS, data: { order: ['postId'], posts: { postId: { id: 'postId', }, }, }, channelId: 'channelId', receivedNewPosts: true, }; const nextState = reducers.handlePosts(state, action); assert.deepEqual(nextState.postsInChannel, {}); }); }); describe(`deleting a post (${PostTypes.POST_DELETED})`, () => { it('should mark the post as deleted and remove the rest of the thread', () => { const state = deepFreeze({ post1: {id: 'post1', file_ids: ['file'], has_reactions: true}, comment1: {id: 'comment1', root_id: 'post1'}, comment2: {id: 'comment2', root_id: 'post1'}, }); const nextState = reducers.handlePosts(state, { type: PostTypes.POST_DELETED, data: {id: 'post1'}, }); expect(nextState).not.toBe(state); expect(nextState.post1).not.toBe(state.post1); expect(nextState).toEqual({ post1: {id: 'post1', file_ids: [], has_reactions: false, state: Posts.POST_DELETED}, }); }); it('should not remove the rest of the thread when deleting a comment', () => { const state = deepFreeze({ post1: {id: 'post1'}, comment1: {id: 'comment1', root_id: 'post1'}, comment2: {id: 'comment2', root_id: 'post1'}, }); const nextState = reducers.handlePosts(state, { type: PostTypes.POST_DELETED, data: {id: 'comment1'}, }); expect(nextState).not.toBe(state); expect(nextState.post1).toBe(state.post1); expect(nextState.comment1).not.toBe(state.comment1); expect(nextState.comment2).toBe(state.comment2); expect(nextState).toEqual({ post1: {id: 'post1'}, comment1: {id: 'comment1', root_id: 'post1', file_ids: [], has_reactions: false, state: Posts.POST_DELETED}, comment2: {id: 'comment2', root_id: 'post1'}, }); }); it('should do nothing if the post is not loaded', () => { const state = deepFreeze({ post1: {id: 'post1', file_ids: ['file'], has_reactions: true}, }); const nextState = reducers.handlePosts(state, { type: PostTypes.POST_DELETED, data: {id: 'post2'}, }); expect(nextState).toBe(state); expect(nextState.post1).toBe(state.post1); }); }); describe(`removing a post (${PostTypes.POST_REMOVED})`, () => { it('should remove the post and the rest and the rest of the thread', () => { const state = deepFreeze({ post1: {id: 'post1', file_ids: ['file'], has_reactions: true}, comment1: {id: 'comment1', root_id: 'post1'}, comment2: {id: 'comment2', root_id: 'post1'}, post2: {id: 'post2'}, }); const nextState = reducers.handlePosts(state, { type: PostTypes.POST_REMOVED, data: {id: 'post1'}, }); expect(nextState).not.toBe(state); expect(nextState.post2).toBe(state.post2); expect(nextState).toEqual({ post2: {id: 'post2'}, }); }); it('should not remove the rest of the thread when removing a comment', () => { const state = deepFreeze({ post1: {id: 'post1'}, comment1: {id: 'comment1', root_id: 'post1'}, comment2: {id: 'comment2', root_id: 'post1'}, post2: {id: 'post2'}, }); const nextState = reducers.handlePosts(state, { type: PostTypes.POST_REMOVED, data: {id: 'comment1'}, }); expect(nextState).not.toBe(state); expect(nextState.post1).toBe(state.post1); expect(nextState.comment1).not.toBe(state.comment1); expect(nextState.comment2).toBe(state.comment2); expect(nextState).toEqual({ post1: {id: 'post1'}, comment2: {id: 'comment2', root_id: 'post1'}, post2: {id: 'post2'}, }); }); it('should do nothing if the post is not loaded', () => { const state = deepFreeze({ post1: {id: 'post1', file_ids: ['file'], has_reactions: true}, }); const nextState = reducers.handlePosts(state, { type: PostTypes.POST_REMOVED, data: {id: 'post2'}, }); expect(nextState).toBe(state); expect(nextState.post1).toBe(state.post1); }); }); for (const actionType of [ ChannelTypes.RECEIVED_CHANNEL_DELETED, ChannelTypes.DELETE_CHANNEL_SUCCESS, ChannelTypes.LEAVE_CHANNEL, ]) { describe(`when a channel is deleted (${actionType})`, () => { it('should remove any posts in that channel', () => { const state = deepFreeze({ post1: {id: 'post1', channel_id: 'channel1'}, post2: {id: 'post2', channel_id: 'channel1'}, post3: {id: 'post3', channel_id: 'channel2'}, }); const nextState = reducers.handlePosts(state, { type: actionType, data: { id: 'channel1', viewArchivedChannels: false, }, }); expect(nextState).not.toBe(state); expect(nextState.post3).toBe(state.post3); expect(nextState).toEqual({ post3: {id: 'post3', channel_id: 'channel2'}, }); }); it('should do nothing if no posts in that channel are loaded', () => { const state = deepFreeze({ post1: {id: 'post1', channel_id: 'channel1'}, post2: {id: 'post2', channel_id: 'channel1'}, post3: {id: 'post3', channel_id: 'channel2'}, }); const nextState = reducers.handlePosts(state, { type: actionType, data: { id: 'channel3', viewArchivedChannels: false, }, }); expect(nextState).toBe(state); expect(nextState.post1).toBe(state.post1); expect(nextState.post2).toBe(state.post2); expect(nextState.post3).toBe(state.post3); }); it('should not remove any posts with viewArchivedChannels enabled', () => { const state = deepFreeze({ post1: {id: 'post1', channel_id: 'channel1'}, post2: {id: 'post2', channel_id: 'channel1'}, post3: {id: 'post3', channel_id: 'channel2'}, }); const nextState = reducers.handlePosts(state, { type: actionType, data: { id: 'channel1', viewArchivedChannels: true, }, }); expect(nextState).toBe(state); expect(nextState.post1).toBe(state.post1); expect(nextState.post2).toBe(state.post2); expect(nextState.post3).toBe(state.post3); }); }); } }); describe('pendingPostIds', () => { describe('making a new pending post', () => { it('should add new entries for pending posts', () => { const state = deepFreeze(['1234']); const nextState = reducers.handlePendingPosts(state, { type: PostTypes.RECEIVED_NEW_POST, data: { pending_post_id: 'abcd', }, }); expect(nextState).not.toBe(state); expect(nextState).toEqual(['1234', 'abcd']); }); it('should not add duplicate entries', () => { const state = deepFreeze(['1234']); const nextState = reducers.handlePendingPosts(state, { type: PostTypes.RECEIVED_NEW_POST, data: { pending_post_id: '1234', }, }); expect(nextState).toBe(state); expect(nextState).toEqual(['1234']); }); it('should do nothing for regular posts', () => { const state = deepFreeze(['1234']); const nextState = reducers.handlePendingPosts(state, { type: PostTypes.RECEIVED_NEW_POST, data: { id: 'abcd', }, }); expect(nextState).toBe(state); expect(nextState).toEqual(['1234']); }); }); describe('removing a pending post', () => { it('should remove an entry when its post is deleted', () => { const state = deepFreeze(['1234', 'abcd']); const nextState = reducers.handlePendingPosts(state, { type: PostTypes.POST_REMOVED, data: { id: 'abcd', }, }); expect(nextState).not.toBe(state); expect(nextState).toEqual(['1234']); }); it('should do nothing without an entry for the post', () => { const state = deepFreeze(['1234', 'abcd']); const nextState = reducers.handlePendingPosts(state, { type: PostTypes.POST_REMOVED, data: { id: 'wxyz', }, }); expect(nextState).toBe(state); expect(nextState).toEqual(['1234', 'abcd']); }); }); describe('marking a pending post as completed', () => { it('should remove an entry when its post is successfully created', () => { const state = deepFreeze(['1234', 'abcd']); const nextState = reducers.handlePendingPosts(state, { type: PostTypes.RECEIVED_POST, data: { id: 'post', pending_post_id: 'abcd', }, }); expect(nextState).not.toBe(state); expect(nextState).toEqual(['1234']); }); it('should do nothing without an entry for the post', () => { const state = deepFreeze(['1234', 'abcd']); const nextState = reducers.handlePendingPosts(state, { type: PostTypes.RECEIVED_POST, data: { id: 'post', pending_post_id: 'wxyz', }, }); expect(nextState).toBe(state); expect(nextState).toEqual(['1234', 'abcd']); }); it('should do nothing when receiving a non-pending post', () => { const state = deepFreeze(['1234', 'abcd']); const nextState = reducers.handlePendingPosts(state, { type: PostTypes.RECEIVED_POST, data: { id: 'post', }, }); expect(nextState).toBe(state); expect(nextState).toEqual(['1234', 'abcd']); }); }); }); describe('postsInChannel', () => { describe('receiving a new post', () => { it('should do nothing without posts loaded for the channel', () => { const state = deepFreeze({}); const nextState = reducers.postsInChannel(state, { type: PostTypes.RECEIVED_NEW_POST, data: {id: 'post1', channel_id: 'channel1'}, }, {}, {}); expect(nextState).toBe(state); expect(nextState).toEqual({}); }); it('should store the new post when the channel is empty', () => { const state = deepFreeze({ channel1: [], }); const nextState = reducers.postsInChannel(state, { type: PostTypes.RECEIVED_NEW_POST, data: {id: 'post1', channel_id: 'channel1'}, }); expect(nextState).not.toBe(state); expect(nextState).toEqual({ channel1: [ {order: ['post1'], recent: true}, ], }); }); it('should store the new post when the channel has recent posts', () => { const state = deepFreeze({ channel1: [ {order: ['post2', 'post3'], recent: true}, ], }); const nextState = reducers.postsInChannel(state, { type: PostTypes.RECEIVED_NEW_POST, data: {id: 'post1', channel_id: 'channel1'}, }); expect(nextState).not.toBe(state); expect(nextState).toEqual({ channel1: [ {order: ['post1', 'post2', 'post3'], recent: true}, ], }); }); it('should not store the new post when the channel only has older posts', () => { const state = deepFreeze({ channel1: [ {order: ['post2', 'post3'], recent: false}, ], }); const nextState = reducers.postsInChannel(state, { type: PostTypes.RECEIVED_NEW_POST, data: {id: 'post1', channel_id: 'channel1'}, }); expect(nextState).toEqual({ channel1: [ {order: ['post2', 'post3'], recent: false}, {order: ['post1'], recent: true}, ], }); }); it('should do nothing for a duplicate post', () => { const state = deepFreeze({ channel1: [ {order: ['post1', 'post2', 'post3'], recent: true}, ], }); const nextState = reducers.postsInChannel(state, { type: PostTypes.RECEIVED_NEW_POST, data: {id: 'post1', channel_id: 'channel1'}, }); expect(nextState).toBe(state); }); it('should remove a previously pending post', () => { const state = deepFreeze({ channel1: [ {order: ['pending', 'post2', 'post1'], recent: true}, ], }); const nextState = reducers.postsInChannel(state, { type: PostTypes.RECEIVED_NEW_POST, data: {id: 'post3', channel_id: 'channel1', pending_post_id: 'pending'}, }, {}, {post1: {create_at: 1}, post2: {create_at: 2}, post3: {create_at: 3}}); expect(nextState).not.toBe(state); expect(nextState).toEqual({ channel1: [ {order: ['post3', 'post2', 'post1'], recent: true}, ], }); }); it('should just add the new post if the pending post was already removed', () => { const state = deepFreeze({ channel1: [ {order: ['post1', 'post2'], recent: true}, ], }); const nextState = reducers.postsInChannel(state, { type: PostTypes.RECEIVED_NEW_POST, data: {id: 'post3', channel_id: 'channel1', pending_post_id: 'pending'}, }); expect(nextState).not.toBe(state); expect(nextState).toEqual({ channel1: [ {order: ['post3', 'post1', 'post2'], recent: true}, ], }); }); it('should not include a previously removed post', () => { const state = deepFreeze({ channel1: [ {order: ['post1'], recent: true}, ], }); const nextState = reducers.postsInChannel(state, { type: PostTypes.POST_REMOVED, data: {id: 'post1', channel_id: 'channel1'}, }); expect(nextState).not.toBe(state); expect(nextState).toEqual({ channel1: [{ order: [], recent: true, }], }); }); }); describe('receiving a single post', () => { it('should replace a previously pending post', () => { const state = deepFreeze({ channel1: [ {order: ['post1', 'pending', 'post2'], recent: true}, ], }); const nextState = reducers.postsInChannel(state, { type: PostTypes.RECEIVED_POST, data: {id: 'post3', channel_id: 'channel1', pending_post_id: 'pending'}, }); expect(nextState).not.toBe(state); expect(nextState).toEqual({ channel1: [ {order: ['post1', 'post3', 'post2'], recent: true}, ], }); }); it('should do nothing for a pending post that was already removed', () => { const state = deepFreeze({ channel1: [ {order: ['post1', 'post2'], recent: true}, ], }); const nextState = reducers.postsInChannel(state, { type: PostTypes.RECEIVED_POST, data: {id: 'post3', channel_id: 'channel1', pending_post_id: 'pending'}, }); expect(nextState).toBe(state); expect(nextState).toEqual({ channel1: [ {order: ['post1', 'post2'], recent: true}, ], }); }); it('should do nothing for a post that was not previously pending', () => { const state = deepFreeze({ channel1: [ {order: ['post1', 'pending', 'post2'], recent: true}, ], }); const nextState = reducers.postsInChannel(state, { type: PostTypes.RECEIVED_POST, data: {id: 'post3', channel_id: 'channel1'}, }); expect(nextState).toBe(state); expect(nextState).toEqual({ channel1: [ {order: ['post1', 'pending', 'post2'], recent: true}, ], }); }); it('should do nothing for a post without posts loaded for the channel', () => { const state = deepFreeze({ channel1: [ {order: ['post1', 'post2'], recent: true}, ], }); const nextState = reducers.postsInChannel(state, { type: PostTypes.RECEIVED_POST, data: {id: 'post3', channel_id: 'channel2', pending_post_id: 'pending'}, }); expect(nextState).toBe(state); expect(nextState).toEqual({ channel1: [ {order: ['post1', 'post2'], recent: true}, ], }); }); }); describe('receiving consecutive recent posts in the channel', () => { it('should save posts in the correct order', () => { const state = deepFreeze({ channel1: [ {order: ['post2', 'post4'], recent: true}, ], }); const nextPosts = { post1: {id: 'post1', channel_id: 'channel1', create_at: 4000}, post2: {id: 'post2', channel_id: 'channel1', create_at: 3000}, post3: {id: 'post3', channel_id: 'channel1', create_at: 2000}, post4: {id: 'post4', channel_id: 'channel1', create_at: 1000}, }; const nextState = reducers.postsInChannel(state, { type: PostTypes.RECEIVED_POSTS_IN_CHANNEL, channelId: 'channel1', data: { posts: { post1: nextPosts.post1, post3: nextPosts.post3, }, order: ['post1', 'post3'], }, recent: true, }, null, nextPosts); expect(nextState).not.toBe(state); expect(nextState).toEqual({ channel1: [ {order: ['post1', 'post2', 'post3', 'post4'], recent: true}, ], }); }); it('should not save duplicate posts', () => { const state = deepFreeze({ channel1: [ {order: ['post1', 'post2', 'post3'], recent: true}, ], }); const nextPosts = { post1: {id: 'post1', channel_id: 'channel1', create_at: 4000}, post2: {id: 'post2', channel_id: 'channel1', create_at: 3000}, post3: {id: 'post3', channel_id: 'channel1', create_at: 2000}, post4: {id: 'post4', channel_id: 'channel1', create_at: 1000}, }; const nextState = reducers.postsInChannel(state, { type: PostTypes.RECEIVED_POSTS_IN_CHANNEL, channelId: 'channel1', data: { posts: { post2: nextPosts.post2, post4: nextPosts.post4, }, order: ['post2', 'post4'], }, recent: true, }, null, nextPosts); expect(nextState).not.toBe(state); expect(nextState).toEqual({ channel1: [ {order: ['post1', 'post2', 'post3', 'post4'], recent: true}, ], }); }); it('should do nothing when receiving no posts for loaded channel', () => { const state = deepFreeze({ channel1: [ {order: ['post1', 'post2', 'post3'], recent: true}, ], }); const nextState = reducers.postsInChannel(state, { type: PostTypes.RECEIVED_POSTS_IN_CHANNEL, channelId: 'channel1', data: { posts: {}, order: [], }, recent: true, }, null, {}); expect(nextState).toBe(state); expect(nextState).toEqual({ channel1: [ {order: ['post1', 'post2', 'post3'], recent: true}, ], }); }); it('should make entry for channel with no posts', () => { const state = deepFreeze({}); const nextState = reducers.postsInChannel(state, { type: PostTypes.RECEIVED_POSTS_IN_CHANNEL, channelId: 'channel1', data: { posts: {}, order: [], }, recent: true, }, null, {}); expect(nextState).not.toBe(state); expect(nextState).toEqual({ channel1: [{ order: [], recent: true, }], }); }); it('should not save posts that are not in data.order', () => { const state = deepFreeze({ channel1: [ {order: ['post2', 'post3'], recent: true}, ], }); const nextPosts = { post1: {id: 'post1', channel_id: 'channel1', create_at: 4000}, post2: {id: 'post2', channel_id: 'channel1', create_at: 3000}, post3: {id: 'post3', channel_id: 'channel1', create_at: 2000}, post4: {id: 'post4', channel_id: 'channel1', create_at: 1000}, }; const nextState = reducers.postsInChannel(state, { type: PostTypes.RECEIVED_POSTS_IN_CHANNEL, channelId: 'channel1', data: { posts: { post1: nextPosts.post1, post2: nextPosts.post2, post3: nextPosts.post3, post4: nextPosts.post4, }, order: ['post1', 'post2'], }, recent: true, }, null, nextPosts); expect(nextState).not.toBe(state); expect(nextState).toEqual({ channel1: [ {order: ['post1', 'post2', 'post3'], recent: true}, ], }); }); it('should not save posts in an older block, even if they may be adjacent', () => { const state = deepFreeze({ channel1: [ {order: ['post3', 'post4'], recent: false}, ], }); const nextPosts = { post1: {id: 'post1', channel_id: 'channel1', create_at: 4000}, post2: {id: 'post2', channel_id: 'channel1', create_at: 3000}, post3: {id: 'post3', channel_id: 'channel1', create_at: 2000}, post4: {id: 'post4', channel_id: 'channel1', create_at: 1000}, }; const nextState = reducers.postsInChannel(state, { type: PostTypes.RECEIVED_POSTS_IN_CHANNEL, channelId: 'channel1', data: { posts: { post1: nextPosts.post1, post2: nextPosts.post2, }, order: ['post1', 'post2'], }, recent: true, }, null, nextPosts); expect(nextState).not.toBe(state); expect(nextState).toEqual({ channel1: [ {order: ['post3', 'post4'], recent: false}, {order: ['post1', 'post2'], recent: true}, ], }); }); it('should not save posts in the recent block even if new posts may be adjacent', () => { const state = deepFreeze({ channel1: [ {order: ['post3', 'post4'], recent: true}, ], }); const nextPosts = { post1: {id: 'post1', channel_id: 'channel1', create_at: 4000}, post2: {id: 'post2', channel_id: 'channel1', create_at: 3000}, post3: {id: 'post3', channel_id: 'channel1', create_at: 2000}, post4: {id: 'post4', channel_id: 'channel1', create_at: 1000}, }; const nextState = reducers.postsInChannel(state, { type: PostTypes.RECEIVED_POSTS_IN_CHANNEL, channelId: 'channel1', data: { posts: { post1: nextPosts.post1, post2: nextPosts.post2, }, order: ['post1', 'post2'], }, recent: true, }, null, nextPosts); expect(nextState).not.toBe(state); expect(nextState).toEqual({ channel1: [ {order: ['post3', 'post4'], recent: false}, {order: ['post1', 'post2'], recent: true}, ], }); }); it('should add posts to non-recent block if there is overlap', () => { const state = deepFreeze({ channel1: [ {order: ['post2', 'post3'], recent: false}, ], }); const nextPosts = { post1: {id: 'post1', channel_id: 'channel1', create_at: 4000}, post2: {id: 'post2', channel_id: 'channel1', create_at: 3000}, post3: {id: 'post3', channel_id: 'channel1', create_at: 2000}, }; const nextState = reducers.postsInChannel(state, { type: PostTypes.RECEIVED_POSTS_IN_CHANNEL, channelId: 'channel1', data: { posts: { post1: nextPosts.post1, post2: nextPosts.post2, }, order: ['post1', 'post2'], }, recent: true, }, null, nextPosts); expect(nextState).not.toBe(state); expect(nextState).toEqual({ channel1: [ {order: ['post1', 'post2', 'post3'], recent: true}, ], }); }); }); describe('receiving consecutive posts in the channel that are not recent', () => { it('should save posts in the correct order', () => { const state = deepFreeze({ channel1: [ {order: ['post2', 'post4'], recent: false}, ], }); const nextPosts = { post1: {id: 'post1', channel_id: 'channel1', create_at: 4000}, post2: {id: 'post2', channel_id: 'channel1', create_at: 3000}, post3: {id: 'post3', channel_id: 'channel1', create_at: 2000}, post4: {id: 'post4', channel_id: 'channel1', create_at: 1000}, }; const nextState = reducers.postsInChannel(state, { type: PostTypes.RECEIVED_POSTS_IN_CHANNEL, channelId: 'channel1', data: { posts: { post1: nextPosts.post1, post3: nextPosts.post3, }, order: ['post1', 'post3'], }, recent: false, }, null, nextPosts); expect(nextState).not.toBe(state); expect(nextState).toEqual({ channel1: [ {order: ['post1', 'post2', 'post3', 'post4'], recent: false}, ], }); }); it('should not save duplicate posts', () => { const state = deepFreeze({ channel1: [ {order: ['post1', 'post2', 'post3'], recent: false}, ], }); const nextPosts = { post1: {id: 'post1', channel_id: 'channel1', create_at: 4000}, post2: {id: 'post2', channel_id: 'channel1', create_at: 3000}, post3: {id: 'post3', channel_id: 'channel1', create_at: 2000}, post4: {id: 'post4', channel_id: 'channel1', create_at: 1000}, }; const nextState = reducers.postsInChannel(state, { type: PostTypes.RECEIVED_POSTS_IN_CHANNEL, channelId: 'channel1', data: { posts: { post2: nextPosts.post2, post4: nextPosts.post4, }, order: ['post2', 'post4'], }, recent: false, }, null, nextPosts); expect(nextState).not.toBe(state); expect(nextState).toEqual({ channel1: [ {order: ['post1', 'post2', 'post3', 'post4'], recent: false}, ], }); }); it('should do nothing when receiving no posts for loaded channel', () => { const state = deepFreeze({ channel1: [ {order: ['post1', 'post2', 'post3'], recent: true}, ], }); const nextState = reducers.postsInChannel(state, { type: PostTypes.RECEIVED_POSTS_IN_CHANNEL, channelId: 'channel1', data: { posts: {}, order: [], }, recent: false, }, null, {}); expect(nextState).toBe(state); expect(nextState).toEqual({ channel1: [ {order: ['post1', 'post2', 'post3'], recent: true}, ], }); }); it('should make entry for channel with no posts', () => { const state = deepFreeze({}); const nextState = reducers.postsInChannel(state, { type: PostTypes.RECEIVED_POSTS_IN_CHANNEL, channelId: 'channel1', data: { posts: {}, order: [], }, recent: false, }, null, {}); expect(nextState).not.toBe(state); expect(nextState).toEqual({ channel1: [{ order: [], recent: false, }], }); }); it('should not save posts that are not in data.order', () => { const state = deepFreeze({ channel1: [ {order: ['post2', 'post3'], recent: false}, ], }); const nextPosts = { post1: {id: 'post1', channel_id: 'channel1', create_at: 4000}, post2: {id: 'post2', channel_id: 'channel1', create_at: 3000}, post3: {id: 'post3', channel_id: 'channel1', create_at: 2000}, post4: {id: 'post4', channel_id: 'channel1', create_at: 1000}, }; const nextState = reducers.postsInChannel(state, { type: PostTypes.RECEIVED_POSTS_IN_CHANNEL, channelId: 'channel1', data: { posts: { post1: nextPosts.post1, post2: nextPosts.post2, post3: nextPosts.post3, post4: nextPosts.post4, }, order: ['post1', 'post2'], }, recent: false, }, null, nextPosts); expect(nextState).not.toBe(state); expect(nextState).toEqual({ channel1: [ {order: ['post1', 'post2', 'post3'], recent: false}, ], }); }); it('should not save posts in another block without overlap', () => { const state = deepFreeze({ channel1: [ {order: ['post3', 'post4'], recent: false}, ], }); const nextPosts = { post1: {id: 'post1', channel_id: 'channel1', create_at: 4000}, post2: {id: 'post2', channel_id: 'channel1', create_at: 3000}, post3: {id: 'post3', channel_id: 'channel1', create_at: 2000}, post4: {id: 'post4', channel_id: 'channel1', create_at: 1000}, }; const nextState = reducers.postsInChannel(state, { type: PostTypes.RECEIVED_POSTS_IN_CHANNEL, channelId: 'channel1', data: { posts: { post1: nextPosts.post1, post2: nextPosts.post2, }, order: ['post1', 'post2'], }, recent: false, }, null, nextPosts); expect(nextState).not.toBe(state); expect(nextState).toEqual({ channel1: [ {order: ['post3', 'post4'], recent: false}, {order: ['post1', 'post2'], recent: false}, ], }); }); it('should add posts to recent block if there is overlap', () => { const state = deepFreeze({ channel1: [ {order: ['post1', 'post2'], recent: true}, ], }); const nextPosts = { post1: {id: 'post1', channel_id: 'channel1', create_at: 4000}, post2: {id: 'post2', channel_id: 'channel1', create_at: 3000}, post3: {id: 'post3', channel_id: 'channel1', create_at: 2000}, post4: {id: 'post4', channel_id: 'channel1', create_at: 1000}, }; const nextState = reducers.postsInChannel(state, { type: PostTypes.RECEIVED_POSTS_IN_CHANNEL, channelId: 'channel1', data: { posts: { post2: nextPosts.post2, post3: nextPosts.post3, }, order: ['post2', 'post3'], }, recent: false, }, null, nextPosts); expect(nextState).not.toBe(state); expect(nextState).toEqual({ channel1: [ {order: ['post1', 'post2', 'post3'], recent: true}, ], }); }); it('should save with chunk as oldest', () => { const state = deepFreeze({ channel1: [ {order: ['post1', 'post2'], recent: true}, ], }); const nextPosts = { post1: {id: 'post1', channel_id: 'channel1', create_at: 4000}, post2: {id: 'post2', channel_id: 'channel1', create_at: 3000}, post3: {id: 'post3', channel_id: 'channel1', create_at: 2000}, post4: {id: 'post4', channel_id: 'channel1', create_at: 1000}, }; const nextState = reducers.postsInChannel(state, { type: PostTypes.RECEIVED_POSTS_IN_CHANNEL, channelId: 'channel1', data: { posts: { post2: nextPosts.post2, post3: nextPosts.post3, }, order: ['post2', 'post3'], }, recent: false, oldest: true, }, null, nextPosts); expect(nextState).not.toBe(state); expect(nextState).toEqual({ channel1: [ {order: ['post1', 'post2', 'post3'], recent: true, oldest: true}, ], }); }); }); describe('receiving posts since', () => { it('should save posts in the channel in the correct order', () => { const state = deepFreeze({ channel1: [ {order: ['post3', 'post4'], recent: true}, ], }); const nextPosts = { post1: {id: 'post1', channel_id: 'channel1', create_at: 4000}, post2: {id: 'post2', channel_id: 'channel1', create_at: 3000}, post3: {id: 'post3', channel_id: 'channel1', create_at: 2000}, post4: {id: 'post4', channel_id: 'channel1', create_at: 1000}, }; const nextState = reducers.postsInChannel(state, { type: PostTypes.RECEIVED_POSTS_SINCE, channelId: 'channel1', data: { posts: { post1: nextPosts.post1, post2: nextPosts.post2, }, order: ['post1', 'post2'], }, }, null, nextPosts); expect(nextState).not.toBe(state); expect(nextState).toEqual({ channel1: [ {order: ['post1', 'post2', 'post3', 'post4'], recent: true}, ], }); }); it('should not save older posts', () => { const state = deepFreeze({ channel1: [ {order: ['post2', 'post3'], recent: true}, ], }); const nextPosts = { post1: {id: 'post1', channel_id: 'channel1', create_at: 4000}, post2: {id: 'post2', channel_id: 'channel1', create_at: 3000}, post3: {id: 'post3', channel_id: 'channel1', create_at: 2000}, post4: {id: 'post4', channel_id: 'channel1', create_at: 1000}, }; const nextState = reducers.postsInChannel(state, { type: PostTypes.RECEIVED_POSTS_SINCE, channelId: 'channel1', data: { posts: { post1: nextPosts.post1, post4: nextPosts.post4, }, order: ['post1', 'post4'], }, }, null, nextPosts); expect(nextState).not.toBe(state); expect(nextState).toEqual({ channel1: [ {order: ['post1', 'post2', 'post3'], recent: true}, ], }); }); it('should save any posts in between', () => { const state = deepFreeze({ channel1: [ {order: ['post2', 'post4'], recent: true}, ], }); const nextPosts = { post1: {id: 'post1', channel_id: 'channel1', create_at: 4000}, post2: {id: 'post2', channel_id: 'channel1', create_at: 3000}, post3: {id: 'post3', channel_id: 'channel1', create_at: 2000}, post4: {id: 'post4', channel_id: 'channel1', create_at: 1000}, post5: {id: 'post5', channel_id: 'channel1', create_at: 500}, post6: {id: 'post6', channel_id: 'channel1', create_at: 300}, }; const nextState = reducers.postsInChannel(state, { type: PostTypes.RECEIVED_POSTS_SINCE,