mattermost-mobile/app/mm-redux/reducers/entities/posts.test.js
Miguel Alatzar 2d81b497cf [MM-23520] Port mattermost-redux (#4088)
* Remove mattermost-redux

* Move mm-redux files into app/redux

* Add @redux path to tsconfig.json

* Fix imports

* Install missing dependencies

* Fix tsc errors

* Fix i18n_utils test

* Fix more imports

* Remove redux websocket

* Fix tests

* Rename @redux

* Apply changes from mattermost-redux PR 1103

* Remove mattermost-redux mention in template

* Add missing imports

* Rename app/redux/ to app/mm-redux/

* Remove test file

* Fix fetching Sidebar GM profiles

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2020-04-17 21:12:09 -07:00

3961 lines
133 KiB
JavaScript

// 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 '@mm-redux/action_types';
import {Posts} from '../../constants';
import * as reducers from '@mm-redux/reducers/entities/posts';
import deepFreeze from '@mm-redux/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,
channelId: 'channel1',
data: {
posts: {
post1: nextPosts.post1,
post2: nextPosts.post2,
post3: nextPosts.post5,
post4: nextPosts.post4,
post5: nextPosts.post5,
post6: nextPosts.post6,
},
order: ['post1', 'post2', 'post3', 'post4', 'post5', 'post6'],
},
}, null, nextPosts);
expect(nextState).not.toBe(state);
expect(nextState).toEqual({
channel1: [
{order: ['post1', 'post2', 'post3', 'post4'], recent: true},
],
});
});
it('should do nothing if only receiving updated posts', () => {
const state = deepFreeze({
channel1: [
{order: ['post1', 'post2', '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,
post4: nextPosts.post4,
},
order: ['post1', 'post4'],
},
}, null, nextPosts);
expect(nextState).toBe(state);
expect(nextState).toEqual({
channel1: [
{order: ['post1', 'post2', 'post3', 'post4'], recent: true},
],
});
});
it('should not save duplicate 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},
};
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'], 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_SINCE,
channelId: 'channel1',
data: {
posts: {},
order: [],
},
}, null, {});
expect(nextState).toBe(state);
expect(nextState).toEqual({
channel1: [
{order: ['post1', 'post2', 'post3'], recent: true},
],
});
});
it('should do nothing for channel with no posts', () => {
const state = deepFreeze({});
const nextState = reducers.postsInChannel(state, {
type: PostTypes.RECEIVED_POSTS_SINCE,
channelId: 'channel1',
data: {
posts: {},
order: [],
},
page: 0,
}, null, {});
expect(nextState).toBe(state);
expect(nextState).toEqual({});
});
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_SINCE,
channelId: 'channel1',
data: {
posts: {
post1: nextPosts.post1,
post4: nextPosts.post4,
},
order: ['post1'],
},
}, 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', () => {
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_SINCE,
channelId: 'channel1',
data: {
posts: {
post1: nextPosts.post1,
post2: nextPosts.post2,
},
order: ['post1', 'post2'],
},
}, null, nextPosts);
expect(nextState).toBe(state);
expect(nextState).toEqual({
channel1: [
{order: ['post3', 'post4'], recent: false},
],
});
});
it('should always save posts in the recent block', () => {
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},
],
});
});
});
describe('receiving posts after', () => {
it('should save posts when channel is not loaded', () => {
const state = deepFreeze({});
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_AFTER,
channelId: 'channel1',
data: {
posts: {
post1: nextPosts.post1,
post2: nextPosts.post2,
},
order: ['post1', 'post2'],
},
afterPostId: 'post3',
recent: false,
}, null, nextPosts);
expect(nextState).not.toBe(state);
expect(nextState).toEqual({
channel1: [
{order: ['post1', 'post2', 'post3'], recent: false},
],
});
});
it('should save posts when channel is empty', () => {
const state = deepFreeze({
channel1: [],
});
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_AFTER,
channelId: 'channel1',
data: {
posts: {
post1: nextPosts.post1,
post2: nextPosts.post2,
},
order: ['post1', 'post2'],
},
afterPostId: 'post3',
recent: false,
}, null, nextPosts);
expect(nextState).not.toBe(state);
expect(nextState).toEqual({
channel1: [
{order: ['post1', 'post2', 'post3'], recent: false},
],
});
});
it('should add posts to existing block', () => {
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_AFTER,
channelId: 'channel1',
data: {
posts: {
post1: nextPosts.post1,
post2: nextPosts.post2,
},
order: ['post1', 'post2'],
},
afterPostId: 'post3',
}, null, nextPosts);
expect(nextState).not.toBe(state);
expect(nextState).toEqual({
channel1: [
{order: ['post1', 'post2', 'post3', 'post4'], recent: false},
],
});
});
it('should merge adjacent posts if we have newer posts', () => {
const state = deepFreeze({
channel1: [
{order: ['post4'], recent: false},
{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_AFTER,
channelId: 'channel1',
data: {
posts: {
post1: nextPosts.post1,
post3: nextPosts.post3,
},
order: ['post2', 'post3'],
},
afterPostId: 'post4',
}, null, nextPosts);
expect(nextState).not.toBe(state);
expect(nextState).toEqual({
channel1: [
{order: ['post1', 'post2', 'post3', 'post4'], recent: true},
],
});
});
it('should do nothing when no posts are received', () => {
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},
};
const nextState = reducers.postsInChannel(state, {
type: PostTypes.RECEIVED_POSTS_AFTER,
channelId: 'channel1',
data: {
posts: {},
order: [],
},
afterPostId: 'post1',
}, null, nextPosts);
expect(nextState).toBe(state);
expect(nextState).toEqual({
channel1: [
{order: ['post1', 'post2'], recent: true},
],
});
});
});
describe('receiving posts before', () => {
it('should save posts when channel is not loaded', () => {
const state = deepFreeze({});
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_BEFORE,
channelId: 'channel1',
data: {
posts: {
post2: nextPosts.post2,
post3: nextPosts.post3,
},
order: ['post2', 'post3'],
},
beforePostId: 'post1',
}, null, nextPosts);
expect(nextState).not.toBe(state);
expect(nextState).toEqual({
channel1: [
{order: ['post1', 'post2', 'post3'], recent: false},
],
});
});
it('should have oldest set to false', () => {
const state = deepFreeze({});
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_BEFORE,
channelId: 'channel1',
data: {
posts: {
post2: nextPosts.post2,
post3: nextPosts.post3,
},
order: ['post2', 'post3'],
},
beforePostId: 'post1',
oldest: false,
}, null, nextPosts);
expect(nextState).not.toBe(state);
expect(nextState).toEqual({
channel1: [
{order: ['post1', 'post2', 'post3'], recent: false, oldest: false},
],
});
});
it('should save posts when channel is empty', () => {
const state = deepFreeze({
channel1: [],
});
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_BEFORE,
channelId: 'channel1',
data: {
posts: {
post2: nextPosts.post2,
post3: nextPosts.post3,
},
order: ['post2', 'post3'],
},
beforePostId: 'post1',
}, null, nextPosts);
expect(nextState).not.toBe(state);
expect(nextState).toEqual({
channel1: [
{order: ['post1', 'post2', 'post3'], recent: false},
],
});
});
it('should add posts to existing block', () => {
const state = deepFreeze({
channel1: [
{order: ['post1', 'post2'], 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_BEFORE,
channelId: 'channel1',
data: {
posts: {
post3: nextPosts.post3,
post4: nextPosts.post4,
},
order: ['post3', 'post4'],
},
beforePostId: 'post2',
}, null, nextPosts);
expect(nextState).not.toBe(state);
expect(nextState).toEqual({
channel1: [
{order: ['post1', 'post2', 'post3', 'post4'], recent: false},
],
});
});
it('should merge adjacent posts if we have newer posts', () => {
const state = deepFreeze({
channel1: [
{order: ['post4'], recent: false},
{order: ['post1'], 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_BEFORE,
channelId: 'channel1',
data: {
posts: {
post1: nextPosts.post1,
post3: nextPosts.post3,
},
order: ['post2', 'post3', 'post4'],
},
beforePostId: 'post1',
}, null, nextPosts);
expect(nextState).not.toBe(state);
expect(nextState).toEqual({
channel1: [
{order: ['post1', 'post2', 'post3', 'post4'], recent: true},
],
});
});
it('should do nothing when no posts are received', () => {
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},
};
const nextState = reducers.postsInChannel(state, {
type: PostTypes.RECEIVED_POSTS_BEFORE,
channelId: 'channel1',
data: {
posts: {},
order: [],
},
beforePostId: 'post2',
}, null, nextPosts);
expect(nextState).toBe(state);
expect(nextState).toEqual({
channel1: [
{order: ['post1', 'post2'], recent: true},
],
});
});
});
describe('deleting a post', () => {
it('should do nothing when deleting a post without comments', () => {
const state = deepFreeze({
channel1: [
{order: ['post1', 'post2', 'post3'], recent: false},
],
});
const prevPosts = {
post1: {id: 'post1', channel_id: 'channel1'},
post2: {id: 'post2', channel_id: 'channel1'},
post3: {id: 'post3', channel_id: 'channel1'},
};
const nextState = reducers.postsInChannel(state, {
type: PostTypes.POST_DELETED,
data: prevPosts.post2,
}, prevPosts, null);
expect(nextState).toBe(state);
expect(nextState).toEqual({
channel1: [
{order: ['post1', 'post2', 'post3'], recent: false},
],
});
});
it('should remove comments on the post when deleting a post with comments', () => {
const state = deepFreeze({
channel1: [
{order: ['post1', 'post2', 'post3', 'post4'], recent: false},
],
});
const prevPosts = {
post1: {id: 'post1', channel_id: 'channel1', root_id: 'post4'},
post2: {id: 'post2', channel_id: 'channel1', root_id: 'post3'},
post3: {id: 'post3', channel_id: 'channel1'},
post4: {id: 'post4', channel_id: 'channel1'},
};
const nextState = reducers.postsInChannel(state, {
type: PostTypes.POST_DELETED,
data: prevPosts.post3,
}, prevPosts, null);
expect(nextState).not.toBe(state);
expect(nextState).toEqual({
channel1: [
{order: ['post1', 'post3', 'post4'], recent: false},
],
});
});
it('should remove comments from multiple blocks', () => {
const state = deepFreeze({
channel1: [
{order: ['post1', 'post2'], recent: false},
{order: ['post3', 'post4'], recent: false},
],
});
const prevPosts = {
post1: {id: 'post1', channel_id: 'channel1', root_id: 'post4'},
post2: {id: 'post2', channel_id: 'channel1'},
post3: {id: 'post3', channel_id: 'channel1', root_id: 'post4'},
post4: {id: 'post4', channel_id: 'channel1'},
};
const nextState = reducers.postsInChannel(state, {
type: PostTypes.POST_DELETED,
data: prevPosts.post4,
}, prevPosts, null);
expect(nextState).not.toBe(state);
expect(nextState).toEqual({
channel1: [
{order: ['post2'], recent: false},
{order: ['post4'], recent: false},
],
});
});
it('should do nothing to blocks without comments', () => {
const state = deepFreeze({
channel1: [
{order: ['post1', 'post2'], recent: false},
{order: ['post3', 'post4'], recent: false},
],
});
const prevPosts = {
post1: {id: 'post1', channel_id: 'channel1'},
post2: {id: 'post2', channel_id: 'channel1'},
post3: {id: 'post3', channel_id: 'channel1', root_id: 'post4'},
post4: {id: 'post4', channel_id: 'channel1'},
};
const nextState = reducers.postsInChannel(state, {
type: PostTypes.POST_DELETED,
data: prevPosts.post4,
}, prevPosts, null);
expect(nextState).not.toBe(state);
expect(nextState[0]).toBe(state[0]);
expect(nextState).toEqual({
channel1: [
{order: ['post1', 'post2'], recent: false},
{order: ['post4'], recent: false},
],
});
});
it('should do nothing when deleting a comment', () => {
const state = deepFreeze({
channel1: [
{order: ['post1', 'post2', 'post3', 'post4'], recent: false},
],
});
const prevPosts = {
post1: {id: 'post1', channel_id: 'channel1', root_id: 'post4'},
post2: {id: 'post2', channel_id: 'channel1', root_id: 'post3'},
post3: {id: 'post3', channel_id: 'channel1'},
post4: {id: 'post4', channel_id: 'channel1'},
};
const nextState = reducers.postsInChannel(state, {
type: PostTypes.POST_DELETED,
data: prevPosts.post2,
}, prevPosts, null);
expect(nextState).toBe(state);
expect(nextState).toEqual({
channel1: [
{order: ['post1', 'post2', 'post3', 'post4'], recent: false},
],
});
});
it('should do nothing if the post has not been loaded', () => {
const state = deepFreeze({
channel1: [
{order: ['post1', 'post2', 'post3'], recent: false},
],
});
const prevPosts = {
post1: {id: 'post1', channel_id: 'channel1'},
post2: {id: 'post2', channel_id: 'channel1'},
post3: {id: 'post3', channel_id: 'channel1'},
post4: {id: 'post4', channel_id: 'channel1'},
};
const nextState = reducers.postsInChannel(state, {
type: PostTypes.POST_DELETED,
data: prevPosts.post4,
}, prevPosts, null);
expect(nextState).toBe(state);
expect(nextState).toEqual({
channel1: [
{order: ['post1', 'post2', 'post3'], recent: false},
],
});
});
it('should do nothing if no posts in the channel have been loaded', () => {
const state = deepFreeze({});
const prevPosts = {
post1: {id: 'post1', channel_id: 'channel1'},
post2: {id: 'post2', channel_id: 'channel1'},
post3: {id: 'post3', channel_id: 'channel1'},
};
const nextState = reducers.postsInChannel(state, {
type: PostTypes.POST_DELETED,
data: prevPosts.post1,
}, prevPosts, null);
expect(nextState).toBe(state);
expect(nextState).toEqual({});
});
it('should remove empty blocks', () => {
const state = deepFreeze({
channel1: [
{order: ['post1', 'post2'], recent: false},
{order: ['post3', 'post4'], recent: false},
],
});
const prevPosts = {
post1: {id: 'post1', channel_id: 'channel1', root_id: 'post4'},
post2: {id: 'post2', channel_id: 'channel1', root_id: 'post4'},
post3: {id: 'post3', channel_id: 'channel1', root_id: 'post4'},
post4: {id: 'post4', channel_id: 'channel1'},
};
const nextState = reducers.postsInChannel(state, {
type: PostTypes.POST_DELETED,
data: prevPosts.post4,
}, prevPosts, null);
expect(nextState).not.toBe(state);
expect(nextState).toEqual({
channel1: [
{order: ['post4'], recent: false},
],
});
});
});
describe('removing a post', () => {
it('should remove the post', () => {
const state = deepFreeze({
channel1: [
{order: ['post1', 'post2', 'post3'], recent: false},
],
});
const prevPosts = {
post1: {id: 'post1', channel_id: 'channel1'},
post2: {id: 'post2', channel_id: 'channel1'},
post3: {id: 'post3', channel_id: 'channel1'},
};
const nextState = reducers.postsInChannel(state, {
type: PostTypes.POST_REMOVED,
data: prevPosts.post2,
}, prevPosts, null);
expect(nextState).not.toBe(state);
expect(nextState).toEqual({
channel1: [
{order: ['post1', 'post3'], recent: false},
],
});
});
it('should remove comments on the post', () => {
const state = deepFreeze({
channel1: [
{order: ['post1', 'post2', 'post3', 'post4'], recent: false},
],
});
const prevPosts = {
post1: {id: 'post1', channel_id: 'channel1', root_id: 'post4'},
post2: {id: 'post2', channel_id: 'channel1', root_id: 'post3'},
post3: {id: 'post3', channel_id: 'channel1'},
post4: {id: 'post4', channel_id: 'channel1'},
};
const nextState = reducers.postsInChannel(state, {
type: PostTypes.POST_REMOVED,
data: prevPosts.post3,
}, prevPosts, null);
expect(nextState).not.toBe(state);
expect(nextState).toEqual({
channel1: [
{order: ['post1', 'post4'], recent: false},
],
});
});
it('should remove a comment without removing the root post', () => {
const state = deepFreeze({
channel1: [
{order: ['post1', 'post2', 'post3', 'post4'], recent: false},
],
});
const prevPosts = {
post1: {id: 'post1', channel_id: 'channel1', root_id: 'post4'},
post2: {id: 'post2', channel_id: 'channel1', root_id: 'post3'},
post3: {id: 'post3', channel_id: 'channel1'},
post4: {id: 'post4', channel_id: 'channel1'},
};
const nextState = reducers.postsInChannel(state, {
type: PostTypes.POST_REMOVED,
data: prevPosts.post2,
}, prevPosts, null);
expect(nextState).not.toBe(state);
expect(nextState).toEqual({
channel1: [
{order: ['post1', 'post3', 'post4'], recent: false},
],
});
});
it('should do nothing if the post has not been loaded', () => {
const state = deepFreeze({
channel1: [
{order: ['post1', 'post2', 'post3'], recent: false},
],
});
const prevPosts = {
post1: {id: 'post1', channel_id: 'channel1'},
post2: {id: 'post2', channel_id: 'channel1'},
post3: {id: 'post3', channel_id: 'channel1'},
post4: {id: 'post4', channel_id: 'channel1'},
};
const nextState = reducers.postsInChannel(state, {
type: PostTypes.POST_REMOVED,
data: prevPosts.post4,
}, prevPosts, null);
expect(nextState).toBe(state);
expect(nextState).toEqual({
channel1: [
{order: ['post1', 'post2', 'post3'], recent: false},
],
});
});
it('should do nothing if no posts in the channel have been loaded', () => {
const state = deepFreeze({});
const prevPosts = {
post1: {id: 'post1', channel_id: 'channel1'},
post2: {id: 'post2', channel_id: 'channel1'},
post3: {id: 'post3', channel_id: 'channel1'},
};
const nextState = reducers.postsInChannel(state, {
type: PostTypes.POST_REMOVED,
data: prevPosts.post1,
}, prevPosts, null);
expect(nextState).toBe(state);
expect(nextState).toEqual({});
});
it('should remove empty blocks', () => {
const state = deepFreeze({
channel1: [
{order: ['post1', 'post2'], recent: false},
{order: ['post3', 'post4'], recent: false},
],
});
const prevPosts = {
post1: {id: 'post1', channel_id: 'channel1', root_id: 'post4'},
post2: {id: 'post2', channel_id: 'channel1'},
post3: {id: 'post3', channel_id: 'channel1', root_id: 'post4'},
post4: {id: 'post4', channel_id: 'channel1'},
};
const nextState = reducers.postsInChannel(state, {
type: PostTypes.POST_REMOVED,
data: prevPosts.post4,
}, prevPosts, null);
expect(nextState).not.toBe(state);
expect(nextState).toEqual({
channel1: [
{order: ['post2'], recent: false},
],
});
});
});
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({
channel1: [
{order: ['post1', 'post2', 'post3'], recent: false},
{order: ['post6', 'post7', 'post8'], recent: false},
],
channel2: [
{order: ['post4', 'post5'], recent: false},
],
});
const nextState = reducers.postsInChannel(state, {
type: actionType,
data: {
id: 'channel1',
viewArchivedChannels: false,
},
});
expect(nextState).not.toBe(state);
expect(nextState.channel2).toBe(state.channel2);
expect(nextState).toEqual({
channel2: [
{order: ['post4', 'post5'], recent: false},
],
});
});
it('should do nothing if no posts in that channel are loaded', () => {
const state = deepFreeze({
channel1: [
{order: ['post1', 'post2', 'post3'], recent: false},
],
channel2: [
{order: ['post4', 'post5'], recent: false},
],
});
const nextState = reducers.postsInChannel(state, {
type: actionType,
data: {
id: 'channel3',
viewArchivedChannels: false,
},
});
expect(nextState).toBe(state);
expect(nextState.channel1).toBe(state.channel1);
expect(nextState.channel2).toBe(state.channel2);
expect(nextState).toEqual({
channel1: [
{order: ['post1', 'post2', 'post3'], recent: false},
],
channel2: [
{order: ['post4', 'post5'], recent: false},
],
});
});
it('should not remove any posts with viewArchivedChannels enabled', () => {
const state = deepFreeze({
channel1: [
{order: ['post1', 'post2', 'post3'], recent: false},
{order: ['post6', 'post7', 'post8'], recent: false},
],
channel2: [
{order: ['post4', 'post5'], recent: false},
],
});
const nextState = reducers.postsInChannel(state, {
type: actionType,
data: {
id: 'channel1',
viewArchivedChannels: true,
},
});
expect(nextState).toBe(state);
expect(nextState.channel1).toBe(state.channel1);
expect(nextState.channel2).toBe(state.channel2);
expect(nextState).toEqual({
channel1: [
{order: ['post1', 'post2', 'post3'], recent: false},
{order: ['post6', 'post7', 'post8'], recent: false},
],
channel2: [
{order: ['post4', 'post5'], recent: false},
],
});
});
});
}
});
describe('mergePostBlocks', () => {
it('should do nothing with no blocks', () => {
const blocks = [];
const posts = {};
const nextBlocks = reducers.mergePostBlocks(blocks, posts);
expect(nextBlocks).toBe(blocks);
});
it('should do nothing with only one block', () => {
const blocks = [
{order: ['a'], recent: false},
];
const posts = {
a: {create_at: 1000},
};
const nextBlocks = reducers.mergePostBlocks(blocks, posts);
expect(nextBlocks).toBe(blocks);
});
it('should do nothing with two separate blocks', () => {
const blocks = [
{order: ['a'], recent: false},
{order: ['b'], recent: false},
];
const posts = {
a: {create_at: 1000},
b: {create_at: 1001},
};
const nextBlocks = reducers.mergePostBlocks(blocks, posts);
expect(nextBlocks).toBe(blocks);
});
it('should merge two blocks containing exactly the same posts', () => {
const blocks = [
{order: ['a'], recent: false},
{order: ['a'], recent: false},
];
const posts = {
a: {create_at: 1000},
};
const nextBlocks = reducers.mergePostBlocks(blocks, posts);
expect(nextBlocks).not.toBe(blocks);
expect(nextBlocks).toEqual([
{order: ['a'], recent: false},
]);
});
it('should merge two blocks containing overlapping posts', () => {
const blocks = [
{order: ['a', 'b', 'c'], recent: false},
{order: ['b', 'c', 'd'], recent: false},
];
const posts = {
a: {create_at: 1003},
b: {create_at: 1002},
c: {create_at: 1001},
d: {create_at: 1000},
};
const nextBlocks = reducers.mergePostBlocks(blocks, posts);
expect(nextBlocks).not.toBe(blocks);
expect(nextBlocks).toEqual([
{order: ['a', 'b', 'c', 'd'], recent: false},
]);
});
it('should merge more than two blocks containing overlapping posts', () => {
const blocks = [
{order: ['d', 'e'], recent: false},
{order: ['a', 'b'], recent: false},
{order: ['c', 'd'], recent: false},
{order: ['b', 'c'], recent: false},
];
const posts = {
a: {create_at: 1004},
b: {create_at: 1003},
c: {create_at: 1002},
d: {create_at: 1001},
e: {create_at: 1000},
};
const nextBlocks = reducers.mergePostBlocks(blocks, posts);
expect(nextBlocks).not.toBe(blocks);
expect(nextBlocks).toEqual([
{order: ['a', 'b', 'c', 'd', 'e'], recent: false},
]);
});
it('should not affect blocks that are not merged', () => {
const blocks = [
{order: ['a', 'b'], recent: false},
{order: ['b', 'c'], recent: false},
{order: ['d', 'e'], recent: false},
];
const posts = {
a: {create_at: 1004},
b: {create_at: 1003},
c: {create_at: 1002},
d: {create_at: 1001},
e: {create_at: 1000},
};
const nextBlocks = reducers.mergePostBlocks(blocks, posts);
expect(nextBlocks).not.toBe(blocks);
expect(nextBlocks[1]).toBe(blocks[2]);
expect(nextBlocks).toEqual([
{order: ['a', 'b', 'c'], recent: false},
{order: ['d', 'e'], recent: false},
]);
});
it('should keep merged blocks marked as recent', () => {
const blocks = [
{order: ['a', 'b'], recent: true},
{order: ['b', 'c'], recent: false},
];
const posts = {
a: {create_at: 1002},
b: {create_at: 1001},
c: {create_at: 1000},
};
const nextBlocks = reducers.mergePostBlocks(blocks, posts);
expect(nextBlocks).not.toBe(blocks);
expect(nextBlocks).toEqual([
{order: ['a', 'b', 'c'], recent: true},
]);
});
it('should keep merged blocks marked as oldest', () => {
const blocks = [
{order: ['a', 'b'], oldest: true},
{order: ['b', 'c'], oldest: false},
];
const posts = {
a: {create_at: 1002},
b: {create_at: 1001},
c: {create_at: 1000},
};
const nextBlocks = reducers.mergePostBlocks(blocks, posts);
expect(nextBlocks).not.toBe(blocks);
expect(nextBlocks).toEqual([
{order: ['a', 'b', 'c'], oldest: true},
]);
});
it('should remove empty blocks', () => {
const blocks = [
{order: ['a', 'b'], recent: true},
{order: [], recent: false},
];
const posts = {
a: {create_at: 1002},
b: {create_at: 1001},
c: {create_at: 1000},
};
const nextBlocks = reducers.mergePostBlocks(blocks, posts);
expect(nextBlocks).not.toBe(blocks);
expect(nextBlocks[0]).toBe(blocks[0]);
expect(nextBlocks).toEqual([
{order: ['a', 'b'], recent: true},
]);
});
});
describe('mergePostOrder', () => {
const tests = [
{
name: 'empty arrays',
left: [],
right: [],
expected: [],
},
{
name: 'empty left array',
left: [],
right: ['c', 'd'],
expected: ['c', 'd'],
},
{
name: 'empty right array',
left: ['a', 'b'],
right: [],
expected: ['a', 'b'],
},
{
name: 'distinct arrays',
left: ['a', 'b'],
right: ['c', 'd'],
expected: ['a', 'b', 'c', 'd'],
},
{
name: 'overlapping arrays',
left: ['a', 'b', 'c', 'd'],
right: ['c', 'd', 'e', 'f'],
expected: ['a', 'b', 'c', 'd', 'e', 'f'],
},
{
name: 'left array is start of right array',
left: ['a', 'b'],
right: ['a', 'b', 'c', 'd'],
expected: ['a', 'b', 'c', 'd'],
},
{
name: 'right array is end of left array',
left: ['a', 'b', 'c', 'd'],
right: ['c', 'd'],
expected: ['a', 'b', 'c', 'd'],
},
{
name: 'left array contains right array',
left: ['a', 'b', 'c', 'd'],
right: ['b', 'c'],
expected: ['a', 'b', 'c', 'd'],
},
{
name: 'items in second array missing from first',
left: ['a', 'c'],
right: ['b', 'd', 'e', 'f'],
expected: ['a', 'b', 'c', 'd', 'e', 'f'],
},
];
const posts = {
a: {create_at: 10000},
b: {create_at: 9000},
c: {create_at: 8000},
d: {create_at: 7000},
e: {create_at: 6000},
f: {create_at: 5000},
};
for (const test of tests) {
it(test.name, () => {
const left = [...test.left];
const right = [...test.right];
const actual = reducers.mergePostOrder(left, right, posts);
expect(actual).toEqual(test.expected);
// Arguments shouldn't be mutated
expect(left).toEqual(test.left);
expect(right).toEqual(test.right);
});
}
});
describe('postsInThread', () => {
for (const actionType of [
PostTypes.RECEIVED_POST,
PostTypes.RECEIVED_NEW_POST,
]) {
describe(`receiving a single post (${actionType})`, () => {
it('should replace a previously pending comment', () => {
const state = deepFreeze({
root1: ['comment1', 'pending', 'comment2'],
});
const nextState = reducers.postsInThread(state, {
type: actionType,
data: {id: 'comment3', root_id: 'root1', pending_post_id: 'pending'},
});
expect(nextState).not.toBe(state);
expect(nextState).toEqual({
root1: ['comment1', 'comment2', 'comment3'],
});
});
it('should do nothing for a pending comment that was already removed', () => {
const state = deepFreeze({
root1: ['comment1', 'comment2'],
});
const nextState = reducers.postsInThread(state, {
type: actionType,
data: {id: 'comment2', root_id: 'root1', pending_post_id: 'pending'},
});
expect(nextState).toBe(state);
expect(nextState).toEqual({
root1: ['comment1', 'comment2'],
});
});
it('should store a comment that was not previously pending', () => {
const state = deepFreeze({
root1: ['comment1', 'comment2'],
});
const nextState = reducers.postsInThread(state, {
type: actionType,
data: {id: 'comment3', root_id: 'root1'},
});
expect(nextState).not.toBe(state);
expect(nextState).toEqual({
root1: ['comment1', 'comment2', 'comment3'],
});
});
it('should store a comment without other comments loaded for the thread', () => {
const state = deepFreeze({});
const nextState = reducers.postsInThread(state, {
type: actionType,
data: {id: 'comment1', root_id: 'root1'},
});
expect(nextState).not.toBe(state);
expect(nextState).toEqual({
root1: ['comment1'],
});
});
it('should do nothing for a non-comment post', () => {
const state = deepFreeze({
root1: ['comment1'],
});
const nextState = reducers.postsInThread(state, {
type: actionType,
data: {id: 'root2'},
});
expect(nextState).toBe(state);
expect(nextState.root1).toBe(state.root1);
expect(nextState).toEqual({
root1: ['comment1'],
});
});
it('should do nothing for a duplicate post', () => {
const state = deepFreeze({
root1: ['comment1', 'comment2'],
});
const nextState = reducers.postsInThread(state, {
type: actionType,
data: {id: 'comment1'},
});
expect(nextState).toBe(state);
expect(nextState).toEqual({
root1: ['comment1', 'comment2'],
});
});
});
}
for (const actionType of [
PostTypes.RECEIVED_POSTS_AFTER,
PostTypes.RECEIVED_POSTS_BEFORE,
PostTypes.RECEIVED_POSTS_IN_CHANNEL,
PostTypes.RECEIVED_POSTS_SINCE,
]) {
describe(`receiving posts in the channel (${actionType})`, () => {
it('should save comments without in the correct threads without sorting', () => {
const state = deepFreeze({
root1: ['comment1'],
});
const posts = {
comment2: {id: 'comment2', root_id: 'root1'},
comment3: {id: 'comment3', root_id: 'root2'},
comment4: {id: 'comment4', root_id: 'root1'},
};
const nextState = reducers.postsInThread(state, {
type: actionType,
data: {
order: [],
posts,
},
});
expect(nextState).not.toBe(state);
expect(nextState).toEqual({
root1: ['comment1', 'comment2', 'comment4'],
root2: ['comment3'],
});
});
it('should not save not-comment posts', () => {
const state = deepFreeze({
root1: ['comment1'],
});
const posts = {
comment2: {id: 'comment2', root_id: 'root1'},
root2: {id: 'root2'},
comment3: {id: 'comment3', root_id: 'root2'},
};
const nextState = reducers.postsInThread(state, {
type: actionType,
data: {
order: [],
posts,
},
});
expect(nextState).not.toBe(state);
expect(nextState).toEqual({
root1: ['comment1', 'comment2'],
root2: ['comment3'],
});
});
it('should not save duplicate posts', () => {
const state = deepFreeze({
root1: ['comment1'],
});
const posts = {
comment1: {id: 'comment2', root_id: 'root1'},
comment2: {id: 'comment2', root_id: 'root1'},
};
const nextState = reducers.postsInThread(state, {
type: actionType,
data: {
order: [],
posts,
},
});
expect(nextState).not.toBe(state);
expect(nextState).toEqual({
root1: ['comment1', 'comment2'],
});
});
it('should do nothing when receiving no posts', () => {
const state = deepFreeze({
root1: ['comment1'],
});
const posts = {};
const nextState = reducers.postsInThread(state, {
type: actionType,
data: {
order: [],
posts,
},
});
expect(nextState).toBe(state);
expect(nextState).toEqual({
root1: ['comment1'],
});
});
it('should do nothing when receiving no comments', () => {
const state = deepFreeze({
root1: ['comment1'],
});
const posts = {
root2: {id: 'root2'},
};
const nextState = reducers.postsInThread(state, {
type: actionType,
data: {
order: [],
posts,
},
});
expect(nextState).toBe(state);
expect(nextState).toEqual({
root1: ['comment1'],
});
});
});
}
describe('receiving posts in a thread', () => {
it('should save comments without sorting', () => {
const state = deepFreeze({
root1: ['comment1'],
});
const posts = {
comment2: {id: 'comment2', root_id: 'root1'},
comment3: {id: 'comment3', root_id: 'root1'},
};
const nextState = reducers.postsInThread(state, {
type: PostTypes.RECEIVED_POSTS_IN_THREAD,
data: {
order: [],
posts,
},
rootId: 'root1',
});
expect(nextState).not.toBe(state);
expect(nextState).toEqual({
root1: ['comment1', 'comment2', 'comment3'],
});
});
it('should not save the root post', () => {
const state = deepFreeze({
root1: ['comment1'],
});
const posts = {
root2: {id: 'root2'},
comment2: {id: 'comment2', root_id: 'root2'},
comment3: {id: 'comment3', root_id: 'root2'},
};
const nextState = reducers.postsInThread(state, {
type: PostTypes.RECEIVED_POSTS_IN_THREAD,
data: {
order: [],
posts,
},
rootId: 'root2',
});
expect(nextState).not.toBe(state);
expect(nextState.root1).toBe(state.root1);
expect(nextState).toEqual({
root1: ['comment1'],
root2: ['comment2', 'comment3'],
});
});
it('should not save duplicate posts', () => {
const state = deepFreeze({
root1: ['comment1'],
});
const posts = {
comment1: {id: 'comment1', root_id: 'root1'},
comment2: {id: 'comment2', root_id: 'root1'},
};
const nextState = reducers.postsInThread(state, {
type: PostTypes.RECEIVED_POSTS_IN_THREAD,
data: {
order: [],
posts,
},
rootId: 'root1',
});
expect(nextState).not.toBe(state);
expect(nextState).toEqual({
root1: ['comment1', 'comment2'],
});
});
it('should do nothing when receiving no posts', () => {
const state = deepFreeze({
root1: ['comment1'],
});
const posts = {};
const nextState = reducers.postsInThread(state, {
type: PostTypes.RECEIVED_POSTS_IN_THREAD,
data: {
order: [],
posts,
},
rootId: 'root2',
});
expect(nextState).toBe(state);
expect(nextState).toEqual({
root1: ['comment1'],
});
});
});
describe('deleting a post', () => {
it('should remove the thread when deleting the root post', () => {
const state = deepFreeze({
root1: ['comment1', 'comment2'],
root2: ['comment3'],
});
const nextState = reducers.postsInThread(state, {
type: PostTypes.POST_DELETED,
data: {id: 'root1'},
});
expect(nextState).not.toBe(state);
expect(nextState.root2).toBe(state.root2);
expect(nextState).toEqual({
root2: ['comment3'],
});
});
it('should do nothing when deleting a comment', () => {
const state = deepFreeze({
root1: ['comment1', 'comment2'],
});
const nextState = reducers.postsInThread(state, {
type: PostTypes.POST_DELETED,
data: {id: 'comment1'},
});
expect(nextState).toBe(state);
expect(nextState).toEqual({
root1: ['comment1', 'comment2'],
});
});
it('should do nothing if deleting a post without comments', () => {
const state = deepFreeze({
root1: ['comment1', 'comment2'],
});
const nextState = reducers.postsInThread(state, {
type: PostTypes.POST_DELETED,
data: {id: 'root2'},
});
expect(nextState).toBe(state);
expect(nextState).toEqual({
root1: ['comment1', 'comment2'],
});
});
});
describe('removing a post', () => {
it('should remove the thread when removing the root post', () => {
const state = deepFreeze({
root1: ['comment1', 'comment2'],
root2: ['comment3'],
});
const nextState = reducers.postsInThread(state, {
type: PostTypes.POST_REMOVED,
data: {id: 'root1'},
});
expect(nextState).not.toBe(state);
expect(nextState.root2).toBe(state.root2);
expect(nextState).toEqual({
root2: ['comment3'],
});
});
it('should remove an entry from the thread when removing a comment', () => {
const state = deepFreeze({
root1: ['comment1', 'comment2'],
root2: ['comment3'],
});
const nextState = reducers.postsInThread(state, {
type: PostTypes.POST_REMOVED,
data: {id: 'comment1', root_id: 'root1'},
});
expect(nextState).not.toBe(state);
expect(nextState.root2).toBe(state.root2);
expect(nextState).toEqual({
root1: ['comment2'],
root2: ['comment3'],
});
});
it('should do nothing if removing a thread that has not been loaded', () => {
const state = deepFreeze({
root1: ['comment1', 'comment2'],
});
const nextState = reducers.postsInThread(state, {
type: PostTypes.POST_REMOVED,
data: {id: 'root2'},
});
expect(nextState).toBe(state);
expect(nextState).toEqual({
root1: ['comment1', 'comment2'],
});
});
});
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 threads in that channel', () => {
const state = deepFreeze({
root1: ['comment1', 'comment2'],
root2: ['comment3'],
root3: ['comment4'],
});
const prevPosts = {
root1: {id: 'root1', channel_id: 'channel1'},
comment1: {id: 'comment1', channel_id: 'channel1', root_id: 'root1'},
comment2: {id: 'comment2', channel_id: 'channel1', root_id: 'root1'},
root2: {id: 'root2', channel_id: 'channel2'},
comment3: {id: 'comment3', channel_id: 'channel2', root_id: 'root2'},
root3: {id: 'root3', channel_id: 'channel1'},
comment4: {id: 'comment3', channel_id: 'channel1', root_id: 'root3'},
};
const nextState = reducers.postsInThread(state, {
type: actionType,
data: {
id: 'channel1',
viewArchivedChannels: false,
},
}, prevPosts);
expect(nextState).not.toBe(state);
expect(nextState.root2).toBe(state.root2);
expect(nextState).toEqual({
root2: ['comment3'],
});
});
it('should do nothing if no threads in that channel are loaded', () => {
const state = deepFreeze({
root1: ['comment1', 'comment2'],
});
const prevPosts = {
root1: {id: 'root1', channel_id: 'channel1'},
comment1: {id: 'comment1', channel_id: 'channel1', root_id: 'root1'},
comment2: {id: 'comment2', channel_id: 'channel1', root_id: 'root1'},
};
const nextState = reducers.postsInThread(state, {
type: actionType,
data: {
id: 'channel2',
viewArchivedChannels: false,
},
}, prevPosts);
expect(nextState).toBe(state);
expect(nextState).toEqual({
root1: ['comment1', 'comment2'],
});
});
it('should not remove any posts with viewArchivedChannels enabled', () => {
const state = deepFreeze({
root1: ['comment1', 'comment2'],
root2: ['comment3'],
});
const prevPosts = {
root1: {id: 'root1', channel_id: 'channel1'},
comment1: {id: 'comment1', channel_id: 'channel1', root_id: 'root1'},
comment2: {id: 'comment2', channel_id: 'channel1', root_id: 'root1'},
root2: {id: 'root2', channel_id: 'channel2'},
comment3: {id: 'comment3', channel_id: 'channel2', root_id: 'root2'},
};
const nextState = reducers.postsInThread(state, {
type: actionType,
data: {
id: 'channel1',
viewArchivedChannels: true,
},
}, prevPosts);
expect(nextState).toBe(state);
expect(nextState).toEqual({
root1: ['comment1', 'comment2'],
root2: ['comment3'],
});
});
it('should not error if a post is missing from prevPosts', () => {
const state = deepFreeze({
root1: ['comment1'],
});
const prevPosts = {
comment1: {id: 'comment1', channel_id: 'channel1', root_id: 'root1'},
};
const nextState = reducers.postsInThread(state, {
type: actionType,
data: {
id: 'channel1',
viewArchivedChannels: false,
},
}, prevPosts);
expect(nextState).toBe(state);
});
});
}
});
describe('removeUnneededMetadata', () => {
it('without metadata', () => {
const post = deepFreeze({
id: 'post',
});
const nextPost = reducers.removeUnneededMetadata(post);
assert.equal(nextPost, post);
});
it('with empty metadata', () => {
const post = deepFreeze({
id: 'post',
metadata: {},
});
const nextPost = reducers.removeUnneededMetadata(post);
assert.equal(nextPost, post);
});
it('should remove emojis', () => {
const post = deepFreeze({
id: 'post',
metadata: {
emojis: [{name: 'emoji'}],
},
});
const nextPost = reducers.removeUnneededMetadata(post);
assert.notEqual(nextPost, post);
assert.deepEqual(nextPost, {
id: 'post',
metadata: {},
});
});
it('should remove files', () => {
const post = deepFreeze({
id: 'post',
metadata: {
files: [{id: 'file', post_id: 'post'}],
},
});
const nextPost = reducers.removeUnneededMetadata(post);
assert.notEqual(nextPost, post);
assert.deepEqual(nextPost, {
id: 'post',
metadata: {},
});
});
it('should remove reactions', () => {
const post = deepFreeze({
id: 'post',
metadata: {
reactions: [
{user_id: 'abcd', emoji_name: '+1'},
{user_id: 'efgh', emoji_name: '+1'},
{user_id: 'abcd', emoji_name: '-1'},
],
},
});
const nextPost = reducers.removeUnneededMetadata(post);
assert.notEqual(nextPost, post);
assert.deepEqual(nextPost, {
id: 'post',
metadata: {},
});
});
it('should remove OpenGraph data', () => {
const post = deepFreeze({
id: 'post',
metadata: {
embeds: [{
type: 'opengraph',
url: 'https://example.com',
data: {
url: 'https://example.com',
images: [{
url: 'https://example.com/logo.png',
width: 100,
height: 100,
}],
},
}],
},
});
const nextPost = reducers.removeUnneededMetadata(post);
assert.notEqual(nextPost, post);
assert.deepEqual(nextPost, {
id: 'post',
metadata: {
embeds: [{
type: 'opengraph',
url: 'https://example.com',
}],
},
});
});
it('should not affect non-OpenGraph embeds', () => {
const post = deepFreeze({
id: 'post',
metadata: {
embeds: [
{type: 'image', url: 'https://example.com/image'},
{type: 'message_attachment'},
],
},
props: {
attachments: [
{text: 'This is an attachment'},
],
},
});
const nextPost = reducers.removeUnneededMetadata(post);
assert.equal(nextPost, post);
});
});
describe('reactions', () => {
for (const actionType of [
PostTypes.RECEIVED_NEW_POST,
PostTypes.RECEIVED_POST,
]) {
describe(`single post received (${actionType})`, () => {
it('no post metadata', () => {
const state = deepFreeze({});
const action = {
type: actionType,
data: {
id: 'post',
},
};
const nextState = reducers.reactions(state, action);
assert.equal(nextState, state);
});
it('no reactions in post metadata', () => {
const state = deepFreeze({});
const action = {
type: actionType,
data: {
id: 'post',
metadata: {reactions: []},
},
};
const nextState = reducers.reactions(state, action);
assert.notEqual(nextState, state);
assert.deepEqual(nextState, {
post: {},
});
});
it('should not clobber reactions when metadata empty', () => {
const state = deepFreeze({post: {name: 'smiley', post_id: 'post'}});
const action = {
type: actionType,
data: {
id: 'post',
metadata: {},
},
};
const nextState = reducers.reactions(state, action);
assert.deepEqual(nextState, {
post: {name: 'smiley', post_id: 'post'},
});
});
it('should save reactions', () => {
const state = deepFreeze({});
const action = {
type: actionType,
data: {
id: 'post',
metadata: {
reactions: [
{user_id: 'abcd', emoji_name: '+1'},
{user_id: 'efgh', emoji_name: '+1'},
{user_id: 'abcd', emoji_name: '-1'},
],
},
},
};
const nextState = reducers.reactions(state, action);
assert.notEqual(nextState, state);
assert.deepEqual(nextState, {
post: {
'abcd-+1': {user_id: 'abcd', emoji_name: '+1'},
'efgh-+1': {user_id: 'efgh', emoji_name: '+1'},
'abcd--1': {user_id: 'abcd', emoji_name: '-1'},
},
});
});
it('should not save reaction for a deleted post', () => {
const state = deepFreeze({});
const action = {
type: actionType,
data: {
id: 'post',
delete_at: '1571366424287',
},
};
const nextState = reducers.reactions(state, action);
assert.equal(nextState, state);
});
});
}
describe('receiving multiple posts', () => {
it('no post metadata', () => {
const state = deepFreeze({});
const action = {
type: PostTypes.RECEIVED_POSTS,
data: {
posts: {
post: {
id: 'post',
},
},
},
};
const nextState = reducers.reactions(state, action);
assert.equal(nextState, state);
});
it('no reactions in post metadata', () => {
const state = deepFreeze({});
const action = {
type: PostTypes.RECEIVED_POSTS,
data: {
posts: {
post: {
id: 'post',
metadata: {reactions: []},
},
},
},
};
const nextState = reducers.reactions(state, action);
assert.notEqual(nextState, state);
assert.deepEqual(nextState, {
post: {},
});
});
it('should save reactions', () => {
const state = deepFreeze({});
const action = {
type: PostTypes.RECEIVED_POSTS,
data: {
posts: {
post: {
id: 'post',
metadata: {
reactions: [
{user_id: 'abcd', emoji_name: '+1'},
{user_id: 'efgh', emoji_name: '+1'},
{user_id: 'abcd', emoji_name: '-1'},
],
},
},
},
},
};
const nextState = reducers.reactions(state, action);
assert.notEqual(nextState, state);
assert.deepEqual(nextState, {
post: {
'abcd-+1': {user_id: 'abcd', emoji_name: '+1'},
'efgh-+1': {user_id: 'efgh', emoji_name: '+1'},
'abcd--1': {user_id: 'abcd', emoji_name: '-1'},
},
});
});
it('should save reactions for multiple posts', () => {
const state = deepFreeze({});
const action = {
type: PostTypes.RECEIVED_POSTS,
data: {
posts: {
post1: {
id: 'post1',
metadata: {
reactions: [
{user_id: 'abcd', emoji_name: '+1'},
],
},
},
post2: {
id: 'post2',
metadata: {
reactions: [
{user_id: 'abcd', emoji_name: '-1'},
],
},
},
},
},
};
const nextState = reducers.reactions(state, action);
assert.notEqual(nextState, state);
assert.deepEqual(nextState, {
post1: {
'abcd-+1': {user_id: 'abcd', emoji_name: '+1'},
},
post2: {
'abcd--1': {user_id: 'abcd', emoji_name: '-1'},
},
});
});
it('should save reactions for multiple posts except deleted posts', () => {
const state = deepFreeze({});
const action = {
type: PostTypes.RECEIVED_POSTS,
data: {
posts: {
post1: {
id: 'post1',
metadata: {
reactions: [
{user_id: 'abcd', emoji_name: '+1'},
],
},
},
post2: {
id: 'post2',
delete_at: '1571366424287',
metadata: {
reactions: [
{user_id: 'abcd', emoji_name: '-1'},
],
},
},
},
},
};
const nextState = reducers.reactions(state, action);
assert.notEqual(nextState, state);
assert.deepEqual(nextState, {
post1: {
'abcd-+1': {user_id: 'abcd', emoji_name: '+1'},
},
});
});
});
});
describe('opengraph', () => {
for (const actionType of [
PostTypes.RECEIVED_NEW_POST,
PostTypes.RECEIVED_POST,
]) {
describe(`single post received (${actionType})`, () => {
it('no post metadata', () => {
const state = deepFreeze({});
const action = {
type: actionType,
data: {
id: 'post',
},
};
const nextState = reducers.openGraph(state, action);
assert.equal(nextState, state);
});
it('no embeds in post metadata', () => {
const state = deepFreeze({});
const action = {
type: actionType,
data: {
id: 'post',
metadata: {},
},
};
const nextState = reducers.openGraph(state, action);
assert.equal(nextState, state);
});
it('other types of embeds in post metadata', () => {
const state = deepFreeze({});
const action = {
type: actionType,
data: {
id: 'post',
metadata: {
embeds: [{
type: 'image',
url: 'https://example.com/image.png',
}, {
type: 'message_attachment',
}],
},
},
};
const nextState = reducers.openGraph(state, action);
assert.equal(nextState, state);
});
it('should save opengraph data', () => {
const state = deepFreeze({});
const action = {
type: actionType,
data: {
id: 'post',
metadata: {
embeds: [{
type: 'opengraph',
url: 'https://example.com',
data: {
title: 'Example',
description: 'Example description',
},
}],
},
},
};
const nextState = reducers.openGraph(state, action);
assert.notEqual(nextState, state);
assert.deepEqual(nextState, {
post: {'https://example.com': action.data.metadata.embeds[0].data},
});
});
});
}
describe('receiving multiple posts', () => {
it('no post metadata', () => {
const state = deepFreeze({});
const action = {
type: PostTypes.RECEIVED_POSTS,
data: {
posts: {
post: {
id: 'post',
},
},
},
};
const nextState = reducers.openGraph(state, action);
assert.equal(nextState, state);
});
it('no embeds in post metadata', () => {
const state = deepFreeze({});
const action = {
type: PostTypes.RECEIVED_POSTS,
data: {
posts: {
post: {
id: 'post',
metadata: {},
},
},
},
};
const nextState = reducers.openGraph(state, action);
assert.equal(nextState, state);
});
it('other types of embeds in post metadata', () => {
const state = deepFreeze({});
const action = {
type: PostTypes.RECEIVED_POSTS,
data: {
posts: {
post: {
id: 'post',
metadata: {
embeds: [{
type: 'image',
url: 'https://example.com/image.png',
}, {
type: 'message_attachment',
}],
},
},
},
},
};
const nextState = reducers.openGraph(state, action);
assert.equal(nextState, state);
});
it('should save opengraph data', () => {
const state = deepFreeze({});
const action = {
type: PostTypes.RECEIVED_POSTS,
data: {
posts: {
post1: {
id: 'post1',
metadata: {
embeds: [{
type: 'opengraph',
url: 'https://example.com',
data: {
title: 'Example',
description: 'Example description',
},
}],
},
},
},
},
};
const nextState = reducers.openGraph(state, action);
assert.notEqual(nextState, state);
assert.deepEqual(nextState, {
post1: {'https://example.com': action.data.posts.post1.metadata.embeds[0].data},
});
});
it('should save reactions for multiple posts', () => {
const state = deepFreeze({});
const action = {
type: PostTypes.RECEIVED_POSTS,
data: {
posts: {
post1: {
id: 'post1',
metadata: {
embeds: [{
type: 'opengraph',
url: 'https://example.com',
data: {
title: 'Example',
description: 'Example description',
},
}],
},
},
post2: {
id: 'post2',
metadata: {
embeds: [{
type: 'opengraph',
url: 'https://google.ca',
data: {
title: 'Google',
description: 'Something about search',
},
}],
},
},
},
},
};
const nextState = reducers.openGraph(state, action);
assert.notEqual(nextState, state);
assert.deepEqual(nextState, {
post1: {'https://example.com': action.data.posts.post1.metadata.embeds[0].data},
post2: {'https://google.ca': action.data.posts.post2.metadata.embeds[0].data},
});
});
});
});
describe('expandedURLs', () => {
it('should store the URLs on REDIRECT_LOCATION_SUCCESS', () => {
const state = deepFreeze({});
const action = {
type: GeneralTypes.REDIRECT_LOCATION_SUCCESS,
data: {
url: 'a',
location: 'b',
},
};
const nextState = reducers.expandedURLs(state, action);
assert.notEqual(state, nextState);
assert.deepEqual(nextState, {
a: 'b',
});
});
it('should store the non-expanded URL on REDIRECT_LOCATION_FAILURE', () => {
const state = deepFreeze({});
const action = {
type: GeneralTypes.REDIRECT_LOCATION_FAILURE,
data: {
url: 'b',
},
};
const nextState = reducers.expandedURLs(state, action);
assert.notEqual(state, nextState);
assert.deepEqual(nextState, {
b: 'b',
});
});
});
describe('removeNonRecentEmptyPostBlocks', () => {
it('should filter empty blocks', () => {
const blocks = [{
order: [],
recent: false,
}, {
order: ['1', '2'],
recent: false,
}];
const filteredBlocks = reducers.removeNonRecentEmptyPostBlocks(blocks);
assert.deepEqual(filteredBlocks, [{
order: ['1', '2'],
recent: false,
}]);
});
it('should not filter empty recent block', () => {
const blocks = [{
order: [],
recent: true,
}, {
order: ['1', '2'],
recent: false,
}, {
order: [],
recent: false,
}];
const filteredBlocks = reducers.removeNonRecentEmptyPostBlocks(blocks);
assert.deepEqual(filteredBlocks, [{
order: [],
recent: true,
}, {
order: ['1', '2'],
recent: false,
}]);
});
});