diff --git a/.eslintrc.json b/.eslintrc.json
index 3746784cd..6cfbf7cc9 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -65,7 +65,6 @@
"linebreak-style": 2,
"lines-around-comment": [2, { "beforeBlockComment": true, "beforeLineComment": true, "allowBlockStart": true, "allowBlockEnd": true }],
"max-lines": [1, {"max": 450, "skipBlankLines": true, "skipComments": false}],
- "max-nested-callbacks": [1, {"max":1}],
"max-nested-callbacks": [2, {"max":2}],
"max-statements-per-line": [2, {"max": 1}],
"multiline-ternary": [1, "never"],
@@ -148,7 +147,7 @@
"no-template-curly-in-string": 2,
"no-ternary": 0,
"no-this-before-super": 2,
- "no-throw-literal": 2,
+ "no-throw-literal": 0,
"no-trailing-spaces": [2, { "skipBlankLines": false }],
"no-undef-init": 2,
"no-undefined": 2,
diff --git a/index.android.js b/index.android.js
index 18e7a7c36..2cbc92c80 100644
--- a/index.android.js
+++ b/index.android.js
@@ -1,24 +1,7 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import React from 'react';
-
import {AppRegistry} from 'react-native';
-import configureStore from 'store/configureStore.js';
-
-import {Provider} from 'react-redux';
-import RootContainer from 'containers/root_container.js';
-
-const store = configureStore();
-
-class Mattermost extends React.Component {
- render() {
- return (
-
-
-
- );
- }
-}
+import Mattermost from 'mattermost';
AppRegistry.registerComponent('Mattermost', () => Mattermost);
diff --git a/index.ios.js b/index.ios.js
index 18e7a7c36..2cbc92c80 100644
--- a/index.ios.js
+++ b/index.ios.js
@@ -1,24 +1,7 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import React from 'react';
-
import {AppRegistry} from 'react-native';
-import configureStore from 'store/configureStore.js';
-
-import {Provider} from 'react-redux';
-import RootContainer from 'containers/root_container.js';
-
-const store = configureStore();
-
-class Mattermost extends React.Component {
- render() {
- return (
-
-
-
- );
- }
-}
+import Mattermost from 'mattermost';
AppRegistry.registerComponent('Mattermost', () => Mattermost);
diff --git a/package.json b/package.json
index d74598b3a..41c4658d1 100644
--- a/package.json
+++ b/package.json
@@ -6,6 +6,7 @@
"babel-preset-es2015": "6.18.0",
"intl": "1.2.5",
"isomorphic-fetch": "2.2.1",
+ "keymirror": "0.1.1",
"lodash": "4.16.4",
"react": "15.3.2",
"react-addons-pure-render-mixin": "15.3.2",
diff --git a/src/actions/channels.js b/src/actions/channels.js
index 75ae50cd8..c86e234ce 100644
--- a/src/actions/channels.js
+++ b/src/actions/channels.js
@@ -1,30 +1,479 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import {ChannelTypes} from 'constants';
+import {Constants, ChannelTypes, UsersTypes} from 'constants';
+import {forceLogoutIfNecessary} from './helpers';
import {batchActions} from 'redux-batched-actions';
-import Client from 'client/client_instance.js';
+import Client from 'client';
+
+export function createChannel(channel, userId) {
+ return async (dispatch, getState) => {
+ try {
+ dispatch(batchActions([
+ {
+ type: ChannelTypes.CREATE_CHANNEL_REQUEST
+ },
+ {
+ type: ChannelTypes.CHANNEL_MEMBERS_REQUEST
+ }
+ ]), getState);
+
+ const created = await Client.createChannel(channel);
+ const member = {
+ channel_id: created.id,
+ user_id: userId,
+ roles: `${Constants.CHANNEL_USER_ROLE} ${Constants.CHANNEL_ADMIN_ROLE}`,
+ last_viewed_at: 0,
+ msg_count: 0,
+ mention_count: 0,
+ notify_props: {desktop: 'default', mark_unread: 'all'},
+ last_update_at: created.create_at
+ };
+
+ dispatch(batchActions([
+ {
+ type: ChannelTypes.RECEIVED_CHANNEL,
+ data: created
+ },
+ {
+ type: ChannelTypes.CREATE_CHANNEL_SUCCESS
+ },
+ {
+ type: ChannelTypes.RECEIVED_MY_CHANNEL_MEMBER,
+ data: member
+ },
+ {
+ type: ChannelTypes.CHANNEL_MEMBERS_SUCCESS
+ }
+ ]), getState);
+ } catch (error) {
+ forceLogoutIfNecessary(error, dispatch);
+ dispatch(batchActions([
+ {
+ type: ChannelTypes.CREATE_CHANNEL_FAILURE,
+ error
+ },
+ {
+ type: ChannelTypes.CHANNEL_MEMBERS_FAILURE,
+ error
+ }
+ ]), getState);
+ }
+ };
+}
+
+export function createDirectChannel(userId, otherUserId) {
+ return async (dispatch, getState) => {
+ try {
+ dispatch(batchActions([
+ {
+ type: ChannelTypes.CREATE_CHANNEL_REQUEST
+ },
+ {
+ type: ChannelTypes.CHANNEL_MEMBERS_REQUEST
+ },
+ {
+ type: UsersTypes.PROFILES_REQUEST
+ }
+ ]), getState);
+
+ const created = await Client.createDirectChannel(otherUserId);
+ const profile = await Client.getUser(otherUserId);
+ const member = {
+ channel_id: created.id,
+ user_id: userId,
+ roles: `${Constants.CHANNEL_USER_ROLE} ${Constants.CHANNEL_ADMIN_ROLE}`,
+ last_viewed_at: 0,
+ msg_count: 0,
+ mention_count: 0,
+ notify_props: {desktop: 'default', mark_unread: 'all'},
+ last_update_at: created.create_at
+ };
+
+ dispatch(batchActions([
+ {
+ type: ChannelTypes.RECEIVED_CHANNEL,
+ data: created
+ },
+ {
+ type: ChannelTypes.CREATE_CHANNEL_SUCCESS
+ },
+ {
+ type: ChannelTypes.RECEIVED_MY_CHANNEL_MEMBER,
+ data: member
+ },
+ {
+ type: ChannelTypes.CHANNEL_MEMBERS_SUCCESS
+ },
+ {
+ type: UsersTypes.RECEIVED_PROFILES,
+ data: {[profile.id]: profile}
+ },
+ {
+ type: UsersTypes.PROFILES_SUCCESS
+ },
+ {
+ type: UsersTypes.RECEIVED_PREFERENCE,
+ data: {category: Constants.CATEGORY_DIRECT_CHANNEL_SHOW, name: otherUserId, value: 'true'}
+ }
+ ]), getState);
+ } catch (error) {
+ forceLogoutIfNecessary(error, dispatch);
+ dispatch(batchActions([
+ {
+ type: ChannelTypes.CREATE_CHANNEL_FAILURE,
+ error
+ },
+ {
+ type: ChannelTypes.CHANNEL_MEMBERS_FAILURE,
+ error
+ },
+ {
+ type: UsersTypes.PROFILES_FAILURE
+ }
+ ]), getState);
+ }
+ };
+}
+
+export function updateChannel(channel) {
+ return async (dispatch, getState) => {
+ try {
+ dispatch({type: ChannelTypes.UPDATE_CHANNEL_REQUEST}, getState);
+
+ const updated = await Client.updateChannel(channel);
+ dispatch(batchActions([
+ {
+ type: ChannelTypes.RECEIVED_CHANNEL,
+ data: updated
+ },
+ {
+ type: ChannelTypes.UPDATE_CHANNEL_SUCCESS
+ }
+ ]), getState);
+ } catch (error) {
+ forceLogoutIfNecessary(error, dispatch);
+ dispatch({type: ChannelTypes.UPDATE_CHANNEL_FAILURE, error}, getState);
+ }
+ };
+}
+
+export function updateChannelNotifyProps(userId, teamId, channelId, props) {
+ return async (dispatch, getState) => {
+ try {
+ dispatch({type: ChannelTypes.NOTIFY_PROPS_REQUEST}, getState);
+
+ const data = {
+ user_id: userId,
+ channel_id: channelId,
+ ...props
+ };
+
+ const notifyProps = await Client.updateChannelNotifyProps(teamId, data);
+ dispatch(batchActions([
+ {
+ type: ChannelTypes.RECEIVED_CHANNEL_PROPS,
+ data: notifyProps,
+ channel_id: channelId
+ },
+ {
+ type: ChannelTypes.NOTIFY_PROPS_SUCCESS
+ }
+ ]), getState);
+ } catch (error) {
+ forceLogoutIfNecessary(error, dispatch);
+ dispatch({type: ChannelTypes.NOTIFY_PROPS_FAILURE, error}, getState);
+ }
+ };
+}
+
+export function getChannel(teamId, channelId) {
+ return async (dispatch, getState) => {
+ try {
+ dispatch({type: ChannelTypes.CHANNEL_REQUEST}, getState);
+
+ const data = await Client.getChannel(teamId, channelId);
+
+ dispatch(batchActions([
+ {
+ type: ChannelTypes.RECEIVED_CHANNEL,
+ data: data.channel
+ },
+ {
+ type: ChannelTypes.CHANNEL_SUCCESS
+ },
+ {
+ type: ChannelTypes.RECEIVED_MY_CHANNEL_MEMBER,
+ data: data.member
+ }
+ ]), getState);
+ } catch (error) {
+ forceLogoutIfNecessary(error, dispatch);
+ dispatch({type: ChannelTypes.CHANNELS_FAILURE, error}, getState);
+ }
+ };
+}
export function fetchMyChannelsAndMembers(teamId) {
return async (dispatch, getState) => {
try {
+ dispatch(batchActions([
+ {
+ type: ChannelTypes.CHANNELS_REQUEST
+ },
+ {
+ type: ChannelTypes.CHANNEL_MEMBERS_REQUEST
+ }
+ ]), getState);
+
const channels = Client.getChannels(teamId);
const channelMembers = Client.getMyChannelMembers(teamId);
dispatch(batchActions([
{
- type: ChannelTypes.CHANNELS_RECEIVED,
- teamId,
- channels: await channels
+ type: ChannelTypes.RECEIVED_CHANNELS,
+ data: await channels
},
{
- type: ChannelTypes.CHANNEL_MEMBERS_RECEIVED,
- teamId,
- channelMembers: await channelMembers
+ type: ChannelTypes.CHANNELS_SUCCESS
+ },
+ {
+ type: ChannelTypes.RECEIVED_MY_CHANNEL_MEMBERS,
+ data: await channelMembers
+ },
+ {
+ type: ChannelTypes.CHANNEL_MEMBERS_SUCCESS
+ }
+ ]), getState);
+ } catch (error) {
+ forceLogoutIfNecessary(error, dispatch);
+ dispatch(batchActions([
+ {
+ type: ChannelTypes.CHANNELS_FAILURE,
+ error
+ },
+ {
+ type: ChannelTypes.CHANNEL_MEMBERS_FAILURE,
+ error
}
]), getState);
- } catch (err) {
- console.error(err); // eslint-disable-line no-console
}
};
}
+
+export function leaveChannel(teamId, channelId) {
+ return async (dispatch, getState) => {
+ try {
+ dispatch({type: ChannelTypes.LEAVE_CHANNEL_REQUEST}, getState);
+
+ await Client.leaveChannel(teamId, channelId);
+ dispatch(batchActions([
+ {
+ type: ChannelTypes.LEAVE_CHANNEL,
+ channel_id: channelId
+ },
+ {
+ type: ChannelTypes.LEAVE_CHANNEL_SUCCESS
+ }
+ ]), getState);
+ } catch (error) {
+ forceLogoutIfNecessary(error, dispatch);
+ dispatch({type: ChannelTypes.LEAVE_CHANNEL_FAILURE, error}, getState);
+ }
+ };
+}
+
+export function joinChannel(userId, teamId, channelId, channelName) {
+ return async (dispatch, getState) => {
+ try {
+ dispatch({type: ChannelTypes.JOIN_CHANNEL_REQUEST}, getState);
+
+ let channel;
+ if (channelId) {
+ channel = await Client.joinChannel(teamId, channelId);
+ } else if (channelName) {
+ channel = await Client.joinChannelByName(teamId, channelName);
+ }
+
+ const channelMember = {
+ channel_id: channel.id,
+ user_id: userId,
+ roles: `${Constants.CHANNEL_USER_ROLE}`,
+ last_viewed_at: 0,
+ msg_count: 0,
+ mention_count: 0,
+ notify_props: {desktop: 'default', mark_unread: 'all'},
+ last_update_at: new Date().getTime()
+ };
+
+ dispatch(batchActions([
+ {
+ type: ChannelTypes.RECEIVED_CHANNEL,
+ data: channel
+ },
+ {
+ type: ChannelTypes.RECEIVED_MY_CHANNEL_MEMBER,
+ data: channelMember
+ },
+ {
+ type: ChannelTypes.JOIN_CHANNEL_SUCCESS
+ }
+ ]), getState);
+ } catch (error) {
+ forceLogoutIfNecessary(error, dispatch);
+ dispatch({type: ChannelTypes.JOIN_CHANNEL_FAILURE, error}, getState);
+ }
+ };
+}
+
+export function deleteChannel(teamId, channelId) {
+ return async (dispatch, getState) => {
+ try {
+ dispatch({type: ChannelTypes.DELETE_CHANNEL_REQUEST}, getState);
+
+ await Client.deleteChannel(teamId, channelId);
+ dispatch(batchActions([
+ {
+ type: ChannelTypes.RECEIVED_CHANNEL_DELETED,
+ channel_id: channelId
+ },
+ {
+ type: ChannelTypes.DELETE_CHANNEL_SUCCESS
+ }
+ ]), getState);
+ } catch (error) {
+ forceLogoutIfNecessary(error, dispatch);
+ dispatch({type: ChannelTypes.DELETE_CHANNEL_FAILURE, error}, getState);
+ }
+ };
+}
+
+export function updateLastViewedAt(teamId, channelId, active) {
+ return async (dispatch, getState) => {
+ try {
+ dispatch({type: ChannelTypes.UPDATE_LAST_VIEWED_REQUEST}, getState);
+
+ // this API should return the timestamp that was set
+ await Client.updateLastViewedAt(teamId, channelId, active);
+ dispatch(batchActions([
+ {
+ type: ChannelTypes.RECEIVED_LAST_VIEWED,
+ channel_id: channelId,
+ last_viewed_at: new Date().getTime()
+ },
+ {
+ type: ChannelTypes.UPDATE_LAST_VIEWED_SUCCESS
+ }
+ ]), getState);
+ } catch (error) {
+ forceLogoutIfNecessary(error, dispatch);
+ dispatch({type: ChannelTypes.UPDATE_LAST_VIEWED_FAILURE, error}, getState);
+ }
+ };
+}
+
+export function getMoreChannels(teamId, offset, limit = Constants.CHANNELS_CHUNK_SIZE) {
+ return async (dispatch, getState) => {
+ try {
+ dispatch({type: ChannelTypes.MORE_CHANNELS_REQUEST}, getState);
+
+ const channels = Client.getMoreChannels(teamId, offset, limit);
+
+ dispatch(batchActions([
+ {
+ type: ChannelTypes.RECEIVED_MORE_CHANNELS,
+ data: await channels
+ },
+ {
+ type: ChannelTypes.MORE_CHANNELS_SUCCESS
+ }
+ ]), getState);
+ } catch (error) {
+ forceLogoutIfNecessary(error, dispatch);
+ dispatch({type: ChannelTypes.MORE_CHANNELS_FAILURE, error}, getState);
+ }
+ };
+}
+
+export function getChannelStats(teamId, channelId) {
+ return async (dispatch, getState) => {
+ try {
+ dispatch({type: ChannelTypes.CHANNEL_STATS_REQUEST}, getState);
+
+ const stat = await Client.getChannelStats(teamId, channelId);
+ dispatch(batchActions([
+ {
+ type: ChannelTypes.RECEIVED_CHANNEL_STATS,
+ data: stat
+ },
+ {
+ type: ChannelTypes.CHANNEL_STATS_SUCCESS
+ }
+ ]), getState);
+ } catch (error) {
+ forceLogoutIfNecessary(error, dispatch);
+ dispatch({type: ChannelTypes.CHANNEL_STATS_FAILURE, error}, getState);
+ }
+ };
+}
+
+export function addChannelMember(teamId, channelId, userId) {
+ return async (dispatch, getState) => {
+ try {
+ dispatch({type: ChannelTypes.ADD_CHANNEL_MEMBER_REQUEST}, getState);
+
+ await Client.addChannelMember(teamId, channelId, userId);
+ dispatch(batchActions([
+ {
+ type: UsersTypes.RECEIVED_PROFILE_IN_CHANNEL,
+ user_id: userId
+ },
+ {
+ type: ChannelTypes.ADD_CHANNEL_MEMBER_SUCCESS
+ }
+ ]), getState);
+ } catch (error) {
+ forceLogoutIfNecessary(error, dispatch);
+ dispatch({type: ChannelTypes.ADD_CHANNEL_MEMBER_FAILURE, error}, getState);
+ }
+ };
+}
+
+export function removeChannelMember(teamId, channelId, userId) {
+ return async (dispatch, getState) => {
+ try {
+ dispatch({type: ChannelTypes.REMOVE_CHANNEL_MEMBER_REQUEST}, getState);
+
+ await Client.addChannelMember(teamId, channelId, userId);
+ dispatch(batchActions([
+ {
+ type: UsersTypes.RECEIVED_PROFILE_NOT_IN_CHANNEL,
+ user_id: userId
+ },
+ {
+ type: ChannelTypes.REMOVE_CHANNEL_MEMBER_SUCCESS
+ }
+ ]), getState);
+ } catch (error) {
+ forceLogoutIfNecessary(error, dispatch);
+ dispatch({type: ChannelTypes.REMOVE_CHANNEL_MEMBER_FAILURE, error}, getState);
+ }
+ };
+}
+
+export default {
+ createChannel,
+ createDirectChannel,
+ updateChannel,
+ updateChannelNotifyProps,
+ getChannel,
+ fetchMyChannelsAndMembers,
+ leaveChannel,
+ joinChannel,
+ deleteChannel,
+ updateLastViewedAt,
+ getMoreChannels,
+ getChannelStats,
+ addChannelMember,
+ removeChannelMember
+};
diff --git a/src/actions/general.js b/src/actions/general.js
index ce69120d9..b284251eb 100644
--- a/src/actions/general.js
+++ b/src/actions/general.js
@@ -1,7 +1,7 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import Client from 'client/client_instance.js';
+import Client from 'client';
import {bindClientFunc} from './helpers.js';
import {GeneralTypes} from 'constants';
@@ -18,11 +18,20 @@ export function getClientConfig() {
return bindClientFunc(
Client.getClientConfig,
GeneralTypes.CLIENT_CONFIG_REQUEST,
- GeneralTypes.CLIENT_CONFIG_SUCCESS,
+ [GeneralTypes.CLIENT_CONFIG_RECEIVED, GeneralTypes.CLIENT_CONFIG_SUCCESS],
GeneralTypes.CLIENT_CONFIG_FAILURE
);
}
+export function getLicenseConfig() {
+ return bindClientFunc(
+ Client.getLicenseConfig,
+ GeneralTypes.CLIENT_LICENSE_REQUEST,
+ [GeneralTypes.CLIENT_LICENSE_RECEIVED, GeneralTypes.CLIENT_LICENSE_SUCCESS],
+ GeneralTypes.CLIENT_LICENSE_FAILURE
+ );
+}
+
export function logClientError(message, level = 'ERROR') {
return bindClientFunc(
Client.logClientError,
diff --git a/src/actions/helpers.js b/src/actions/helpers.js
index 3531a8729..5ffc35a6d 100644
--- a/src/actions/helpers.js
+++ b/src/actions/helpers.js
@@ -1,6 +1,25 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
+import Client from 'client';
+import {UsersTypes} from 'constants';
+const HTTP_UNAUTHORIZED = 401;
+
+export async function forceLogoutIfNecessary(err, dispatch) {
+ if (err.status_code === HTTP_UNAUTHORIZED && err.url.indexOf('/login') === -1) {
+ await Client.logout();
+ dispatch({type: UsersTypes.LOGOUT_SUCCESS});
+ }
+}
+
+function dispatcher(type, data, dispatch, getState) {
+ if (type.indexOf('SUCCESS') === -1) { // we don't want to pass the data for the request types
+ dispatch(requestSuccess(type, data), getState);
+ } else {
+ dispatch(requestData(type), getState);
+ }
+}
+
export function requestData(type) {
return {
type
@@ -21,24 +40,22 @@ export function requestFailure(type, error) {
};
}
-export function emptyError() {
- return {
- id: '',
- message: '',
- detailed_error: '',
- status_code: ''
- };
-}
-
export function bindClientFunc(clientFunc, request, success, failure, ...args) {
return async (dispatch, getState) => {
dispatch(requestData(request), getState);
try {
const data = await clientFunc(...args);
- dispatch(requestSuccess(success, data), getState);
+ if (Array.isArray(success)) {
+ success.forEach((s) => {
+ dispatcher(s, data, dispatch, getState);
+ });
+ } else {
+ dispatcher(success, data, dispatch, getState);
+ }
} catch (err) {
- dispatch(requestFailure(failure, err));
+ forceLogoutIfNecessary(err, dispatch);
+ dispatch(requestFailure(failure, err), getState);
}
};
}
diff --git a/src/actions/login.js b/src/actions/login.js
deleted file mode 100644
index cc77081c4..000000000
--- a/src/actions/login.js
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {LoginTypes} from 'constants';
-import Client from 'client/client_instance';
-import {bindClientFunc} from 'actions/helpers.js';
-
-export function login(loginId, password, mfaToken = '') {
- return bindClientFunc(
- Client.login,
- LoginTypes.LOGIN_REQUEST,
- LoginTypes.LOGIN_SUCCESS,
- LoginTypes.LOGIN_FAILURE,
- loginId,
- password,
- mfaToken
- );
-}
diff --git a/src/actions/logout.js b/src/actions/logout.js
deleted file mode 100644
index fce6e4f1d..000000000
--- a/src/actions/logout.js
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {LogoutTypes} from 'constants';
-import Client from 'client/client_instance';
-import {bindClientFunc} from 'actions/helpers.js';
-
-export function logout() {
- return bindClientFunc(
- Client.logout,
- LogoutTypes.LOGOUT_REQUEST,
- LogoutTypes.LOGOUT_SUCCESS,
- LogoutTypes.LOGOUT_FAILURE
- );
-}
diff --git a/src/actions/posts.js b/src/actions/posts.js
index 7c1fe0a5a..71ccd3c79 100644
--- a/src/actions/posts.js
+++ b/src/actions/posts.js
@@ -2,15 +2,15 @@
// See License.txt for license information.
import {bindClientFunc} from './helpers.js';
-import Client from 'client/client_instance';
-import {PostsTypes as types} from 'constants';
+import Client from 'client';
+import {PostsTypes} from 'constants';
export function fetchPosts(teamId, channelId) {
return bindClientFunc(
Client.fetchPosts,
- types.FETCH_POSTS_REQUEST,
- types.FETCH_POSTS_SUCCESS,
- types.FETCH_POSTS_FAILURE,
+ PostsTypes.FETCH_POSTS_REQUEST,
+ PostsTypes.FETCH_POSTS_SUCCESS,
+ PostsTypes.FETCH_POSTS_FAILURE,
teamId,
channelId
);
diff --git a/src/actions/teams.js b/src/actions/teams.js
index 2c9e80cc8..6689158eb 100644
--- a/src/actions/teams.js
+++ b/src/actions/teams.js
@@ -2,14 +2,15 @@
// See License.txt for license information.
import {bindClientFunc} from './helpers.js';
-import Client from 'client/client_instance';
+import Client from 'client';
import {TeamsTypes} from 'constants';
export function selectTeam(team) {
- Client.setTeamId(team.id);
- return {
- type: TeamsTypes.SELECT_TEAM,
- teamId: team.id
+ return async (dispatch, getState) => {
+ dispatch({
+ type: TeamsTypes.SELECT_TEAM,
+ teamId: team.id
+ }, getState);
};
}
@@ -17,7 +18,7 @@ export function fetchTeams() {
return bindClientFunc(
Client.getAllTeams,
TeamsTypes.FETCH_TEAMS_REQUEST,
- TeamsTypes.FETCH_TEAMS_SUCCESS,
+ [TeamsTypes.RECEIVED_ALL_TEAMS, TeamsTypes.FETCH_TEAMS_SUCCESS],
TeamsTypes.FETCH_TEAMS_FAILURE
);
}
diff --git a/src/actions/users.js b/src/actions/users.js
new file mode 100644
index 000000000..e9eb76612
--- /dev/null
+++ b/src/actions/users.js
@@ -0,0 +1,215 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {batchActions} from 'redux-batched-actions';
+import Client from 'client';
+
+// TODO: uncomment when PLT-4167 is merged
+// import {Constants, UsersTypes, TeamsTypes} from 'constants';
+import {Constants, UsersTypes} from 'constants';
+import {bindClientFunc} from 'actions/helpers';
+import {forceLogoutIfNecessary} from './helpers';
+
+export function login(loginId, password, mfaToken = '') {
+ return async (dispatch, getState) => {
+ dispatch({type: UsersTypes.LOGIN_REQUEST}, getState);
+ Client.login(loginId, password, mfaToken).
+ then(async (data) => {
+ try {
+ const preferences = Client.getMyPreferences();
+
+ // TODO: uncomment when PLT-4167 is merged
+ // const teamMembers = Client.getMyTeamMembers();
+ dispatch(batchActions([
+ {
+ type: UsersTypes.RECEIVED_ME,
+ data
+ },
+ {
+ type: UsersTypes.RECEIVED_PREFERENCES,
+ data: await preferences
+ },
+
+ // TODO: uncomment when PLT-4167 is merged
+ // {
+ // type: TeamsTypes.RECEIVED_MY_TEAM_MEMBERS,
+ // data: await teamMembers
+ // },
+ {
+ type: UsersTypes.LOGIN_SUCCESS
+ }
+ ]), getState);
+ } catch (err) {
+ dispatch({type: UsersTypes.LOGIN_FAILURE, error: err}, getState);
+ }
+ }).
+ catch((err) => {
+ dispatch({type: UsersTypes.LOGIN_FAILURE, error: err}, getState);
+ });
+ };
+}
+
+export function logout() {
+ return bindClientFunc(
+ Client.logout,
+ UsersTypes.LOGOUT_REQUEST,
+ UsersTypes.LOGOUT_SUCCESS,
+ UsersTypes.LOGOUT_FAILURE,
+ );
+}
+
+export function getProfiles(offset, limit = Constants.PROFILE_CHUNK_SIZE) {
+ return bindClientFunc(
+ Client.getProfiles,
+ UsersTypes.PROFILES_REQUEST,
+ [UsersTypes.RECEIVED_PROFILES, UsersTypes.PROFILES_SUCCESS],
+ UsersTypes.PROFILES_FAILURE,
+ offset,
+ limit
+ );
+}
+
+export function getProfilesByIds(userIds) {
+ return bindClientFunc(
+ Client.getProfilesByIds,
+ UsersTypes.PROFILES_REQUEST,
+ [UsersTypes.RECEIVED_PROFILES, UsersTypes.PROFILES_SUCCESS],
+ UsersTypes.PROFILES_FAILURE,
+ userIds
+ );
+}
+
+export function getProfilesInTeam(teamId, offset, limit = Constants.PROFILE_CHUNK_SIZE) {
+ return async (dispatch, getState) => {
+ try {
+ dispatch({type: UsersTypes.PROFILES_IN_TEAM_REQUEST}, getState);
+ const profiles = await Client.getProfilesInTeam(teamId, offset, limit);
+ dispatch(batchActions([
+ {
+ type: UsersTypes.RECEIVED_PROFILES_IN_TEAM,
+ data: profiles,
+ offset,
+ count: Object.keys(profiles).length
+ },
+ {
+ type: UsersTypes.RECEIVED_PROFILES,
+ data: profiles
+ },
+ {
+ type: UsersTypes.PROFILES_IN_TEAM_SUCCESS
+ }
+ ]), getState);
+ } catch (error) {
+ forceLogoutIfNecessary(error, dispatch);
+ dispatch({type: UsersTypes.PROFILES_IN_TEAM_FAILURE, error}, getState);
+ }
+ };
+}
+
+export function getProfilesInChannel(teamId, channelId, offset, limit = Constants.PROFILE_CHUNK_SIZE) {
+ return async (dispatch, getState) => {
+ try {
+ dispatch({type: UsersTypes.PROFILES_IN_CHANNEL_REQUEST}, getState);
+ const profiles = await Client.getProfilesInChannel(teamId, channelId, offset, limit);
+ dispatch(batchActions([
+ {
+ type: UsersTypes.RECEIVED_PROFILES_IN_CHANNEL,
+ data: profiles,
+ offset,
+ count: Object.keys(profiles).length
+ },
+ {
+ type: UsersTypes.RECEIVED_PROFILES,
+ data: profiles
+ },
+ {
+ type: UsersTypes.PROFILES_IN_CHANNEL_SUCCESS
+ }
+ ]), getState);
+ } catch (error) {
+ forceLogoutIfNecessary(error, dispatch);
+ dispatch({type: UsersTypes.PROFILES_IN_CHANNEL_FAILURE, error}, getState);
+ }
+ };
+}
+
+export function getProfilesNotInChannel(teamId, channelId, offset, limit = Constants.PROFILE_CHUNK_SIZE) {
+ return async (dispatch, getState) => {
+ try {
+ dispatch({type: UsersTypes.PROFILES_NOT_IN_CHANNEL_REQUEST}, getState);
+ const profiles = await Client.getProfilesInChannel(teamId, channelId, offset, limit);
+ dispatch(batchActions([
+ {
+ type: UsersTypes.RECEIVED_PROFILES_NOT_IN_CHANNEL,
+ data: profiles,
+ offset,
+ count: Object.keys(profiles).length
+ },
+ {
+ type: UsersTypes.RECEIVED_PROFILES,
+ data: profiles
+ },
+ {
+ type: UsersTypes.PROFILES_NOT_IN_CHANNEL_SUCCESS
+ }
+ ]), getState);
+ } catch (error) {
+ forceLogoutIfNecessary(error, dispatch);
+ dispatch({type: UsersTypes.PROFILES_NOT_IN_CHANNEL_FAILURE, error}, getState);
+ }
+ };
+}
+
+export function getStatusesByIds(userIds) {
+ return bindClientFunc(
+ Client.getStatusesByIds,
+ UsersTypes.PROFILES_STATUSES_REQUEST,
+ [UsersTypes.RECEIVED_STATUSES, UsersTypes.PROFILES_STATUSES_SUCCESS],
+ UsersTypes.PROFILES_STATUSES_FAILURE,
+ userIds
+ );
+}
+
+export function getSessions(userId) {
+ return bindClientFunc(
+ Client.getSessions,
+ UsersTypes.SESSIONS_REQUEST,
+ [UsersTypes.RECEIVED_SESSIONS, UsersTypes.SESSIONS_SUCCESS],
+ UsersTypes.SESSIONS_FAILURE,
+ userId
+ );
+}
+
+export function revokeSession(id) {
+ return bindClientFunc(
+ Client.revokeSession,
+ UsersTypes.REVOKE_SESSION_REQUEST,
+ [UsersTypes.RECEIVED_REVOKED_SESSION, UsersTypes.REVOKE_SESSION_SUCCESS],
+ UsersTypes.REVOKE_SESSION_FAILURE,
+ id
+ );
+}
+
+export function getAudits(userId) {
+ return bindClientFunc(
+ Client.getAudits,
+ UsersTypes.AUDITS_REQUEST,
+ [UsersTypes.RECEIVED_AUDITS, UsersTypes.AUDITS_SUCCESS],
+ UsersTypes.AUDITS_FAILURE,
+ userId
+ );
+}
+
+export default {
+ login,
+ logout,
+ getProfiles,
+ getProfilesByIds,
+ getProfilesInTeam,
+ getProfilesInChannel,
+ getProfilesNotInChannel,
+ getStatusesByIds,
+ getSessions,
+ revokeSession,
+ getAudits
+};
diff --git a/src/actions/views/login.js b/src/actions/views/login.js
new file mode 100644
index 000000000..aa2ec9232
--- /dev/null
+++ b/src/actions/views/login.js
@@ -0,0 +1,27 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {UsersTypes} from 'constants';
+
+export function handleLoginIdChanged(loginId) {
+ return async (dispatch, getState) => {
+ dispatch({
+ type: UsersTypes.LOGIN_ID_CHANGED,
+ loginId
+ }, getState);
+ };
+}
+
+export function handlePasswordChanged(password) {
+ return async (dispatch, getState) => {
+ dispatch({
+ type: UsersTypes.PASSWORD_CHANGED,
+ password
+ }, getState);
+ };
+}
+
+export default {
+ handleLoginIdChanged,
+ handlePasswordChanged
+};
diff --git a/src/actions/views/select_server.js b/src/actions/views/select_server.js
new file mode 100644
index 000000000..5ed955ebb
--- /dev/null
+++ b/src/actions/views/select_server.js
@@ -0,0 +1,17 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {GeneralTypes} from 'constants';
+
+export function handleServerUrlChanged(serverUrl) {
+ return async (dispatch, getState) => {
+ dispatch({
+ type: GeneralTypes.SERVER_URL_CHANGED,
+ serverUrl
+ }, getState);
+ };
+}
+
+export default {
+ handleServerUrlChanged
+};
diff --git a/src/client/client.js b/src/client/client.js
index b45ee479b..b2c942687 100644
--- a/src/client/client.js
+++ b/src/client/client.js
@@ -46,6 +46,10 @@ export default class Client {
return `${this.url}${this.urlVersion}/teams`;
}
+ getPreferencesRoute() {
+ return `${this.url}${this.urlVersion}/preferences`;
+ }
+
getTeamNeededRoute(teamId) {
return `${this.url}${this.urlVersion}/teams/${teamId}`;
}
@@ -120,14 +124,21 @@ export default class Client {
`${this.getGeneralRoute()}/client_props`,
{method: 'get'}
);
- }
+ };
+
+ getLicenseConfig = async () => {
+ return this.doFetch(
+ `${this.getLicenseRoute()}/client_config`,
+ {method: 'get'}
+ );
+ };
getPing = async () => {
return this.doFetch(
`${this.getGeneralRoute()}/ping`,
{method: 'get'}
);
- }
+ };
logClientError = async (message, level = 'ERROR') => {
const body = {
@@ -139,16 +150,33 @@ export default class Client {
`${this.getGeneralRoute()}/log_client`,
{method: 'post', body}
);
- }
+ };
// User routes
-
createUser = async (user) => {
+ return this.createUserWithInvite(user);
+ };
+
+ // TODO: add deep linking to emails so we can create accounts from within
+ // the mobile app
+ createUserWithInvite = async(user, data, emailHash, inviteId) => {
+ let url = `${this.getUsersRoute()}/create`;
+
+ url += '?d=' + encodeURIComponent(data);
+
+ if (emailHash) {
+ url += '&h=' + encodeURIComponent(emailHash);
+ }
+
+ if (inviteId) {
+ url += '&iid=' + encodeURIComponent(inviteId);
+ }
+
return this.doFetch(
- `${this.getUsersRoute()}/create`,
+ url,
{method: 'post', body: JSON.stringify(user)}
);
- }
+ };
login = async (loginId, password, token = '') => {
const body = {
@@ -167,7 +195,7 @@ export default class Client {
}
return data;
- }
+ };
logout = async () => {
const {response} = await this.doFetchWithResponse(
@@ -178,14 +206,125 @@ export default class Client {
this.token = '';
}
return response;
- }
+ };
+
+ updateUser = async (user) => {
+ return this.doFetch(
+ `${this.getUsersRoute()}/update`,
+ {method: 'post', body: JSON.stringify(user)}
+ );
+ };
+
+ updatePassword = async (userId, currentPassword, newPassword) => {
+ const data = {
+ user_id: userId,
+ current_password: currentPassword,
+ new_password: newPassword
+ };
+
+ return this.doFetch(
+ `${this.getUsersRoute()}/newpassword`,
+ {method: 'post', body: JSON.stringify(data)}
+ );
+ };
+
+ updateUserNotifyProps = async (notifyProps) => {
+ return this.doFetch(
+ `${this.getUsersRoute()}/update_notify`,
+ {method: 'post', body: JSON.stringify(notifyProps)}
+ );
+ };
+
+ updateUserRoles = async (userId, newRoles) => {
+ return this.doFetch(
+ `${this.getUserNeededRoute(userId)}/update_roles`,
+ {method: 'post', body: JSON.stringify({new_roles: newRoles})}
+ );
+ };
+
+ getMyPreferences = async () => {
+ return this.doFetch(
+ `${this.getPreferencesRoute()}/`,
+ {method: 'get'}
+ );
+ };
+
+ getProfiles = async (offset, limit) => {
+ return this.doFetch(
+ `${this.getUsersRoute()}/${offset}/${limit}`,
+ {method: 'get'}
+ );
+ };
+
+ getProfilesByIds = async (userIds) => {
+ return this.doFetch(
+ `${this.getUsersRoute()}/ids`,
+ {method: 'post', body: JSON.stringify(userIds)}
+ );
+ };
+
+ getProfilesInTeam = async (teamId, offset, limit) => {
+ return this.doFetch(
+ `${this.getTeamNeededRoute(teamId)}/users/${offset}/${limit}`,
+ {method: 'get'}
+ );
+ };
+
+ getProfilesInChannel = async (teamId, channelId, offset, limit) => {
+ return this.doFetch(
+ `${this.getChannelNeededRoute(teamId, channelId)}/users/${offset}/${limit}`,
+ {method: 'get'}
+ );
+ };
+
+ getProfilesNotInChannel = async (teamId, channelId, offset, limit) => {
+ return this.doFetch(
+ `${this.getChannelNeededRoute(teamId, channelId)}/users/not_in_channel/${offset}/${limit}`,
+ {method: 'get'}
+ );
+ };
+
+ getUser = async (userId) => {
+ return this.doFetch(
+ `${this.getUserNeededRoute(userId)}/get`,
+ {method: 'get'}
+ );
+ };
+
+ getStatusesByIds = async (userIds) => {
+ return this.doFetch(
+ `${this.getUsersRoute()}/status/ids`,
+ {method: 'post', body: JSON.stringify(userIds)}
+ );
+ };
+
+ getSessions = async (userId) => {
+ return this.doFetch(
+ `${this.getUserNeededRoute(userId)}/sessions`,
+ {method: 'get'}
+ );
+ };
+
+ revokeSession = async (id) => {
+ return this.doFetch(
+ `${this.getUsersRoute()}/revoke_session`,
+ {method: 'post', body: JSON.stringify({id})}
+ );
+ };
+
+ getAudits = async (userId) => {
+ return this.doFetch(
+ `${this.getUserNeededRoute(userId)}/audits`,
+ {method: 'get'}
+ );
+ };
getInitialLoad = async () => {
return this.doFetch(
`${this.getUsersRoute()}/initial_load`,
{method: 'get'}
);
- }
+ };
// Team routes
@@ -194,21 +333,49 @@ export default class Client {
`${this.getTeamsRoute()}/create`,
{method: 'post', body: JSON.stringify(team)}
);
- }
+ };
getAllTeams = async() => {
return this.doFetch(
`${this.getTeamsRoute()}/all`,
{method: 'get'}
);
- }
+ };
+
+ getMyTeamMembers = async() => {
+ return this.doFetch(
+ `${this.getTeamsRoute()}/members`,
+ {method: 'get'}
+ );
+ };
getAllTeamListings = async () => {
return this.doFetch(
`${this.getTeamsRoute()}/all_team_listings`,
{method: 'get'}
);
- }
+ };
+
+ addUserToTeamFromInvite = async (inviteId, data, hash) => {
+ return this.doFetch(
+ `${this.getTeamsRoute()}/add_user_to_team_from_invite`,
+ {method: 'post', body: JSON.stringify({hash, data, invite_id: inviteId})}
+ );
+ };
+
+ addChannelMember = async (teamId, channelId, userId) => {
+ return this.doFetch(
+ `${this.getChannelNeededRoute(teamId, channelId)}/add`,
+ {method: 'post', body: JSON.stringify({user_id: userId})}
+ );
+ };
+
+ removeChannelMember = async (teamId, channelId, userId) => {
+ return this.doFetch(
+ `${this.getChannelNeededRoute(teamId, channelId)}/remove`,
+ {method: 'post', body: JSON.stringify({user_id: userId})}
+ );
+ };
// Channel routes
@@ -217,21 +384,98 @@ export default class Client {
`${this.getChannelsRoute(channel.team_id)}/create`,
{method: 'post', body: JSON.stringify(channel)}
);
- }
+ };
+
+ createDirectChannel = async (userId) => {
+ return this.doFetch(
+ `${this.getChannelsRoute('fake')}/create_direct`,
+ {method: 'post', body: JSON.stringify({user_id: userId})}
+ );
+ };
+
+ getChannel = async(teamId, channelId) => {
+ return this.doFetch(
+ `${this.getChannelNeededRoute(teamId, channelId)}/`,
+ {method: 'get'}
+ );
+ };
getChannels = async (teamId) => {
return this.doFetch(
`${this.getChannelsRoute(teamId)}/`,
{method: 'get'}
);
- }
+ };
getMyChannelMembers = async (teamId) => {
return this.doFetch(
`${this.getChannelsRoute(teamId)}/members`,
{method: 'get'}
);
- }
+ };
+
+ updateChannel = async (channel) => {
+ return this.doFetch(
+ `${this.getChannelsRoute(channel.team_id)}/update`,
+ {method: 'post', body: JSON.stringify(channel)}
+ );
+ };
+
+ updateChannelNotifyProps = async (teamId, data) => {
+ return this.doFetch(
+ `${this.getChannelsRoute(teamId)}/update_notify_props`,
+ {method: 'post', body: JSON.stringify(data)}
+ );
+ };
+
+ leaveChannel = async(teamId, channelId) => {
+ return this.doFetch(
+ `${this.getChannelNeededRoute(teamId, channelId)}/leave`,
+ {method: 'post'}
+ );
+ };
+
+ joinChannel = async(teamId, channelId) => {
+ return this.doFetch(
+ `${this.getChannelNeededRoute(teamId, channelId)}/join`,
+ {method: 'post'}
+ );
+ };
+
+ joinChannelByName = async(teamId, channelName) => {
+ return this.doFetch(
+ `${this.getChannelNameRoute(teamId, channelName)}/join`,
+ {method: 'post'}
+ );
+ };
+
+ deleteChannel = async(teamId, channelId) => {
+ return this.doFetch(
+ `${this.getChannelNeededRoute(teamId, channelId)}/delete`,
+ {method: 'post'}
+ );
+ };
+
+ updateLastViewedAt = async(teamId, channelId, active) => {
+ return this.doFetch(
+ `${this.getChannelNeededRoute(teamId, channelId)}/update_last_viewed_at`,
+ {method: 'post', body: JSON.stringify({active})}
+ );
+ };
+
+ getMoreChannels = async(teamId, offset, limit) => {
+ return this.doFetch(
+ `${this.getChannelsRoute(teamId)}/more/${offset}/${limit}`,
+ {method: 'get'}
+ );
+ };
+
+ getChannelStats = async(teamId, channelId) => {
+ return this.doFetch(
+ `${this.getChannelNeededRoute(teamId, channelId)}/stats`,
+ {method: 'get'}
+ );
+ };
// Post routes
fetchPosts = (teamId, channelId, onRequest, onSuccess, onFailure) => {
@@ -242,20 +486,20 @@ export default class Client {
onSuccess,
onFailure
);
- }
+ };
createPost = async (teamId, post) => {
return this.doFetch(
`${this.getPostsRoute(teamId, post.channel_id)}/create`,
{method: 'post', body: JSON.stringify(post)}
);
- }
+ };
doFetch = async (url, options) => {
const {data} = await this.doFetchWithResponse(url, options);
return data;
- }
+ };
doFetchWithResponse = async (url, options) => {
const response = await fetch(url, this.getOptions(options));
@@ -288,6 +532,6 @@ export default class Client {
console.error(msg); // eslint-disable-line no-console
}
- throw new Error(msg);
- }
+ throw {message: msg, status_code: data.status_code, url};
+ };
}
diff --git a/src/client/client_instance.js b/src/client/index.js
similarity index 100%
rename from src/client/client_instance.js
rename to src/client/index.js
diff --git a/src/components/logout.js b/src/components/logout.js
index 49a78e22e..af3397240 100644
--- a/src/components/logout.js
+++ b/src/components/logout.js
@@ -3,7 +3,8 @@
import React, {Component, PropTypes} from 'react';
import {TouchableHighlight, Text} from 'react-native';
-import * as logoutActions from 'actions/logout';
+import {logout} from 'actions/users';
+import {RequestStatus} from 'constants';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {Actions as Routes} from 'react-native-router-flux';
@@ -17,8 +18,8 @@ class Logout extends Component {
static propTypes = propTypes;
componentWillReceiveProps(nextProps) {
- if (this.props.logout.status === 'fetching' &&
- nextProps.logout.status === 'fetched') {
+ if (this.props.logout.status === RequestStatus.STARTED &&
+ nextProps.logout.status === RequestStatus.SUCCESS) {
Routes.popTo('goToSelectServer');
}
}
@@ -36,13 +37,13 @@ class Logout extends Component {
function mapStateToProps(state) {
return {
- logout: state.views.logout
+ logout: state.requests.users.logout
};
}
function mapDispatchToProps(dispatch) {
return {
- actions: bindActionCreators(logoutActions, dispatch)
+ actions: bindActionCreators({logout}, dispatch)
};
}
diff --git a/src/config/config.js b/src/config/index.js
similarity index 100%
rename from src/config/config.js
rename to src/config/index.js
diff --git a/src/constants/channel.js b/src/constants/channel.js
deleted file mode 100644
index f82fc9448..000000000
--- a/src/constants/channel.js
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-export const CHANNEL_RECEIVED = 'mattermost/channels/channel_received';
-export const CHANNELS_RECEIVED = 'mattermost/channels/channels_received';
-
-export const CHANNEL_MEMBER_RECEIVED = 'mattermost/channels/channel_member_received';
-export const CHANNEL_MEMBERS_RECEVED = 'mattermost/channels/channel_members_received';
diff --git a/src/constants/channels.js b/src/constants/channels.js
new file mode 100644
index 000000000..d538c8c83
--- /dev/null
+++ b/src/constants/channels.js
@@ -0,0 +1,76 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import keymirror from 'keymirror';
+
+const ChannelTypes = keymirror({
+ CHANNEL_REQUEST: null,
+ CHANNEL_SUCCESS: null,
+ CHANNEL_FAILURE: null,
+
+ CHANNELS_REQUEST: null,
+ CHANNELS_SUCCESS: null,
+ CHANNELS_FAILURE: null,
+
+ CHANNEL_MEMBERS_REQUEST: null,
+ CHANNEL_MEMBERS_SUCCESS: null,
+ CHANNEL_MEMBERS_FAILURE: null,
+
+ CREATE_CHANNEL_REQUEST: null,
+ CREATE_CHANNEL_SUCCESS: null,
+ CREATE_CHANNEL_FAILURE: null,
+
+ UPDATE_CHANNEL_REQUEST: null,
+ UPDATE_CHANNEL_SUCCESS: null,
+ UPDATE_CHANNEL_FAILURE: null,
+
+ NOTIFY_PROPS_REQUEST: null,
+ NOTIFY_PROPS_SUCCESS: null,
+ NOTIFY_PROPS_FAILURE: null,
+
+ LEAVE_CHANNEL_REQUEST: null,
+ LEAVE_CHANNEL_SUCCESS: null,
+ LEAVE_CHANNEL_FAILURE: null,
+
+ JOIN_CHANNEL_REQUEST: null,
+ JOIN_CHANNEL_SUCCESS: null,
+ JOIN_CHANNEL_FAILURE: null,
+
+ DELETE_CHANNEL_REQUEST: null,
+ DELETE_CHANNEL_SUCCESS: null,
+ DELETE_CHANNEL_FAILURE: null,
+
+ UPDATE_LAST_VIEWED_REQUEST: null,
+ UPDATE_LAST_VIEWED_SUCCESS: null,
+ UPDATE_LAST_VIEWED_FAILURE: null,
+
+ MORE_CHANNELS_REQUEST: null,
+ MORE_CHANNELS_SUCCESS: null,
+ MORE_CHANNELS_FAILURE: null,
+
+ CHANNEL_STATS_REQUEST: null,
+ CHANNEL_STATS_SUCCESS: null,
+ CHANNEL_STATS_FAILURE: null,
+
+ ADD_CHANNEL_MEMBER_REQUEST: null,
+ ADD_CHANNEL_MEMBER_SUCCESS: null,
+ ADD_CHANNEL_MEMBER_FAILURE: null,
+
+ REMOVE_CHANNEL_MEMBER_REQUEST: null,
+ REMOVE_CHANNEL_MEMBER_SUCCESS: null,
+ REMOVE_CHANNEL_MEMBER_FAILURE: null,
+
+ SELECTED_CHANNEL: null,
+ LEAVE_CHANNEL: null,
+ RECEIVED_CHANNEL: null,
+ RECEIVED_CHANNELS: null,
+ RECEIVED_MY_CHANNEL_MEMBERS: null,
+ RECEIVED_MY_CHANNEL_MEMBER: null,
+ RECEIVED_MORE_CHANNELS: null,
+ RECEIVED_CHANNEL_STATS: null,
+ RECEIVED_CHANNEL_PROPS: null,
+ RECEIVED_CHANNEL_DELETED: null,
+ RECEIVED_LAST_VIEWED: null
+});
+
+export default ChannelTypes;
diff --git a/src/constants/constants.js b/src/constants/constants.js
new file mode 100644
index 000000000..830d6e1bf
--- /dev/null
+++ b/src/constants/constants.js
@@ -0,0 +1,15 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+const Constants = {
+ POST_CHUNK_SIZE: 60,
+ PROFILE_CHUNK_SIZE: 100,
+ CHANNELS_CHUNK_SIZE: 50,
+
+ CHANNEL_USER_ROLE: 'channel_user',
+ CHANNEL_ADMIN_ROLE: 'channel_admin',
+
+ CATEGORY_DIRECT_CHANNEL_SHOW: 'direct_channel_show'
+};
+
+export default Constants;
diff --git a/src/constants/device.js b/src/constants/device.js
index 4f125dbf4..55a541dd4 100644
--- a/src/constants/device.js
+++ b/src/constants/device.js
@@ -1,6 +1,12 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-export const DEVICE_REQUEST = 'DEVICE_REQUEST';
-export const DEVICE_SUCCESS = 'DEVICE_SUCCESS';
-export const DEVICE_FAILURE = 'DEVICE_FAILURE';
+import keymirror from 'keymirror';
+
+const DeviceTypes = keymirror({
+ DEVICE_REQUEST: null,
+ DEVICE_SUCCESS: null,
+ DEVICE_FAILURE: null
+});
+
+export default DeviceTypes;
diff --git a/src/constants/general.js b/src/constants/general.js
index f703ecede..ab80d3a36 100644
--- a/src/constants/general.js
+++ b/src/constants/general.js
@@ -1,14 +1,28 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-export const PING_REQUEST = 'PING_REQUEST';
-export const PING_SUCCESS = 'PING_SUCCESS';
-export const PING_FAILURE = 'PING_FAILURE';
+import keymirror from 'keymirror';
-export const CLIENT_CONFIG_REQUEST = 'CLIENT_CONFIG_REQUEST';
-export const CLIENT_CONFIG_SUCCESS = 'CLIENT_CONFIG_SUCCESS';
-export const CLIENT_CONFIG_FAILURE = 'CLIENT_CONFIG_FAILURE';
+const GeneralTypes = keymirror({
+ PING_REQUEST: null,
+ PING_SUCCESS: null,
+ PING_FAILURE: null,
-export const LOG_CLIENT_ERROR_REQUEST = 'LOG_CLIENT_ERROR_REQUEST';
-export const LOG_CLIENT_ERROR_SUCCESS = 'LOG_CLIENT_ERROR_SUCCESS';
-export const LOG_CLIENT_ERROR_FAILURE = 'LOG_CLIENT_ERROR_FAILURE';
+ CLIENT_CONFIG_REQUEST: null,
+ CLIENT_CONFIG_SUCCESS: null,
+ CLIENT_CONFIG_FAILURE: null,
+ CLIENT_CONFIG_RECEIVED: null,
+
+ CLIENT_LICENSE_REQUEST: null,
+ CLIENT_LICENSE_SUCCESS: null,
+ CLIENT_LICENSE_FAILURE: null,
+ CLIENT_LICENSE_RECEIVED: null,
+
+ LOG_CLIENT_ERROR_REQUEST: null,
+ LOG_CLIENT_ERROR_SUCCESS: null,
+ LOG_CLIENT_ERROR_FAILURE: null,
+
+ SERVER_URL_CHANGED: null
+});
+
+export default GeneralTypes;
diff --git a/src/constants/index.js b/src/constants/index.js
index 426aedd60..d982e1852 100644
--- a/src/constants/index.js
+++ b/src/constants/index.js
@@ -1,20 +1,22 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import * as ChannelTypes from './channel';
-import * as DeviceTypes from './device';
-import * as GeneralTypes from './general';
-import * as LoginTypes from './login';
-import * as LogoutTypes from './logout';
-import * as TeamsTypes from './teams';
-import * as PostsTypes from './posts';
+import Constants from './constants';
+import ChannelTypes from './channels';
+import DeviceTypes from './device';
+import GeneralTypes from './general';
+import UsersTypes from './users';
+import TeamsTypes from './teams';
+import PostsTypes from './posts';
+import RequestStatus from './request_status';
export {
+ Constants,
DeviceTypes,
GeneralTypes,
- LoginTypes,
- LogoutTypes,
+ UsersTypes,
TeamsTypes,
ChannelTypes,
- PostsTypes
+ PostsTypes,
+ RequestStatus
};
diff --git a/src/constants/login.js b/src/constants/login.js
deleted file mode 100644
index b6f56143f..000000000
--- a/src/constants/login.js
+++ /dev/null
@@ -1,6 +0,0 @@
-// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-export const LOGIN_REQUEST = 'LOGIN_REQUEST';
-export const LOGIN_SUCCESS = 'LOGIN_SUCCESS';
-export const LOGIN_FAILURE = 'LOGIN_FAILURE';
diff --git a/src/constants/logout.js b/src/constants/logout.js
deleted file mode 100644
index b52a49bc3..000000000
--- a/src/constants/logout.js
+++ /dev/null
@@ -1,6 +0,0 @@
-// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-export const LOGOUT_REQUEST = 'LOGOUT_REQUEST';
-export const LOGOUT_SUCCESS = 'LOGOUT_SUCCESS';
-export const LOGOUT_FAILURE = 'LOGOUT_FAILURE';
diff --git a/src/constants/posts.js b/src/constants/posts.js
index a530d611d..4c06a5cc9 100644
--- a/src/constants/posts.js
+++ b/src/constants/posts.js
@@ -1,6 +1,22 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-export const FETCH_POSTS_REQUEST = 'FETCH_POSTS_REQUEST';
-export const FETCH_POSTS_SUCCESS = 'FETCH_POSTS_SUCCESS';
-export const FETCH_POSTS_FAILURE = 'FETCH_POSTS_FAILURE';
+import keymirror from 'keymirror';
+
+const PostsTypes = keymirror({
+ FETCH_POSTS_REQUEST: null,
+ FETCH_POSTS_SUCCESS: null,
+ FETCH_POSTS_FAILURE: null,
+
+ RECEIVED_POST: null,
+ RECEIVED_POSTS: null,
+ RECEIVED_FOCUSED_POST: null,
+ RECEIVED_POST_SELECTED: null,
+ RECEIVED_EDIT_POST: null,
+ CREATE_POST: null,
+ CREATE_COMMENT: null,
+ POST_DELETED: null,
+ REMOVE_POST: null
+});
+
+export default PostsTypes;
diff --git a/src/constants/request_status.js b/src/constants/request_status.js
new file mode 100644
index 000000000..1ff6116e0
--- /dev/null
+++ b/src/constants/request_status.js
@@ -0,0 +1,9 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+export default {
+ NOT_STARTED: 'not_started',
+ STARTED: 'started',
+ SUCCESS: 'success',
+ FAILURE: 'failure'
+};
diff --git a/src/constants/teams.js b/src/constants/teams.js
index 12ce418f8..14bea65e6 100644
--- a/src/constants/teams.js
+++ b/src/constants/teams.js
@@ -1,8 +1,22 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-export const SELECT_TEAM = 'SELECT_TEAM';
+import keymirror from 'keymirror';
-export const FETCH_TEAMS_REQUEST = 'FETCH_TEAMS_REQUEST';
-export const FETCH_TEAMS_SUCCESS = 'FETCH_TEAMS_SUCCESS';
-export const FETCH_TEAMS_FAILURE = 'FETCH_TEAMS_FAILURE';
+const TeamTypes = keymirror({
+ FETCH_TEAMS_REQUEST: null,
+ FETCH_TEAMS_SUCCESS: null,
+ FETCH_TEAMS_FAILURE: null,
+
+ CREATED_TEAM: null,
+ SELECT_TEAM: null,
+ UPDATED_TEAM: null,
+ RECEIVED_ALL_TEAMS: null,
+ RECEIVED_MY_TEAM_MEMBERS: null,
+ RECEIVED_TEAM_LISTINGS: null,
+ RECEIVED_MEMBERS_IN_TEAM: null,
+ RECEIVED_TEAM_STATS: null,
+ LEAVE_TEAM: null
+});
+
+export default TeamTypes;
diff --git a/src/constants/users.js b/src/constants/users.js
new file mode 100644
index 000000000..e2416ac32
--- /dev/null
+++ b/src/constants/users.js
@@ -0,0 +1,66 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import keymirror from 'keymirror';
+
+const UserTypes = keymirror({
+ LOGIN_ID_CHANGED: null,
+ PASSWORD_CHANGED: null,
+
+ LOGIN_REQUEST: null,
+ LOGIN_SUCCESS: null,
+ LOGIN_FAILURE: null,
+
+ LOGOUT_REQUEST: null,
+ LOGOUT_SUCCESS: null,
+ LOGOUT_FAILURE: null,
+
+ PROFILES_REQUEST: null,
+ PROFILES_SUCCESS: null,
+ PROFILES_FAILURE: null,
+
+ PROFILES_IN_TEAM_REQUEST: null,
+ PROFILES_IN_TEAM_SUCCESS: null,
+ PROFILES_IN_TEAM_FAILURE: null,
+
+ PROFILES_IN_CHANNEL_REQUEST: null,
+ PROFILES_IN_CHANNEL_SUCCESS: null,
+ PROFILES_IN_CHANNEL_FAILURE: null,
+
+ PROFILES_NOT_IN_CHANNEL_REQUEST: null,
+ PROFILES_NOT_IN_CHANNEL_SUCCESS: null,
+ PROFILES_NOT_IN_CHANNEL_FAILURE: null,
+
+ PROFILES_STATUSES_REQUEST: null,
+ PROFILES_STATUSES_SUCCESS: null,
+ PROFILES_STATUSES_FAILURE: null,
+
+ SESSIONS_REQUEST: null,
+ SESSIONS_SUCCESS: null,
+ SESSIONS_FAILURE: null,
+
+ REVOKE_SESSION_REQUEST: null,
+ REVOKE_SESSION_SUCCESS: null,
+ REVOKE_SESSION_FAILURE: null,
+
+ AUDITS_REQUEST: null,
+ AUDITS_SUCCESS: null,
+ AUDITS_FAILURE: null,
+
+ RECEIVED_ME: null,
+ RECEIVED_PROFILES: null,
+ RECEIVED_PROFILES_IN_TEAM: null,
+ RECEIVED_PROFILES_IN_CHANNEL: null,
+ RECEIVED_PROFILE_IN_CHANNEL: null,
+ RECEIVED_PROFILES_NOT_IN_CHANNEL: null,
+ RECEIVED_PROFILE_NOT_IN_CHANNEL: null,
+ RECEIVED_SESSIONS: null,
+ RECEIVED_REVOKED_SESSION: null,
+ RECEIVED_AUDITS: null,
+ RECEIVED_STATUSES: null,
+ RECEIVED_PREFERENCE: null,
+ RECEIVED_PREFERENCES: null,
+ DELETED_PREFERENCES: null
+});
+
+export default UserTypes;
diff --git a/src/containers/root_container.js b/src/containers/root_container.js
index 481c45d6d..4ffc751fe 100644
--- a/src/containers/root_container.js
+++ b/src/containers/root_container.js
@@ -3,20 +3,20 @@
import React from 'react';
-import Config from 'config/config.js';
+import Config from 'config';
import {connect} from 'react-redux';
import {loadDevice} from 'actions/device';
import Loading from 'components/loading';
import Routes from 'routes';
-import {getTranslations} from 'i18n/i18n.js';
+import {getTranslations} from 'i18n';
import {IntlProvider} from 'react-intl';
class RootContainer extends React.Component {
static propTypes = {
device: React.PropTypes.object.isRequired,
loadDevice: React.PropTypes.func.isRequired
- }
+ };
constructor(props) {
super(props);
diff --git a/src/i18n/index.js b/src/i18n/index.js
new file mode 100644
index 000000000..0085db75f
--- /dev/null
+++ b/src/i18n/index.js
@@ -0,0 +1,18 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import 'intl';
+
+import en from './en.json';
+import es from './es.json';
+
+const DEFAULT_LOCALE = 'en';
+
+const TRANSLATIONS = {
+ en,
+ es
+};
+
+export function getTranslations(locale) {
+ return TRANSLATIONS[locale] || TRANSLATIONS[DEFAULT_LOCALE];
+}
diff --git a/src/mattermost.js b/src/mattermost.js
new file mode 100644
index 000000000..00586f925
--- /dev/null
+++ b/src/mattermost.js
@@ -0,0 +1,18 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React from 'react';
+import {Provider} from 'react-redux';
+
+import store from 'store';
+import RootContainer from 'containers/root_container.js';
+
+export default class Mattermost extends React.Component {
+ render() {
+ return (
+
+
+
+ );
+ }
+}
diff --git a/src/reducers/channel.js b/src/reducers/channel.js
deleted file mode 100644
index d7b1c11b4..000000000
--- a/src/reducers/channel.js
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {ChannelTypes} from 'constants';
-import {combineReducers} from 'redux';
-
-export function channels(state = {}, action) {
- switch (action.type) {
- case ChannelTypes.CHANNEL_RECEIVED:
- return {
- ...state,
- [action.channel.id]: action.channel
- };
-
- case ChannelTypes.CHANNELS_RECEIVED: {
- const nextState = {...state};
-
- for (const channel of action.channels) {
- nextState[channel.id] = channel;
- }
-
- return nextState;
- }
-
- default:
- return state;
- }
-}
-
-export function channelIdsByTeamId(state = {}, action) {
- switch (action.type) {
- case ChannelTypes.CHANNEL_RECEIVED: {
- const channel = action.channel;
-
- return {
- ...state,
- [channel.team_id]: {
- ...state[channel.team_id],
- [channel.id]: channel.id
- }
- };
- }
-
- case ChannelTypes.CHANNELS_RECEIVED: {
- const nextState = {...state};
-
- for (const channel of action.channels) {
- nextState[channel.team_id] = {
- ...nextState[channel.team_id],
- [channel.id]: channel.id
- };
- }
-
- return nextState;
- }
-
- default:
- return state;
- }
-}
-
-export function channelMembers(state = {}, action) {
- switch (action.type) {
-
- case ChannelTypes.CHANNEL_MEMBER_RECEIVED: {
- const channelMember = action.channelMember;
-
- return {
- ...state,
- [`${channelMember.channel_id}-${channelMember.user_id}`]: channelMember
- };
- }
-
- case ChannelTypes.CHANNEL_MEMBERS_RECEIVED: {
- const nextState = {...state};
-
- for (const channelMember of action.channelMembers) {
- nextState[`${channelMember.channel_id}-${channelMember.user_id}`] = channelMember;
- }
-
- return nextState;
- }
-
- default:
- return state;
- }
-}
-
-export default combineReducers({
-
- // A map of channel IDs to channels
- channels,
-
- // A map of team IDs to pseudo-sets of channelIds
- channelIdsByTeamId,
-
- // A map of "channelId-userId" to channel members
- channelMembers
-});
diff --git a/src/reducers/device.js b/src/reducers/device.js
deleted file mode 100644
index 3e58b00e3..000000000
--- a/src/reducers/device.js
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {handle, initialState} from './helpers.js';
-import {DeviceTypes} from 'constants';
-export const initState = initialState();
-
-export default function device(state = initState, action) {
- return handle(
- DeviceTypes.DEVICE_REQUEST,
- DeviceTypes.DEVICE_SUCCESS,
- DeviceTypes.DEVICE_FAILURE,
- state,
- action
- );
-}
diff --git a/src/reducers/entities/channels.js b/src/reducers/entities/channels.js
new file mode 100644
index 000000000..dfb6b29fd
--- /dev/null
+++ b/src/reducers/entities/channels.js
@@ -0,0 +1,146 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {ChannelTypes, UsersTypes} from 'constants';
+import {combineReducers} from 'redux';
+
+function currentId(state = '', action) {
+ switch (action.type) {
+ case UsersTypes.LOGOUT_SUCCESS:
+ return '';
+ default:
+ return state;
+ }
+}
+
+function channels(state = {}, action) {
+ const nextState = {...state};
+
+ switch (action.type) {
+ case ChannelTypes.RECEIVED_CHANNEL:
+ return {
+ ...state,
+ [action.data.id]: action.data
+ };
+
+ case ChannelTypes.RECEIVED_CHANNELS: {
+ for (const channel of action.data) {
+ nextState[channel.id] = channel;
+ }
+ return nextState;
+ }
+ case ChannelTypes.LEAVE_CHANNEL:
+ case ChannelTypes.RECEIVED_CHANNEL_DELETED: {
+ Reflect.deleteProperty(nextState, action.channel_id);
+ return nextState;
+ }
+ case UsersTypes.LOGOUT_SUCCESS:
+ return {};
+
+ default:
+ return state;
+ }
+}
+
+function myMembers(state = {}, action) {
+ const nextState = {...state};
+
+ switch (action.type) {
+ case ChannelTypes.RECEIVED_MY_CHANNEL_MEMBER: {
+ const channelMember = action.data;
+ return {
+ ...state,
+ [channelMember.channel_id]: channelMember
+ };
+ }
+ case ChannelTypes.RECEIVED_MY_CHANNEL_MEMBERS: {
+ for (const cm of action.data) {
+ nextState[cm.channel_id] = cm;
+ }
+ return nextState;
+ }
+ case ChannelTypes.RECEIVED_CHANNEL_PROPS: {
+ const member = {...state[action.channel_id]};
+ member.notify_props = action.data;
+
+ return {
+ ...state,
+ [action.channel_id]: member
+ };
+ }
+ case ChannelTypes.RECEIVED_LAST_VIEWED: {
+ const member = {...state[action.channel_id]};
+ member.last_viewed_at = action.last_viewed_at;
+
+ return {
+ ...state,
+ [action.channel_id]: member
+ };
+ }
+ case ChannelTypes.LEAVE_CHANNEL:
+ case ChannelTypes.RECEIVED_CHANNEL_DELETED:
+ Reflect.deleteProperty(nextState, action.channel_id);
+ return nextState;
+
+ case UsersTypes.LOGOUT_SUCCESS:
+ return {};
+ default:
+ return state;
+ }
+}
+
+function moreChannels(state = {}, action) {
+ const nextState = {...state};
+
+ switch (action.type) {
+ case ChannelTypes.RECEIVED_MORE_CHANNELS: {
+ for (const channel of action.data) {
+ nextState[channel.id] = channel;
+ }
+ return nextState;
+ }
+ case ChannelTypes.RECEIVED_MY_CHANNEL_MEMBER: {
+ const channelMember = action.data;
+ Reflect.deleteProperty(nextState, channelMember.channel_id);
+ return nextState;
+ }
+ case UsersTypes.LOGOUT_SUCCESS:
+ return {};
+ default:
+ return state;
+ }
+}
+
+function stats(state = {}, action) {
+ switch (action.type) {
+ case ChannelTypes.RECEIVED_CHANNEL_STATS: {
+ const nextState = {...state};
+ const stat = action.data;
+ nextState[stat.channel_id] = stat;
+
+ return nextState;
+ }
+ case UsersTypes.LOGOUT_SUCCESS:
+ return {};
+ default:
+ return state;
+ }
+}
+
+export default combineReducers({
+
+ // the current selected channel
+ currentId,
+
+ // object where every key is the channel id and has and object with the channel detail
+ channels,
+
+ //object where every key is the channel id and has and object with the channel members detail
+ myMembers,
+
+ // object where every key is the channel id and has a object with the channel detail where the user is not a current member
+ moreChannels,
+
+ // object where every key is the team id and has an object with the team stats
+ stats
+});
diff --git a/src/reducers/entities/general.js b/src/reducers/entities/general.js
new file mode 100644
index 000000000..8bed57ec7
--- /dev/null
+++ b/src/reducers/entities/general.js
@@ -0,0 +1,32 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {combineReducers} from 'redux';
+import {GeneralTypes, UsersTypes} from 'constants';
+
+function config(state = {}, action) {
+ switch (action.type) {
+ case GeneralTypes.CLIENT_CONFIG_RECEIVED:
+ return Object.assign({}, state, action.data);
+ case UsersTypes.LOGOUT_SUCCESS:
+ return {};
+ default:
+ return state;
+ }
+}
+
+function license(state = {}, action) {
+ switch (action.type) {
+ case GeneralTypes.CLIENT_LICENSE_RECEIVED:
+ return Object.assign({}, state, action.data);
+ case UsersTypes.LOGOUT_SUCCESS:
+ return {};
+ default:
+ return state;
+ }
+}
+
+export default combineReducers({
+ config,
+ license
+});
diff --git a/src/reducers/entities/helpers.js b/src/reducers/entities/helpers.js
new file mode 100644
index 000000000..506faadab
--- /dev/null
+++ b/src/reducers/entities/helpers.js
@@ -0,0 +1,136 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+export function initialPagingState() {
+ return {
+ items: new Set(),
+ offset: 0,
+ count: 0
+ };
+}
+
+function isPostListNull(pl) {
+ if (!pl) {
+ return true;
+ }
+
+ if (!pl.posts) {
+ return true;
+ }
+
+ return !pl.order;
+}
+
+function makePostListNonNull(pl) {
+ let postList = {...pl};
+ if (!postList) {
+ postList = {order: [], posts: {}};
+ }
+
+ if (!postList.order) {
+ postList.order = [];
+ }
+
+ if (!postList.posts) {
+ postList.posts = {};
+ }
+
+ return postList;
+}
+
+export function addPosts(state, action) {
+ const newState = {...state};
+ const newPosts = action.data;
+ const id = action.channel_id;
+
+ if (isPostListNull(newPosts)) {
+ return state;
+ }
+
+ if (action.checkLatest) {
+ const currentLatest = newState.latestPageTime[id] || 0;
+ if (newPosts.order.length >= 1) {
+ const newLatest = newPosts.posts[newPosts.order[0]].create_at || 0;
+ if (newLatest > currentLatest) {
+ newState.latestPageTime = {
+ ...newState.latestPageTime,
+ [id]: newLatest
+ };
+ }
+ } else if (currentLatest === 0) {
+ // Mark that an empty page was received
+ newState.latestPageTime = {
+ ...newState.latestPageTime,
+ [id]: 1
+ };
+ }
+ }
+
+ const combinedPosts = {...makePostListNonNull(newState.postsInfo[id].postList)};
+
+ for (const pid in newPosts.posts) {
+ if (newPosts.posts.hasOwnProperty(pid)) {
+ const np = newPosts.posts[pid];
+ if (np.delete_at === 0) {
+ combinedPosts.posts[pid] = np;
+ if (combinedPosts.order.indexOf(pid) === -1 && newPosts.order.indexOf(pid) !== -1) {
+ combinedPosts.order.push(pid);
+ }
+ } else if (combinedPosts.posts.hasOwnProperty(pid)) {
+ combinedPosts.posts[pid] = {...np, state: 'DELETED', fileIds: []};
+ }
+ }
+ }
+
+ combinedPosts.order.sort((a, b) => {
+ if (combinedPosts.posts[a].create_at > combinedPosts.posts[b].create_at) {
+ return -1;
+ }
+ if (combinedPosts.posts[a].create_at < combinedPosts.posts[b].create_at) {
+ return 1;
+ }
+
+ return 0;
+ });
+
+ newState.postsInfo = {
+ ...newState.postsInfo,
+ [id]: {
+ ...newState.postsInfo[id],
+ postList: combinedPosts
+ }
+ };
+
+ return newState;
+}
+
+export function profilesToSet(state, action) {
+ const nextState = {...state};
+ if (action.offset != null && action.count != null) {
+ nextState.offset = action.offset + action.count;
+ nextState.count += action.count;
+ }
+
+ nextState.items = new Set(state.items);
+ Object.keys(action.data).forEach((key) => {
+ nextState.items.add(key);
+ });
+
+ return nextState;
+}
+
+export function addProfileToSet(state, profileId) {
+ const nextState = {...state};
+ nextState.items = new Set(state.items);
+ nextState.items.add(profileId);
+
+ return nextState;
+}
+
+export function removeProfileFromSet(state, profileId) {
+ const nextState = {...state};
+ nextState.items = new Set(state.items);
+ nextState.items.delete(profileId);
+
+ return nextState;
+}
diff --git a/src/reducers/entities/index.js b/src/reducers/entities/index.js
new file mode 100644
index 000000000..2b241ad10
--- /dev/null
+++ b/src/reducers/entities/index.js
@@ -0,0 +1,18 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {combineReducers} from 'redux';
+
+import channels from './channels';
+import general from './general';
+import users from './users';
+import teams from './teams';
+import posts from './posts';
+
+export default combineReducers({
+ general,
+ users,
+ teams,
+ channels,
+ posts
+});
diff --git a/src/reducers/entities/posts.js b/src/reducers/entities/posts.js
new file mode 100644
index 000000000..f43b07b3b
--- /dev/null
+++ b/src/reducers/entities/posts.js
@@ -0,0 +1,59 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {PostsTypes, UsersTypes} from 'constants';
+import {combineReducers} from 'redux';
+import {addPosts} from './helpers';
+
+function selectedPostId(state = '', action) {
+ switch (action.type) {
+ case UsersTypes.LOGOUT_SUCCESS:
+ return '';
+ default:
+ return state;
+ }
+}
+
+function currentFocusedPostId(state = '', action) {
+ switch (action.type) {
+ case UsersTypes.LOGOUT_SUCCESS:
+ return '';
+ default:
+ return state;
+ }
+}
+
+function postsInfo(state = {}, action) {
+ switch (action.type) {
+ case PostsTypes.FETCH_POSTS_SUCCESS:
+ return addPosts(state, action);
+ case UsersTypes.LOGOUT_SUCCESS:
+ return {};
+ default:
+ return state;
+ }
+}
+
+function latestPageTime(state = {}, action) {
+ switch (action.type) {
+ case UsersTypes.LOGOUT_SUCCESS:
+ return {};
+ default:
+ return state;
+ }
+}
+
+export default combineReducers({
+
+ // the current selected post
+ selectedPostId,
+
+ // the current selected focused post (permalink view)
+ currentFocusedPostId,
+
+ // object where every key is the channel id and has and object with the postList
+ postsInfo,
+
+ // object where the every key is the channel id and has the timestamp of the latest post created in that channel
+ latestPageTime
+});
diff --git a/src/reducers/entities/teams.js b/src/reducers/entities/teams.js
new file mode 100644
index 000000000..5047ff344
--- /dev/null
+++ b/src/reducers/entities/teams.js
@@ -0,0 +1,100 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {combineReducers} from 'redux';
+import {TeamsTypes, UsersTypes} from 'constants';
+
+function currentId(state = '', action) {
+ switch (action.type) {
+ case TeamsTypes.SELECT_TEAM:
+ return action.teamId;
+ default:
+ return state;
+ }
+}
+
+function teams(state = {}, action) {
+ switch (action.type) {
+ case TeamsTypes.RECEIVED_ALL_TEAMS:
+ return Object.assign({}, state, action.data);
+ case UsersTypes.LOGOUT_SUCCESS:
+ return {};
+ default:
+ return state;
+ }
+}
+
+function myMembers(state = {}, action) {
+ const nextState = {...state};
+
+ switch (action.type) {
+ case TeamsTypes.RECEIVED_MY_TEAM_MEMBERS: {
+ const members = action.data;
+ for (const m of members) {
+ nextState[m.team_id] = m;
+ }
+ return nextState;
+ }
+ case TeamsTypes.LEAVE_TEAM: {
+ const data = action.team;
+ Reflect.deleteProperty(nextState, data.team_id);
+ return nextState;
+ }
+ case UsersTypes.LOGOUT_SUCCESS:
+ return {};
+ default:
+ return state;
+ }
+}
+
+function membersInTeam(state = {}, action) {
+ switch (action.type) {
+ default:
+ return state;
+ }
+}
+
+function membersNotInTeam(state = {}, action) {
+ switch (action.type) {
+ default:
+ return state;
+ }
+}
+
+function stats(state = {}, action) {
+ switch (action.type) {
+ default:
+ return state;
+ }
+}
+
+function openTeamIds(state = new Set(), action) {
+ switch (action.type) {
+ default:
+ return state;
+ }
+}
+
+export default combineReducers({
+
+ // the current selected team
+ currentId,
+
+ // object where every key is the team id and has and object with the team detail
+ teams,
+
+ //object where every key is the team id and has and object with the team members detail
+ myMembers,
+
+ // object where every key is the team id and has a Set of user ids that are members in the team
+ membersInTeam,
+
+ // object where every key is the team id and has a Set of user ids that aren't members in the team
+ membersNotInTeam,
+
+ // object where every key is the team id and has an object with the team stats
+ stats,
+
+ // Set with the team ids the user is not a member of
+ openTeamIds
+});
diff --git a/src/reducers/entities/users.js b/src/reducers/entities/users.js
new file mode 100644
index 000000000..1508a349f
--- /dev/null
+++ b/src/reducers/entities/users.js
@@ -0,0 +1,216 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {combineReducers} from 'redux';
+import {UsersTypes} from 'constants';
+import {initialPagingState, profilesToSet, addProfileToSet, removeProfileFromSet} from './helpers';
+
+function currentId(state = '', action) {
+ switch (action.type) {
+ case UsersTypes.RECEIVED_ME:
+ return action.data.id;
+
+ case UsersTypes.LOGOUT_SUCCESS:
+ return '';
+
+ }
+
+ return state;
+}
+
+function myPreferences(state = {}, action) {
+ const nextState = {...state};
+
+ switch (action.type) {
+ case UsersTypes.RECEIVED_PREFERENCES: {
+ const preferences = action.data;
+ for (const p of preferences) {
+ nextState[`${p.category}--${p.name}`] = p.value;
+ }
+ return nextState;
+ }
+ case UsersTypes.RECEIVED_PREFERENCE: {
+ const p = action.data;
+ nextState[`${p.category}--${p.name}`] = p.value;
+ return nextState;
+ }
+ case UsersTypes.LOGOUT_SUCCESS:
+ return {};
+
+ default:
+ return state;
+ }
+}
+
+function mySessions(state = [], action) {
+ switch (action.type) {
+ case UsersTypes.RECEIVED_SESSIONS:
+ return [...action.data];
+
+ case UsersTypes.RECEIVED_REVOKED_SESSION: {
+ let index = -1;
+ const length = state.length;
+ for (let i = 0; i < length; i++) {
+ if (state[i].id === action.data.id) {
+ index = i;
+ break;
+ }
+ }
+ if (index > -1) {
+ return state.slice(0, index).concat(state.slice(index + 1));
+ }
+
+ return state;
+ }
+ case UsersTypes.LOGOUT_SUCCESS:
+ return [];
+
+ default:
+ return state;
+ }
+}
+
+function myAudits(state = [], action) {
+ switch (action.type) {
+ case UsersTypes.RECEIVED_AUDITS:
+ return [...action.data];
+
+ case UsersTypes.LOGOUT_SUCCESS:
+ return [];
+
+ default:
+ return state;
+ }
+}
+
+function profiles(state = {items: {}, offset: 0, count: 0}, action) {
+ const nextState = {...state};
+
+ switch (action.type) {
+ case UsersTypes.RECEIVED_ME: {
+ const id = action.data.id;
+ nextState.items = {
+ ...nextState.items,
+ [id]: {
+ ...nextState.items[id],
+ ...action.data
+ }
+ };
+
+ return nextState;
+ }
+ case UsersTypes.RECEIVED_PROFILES:
+ if (action.offset != null && action.count != null) {
+ nextState.offset = action.offset + action.count;
+ nextState.count += action.count;
+ }
+ nextState.items = Object.assign({}, nextState.items, action.data);
+
+ return nextState;
+
+ case UsersTypes.LOGOUT_SUCCESS:
+ return {items: {}, offset: 0, count: 0};
+
+ default:
+ return state;
+ }
+}
+
+function profilesInTeam(state = initialPagingState(), action) {
+ switch (action.type) {
+ case UsersTypes.RECEIVED_PROFILES_IN_TEAM:
+ return profilesToSet(state, action);
+
+ case UsersTypes.LOGOUT_SUCCESS:
+ return initialPagingState();
+
+ default:
+ return state;
+ }
+}
+
+function profilesInChannel(state = initialPagingState(), action) {
+ switch (action.type) {
+ case UsersTypes.RECEIVED_PROFILE_IN_CHANNEL:
+ return addProfileToSet(state, action.user_id);
+
+ case UsersTypes.RECEIVED_PROFILES_IN_CHANNEL:
+ return profilesToSet(state, action);
+
+ case UsersTypes.RECEIVED_PROFILE_NOT_IN_CHANNEL:
+ return removeProfileFromSet(state, action.user_id);
+
+ case UsersTypes.LOGOUT_SUCCESS:
+ return initialPagingState();
+
+ default:
+ return state;
+ }
+}
+
+function profilesNotInChannel(state = initialPagingState(), action) {
+ switch (action.type) {
+ case UsersTypes.RECEIVED_PROFILE_NOT_IN_CHANNEL:
+ return addProfileToSet(state, action.user_id);
+
+ case UsersTypes.RECEIVED_PROFILES_NOT_IN_CHANNEL:
+ return profilesToSet(state, action);
+
+ case UsersTypes.RECEIVED_PROFILE_IN_CHANNEL:
+ return removeProfileFromSet(state, action.user_id);
+
+ case UsersTypes.LOGOUT_SUCCESS:
+ return initialPagingState();
+
+ default:
+ return state;
+ }
+}
+
+function statuses(state = {}, action) {
+ switch (action.type) {
+ case UsersTypes.RECEIVED_STATUSES: {
+ const nextState = {...state};
+ return Object.assign({}, nextState, action.data);
+ }
+ case UsersTypes.LOGOUT_SUCCESS:
+ return {};
+
+ default:
+ return state;
+ }
+}
+
+export default combineReducers({
+
+ // the current selected user
+ currentId,
+
+ // object where the key is the category-name and has the corresponding value
+ myPreferences,
+
+ // array with the user's sessions
+ mySessions,
+
+ // array with the user's audits
+ myAudits,
+
+ // object containing items, count and offset where items is an object where every key is a user id
+ // and has an object with the users details
+ profiles,
+
+ // object containing items, count and offset where items is an object where every key is a user id
+ // and has a Set with the users id that are members of the team
+ profilesInTeam,
+
+ // object containing items, count and offset where items is an object where every key is a user id
+ // and has a Set with the users id that are members of the channel
+ profilesInChannel,
+
+ // object containing items, count and offset where items is an object where every key is a user id
+ // and has a Set with the users id that are members of the channel
+ profilesNotInChannel,
+
+ // object where every key is the user id and has a value with the current status of each user
+ statuses
+});
diff --git a/src/reducers/general.js b/src/reducers/general.js
deleted file mode 100644
index 5e1a8e7c3..000000000
--- a/src/reducers/general.js
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {combineReducers} from 'redux';
-import {GeneralTypes} from 'constants';
-
-function ping(state = {}, action) {
- switch (action.type) {
- case GeneralTypes.PING_REQUEST:
- return {
- ...action.data,
- loading: true
- };
- case GeneralTypes.PING_SUCCESS:
- return {
- ...action.data,
- loading: false,
- error: null
- };
- case GeneralTypes.PING_FAILURE:
- return {
- ...action.data,
- loading: false,
- error: action.error
- };
-
- default:
- return state;
- }
-}
-
-function clientConfig(state = {}, action) {
- switch (action.type) {
- case GeneralTypes.CLIENT_CONFIG_REQUEST:
- return {
- ...action.data,
- loading: true
- };
- case GeneralTypes.CLIENT_CONFIG_SUCCESS:
- return {
- ...action.data,
- loading: false,
- error: null
- };
- case GeneralTypes.CLIENT_CONFIG_FAILURE:
- return {
- ...action.data,
- loading: false,
- error: action.error
- };
-
- default:
- return state;
- }
-}
-
-export default combineReducers({
- ping,
- clientConfig
-});
diff --git a/src/reducers/index.js b/src/reducers/index.js
index 8801a41dc..cd1202fb2 100644
--- a/src/reducers/index.js
+++ b/src/reducers/index.js
@@ -3,28 +3,12 @@
import {combineReducers} from 'redux';
-import channel from './channel.js';
-import device from './device.js';
-import general from './general.js';
-import login from './login';
-import logout from './logout';
-import teams from './teams';
-import posts from './posts';
-
-const entities = combineReducers({
- channel,
- general,
- teams,
- posts
-});
-
-const views = combineReducers({
- device,
- login,
- logout
-});
+import entities from './entities';
+import requests from './requests';
+import views from './views';
export default combineReducers({
entities,
+ requests,
views
});
diff --git a/src/reducers/login.js b/src/reducers/login.js
deleted file mode 100644
index 50e270db4..000000000
--- a/src/reducers/login.js
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {LoginTypes, LogoutTypes} from 'constants';
-
-export const initState = {
- status: 'not fetched',
- error: null
-};
-
-export default function reduceLogin(state = initState, action) {
- switch (action.type) {
-
- case LoginTypes.LOGIN_REQUEST:
- return {
- ...state,
- status: 'fetching',
- error: null
- };
- case LoginTypes.LOGIN_SUCCESS:
- return {
- ...state,
- status: 'fetched'
- };
- case LoginTypes.LOGIN_FAILURE:
- return {
- ...state,
- status: 'failed',
- error: action.error
- };
-
- case LogoutTypes.LOGOUT_SUCCESS:
- return initState;
- default:
- return state;
- }
-}
diff --git a/src/reducers/logout.js b/src/reducers/logout.js
deleted file mode 100644
index 65d8181a8..000000000
--- a/src/reducers/logout.js
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {LogoutTypes} from 'constants';
-
-export const initState = {
- status: 'not fetched',
- error: null
-};
-
-export default function reduceLogout(state = initState, action) {
- switch (action.type) {
- case LogoutTypes.LOGOUT_REQUEST:
- return {...state,
- status: 'fetching',
- error: null
- };
- case LogoutTypes.LOGOUT_SUCCESS:
- return {...state,
- status: 'fetched'
- };
- case LogoutTypes.LOGOUT_FAILURE:
- return {...state,
- status: 'failed',
- error: action.error
- };
-
- default:
- return state;
- }
-}
diff --git a/src/reducers/posts.js b/src/reducers/posts.js
deleted file mode 100644
index 815637ce4..000000000
--- a/src/reducers/posts.js
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {PostsTypes, LogoutTypes} from 'constants';
-
-export const initState = {
- status: 'not fetched',
- error: null,
- data: {}
-};
-
-export default function reducePosts(state = initState, action) {
- switch (action.type) {
-
- case PostsTypes.FETCH_POSTS_REQUEST:
- return {...state,
- status: 'fetching',
- error: null
- };
- case PostsTypes.FETCH_POSTS_SUCCESS:
- return {...state,
- status: 'fetched',
- data: action.data.posts
- };
- case PostsTypes.FETCH_POSTS_FAILURE:
- return {...state,
- status: 'failed',
- error: action.error
- };
-
- case LogoutTypes.LOGOUT_SUCCESS:
- return initState;
- default:
- return state;
- }
-}
diff --git a/src/reducers/requests/channels.js b/src/reducers/requests/channels.js
new file mode 100644
index 000000000..6b66ba820
--- /dev/null
+++ b/src/reducers/requests/channels.js
@@ -0,0 +1,164 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {handleRequest, initialRequestState} from './helpers';
+import {ChannelTypes} from 'constants';
+
+import {combineReducers} from 'redux';
+
+function getChannel(state = initialRequestState(), action) {
+ return handleRequest(
+ ChannelTypes.CHANNEL_REQUEST,
+ ChannelTypes.CHANNEL_SUCCESS,
+ ChannelTypes.CHANNEL_FAILURE,
+ state,
+ action
+ );
+}
+
+function getChannels(state = initialRequestState(), action) {
+ return handleRequest(
+ ChannelTypes.CHANNELS_REQUEST,
+ ChannelTypes.CHANNELS_SUCCESS,
+ ChannelTypes.CHANNELS_FAILURE,
+ state,
+ action
+ );
+}
+
+function myMembers(state = initialRequestState(), action) {
+ return handleRequest(
+ ChannelTypes.CHANNEL_MEMBERS_REQUEST,
+ ChannelTypes.CHANNEL_MEMBERS_SUCCESS,
+ ChannelTypes.CHANNEL_MEMBERS_FAILURE,
+ state,
+ action
+ );
+}
+
+function createChannel(state = initialRequestState(), action) {
+ return handleRequest(
+ ChannelTypes.CREATE_CHANNEL_REQUEST,
+ ChannelTypes.CREATE_CHANNEL_SUCCESS,
+ ChannelTypes.CREATE_CHANNEL_FAILURE,
+ state,
+ action
+ );
+}
+
+function updateChannel(state = initialRequestState(), action) {
+ return handleRequest(
+ ChannelTypes.UPDATE_CHANNEL_REQUEST,
+ ChannelTypes.UPDATE_CHANNEL_SUCCESS,
+ ChannelTypes.UPDATE_CHANNEL_FAILURE,
+ state,
+ action
+ );
+}
+
+function updateChannelNotifyProps(state = initialRequestState(), action) {
+ return handleRequest(
+ ChannelTypes.NOTIFY_PROPS_REQUEST,
+ ChannelTypes.NOTIFY_PROPS_SUCCESS,
+ ChannelTypes.NOTIFY_PROPS_FAILURE,
+ state,
+ action
+ );
+}
+
+function leaveChannel(state = initialRequestState(), action) {
+ return handleRequest(
+ ChannelTypes.LEAVE_CHANNEL_REQUEST,
+ ChannelTypes.LEAVE_CHANNEL_SUCCESS,
+ ChannelTypes.LEAVE_CHANNEL_FAILURE,
+ state,
+ action
+ );
+}
+
+function joinChannel(state = initialRequestState(), action) {
+ return handleRequest(
+ ChannelTypes.JOIN_CHANNEL_REQUEST,
+ ChannelTypes.JOIN_CHANNEL_SUCCESS,
+ ChannelTypes.JOIN_CHANNEL_FAILURE,
+ state,
+ action
+ );
+}
+
+function deleteChannel(state = initialRequestState(), action) {
+ return handleRequest(
+ ChannelTypes.DELETE_CHANNEL_REQUEST,
+ ChannelTypes.DELETE_CHANNEL_SUCCESS,
+ ChannelTypes.DELETE_CHANNEL_FAILURE,
+ state,
+ action
+ );
+}
+
+function updateLastViewedAt(state = initialRequestState(), action) {
+ return handleRequest(
+ ChannelTypes.UPDATE_LAST_VIEWED_REQUEST,
+ ChannelTypes.UPDATE_LAST_VIEWED_SUCCESS,
+ ChannelTypes.UPDATE_LAST_VIEWED_FAILURE,
+ state,
+ action
+ );
+}
+
+function getMoreChannels(state = initialRequestState(), action) {
+ return handleRequest(
+ ChannelTypes.MORE_CHANNELS_REQUEST,
+ ChannelTypes.MORE_CHANNELS_SUCCESS,
+ ChannelTypes.MORE_CHANNELS_FAILURE,
+ state,
+ action
+ );
+}
+
+function getChannelStats(state = initialRequestState(), action) {
+ return handleRequest(
+ ChannelTypes.CHANNEL_STATS_REQUEST,
+ ChannelTypes.CHANNEL_STATS_SUCCESS,
+ ChannelTypes.CHANNEL_STATS_FAILURE,
+ state,
+ action
+ );
+}
+
+function addChannelMember(state = initialRequestState(), action) {
+ return handleRequest(
+ ChannelTypes.ADD_CHANNEL_MEMBER_REQUEST,
+ ChannelTypes.ADD_CHANNEL_MEMBER_SUCCESS,
+ ChannelTypes.ADD_CHANNEL_MEMBER_FAILURE,
+ state,
+ action
+ );
+}
+
+function removeChannelMember(state = initialRequestState(), action) {
+ return handleRequest(
+ ChannelTypes.REMOVE_CHANNEL_MEMBER_REQUEST,
+ ChannelTypes.REMOVE_CHANNEL_MEMBER_SUCCESS,
+ ChannelTypes.REMOVE_CHANNEL_MEMBER_FAILURE,
+ state,
+ action
+ );
+}
+
+export default combineReducers({
+ getChannel,
+ getChannels,
+ myMembers,
+ createChannel,
+ updateChannel,
+ updateChannelNotifyProps,
+ leaveChannel,
+ joinChannel,
+ deleteChannel,
+ updateLastViewedAt,
+ getMoreChannels,
+ getChannelStats,
+ addChannelMember,
+ removeChannelMember
+});
diff --git a/src/reducers/requests/general.js b/src/reducers/requests/general.js
new file mode 100644
index 000000000..563ab1e95
--- /dev/null
+++ b/src/reducers/requests/general.js
@@ -0,0 +1,42 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {combineReducers} from 'redux';
+import {GeneralTypes} from 'constants';
+import {handleRequest, initialRequestState} from './helpers';
+
+function server(state = initialRequestState(), action) {
+ return handleRequest(
+ GeneralTypes.PING_REQUEST,
+ GeneralTypes.PING_SUCCESS,
+ GeneralTypes.PING_FAILURE,
+ state,
+ action
+ );
+}
+
+function config(state = initialRequestState(), action) {
+ return handleRequest(
+ GeneralTypes.CLIENT_CONFIG_REQUEST,
+ GeneralTypes.CLIENT_CONFIG_SUCCESS,
+ GeneralTypes.CLIENT_CONFIG_FAILURE,
+ state,
+ action
+ );
+}
+
+function license(state = initialRequestState(), action) {
+ return handleRequest(
+ GeneralTypes.CLIENT_LICENSE_REQUEST,
+ GeneralTypes.CLIENT_LICENSE_SUCCESS,
+ GeneralTypes.CLIENT_LICENSE_FAILURE,
+ state,
+ action
+ );
+}
+
+export default combineReducers({
+ server,
+ config,
+ license
+});
diff --git a/src/reducers/helpers.js b/src/reducers/requests/helpers.js
similarity index 58%
rename from src/reducers/helpers.js
rename to src/reducers/requests/helpers.js
index b05baf604..a80f3ede9 100644
--- a/src/reducers/helpers.js
+++ b/src/reducers/requests/helpers.js
@@ -1,38 +1,37 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-export function initialState() {
+import {RequestStatus} from 'constants';
+
+export function initialRequestState() {
return {
- data: {},
- loading: false,
+ status: RequestStatus.NOT_STARTED,
error: null
};
}
-export function handle(REQUEST, SUCCESS, FAILURE, state, action) {
+export function handleRequest(REQUEST, SUCCESS, FAILURE, state, action) {
switch (action.type) {
-
case REQUEST:
return {
...state,
- loading: true
+ status: RequestStatus.STARTED
};
-
case SUCCESS:
return {
+ ...state,
+ status: RequestStatus.SUCCESS,
data: action.data,
- loading: false,
error: null
};
-
case FAILURE:
return {
...state,
- loading: false,
+ status: RequestStatus.FAILURE,
error: action.error
};
default:
return state;
}
-}
\ No newline at end of file
+}
diff --git a/src/reducers/requests/index.js b/src/reducers/requests/index.js
new file mode 100644
index 000000000..eaa89fb6a
--- /dev/null
+++ b/src/reducers/requests/index.js
@@ -0,0 +1,16 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {combineReducers} from 'redux';
+
+import general from './general';
+import channels from './channels';
+import users from './users';
+import teams from './teams';
+
+export default combineReducers({
+ general,
+ channels,
+ users,
+ teams
+});
diff --git a/src/reducers/requests/teams.js b/src/reducers/requests/teams.js
new file mode 100644
index 000000000..5dab01efc
--- /dev/null
+++ b/src/reducers/requests/teams.js
@@ -0,0 +1,21 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {handleRequest, initialRequestState} from './helpers';
+import {TeamsTypes} from 'constants';
+
+import {combineReducers} from 'redux';
+
+function allTeams(state = initialRequestState(), action) {
+ return handleRequest(
+ TeamsTypes.FETCH_TEAMS_REQUEST,
+ TeamsTypes.FETCH_TEAMS_SUCCESS,
+ TeamsTypes.FETCH_TEAMS_FAILURE,
+ state,
+ action
+ );
+}
+
+export default combineReducers({
+ allTeams
+});
diff --git a/src/reducers/requests/users.js b/src/reducers/requests/users.js
new file mode 100644
index 000000000..a54267d0c
--- /dev/null
+++ b/src/reducers/requests/users.js
@@ -0,0 +1,129 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {handleRequest, initialRequestState} from './helpers';
+import {UsersTypes, RequestStatus} from 'constants';
+
+import {combineReducers} from 'redux';
+
+function login(state = initialRequestState(), action) {
+ switch (action.type) {
+ case UsersTypes.LOGIN_REQUEST:
+ return {...state, status: RequestStatus.STARTED};
+
+ case UsersTypes.LOGIN_SUCCESS:
+ return {...state, status: RequestStatus.SUCCESS, error: null};
+
+ case UsersTypes.LOGIN_FAILURE:
+ return {...state, status: RequestStatus.FAILURE, error: action.error};
+
+ case UsersTypes.LOGOUT_SUCCESS:
+ return {...state, status: RequestStatus.NOT_STARTED, error: null};
+
+ default:
+ return state;
+ }
+}
+
+function logout(state = initialRequestState(), action) {
+ return handleRequest(
+ UsersTypes.LOGOUT_REQUEST,
+ UsersTypes.LOGOUT_SUCCESS,
+ UsersTypes.LOGOUT_FAILURE,
+ state,
+ action
+ );
+}
+
+function getProfiles(state = initialRequestState(), action) {
+ return handleRequest(
+ UsersTypes.PROFILES_REQUEST,
+ UsersTypes.PROFILES_SUCCESS,
+ UsersTypes.PROFILES_FAILURE,
+ state,
+ action
+ );
+}
+
+function getProfilesInTeam(state = initialRequestState(), action) {
+ return handleRequest(
+ UsersTypes.PROFILES_IN_TEAM_REQUEST,
+ UsersTypes.PROFILES_IN_TEAM_SUCCESS,
+ UsersTypes.PROFILES_IN_TEAM_FAILURE,
+ state,
+ action
+ );
+}
+
+function getProfilesInChannel(state = initialRequestState(), action) {
+ return handleRequest(
+ UsersTypes.PROFILES_IN_CHANNEL_REQUEST,
+ UsersTypes.PROFILES_IN_CHANNEL_SUCCESS,
+ UsersTypes.PROFILES_IN_CHANNEL_FAILURE,
+ state,
+ action
+ );
+}
+
+function getProfilesNotInChannel(state = initialRequestState(), action) {
+ return handleRequest(
+ UsersTypes.PROFILES_NOT_IN_CHANNEL_REQUEST,
+ UsersTypes.PROFILES_NOT_IN_CHANNEL_SUCCESS,
+ UsersTypes.PROFILES_NOT_IN_CHANNEL_FAILURE,
+ state,
+ action
+ );
+}
+
+function getStatusesByIds(state = initialRequestState(), action) {
+ return handleRequest(
+ UsersTypes.PROFILES_STATUSES_REQUEST,
+ UsersTypes.PROFILES_STATUSES_SUCCESS,
+ UsersTypes.PROFILES_STATUSES_FAILURE,
+ state,
+ action
+ );
+}
+
+function getSessions(state = initialRequestState(), action) {
+ return handleRequest(
+ UsersTypes.SESSIONS_REQUEST,
+ UsersTypes.SESSIONS_SUCCESS,
+ UsersTypes.SESSIONS_FAILURE,
+ state,
+ action
+ );
+}
+
+function revokeSession(state = initialRequestState(), action) {
+ return handleRequest(
+ UsersTypes.REVOKE_SESSION_REQUEST,
+ UsersTypes.REVOKE_SESSION_SUCCESS,
+ UsersTypes.REVOKE_SESSION_FAILURE,
+ state,
+ action
+ );
+}
+
+function getAudits(state = initialRequestState(), action) {
+ return handleRequest(
+ UsersTypes.AUDITS_REQUEST,
+ UsersTypes.AUDITS_SUCCESS,
+ UsersTypes.AUDITS_FAILURE,
+ state,
+ action
+ );
+}
+
+export default combineReducers({
+ login,
+ logout,
+ getProfiles,
+ getProfilesInTeam,
+ getProfilesInChannel,
+ getProfilesNotInChannel,
+ getStatusesByIds,
+ getSessions,
+ revokeSession,
+ getAudits
+});
diff --git a/src/reducers/teams.js b/src/reducers/teams.js
deleted file mode 100644
index 57a8a40d9..000000000
--- a/src/reducers/teams.js
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import {TeamsTypes, LogoutTypes} from 'constants';
-
-export const initState = {
- status: 'not fetched',
- error: null,
- data: {},
- currentTeamId: null
-};
-
-export default function reduceTeams(state = initState, action) {
- switch (action.type) {
-
- case TeamsTypes.SELECT_TEAM:
- return {
- ...state,
- currentTeamId: action.teamId
- };
-
- case TeamsTypes.FETCH_TEAMS_REQUEST:
- return {
- ...state,
- status: 'fetching',
- error: null
- };
- case TeamsTypes.FETCH_TEAMS_SUCCESS:
- return {
- ...state,
- status: 'fetched',
- data: action.data
- };
- case TeamsTypes.FETCH_TEAMS_FAILURE:
- return {
- ...state,
- status: 'failed',
- error: action.error
- };
-
- case LogoutTypes.LOGOUT_SUCCESS:
- return initState;
- default:
- return state;
- }
-}
diff --git a/src/reducers/views/device.js b/src/reducers/views/device.js
new file mode 100644
index 000000000..865f80e70
--- /dev/null
+++ b/src/reducers/views/device.js
@@ -0,0 +1,36 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {DeviceTypes} from 'constants';
+import RequestStatus from 'constants/request_status';
+
+export const initState = {
+ status: RequestStatus.NOT_STARTED,
+ error: null
+};
+
+export default function device(state = initState, action) {
+ switch (action.type) {
+ case DeviceTypes.DEVICE_REQUEST:
+ return {
+ ...state,
+ status: RequestStatus.STARTED
+ };
+ case DeviceTypes.DEVICE_SUCCESS:
+ return {
+ ...state,
+ status: RequestStatus.SUCCESS,
+ data: action.data,
+ error: null
+ };
+ case DeviceTypes.DEVICE_FAILURE:
+ return {
+ ...state,
+ status: RequestStatus.FAILURE,
+ error: action.error
+ };
+
+ default:
+ return state;
+ }
+}
diff --git a/src/reducers/views/i18n.js b/src/reducers/views/i18n.js
new file mode 100644
index 000000000..35b686436
--- /dev/null
+++ b/src/reducers/views/i18n.js
@@ -0,0 +1,23 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {combineReducers} from 'redux';
+
+import Config from 'config/index';
+import {UsersTypes} from 'constants';
+
+function locale(state = Config.DefaultLocale, action) {
+ switch (action.type) {
+ case UsersTypes.RECEIVED_ME:
+ return action.data.locale;
+
+ case UsersTypes.LOGOUT_SUCCESS:
+ return Config.DefaultLocale;
+ }
+
+ return state;
+}
+
+export default combineReducers({
+ locale
+});
diff --git a/src/reducers/views/index.js b/src/reducers/views/index.js
new file mode 100644
index 000000000..4ec6a3dfd
--- /dev/null
+++ b/src/reducers/views/index.js
@@ -0,0 +1,16 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {combineReducers} from 'redux';
+
+import login from './login';
+import selectServer from './select_server';
+import device from './device';
+import i18n from './i18n';
+
+export default combineReducers({
+ device,
+ i18n,
+ login,
+ selectServer
+});
diff --git a/src/reducers/views/login.js b/src/reducers/views/login.js
new file mode 100644
index 000000000..5b4c28f87
--- /dev/null
+++ b/src/reducers/views/login.js
@@ -0,0 +1,33 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {combineReducers} from 'redux';
+
+import {UsersTypes} from 'constants';
+
+function loginId(state = '', action) {
+ switch (action.type) {
+ case UsersTypes.LOGIN_ID_CHANGED:
+ return action.loginId;
+ case UsersTypes.LOGOUT_SUCCESS:
+ return '';
+ default:
+ return state;
+ }
+}
+
+function password(state = '', action) {
+ switch (action.type) {
+ case UsersTypes.PASSWORD_CHANGED:
+ return action.password;
+ case UsersTypes.LOGOUT_SUCCESS:
+ return '';
+ default:
+ return state;
+ }
+}
+
+export default combineReducers({
+ loginId,
+ password
+});
diff --git a/src/reducers/views/select_server.js b/src/reducers/views/select_server.js
new file mode 100644
index 000000000..466db757c
--- /dev/null
+++ b/src/reducers/views/select_server.js
@@ -0,0 +1,22 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {combineReducers} from 'redux';
+
+import Config from 'config/index';
+
+import {GeneralTypes} from 'constants';
+
+function serverUrl(state = Config.DefaultServerUrl, action) {
+ switch (action.type) {
+ case GeneralTypes.SERVER_URL_CHANGED:
+ return action.serverUrl;
+
+ default:
+ return state;
+ }
+}
+
+export default combineReducers({
+ serverUrl
+});
diff --git a/src/routes/channel/channel.js b/src/routes/channel/channel.js
index 82a4c403c..5c67047ce 100644
--- a/src/routes/channel/channel.js
+++ b/src/routes/channel/channel.js
@@ -17,7 +17,7 @@ export default class Channel extends React.Component {
currentTeam: React.PropTypes.object.isRequired,
currentChannel: React.PropTypes.object,
channels: React.PropTypes.arrayOf(React.PropTypes.object).isRequired
- }
+ };
constructor(props) {
super(props);
diff --git a/src/routes/channel/channel_container.js b/src/routes/channel/channel_container.js
index 78f5c105d..41ee9203f 100644
--- a/src/routes/channel/channel_container.js
+++ b/src/routes/channel/channel_container.js
@@ -7,18 +7,17 @@ import {fetchMyChannelsAndMembers} from 'actions/channels.js';
import Channel from './channel.js';
function mapStateToProps(state, ownProps) {
- const currentTeamId = ownProps.currentTeamId;
- const currentTeam = state.entities.teams.data[currentTeamId];
+ const currentTeamId = state.entities.teams.currentId;
+ const currentTeam = state.entities.teams.teams[currentTeamId];
+ let channels = state.entities.channels.channels;
let currentChannel;
if (ownProps.currentChannelId) {
- currentChannel = state.entities.channel.channels[ownProps.currentChannelId];
+ currentChannel = state.entities.channels.channels[ownProps.currentChannelId];
} else {
// TODO figure out the town square id before this
- const channelIds = state.entities.channel.channelIdsByTeamId[currentTeamId] || {};
-
- for (const channelId of Object.keys(channelIds)) {
- const channel = state.entities.channel.channels[channelId];
+ for (const channelId of Object.keys(channels)) {
+ const channel = channels[channelId];
if (channel.name === 'town-square') {
currentChannel = channel;
@@ -27,8 +26,7 @@ function mapStateToProps(state, ownProps) {
}
}
- const channelIdsForTeam = state.entities.channel.channelIdsByTeamId[currentTeamId] || {};
- const channels = Object.keys(channelIdsForTeam).map((channelId) => state.entities.channel.channels[channelId]);
+ channels = Object.keys(channels).map((channelId) => channels[channelId]);
return {
...ownProps,
diff --git a/src/routes/index.js b/src/routes/index.js
index 51037fe81..515f019d4 100644
--- a/src/routes/index.js
+++ b/src/routes/index.js
@@ -9,14 +9,14 @@ import SelectServerContainer from './select_server/select_server_container.js';
import SelectTeamContainer from './select_team/select_team_container.js';
import ChannelContainer from './channel/channel_container.js';
import Logout from 'components/logout';
-import * as logout from 'actions/logout';
+import {logout} from 'actions/users';
import {injectIntl, intlShape} from 'react-intl';
class Routes extends Component {
static propTypes = {
intl: intlShape.isRequired
- }
+ };
render() {
const {formatMessage} = this.props.intl;
diff --git a/src/routes/login/login.js b/src/routes/login/login.js
index 0a8cabf33..cecc5677a 100644
--- a/src/routes/login/login.js
+++ b/src/routes/login/login.js
@@ -14,52 +14,55 @@ import logo from 'images/logo.png';
import {injectIntl, intlShape} from 'react-intl';
+import {RequestStatus} from 'constants';
+
class Login extends Component {
static propTypes = {
intl: intlShape.isRequired,
- clientConfig: PropTypes.object.isRequired,
- login: PropTypes.object.isRequired,
- actions: PropTypes.object.isRequired
- };
-
- state = {
- loginId: '',
- password: ''
+ config: PropTypes.object.isRequired,
+ license: PropTypes.object.isRequired,
+ actions: PropTypes.object.isRequired,
+ loginId: PropTypes.string.isRequired,
+ password: PropTypes.string.isRequired,
+ loginRequest: PropTypes.object.isRequired,
+ configRequest: PropTypes.object.isRequired,
+ licenseRequest: PropTypes.object.isRequired
};
componentWillMount() {
this.props.actions.getClientConfig();
+ this.props.actions.getLicenseConfig();
}
componentWillReceiveProps(nextProps) {
- if (this.props.login.status === 'fetching' && nextProps.login.status === 'fetched') {
+ if (this.props.loginRequest.status === RequestStatus.STARTED && nextProps.loginRequest.status === RequestStatus.SUCCESS) {
Routes.goToSelectTeam();
}
}
- signIn = () => {
- if (this.props.login.status !== 'fetching') {
- const {loginId, password} = this.state;
- this.props.actions.login(loginId, password);
+ signIn() {
+ if (this.props.loginRequest.status !== RequestStatus.STARTED) {
+ this.props.actions.login(this.props.loginId, this.props.password);
}
}
createLoginPlaceholder() {
const {formatMessage} = this.props.intl;
- const clientConfig = this.props.clientConfig;
+ const license = this.props.license;
+ const config = this.props.config;
const loginPlaceholders = [];
- if (clientConfig.EnableSignInWithEmail === 'true') {
+ if (config.EnableSignInWithEmail === 'true') {
loginPlaceholders.push(formatMessage({id: 'login.email', defaultMessage: 'Email'}));
}
- if (clientConfig.EnableSignInWithUsername === 'true') {
+ if (config.EnableSignInWithUsername === 'true') {
loginPlaceholders.push(formatMessage({id: 'login.username', defaultMessage: 'Username'}));
}
- if (clientConfig.EnableLdap === 'true') { // TODO check if we're licensed once we have that
- if (clientConfig.LdapLoginFieldName) {
- loginPlaceholders.push(clientConfig.LdapLoginFieldName);
+ if (license.IsLicensed === 'true' && license.LDAP === 'true' && config.EnableLdap === 'true') {
+ if (config.LdapLoginFieldName) {
+ loginPlaceholders.push(config.LdapLoginFieldName);
} else {
loginPlaceholders.push(formatMessage({id: 'login.ldapUsername', defaultMessage: 'AD/LDAP Username'}));
}
@@ -77,7 +80,7 @@ class Login extends Component {
}
render() {
- if (this.props.clientConfig.loading || this.props.clientConfig.error) {
+ if (this.props.configRequest.status === RequestStatus.STARTED || this.props.licenseRequest.status === RequestStatus.STARTED) {
return ;
}
@@ -88,7 +91,7 @@ class Login extends Component {
source={logo}
/>
- {this.props.clientConfig.SiteName}
+ {this.props.config.SiteName}
this.setState({loginId})}
+ value={this.props.loginId}
+ onChangeText={this.props.actions.handleLoginIdChanged}
style={GlobalStyles.inputBox}
placeholder={this.createLoginPlaceholder()}
autoCorrect={false}
@@ -106,8 +109,8 @@ class Login extends Component {
underlineColorAndroid='transparent'
/>
this.setState({password})}
+ value={this.props.password}
+ onChangeText={this.props.actions.handlePasswordChanged}
style={GlobalStyles.inputBox}
placeholder={this.props.intl.formatMessage({id: 'login.password', defaultMessage: 'Password'})}
secureTextEntry={true}
@@ -115,15 +118,15 @@ class Login extends Component {
autoCapitalize='none'
underlineColorAndroid='transparent'
returnKeyType='go'
- onSubmitEditing={this.signIn}
+ onSubmitEditing={this.signIn.bind(this)}
/>
-
);
+
+ // }
}
return (
@@ -62,7 +70,7 @@ export default class SelectTeam extends Component {
source={logo}
/>
- {this.props.clientConfig.SiteName}
+ {this.props.config.SiteName}
{teams}
-
+
);
}
diff --git a/src/routes/select_team/select_team_container.js b/src/routes/select_team/select_team_container.js
index e6ed55c09..8bdd05550 100644
--- a/src/routes/select_team/select_team_container.js
+++ b/src/routes/select_team/select_team_container.js
@@ -8,8 +8,10 @@ import SelectTeamView from './select_team.js';
function mapStateToProps(state) {
return {
- clientConfig: state.entities.general.clientConfig,
- teams: state.entities.teams
+ config: state.entities.general.config,
+ teamsRequest: state.requests.teams.allTeams,
+ teams: state.entities.teams.teams,
+ myMembers: state.entities.teams.myMembers
};
}
diff --git a/src/store/index.js b/src/store/index.js
new file mode 100644
index 000000000..d261021fc
--- /dev/null
+++ b/src/store/index.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import configureStore from 'store/configureStore.js';
+
+const store = configureStore();
+
+export default store;
diff --git a/test/actions/channels.test.js b/test/actions/channels.test.js
new file mode 100644
index 000000000..4bef58f55
--- /dev/null
+++ b/test/actions/channels.test.js
@@ -0,0 +1,496 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import assert from 'assert';
+
+import * as Actions from 'actions/channels';
+import Client from 'client';
+import configureStore from 'store/configureStore';
+import {RequestStatus} from 'constants';
+import TestHelper from 'test_helper';
+
+describe('Actions.Channels', () => {
+ it('createChannel', (done) => {
+ TestHelper.initBasic(Client).then(() => {
+ const store = configureStore();
+
+ store.subscribe(() => {
+ const channels = store.getState().entities.channels.channels;
+ const members = store.getState().entities.channels.myMembers;
+
+ const createRequest = store.getState().requests.channels.createChannel;
+ const membersRequest = store.getState().requests.channels.myMembers;
+
+ if (createRequest.status === RequestStatus.SUCCESS && membersRequest.status === RequestStatus.SUCCESS) {
+ const channelsCount = Object.keys(channels).length;
+ const membersCount = Object.keys(members).length;
+ assert.ok(channels);
+ assert.ok(members);
+ assert.ok(channels[Object.keys(members)[0]]);
+ assert.ok(members[Object.keys(channels)[0]]);
+ assert.equal(members[Object.keys(channels)[0]].user_id, TestHelper.basicUser.id);
+ assert.equal(channelsCount, membersCount);
+ assert.equal(channelsCount, 1);
+ assert.equal(membersCount, 1);
+ done();
+ } else if (createRequest.status === RequestStatus.FAILURE && membersRequest.status === RequestStatus.FAILURE) {
+ done(new Error(JSON.stringify(createRequest.error)));
+ }
+ });
+
+ const channel = {
+ team_id: TestHelper.basicTeam.id,
+ name: 'redux-test',
+ display_name: 'Redux Test',
+ purpose: 'This is to test redux',
+ header: 'MM with Redux',
+ type: 'O'
+ };
+
+ Actions.createChannel(channel, TestHelper.basicUser.id)(store.dispatch, store.getState);
+ });
+ });
+
+ it('createDirectChannel', (done) => {
+ TestHelper.initBasic(Client).then(async () => {
+ const store = configureStore();
+ const user = await TestHelper.basicClient.createUser(TestHelper.fakeUser());
+
+ store.subscribe(() => {
+ const channels = store.getState().entities.channels.channels;
+ const members = store.getState().entities.channels.myMembers;
+ const profiles = store.getState().entities.users.profiles;
+ const preferences = store.getState().entities.users.myPreferences;
+
+ const createRequest = store.getState().requests.channels.createChannel;
+ const membersRequest = store.getState().requests.channels.myMembers;
+
+ if (createRequest.status === RequestStatus.SUCCESS && membersRequest.status === RequestStatus.SUCCESS) {
+ const channelsCount = Object.keys(channels).length;
+ const membersCount = Object.keys(members).length;
+ assert.ok(channels);
+ assert.ok(members);
+ assert.ok(profiles.items[user.id]);
+ assert.ok(Object.keys(preferences).length);
+ assert.ok(channels[Object.keys(members)[0]]);
+ assert.ok(members[Object.keys(channels)[0]]);
+ assert.equal(members[Object.keys(channels)[0]].user_id, TestHelper.basicUser.id);
+ assert.equal(channelsCount, membersCount);
+ assert.equal(channels[Object.keys(channels)[0]].type, 'D');
+ assert.equal(channelsCount, 1);
+ assert.equal(membersCount, 1);
+ done();
+ } else if (createRequest.status === RequestStatus.FAILURE && membersRequest.status === RequestStatus.FAILURE) {
+ done(new Error(JSON.stringify(createRequest.error)));
+ }
+ });
+
+ Actions.createDirectChannel(TestHelper.basicUser.id, user.id)(store.dispatch, store.getState);
+ });
+ });
+
+ it('updateChannel', (done) => {
+ TestHelper.initBasic(Client).then(() => {
+ const store = configureStore();
+
+ store.subscribe(() => {
+ const channels = store.getState().entities.channels.channels;
+ const updateRequest = store.getState().requests.channels.updateChannel;
+
+ if (updateRequest.status === RequestStatus.SUCCESS) {
+ const channelId = Object.keys(channels)[0];
+ const channel = channels[channelId];
+ assert.ok(channelId);
+ assert.ok(channel);
+ assert.equal(channel.header, 'MM with Redux');
+ done();
+ } else if (updateRequest.status === RequestStatus.FAILURE) {
+ done(new Error(JSON.stringify(updateRequest.error)));
+ }
+ });
+
+ const channel = {
+ ...TestHelper.basicChannel,
+ purpose: 'This is to test redux',
+ header: 'MM with Redux'
+ };
+
+ Actions.updateChannel(channel)(store.dispatch, store.getState);
+ });
+ });
+
+ it('getChannel', (done) => {
+ TestHelper.initBasic(Client).then(() => {
+ const store = configureStore();
+
+ store.subscribe(() => {
+ const channels = store.getState().entities.channels.channels;
+ const members = store.getState().entities.channels.myMembers;
+
+ const channelRequest = store.getState().requests.channels.getChannel;
+
+ if (channelRequest.status === RequestStatus.SUCCESS) {
+ assert.ok(channels[TestHelper.basicChannel.id]);
+ assert.ok(members[TestHelper.basicChannel.id]);
+ done();
+ } else if (channelRequest.status === RequestStatus.FAILURE) {
+ done(new Error(JSON.stringify(channelRequest.error)));
+ }
+ });
+
+ Actions.getChannel(TestHelper.basicTeam.id, TestHelper.basicChannel.id)(store.dispatch, store.getState);
+ });
+ });
+
+ it('fetchMyChannelsAndMembers', (done) => {
+ TestHelper.initBasic(Client).then(() => {
+ const store = configureStore();
+
+ store.subscribe(() => {
+ const channels = store.getState().entities.channels.channels;
+ const members = store.getState().entities.channels.myMembers;
+
+ const channelsRequest = store.getState().requests.channels.getChannels;
+ const membersRequest = store.getState().requests.channels.myMembers;
+
+ if (channelsRequest.status === RequestStatus.SUCCESS && membersRequest.status === RequestStatus.SUCCESS) {
+ assert.ok(channels);
+ assert.ok(members);
+ assert.ok(channels[Object.keys(members)[0]]);
+ assert.ok(members[Object.keys(channels)[0]]);
+ assert.equal(Object.keys(channels).length, Object.keys(members).length);
+ done();
+ } else if (channelsRequest.status === RequestStatus.FAILURE && membersRequest.status === RequestStatus.FAILURE) {
+ done(new Error(JSON.stringify(channelsRequest.error)));
+ }
+ });
+
+ Actions.fetchMyChannelsAndMembers(TestHelper.basicTeam.id)(store.dispatch, store.getState);
+ });
+ });
+
+ it('updateChannelNotifyProps', (done) => {
+ TestHelper.initBasic(Client).then(async () => {
+ const store = configureStore();
+
+ store.subscribe(() => {
+ const members = store.getState().entities.channels.myMembers;
+ const updateRequest = store.getState().requests.channels.updateChannelNotifyProps;
+
+ if (updateRequest.status === RequestStatus.SUCCESS) {
+ const member = members[TestHelper.basicChannel.id];
+ assert.ok(member);
+ assert.equal(member.notify_props.mark_unread, 'mention');
+ assert.equal(member.notify_props.desktop, 'none');
+ done();
+ } else if (updateRequest.status === RequestStatus.FAILURE) {
+ done(new Error(JSON.stringify(updateRequest.error)));
+ }
+ });
+
+ const notifyProps = {
+ mark_unread: 'mention',
+ desktop: 'none'
+ };
+
+ await Actions.fetchMyChannelsAndMembers(TestHelper.basicTeam.id)(store.dispatch, store.getState);
+ Actions.updateChannelNotifyProps(
+ TestHelper.basicUser.id,
+ TestHelper.basicTeam.id,
+ TestHelper.basicChannel.id,
+ notifyProps)(store.dispatch, store.getState);
+ });
+ });
+
+ it('leaveChannel', (done) => {
+ TestHelper.initBasic(Client).then(() => {
+ const store = configureStore();
+
+ store.subscribe(() => {
+ const channels = store.getState().entities.channels.channels;
+ const members = store.getState().entities.channels.myMembers;
+ const leaveRequest = store.getState().requests.channels.leaveChannel;
+
+ if (leaveRequest.status === RequestStatus.SUCCESS) {
+ const channel = channels[TestHelper.basicChannel.id];
+ const member = members[TestHelper.basicChannel.id];
+
+ assert.ifError(channel);
+ assert.ifError(member);
+ done();
+ } else if (leaveRequest.status === RequestStatus.FAILURE) {
+ done(new Error(JSON.stringify(leaveRequest.error)));
+ }
+ });
+
+ Actions.leaveChannel(
+ TestHelper.basicTeam.id,
+ TestHelper.basicChannel.id)(store.dispatch, store.getState);
+ });
+ });
+
+ it('joinChannel', (done) => {
+ TestHelper.initBasic(Client).then(async () => {
+ const store = configureStore();
+ const secondClient = TestHelper.createClient();
+ const user = await TestHelper.basicClient.createUserWithInvite(
+ TestHelper.fakeUser(),
+ null,
+ null,
+ TestHelper.basicTeam.invite_id
+ );
+ await secondClient.login(user.email, 'password1');
+
+ const secondChannel = await secondClient.createChannel(
+ TestHelper.fakeChannel(TestHelper.basicTeam.id));
+
+ store.subscribe(() => {
+ const channels = store.getState().entities.channels.channels;
+ const members = store.getState().entities.channels.myMembers;
+ const joinRequest = store.getState().requests.channels.joinChannel;
+
+ if (joinRequest.status === RequestStatus.SUCCESS) {
+ const channel = channels[secondChannel.id];
+ const member = members[secondChannel.id];
+
+ assert.ok(channel);
+ assert.ok(member);
+ done();
+ } else if (joinRequest.status === RequestStatus.FAILURE) {
+ done(new Error(JSON.stringify(joinRequest.error)));
+ }
+ });
+
+ Actions.joinChannel(
+ TestHelper.basicUser.id,
+ TestHelper.basicTeam.id,
+ secondChannel.id)(store.dispatch, store.getState);
+ });
+ }).timeout(3000);
+
+ it('joinChannelByName', (done) => {
+ TestHelper.initBasic(Client).then(async () => {
+ const store = configureStore();
+ const secondClient = TestHelper.createClient();
+ const user = await TestHelper.basicClient.createUserWithInvite(
+ TestHelper.fakeUser(),
+ null,
+ null,
+ TestHelper.basicTeam.invite_id
+ );
+ await secondClient.login(user.email, 'password1');
+
+ const secondChannel = await secondClient.createChannel(
+ TestHelper.fakeChannel(TestHelper.basicTeam.id));
+
+ store.subscribe(() => {
+ const channels = store.getState().entities.channels.channels;
+ const members = store.getState().entities.channels.myMembers;
+ const joinRequest = store.getState().requests.channels.joinChannel;
+
+ if (joinRequest.status === RequestStatus.SUCCESS) {
+ const channel = channels[secondChannel.id];
+ const member = members[secondChannel.id];
+
+ assert.ok(channel);
+ assert.ok(member);
+ done();
+ } else if (joinRequest.status === RequestStatus.FAILURE) {
+ done(new Error(JSON.stringify(joinRequest.error)));
+ }
+ });
+
+ Actions.joinChannel(
+ TestHelper.basicUser.id,
+ TestHelper.basicTeam.id,
+ null,
+ secondChannel.name)(store.dispatch, store.getState);
+ });
+ }).timeout(3000);
+
+ it('deleteChannel', (done) => {
+ TestHelper.initBasic(Client).then(() => {
+ const store = configureStore();
+
+ store.subscribe(() => {
+ const channels = store.getState().entities.channels.channels;
+ const members = store.getState().entities.channels.myMembers;
+ const deleteRequest = store.getState().requests.channels.deleteChannel;
+
+ if (deleteRequest.status === RequestStatus.SUCCESS) {
+ const channel = channels[TestHelper.basicChannel.id];
+ const member = members[TestHelper.basicChannel.id];
+
+ assert.ifError(channel);
+ assert.ifError(member);
+ done();
+ } else if (deleteRequest.status === RequestStatus.FAILURE) {
+ done(new Error(JSON.stringify(deleteRequest.error)));
+ }
+ });
+
+ Actions.deleteChannel(
+ TestHelper.basicTeam.id,
+ TestHelper.basicChannel.id)(store.dispatch, store.getState);
+ });
+ });
+
+ it('updateLastViewedAt', (done) => {
+ TestHelper.initBasic(Client).then(async () => {
+ const store = configureStore();
+ let lastViewed;
+
+ store.subscribe(() => {
+ const members = store.getState().entities.channels.myMembers;
+ const updateRequest = store.getState().requests.channels.updateLastViewedAt;
+ let member;
+
+ if (updateRequest.status === RequestStatus.STARTED) {
+ member = members[TestHelper.basicChannel.id];
+ assert.ok(member);
+ lastViewed = member.last_viewed_at;
+ } else if (updateRequest.status === RequestStatus.SUCCESS) {
+ member = members[TestHelper.basicChannel.id];
+ assert.ok(member.last_viewed_at > lastViewed);
+
+ done();
+ } else if (updateRequest.status === RequestStatus.FAILURE) {
+ done(new Error(JSON.stringify(updateRequest.error)));
+ }
+ });
+
+ await Actions.fetchMyChannelsAndMembers(TestHelper.basicTeam.id)(store.dispatch, store.getState);
+ Actions.updateLastViewedAt(
+ TestHelper.basicTeam.id,
+ TestHelper.basicChannel.id, true)(store.dispatch, store.getState);
+ });
+ });
+
+ it('getMoreChannels', (done) => {
+ TestHelper.initBasic(Client).then(async () => {
+ const store = configureStore();
+ const secondClient = TestHelper.createClient();
+ const user = await TestHelper.basicClient.createUserWithInvite(
+ TestHelper.fakeUser(),
+ null,
+ null,
+ TestHelper.basicTeam.invite_id
+ );
+ await secondClient.login(user.email, 'password1');
+
+ const secondChannel = await secondClient.createChannel(
+ TestHelper.fakeChannel(TestHelper.basicTeam.id));
+
+ store.subscribe(() => {
+ const channels = store.getState().entities.channels.moreChannels;
+ const moreRequest = store.getState().requests.channels.getMoreChannels;
+
+ if (moreRequest.status === RequestStatus.SUCCESS) {
+ const channel = channels[secondChannel.id];
+
+ assert.ok(channel);
+ done();
+ } else if (moreRequest.status === RequestStatus.FAILURE) {
+ done(new Error(JSON.stringify(moreRequest.error)));
+ }
+ });
+
+ Actions.getMoreChannels(TestHelper.basicTeam.id, 0)(store.dispatch, store.getState);
+ });
+ }).timeout(3000);
+
+ it('getChannelStats', (done) => {
+ TestHelper.initBasic(Client).then(async () => {
+ const store = configureStore();
+
+ store.subscribe(() => {
+ const stats = store.getState().entities.channels.stats;
+ const statsRequest = store.getState().requests.channels.getChannelStats;
+
+ if (statsRequest.status === RequestStatus.SUCCESS) {
+ const stat = stats[TestHelper.basicChannel.id];
+ assert.ok(stat);
+ assert.equal(stat.member_count, 1);
+ done();
+ } else if (statsRequest.status === RequestStatus.FAILURE) {
+ done(new Error(JSON.stringify(statsRequest.error)));
+ }
+ });
+
+ Actions.getChannelStats(
+ TestHelper.basicTeam.id,
+ TestHelper.basicChannel.id
+ )(store.dispatch, store.getState);
+ });
+ });
+
+ it('addChannelMember', (done) => {
+ TestHelper.initBasic(Client).then(async () => {
+ const store = configureStore();
+ const user = await TestHelper.basicClient.createUserWithInvite(
+ TestHelper.fakeUser(),
+ null,
+ null,
+ TestHelper.basicTeam.invite_id
+ );
+
+ store.subscribe(() => {
+ const profilesInChannel = store.getState().entities.users.profilesInChannel;
+ const profilesNotInChannel = store.getState().entities.users.profilesNotInChannel;
+ const addRequest = store.getState().requests.channels.addChannelMember;
+
+ if (addRequest.status === RequestStatus.SUCCESS) {
+ assert.ok(profilesInChannel.items.has(user.id));
+ assert.ifError(profilesNotInChannel.items.has(user.id));
+ done();
+ } else if (addRequest.status === RequestStatus.FAILURE) {
+ done(new Error(JSON.stringify(addRequest.error)));
+ }
+ });
+
+ Actions.addChannelMember(
+ TestHelper.basicTeam.id,
+ TestHelper.basicChannel.id,
+ user.id
+ )(store.dispatch, store.getState);
+ });
+ });
+
+ it('removeChannelMember', (done) => {
+ TestHelper.initBasic(Client).then(async () => {
+ const store = configureStore();
+ const user = await TestHelper.basicClient.createUserWithInvite(
+ TestHelper.fakeUser(),
+ null,
+ null,
+ TestHelper.basicTeam.invite_id
+ );
+
+ store.subscribe(() => {
+ const profilesInChannel = store.getState().entities.users.profilesInChannel;
+ const profilesNotInChannel = store.getState().entities.users.profilesNotInChannel;
+ const removeRequest = store.getState().requests.channels.removeChannelMember;
+
+ if (removeRequest.status === RequestStatus.SUCCESS) {
+ assert.ok(profilesNotInChannel.items.has(user.id));
+ assert.ifError(profilesInChannel.items.has(user.id));
+ done();
+ } else if (removeRequest.status === RequestStatus.FAILURE) {
+ done(new Error(JSON.stringify(removeRequest.error)));
+ }
+ });
+
+ await Actions.addChannelMember(
+ TestHelper.basicTeam.id,
+ TestHelper.basicChannel.id,
+ user.id
+ )(store.dispatch, store.getState);
+
+ Actions.removeChannelMember(
+ TestHelper.basicTeam.id,
+ TestHelper.basicChannel.id,
+ user.id
+ )(store.dispatch, store.getState);
+ });
+ });
+});
diff --git a/test/actions/general.test.js b/test/actions/general.test.js
index c4a178904..626e0427a 100644
--- a/test/actions/general.test.js
+++ b/test/actions/general.test.js
@@ -3,22 +3,41 @@
import assert from 'assert';
-import * as Actions from 'actions/general.js';
-import Client from 'client/client_instance.js';
-import configureStore from 'store/configureStore.js';
-import TestHelper from 'test_helper.js';
+import * as Actions from 'actions/general';
+import Client from 'client';
+import configureStore from 'store/configureStore';
+import {RequestStatus} from 'constants';
+import TestHelper from 'test_helper';
describe('Actions.General', () => {
+ it('getPing', (done) => {
+ TestHelper.initBasic(Client).then(() => {
+ const store = configureStore();
+
+ store.subscribe(() => {
+ const ping = store.getState().requests.general.server;
+
+ if (ping.error) {
+ done(new Error(JSON.stringify(ping.error)));
+ } else if (ping.status !== RequestStatus.STARTED) {
+ done();
+ }
+ });
+
+ Actions.getPing()(store.dispatch, store.getState);
+ });
+ });
+
it('getClientConfig', (done) => {
TestHelper.initBasic(Client).then(() => {
const store = configureStore();
store.subscribe(() => {
- const clientConfig = store.getState().entities.general.clientConfig;
-
- if (!clientConfig.loading) {
- if (clientConfig.error) {
- done(new Error(clientConfig.error));
+ const clientConfig = store.getState().entities.general.config;
+ const configRequest = store.getState().requests.general.config;
+ if (configRequest.status === RequestStatus.SUCCESS || configRequest.status === RequestStatus.FAILURE) {
+ if (configRequest.error) {
+ done(new Error(JSON.stringify(configRequest.error)));
} else {
// Check a few basic fields since they may change over time
assert.ok(clientConfig.Version);
@@ -35,21 +54,26 @@ describe('Actions.General', () => {
});
});
- it('getPing', (done) => {
+ it('getLicenseConfig', (done) => {
TestHelper.initBasic(Client).then(() => {
const store = configureStore();
store.subscribe(() => {
- const ping = store.getState().entities.general.ping;
+ const licenseConfig = store.getState().entities.general.license;
+ const licenseRequest = store.getState().requests.general.license;
+ if (licenseRequest.status === RequestStatus.SUCCESS || licenseRequest.status === RequestStatus.FAILURE) {
+ if (licenseRequest.error) {
+ done(new Error(JSON.stringify(licenseRequest.error)));
+ } else {
+ // Check a few basic fields since they may change over time
+ assert.notStrictEqual(licenseConfig.IsLicensed, undefined);
- if (ping.error) {
- done(new Error(ping.error));
- } else if (!ping.loading) {
- done();
+ done();
+ }
}
});
- Actions.getPing()(store.dispatch, store.getState);
+ Actions.getLicenseConfig()(store.dispatch, store.getState);
});
});
@@ -58,15 +82,15 @@ describe('Actions.General', () => {
const store = configureStore();
store.subscribe(() => {
- const ping = store.getState().entities.general.ping;
+ const ping = store.getState().requests.general.server;
- if (!ping.loading && ping.error) {
+ if (ping.status === RequestStatus.FAILURE && ping.error) {
done();
}
});
- Client.setUrl('https://example.com/fake/url');
+ Client.setUrl('https://google.com/fake/url');
Actions.getPing()(store.dispatch, store.getState);
});
- });
+ }).timeout(5000);
});
diff --git a/test/actions/teams.test.js b/test/actions/teams.test.js
new file mode 100644
index 000000000..e989b9aac
--- /dev/null
+++ b/test/actions/teams.test.js
@@ -0,0 +1,50 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import assert from 'assert';
+
+import * as Actions from 'actions/teams';
+import Client from 'client';
+import configureStore from 'store/configureStore';
+import {RequestStatus} from 'constants';
+import TestHelper from 'test_helper';
+
+describe('Actions.Teams', () => {
+ it('fetchTeams', (done) => {
+ TestHelper.initBasic(Client).then(() => {
+ const store = configureStore();
+
+ store.subscribe(() => {
+ const teamsRequest = store.getState().requests.teams.allTeams;
+ const teams = store.getState().entities.teams.teams;
+
+ if (teamsRequest.status === RequestStatus.SUCCESS || teamsRequest.status === RequestStatus.FAILURE) {
+ if (teamsRequest.error) {
+ done(new Error(JSON.stringify(teamsRequest.error)));
+ } else {
+ assert.ok(teams);
+ assert.ok(teams[TestHelper.basicTeam.id]);
+ done();
+ }
+ }
+ });
+
+ Actions.fetchTeams()(store.dispatch, store.getState);
+ });
+ });
+
+ it('selectTeam', (done) => {
+ TestHelper.initBasic(Client).then(() => {
+ const store = configureStore();
+
+ store.subscribe(() => {
+ const currentTeamId = store.getState().entities.teams.currentId;
+ assert.ok(currentTeamId);
+ assert.equal(currentTeamId, TestHelper.basicTeam.id);
+ done();
+ });
+
+ Actions.selectTeam(TestHelper.basicTeam)(store.dispatch, store.getState);
+ });
+ });
+});
diff --git a/test/actions/users.test.js b/test/actions/users.test.js
new file mode 100644
index 000000000..53009fdbb
--- /dev/null
+++ b/test/actions/users.test.js
@@ -0,0 +1,359 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import assert from 'assert';
+
+import * as Actions from 'actions/users';
+import Client from 'client';
+import configureStore from 'store/configureStore';
+import {RequestStatus} from 'constants';
+import TestHelper from 'test_helper';
+
+describe('Actions.Users', () => {
+ it('login', (done) => {
+ TestHelper.initBasic(Client).then(async () => {
+ const store = configureStore();
+
+ store.subscribe(() => {
+ const loginRequest = store.getState().requests.users.login;
+ const currentUserId = store.getState().entities.users.currentId;
+ const profiles = store.getState().entities.users.profiles;
+ const preferences = store.getState().entities.users.myPreferences;
+
+ // TODO: uncomment when PLT-4167 is merged
+ // const teamMembers = store.getState().entities.teams.myMembers;
+
+ if (loginRequest.status === RequestStatus.SUCCESS || loginRequest.status === RequestStatus.FAILURE) {
+ if (loginRequest.error) {
+ done(new Error(JSON.stringify(loginRequest.error)));
+ } else {
+ assert.ok(currentUserId);
+ assert.ok(profiles);
+ assert.ok(profiles.items[currentUserId]);
+ assert.ok(Object.keys(preferences).length);
+
+ // TODO: uncomment when PLT-4167 is merged
+ // Object.keys(teamMembers).forEach((id) => {
+ // assert.ok(teamMembers[id].team_id);
+ // assert.equal(teamMembers[id].user_id, currentUserId);
+ // });
+
+ done();
+ }
+ }
+ });
+
+ const user = TestHelper.basicUser;
+ await TestHelper.basicClient.logout();
+ Actions.login(user.email, 'password1')(store.dispatch, store.getState);
+ });
+ });
+
+ it('logout', (done) => {
+ TestHelper.initBasic(Client).then(async () => {
+ const store = configureStore();
+
+ store.subscribe(() => {
+ const logoutRequest = store.getState().requests.users.logout;
+ const general = store.getState().entities.general;
+ const users = store.getState().entities.users;
+ const loginView = store.getState().views.login;
+ const teams = store.getState().entities.teams;
+ const channels = store.getState().entities.channels;
+ const posts = store.getState().entities.posts;
+
+ if (logoutRequest.status === RequestStatus.SUCCESS || logoutRequest.status === RequestStatus.FAILURE) {
+ if (logoutRequest.error) {
+ done(new Error(JSON.stringify(logoutRequest.error)));
+ } else {
+ assert.deepStrictEqual(general.config, {}, 'config not empty');
+ assert.deepStrictEqual(general.license, {}, 'license not empty');
+ assert.strictEqual(users.currentId, '', 'current user id not empty');
+ assert.deepStrictEqual(users.myPreferences, {}, 'user preferences not empty');
+ assert.deepStrictEqual(users.mySessions, [], 'user sessions not empty');
+ assert.deepStrictEqual(users.myAudits, [], 'user audits not empty');
+ assert.deepStrictEqual(users.profiles, {items: {}, offset: 0, count: 0}, 'user profiles not empty');
+ assert.deepStrictEqual(users.profilesInTeam, {items: new Set(), offset: 0, count: 0}, 'users profiles in team not empty');
+ assert.deepStrictEqual(users.profilesInChannel, {items: new Set(), offset: 0, count: 0}, 'users profiles in channel not empty');
+ assert.deepStrictEqual(users.profilesNotInChannel, {items: new Set(), offset: 0, count: 0}, 'users profiles NOT in channel not empty');
+ assert.deepStrictEqual(users.statuses, {}, 'users statuses not empty');
+ assert.strictEqual(loginView.loginId, '', 'login id not empty');
+ assert.strictEqual(loginView.password, '', 'password not empty');
+ assert.strictEqual(teams.currentId, '', 'current team id is not empty');
+ assert.deepStrictEqual(teams.teams, {}, 'teams is not empty');
+ assert.deepStrictEqual(teams.myMembers, {}, 'team members is not empty');
+ assert.deepStrictEqual(teams.membersInTeam, {}, 'members in team is not empty');
+ assert.deepStrictEqual(teams.membersNotInTeam, {}, 'members NOT in team is not empty');
+ assert.deepStrictEqual(teams.stats, {}, 'team stats is not empty');
+ assert.deepStrictEqual(teams.openTeamIds, new Set(), 'team open ids is not empty');
+ assert.strictEqual(channels.currentId, '', 'current channel id is not empty');
+ assert.deepStrictEqual(channels.channels, {}, 'channels is not empty');
+ assert.deepStrictEqual(channels.myMembers, {}, 'channel members is not empty');
+ assert.deepStrictEqual(channels.moreChannels, {}, 'more channels is not empty');
+ assert.deepStrictEqual(channels.stats, {}, 'channel stats is not empty');
+ assert.strictEqual(posts.selectedPostId, '', 'selected post id is not empty');
+ assert.strictEqual(posts.currentFocusedPostId, '', 'current focused post id is not empty');
+ assert.deepStrictEqual(posts.postsInfo, {}, 'posts info is not empty');
+ assert.deepStrictEqual(posts.latestPageTime, {}, 'posts latest page time is not empty');
+
+ done();
+ }
+ }
+ });
+
+ Actions.logout()(store.dispatch, store.getState);
+ });
+ });
+
+ it('getProfiles', (done) => {
+ TestHelper.initBasic(Client).then(async () => {
+ const store = configureStore();
+ const user = await TestHelper.basicClient.createUser(TestHelper.fakeUser());
+
+ store.subscribe(() => {
+ const profilesRequest = store.getState().requests.users.getProfiles;
+ const profiles = store.getState().entities.users.profiles;
+
+ if (profilesRequest.status === RequestStatus.SUCCESS || profilesRequest.status === RequestStatus.FAILURE) {
+ if (profilesRequest.error) {
+ done(new Error(JSON.stringify(profilesRequest.error)));
+ } else {
+ assert.strictEqual(profiles.offset, 0, 'offset should be 0');
+ assert.strictEqual(profiles.count, 0, 'count should be 0');
+ assert.ok(profiles.items[user.id]);
+
+ done();
+ }
+ }
+ });
+
+ Actions.getProfiles(0)(store.dispatch, store.getState);
+ });
+ });
+
+ it('getProfilesInTeam', (done) => {
+ TestHelper.initBasic(Client).then(async () => {
+ const store = configureStore();
+
+ store.subscribe(() => {
+ const profilesRequest = store.getState().requests.users.getProfilesInTeam;
+ const profilesInTeam = store.getState().entities.users.profilesInTeam;
+ const profiles = store.getState().entities.users.profiles;
+
+ if (profilesRequest.status === RequestStatus.SUCCESS || profilesRequest.status === RequestStatus.FAILURE) {
+ if (profilesRequest.error) {
+ done(new Error(JSON.stringify(profilesRequest.error)));
+ } else {
+ assert.ok(profilesInTeam.offset > 0, 'offset should be > 0');
+ assert.ok(profilesInTeam.count > 0, 'count should be > 0');
+ assert.equal(profilesInTeam.count, profilesInTeam.items.size, 'count should be equal to the amount of profiles');
+ assert.ok(profilesInTeam.items.has(TestHelper.basicUser.id));
+ assert.equal(Object.keys(profiles.items).length, profilesInTeam.count, 'profiles != profiles in team');
+
+ done();
+ }
+ }
+ });
+
+ Actions.getProfilesInTeam(TestHelper.basicTeam.id, 0)(store.dispatch, store.getState);
+ });
+ });
+
+ it('getProfilesInChannel', (done) => {
+ TestHelper.initBasic(Client).then(async () => {
+ const store = configureStore();
+
+ store.subscribe(() => {
+ const profilesRequest = store.getState().requests.users.getProfilesInChannel;
+ const profilesInChannel = store.getState().entities.users.profilesInChannel;
+ const profiles = store.getState().entities.users.profiles;
+
+ if (profilesRequest.status === RequestStatus.SUCCESS || profilesRequest.status === RequestStatus.FAILURE) {
+ if (profilesRequest.error) {
+ done(new Error(JSON.stringify(profilesRequest.error)));
+ } else {
+ assert.ok(profilesInChannel.offset > 0, 'offset should be > 0');
+ assert.ok(profilesInChannel.count > 0, 'count should be > 0');
+ assert.equal(profilesInChannel.count, profilesInChannel.items.size, 'count should be equal to the amount of profiles');
+ assert.ok(profilesInChannel.items.has(TestHelper.basicUser.id));
+ assert.equal(Object.keys(profiles.items).length, profilesInChannel.count, 'profiles != profiles in channel');
+
+ done();
+ }
+ }
+ });
+
+ Actions.getProfilesInChannel(TestHelper.basicTeam.id, TestHelper.basicChannel.id, 0)(store.dispatch, store.getState);
+ });
+ });
+
+ it('getProfilesNotInChannel', (done) => {
+ TestHelper.initBasic(Client).then(async () => {
+ const store = configureStore();
+
+ store.subscribe(() => {
+ const profilesRequest = store.getState().requests.users.getProfilesNotInChannel;
+ const profilesNotInChannel = store.getState().entities.users.profilesNotInChannel;
+ const profiles = store.getState().entities.users.profiles;
+
+ if (profilesRequest.status === RequestStatus.SUCCESS || profilesRequest.status === RequestStatus.FAILURE) {
+ if (profilesRequest.error) {
+ done(new Error(JSON.stringify(profilesRequest.error)));
+ } else {
+ assert.ok(profilesNotInChannel.offset > 0, 'offset should be > 0');
+ assert.ok(profilesNotInChannel.count > 0, 'count should be > 0');
+ assert.equal(profilesNotInChannel.count, profilesNotInChannel.items.size, 'count should be equal to the amount of profiles');
+ assert.ok(profilesNotInChannel.items.has(TestHelper.basicUser.id));
+ assert.equal(Object.keys(profiles.items).length, profilesNotInChannel.count, 'profiles != profiles in channel');
+
+ done();
+ }
+ }
+ });
+
+ Actions.getProfilesNotInChannel(TestHelper.basicTeam.id, TestHelper.basicChannel.id, 0)(store.dispatch, store.getState);
+ });
+ });
+
+ it('getStatusesByIds', (done) => {
+ TestHelper.initBasic(Client).then(async () => {
+ const store = configureStore();
+ const user = await TestHelper.basicClient.createUser(TestHelper.fakeUser());
+
+ store.subscribe(() => {
+ const statusesRequest = store.getState().requests.users.getStatusesByIds;
+ const statuses = store.getState().entities.users.statuses;
+
+ if (statusesRequest.status === RequestStatus.SUCCESS || statusesRequest.status === RequestStatus.FAILURE) {
+ if (statusesRequest.error) {
+ done(new Error(JSON.stringify(statusesRequest.error)));
+ } else {
+ assert.ok(statuses[TestHelper.basicUser.id]);
+ assert.ok(statuses[user.id]);
+ assert.equal(Object.keys(statuses).length, 2);
+ done();
+ }
+ }
+ });
+
+ Actions.getStatusesByIds([TestHelper.basicUser.id, user.id])(store.dispatch, store.getState);
+ });
+ });
+
+ it('getSessions', (done) => {
+ TestHelper.initBasic(Client).then(async () => {
+ const store = configureStore();
+
+ store.subscribe(() => {
+ const sessionsRequest = store.getState().requests.users.getSessions;
+ const sessions = store.getState().entities.users.mySessions;
+
+ if (sessionsRequest.status === RequestStatus.SUCCESS || sessionsRequest.status === RequestStatus.FAILURE) {
+ if (sessionsRequest.error) {
+ done(new Error(JSON.stringify(sessionsRequest.error)));
+ } else {
+ assert.ok(sessions.length);
+ assert.equal(sessions[0].user_id, TestHelper.basicUser.id);
+ done();
+ }
+ }
+ });
+
+ Actions.getSessions(TestHelper.basicUser.id)(store.dispatch, store.getState);
+ });
+ });
+
+ it('revokeSession', (done) => {
+ TestHelper.initBasic(Client).then(async () => {
+ const store = configureStore();
+
+ store.subscribe(() => {
+ const sessionsRequest = store.getState().requests.users.getSessions;
+ const revokeRequest = store.getState().requests.users.revokeSession;
+ const sessions = store.getState().entities.users.mySessions;
+
+ if (revokeRequest.status === RequestStatus.SUCCESS || revokeRequest.status === RequestStatus.FAILURE) {
+ if (revokeRequest.error) {
+ done(new Error(JSON.stringify(revokeRequest.error)));
+ } else {
+ assert.ok(sessions.length === 0);
+ done();
+ }
+ }
+
+ if (sessionsRequest.status === RequestStatus.SUCCESS || sessionsRequest.status === RequestStatus.FAILURE) {
+ if (sessionsRequest.error) {
+ done(new Error(JSON.stringify(sessionsRequest.error)));
+ } else if (revokeRequest.status === RequestStatus.NOT_STARTED) {
+ Actions.revokeSession(sessions[0].id)(store.dispatch, store.getState);
+ }
+ }
+ });
+
+ Actions.getSessions(TestHelper.basicUser.id)(store.dispatch, store.getState);
+ });
+ });
+
+ it('revokeSession and logout', (done) => {
+ TestHelper.initBasic(Client).then(async () => {
+ const store = configureStore();
+
+ store.subscribe(() => {
+ const sessionsRequest = store.getState().requests.users.getSessions;
+ const revokeRequest = store.getState().requests.users.revokeSession;
+ const logoutRequest = store.getState().requests.users.logout;
+ const profilesRequest = store.getState().requests.users.getProfiles;
+ const sessions = store.getState().entities.users.mySessions;
+
+ if (logoutRequest.status === RequestStatus.SUCCESS || logoutRequest.status === RequestStatus.FAILURE) {
+ if (logoutRequest.error) {
+ done(new Error(JSON.stringify(logoutRequest.error)));
+ } else {
+ done();
+ }
+ }
+
+ if (revokeRequest.status === RequestStatus.SUCCESS || revokeRequest.status === RequestStatus.FAILURE) {
+ if (revokeRequest.error) {
+ done(new Error(JSON.stringify(revokeRequest.error)));
+ } else if (logoutRequest.status === RequestStatus.NOT_STARTED && profilesRequest.status === RequestStatus.NOT_STARTED) {
+ Actions.getProfiles(0)(store.dispatch, store.getState);
+ }
+ }
+
+ if (sessionsRequest.status === RequestStatus.SUCCESS || sessionsRequest.status === RequestStatus.FAILURE) {
+ if (sessionsRequest.error) {
+ done(new Error(JSON.stringify(sessionsRequest.error)));
+ } else if (revokeRequest.status === RequestStatus.NOT_STARTED) {
+ Actions.revokeSession(sessions[0].id)(store.dispatch, store.getState);
+ }
+ }
+ });
+
+ Actions.getSessions(TestHelper.basicUser.id)(store.dispatch, store.getState);
+ });
+ });
+
+ it('getAudits', (done) => {
+ TestHelper.initBasic(Client).then(async () => {
+ const store = configureStore();
+
+ store.subscribe(() => {
+ const auditsRequest = store.getState().requests.users.getAudits;
+ const audits = store.getState().entities.users.myAudits;
+
+ if (auditsRequest.status === RequestStatus.SUCCESS || auditsRequest.status === RequestStatus.FAILURE) {
+ if (auditsRequest.error) {
+ done(new Error(JSON.stringify(auditsRequest.error)));
+ } else {
+ assert.ok(audits.length);
+ assert.equal(audits[0].user_id, TestHelper.basicUser.id);
+ done();
+ }
+ }
+ });
+
+ Actions.getAudits(TestHelper.basicUser.id)(store.dispatch, store.getState);
+ });
+ });
+});
diff --git a/test/actions/views/login.test.js b/test/actions/views/login.test.js
new file mode 100644
index 000000000..0efaf0dd4
--- /dev/null
+++ b/test/actions/views/login.test.js
@@ -0,0 +1,33 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import assert from 'assert';
+
+import * as Actions from 'actions/views/login';
+import configureStore from 'store/configureStore';
+
+describe('Actions.Views.Login', () => {
+ it('handleLoginIdChanged', (done) => {
+ const store = configureStore();
+
+ store.subscribe(() => {
+ const loginId = store.getState().views.login.loginId;
+ assert.equal('email@example.com', loginId);
+ done();
+ });
+
+ Actions.handleLoginIdChanged('email@example.com')(store.dispatch, store.getState);
+ });
+
+ it('handlePasswordChanged', (done) => {
+ const store = configureStore();
+
+ store.subscribe(() => {
+ const password = store.getState().views.login.password;
+ assert.equal('password', password);
+ done();
+ });
+
+ Actions.handlePasswordChanged('password')(store.dispatch, store.getState);
+ });
+});
diff --git a/test/actions/views/select_server.test.js b/test/actions/views/select_server.test.js
new file mode 100644
index 000000000..50fa22800
--- /dev/null
+++ b/test/actions/views/select_server.test.js
@@ -0,0 +1,21 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import assert from 'assert';
+
+import * as Actions from 'actions/views/select_server';
+import configureStore from 'store/configureStore';
+
+describe('Actions.Views.SelectServer', () => {
+ it('handleServerUrlChanged', (done) => {
+ const store = configureStore();
+
+ store.subscribe(() => {
+ const serverUrl = store.getState().views.selectServer.serverUrl;
+ assert.equal('https://mattermost.example.com', serverUrl);
+ done();
+ });
+
+ Actions.handleServerUrlChanged('https://mattermost.example.com')(store.dispatch, store.getState);
+ });
+});
diff --git a/test/client/client.test.js b/test/client/client.test.js
deleted file mode 100644
index d5647b6c0..000000000
--- a/test/client/client.test.js
+++ /dev/null
@@ -1,12 +0,0 @@
-// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import TestHelper from 'test_helper.js';
-
-describe('Client', () => {
- it('doFetch', async () => {
- const client = TestHelper.createClient();
-
- return client.doFetch(`${client.getGeneralRoute()}/ping`, {});
- });
-});
diff --git a/test/client/client_channel.test.js b/test/client/client_channel.test.js
deleted file mode 100644
index 9429cbe67..000000000
--- a/test/client/client_channel.test.js
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import assert from 'assert';
-
-import TestHelper from 'test_helper.js';
-
-describe('Client.Channel', () => {
- it('createChannel', async () => {
- const {client, team} = await TestHelper.initBasic();
- const channel = TestHelper.fakeChannel(team.id);
-
- const rchannel = await client.createChannel(channel);
-
- assert.ok(rchannel.id, 'id is empty');
- assert.equal(rchannel.name, channel.name, 'name doesn\'t match');
- assert.equal(rchannel.team_id, channel.team_id, 'team id doesn\'t match');
- });
-});
diff --git a/test/client/client_general.test.js b/test/client/client_general.test.js
deleted file mode 100644
index 6a6491aa7..000000000
--- a/test/client/client_general.test.js
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import assert from 'assert';
-
-import TestHelper from 'test_helper.js';
-
-describe('Client.General', function() {
- it('getClientConfig', async () => {
- const {client} = await TestHelper.initBasic();
-
- const clientConfig = await client.getClientConfig('foo');
-
- assert.ok(clientConfig.Version);
- assert.ok(clientConfig.BuildNumber);
- assert.ok(clientConfig.BuildDate);
- assert.ok(clientConfig.BuildHash);
- });
-
- it('getPing', async () => {
- const {client} = await TestHelper.initBasic();
-
- await client.getPing();
- });
-
- it('getPing - Invalid URL', async () => {
- const {client} = await TestHelper.initBasic();
- client.setUrl('https://example.com/fake/url');
-
- let errored;
- try {
- await client.getPing();
-
- errored = false;
- } catch (err) {
- errored = true;
- }
-
- assert.ok(errored, 'should have errored');
- });
-
- it('logClientError', async () => {
- const {client} = await TestHelper.initBasic();
-
- await client.logClientError('this is a test');
- });
-});
diff --git a/test/client/client_post.test.js b/test/client/client_post.test.js
deleted file mode 100644
index d96b615e4..000000000
--- a/test/client/client_post.test.js
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import assert from 'assert';
-
-import TestHelper from 'test_helper.js';
-
-describe('Client.Post', () => {
- it('createPost', async () => {
- const {
- client,
- channel,
- team
- } = await TestHelper.initBasic();
- const post = TestHelper.fakePost(channel.id);
-
- const rpost = await client.createPost(team.id, post);
-
- assert.ok(rpost.id, 'id is empty');
- });
-});
diff --git a/test/client/client_team.test.js b/test/client/client_team.test.js
deleted file mode 100644
index aeab356f5..000000000
--- a/test/client/client_team.test.js
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import assert from 'assert';
-
-import TestHelper from 'test_helper.js';
-
-describe('Client.Team', () => {
- it('createTeam', async () => {
- const client = TestHelper.createClient();
- const team = TestHelper.fakeTeam();
-
- const rteam = await client.createTeam(team);
-
- assert.equal(rteam.id.length > 0, true);
- assert.equal(rteam.name, team.name);
- });
-});
diff --git a/test/client/client_user.test.js b/test/client/client_user.test.js
deleted file mode 100644
index b933ea67f..000000000
--- a/test/client/client_user.test.js
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import assert from 'assert';
-
-import TestHelper from 'test_helper.js';
-
-describe('Client.User', () => {
- it('createUser', async () => {
- const client = TestHelper.createClient();
- const user = TestHelper.fakeUser();
-
- const ruser = await client.createUser(user);
-
- assert.ok(ruser.id, 'id is empty');
- assert.equal(ruser.email, user.email, 'email addresses aren\'t equal');
- });
-
- it('login', async () => {
- const client = TestHelper.createClient();
- const user = TestHelper.fakeUser();
-
- user.password = 'password';
- await client.createUser(user);
-
- const ruser = await client.login(user.email, user.password);
-
- assert.ok(ruser.id, 'id is empty');
- assert.equal(ruser.email, user.email, 'email addresses aren\'t equal');
- assert.ok(!ruser.password, 'returned password should be empty');
- assert.ok(client.token, 'token is empty');
- });
-
- it('getInitialLoad', async () => {
- const {client, user} = await TestHelper.initBasic();
-
- const data = await client.getInitialLoad();
-
- assert.ok(data.user.id.length, 'id is empty');
- assert.equal(data.user.id, user.id, 'user ids don\'t match');
- });
-});
diff --git a/test/reducers/channel.test.js b/test/reducers/channel.test.js
deleted file mode 100644
index b0aa12e2d..000000000
--- a/test/reducers/channel.test.js
+++ /dev/null
@@ -1,268 +0,0 @@
-// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import assert from 'assert';
-import deepFreeze from 'deep-freeze';
-import TestHelper from 'test_helper.js';
-
-import reduceChannel from 'reducers/channel';
-
-import {ChannelTypes} from 'constants';
-
-describe('reducers/channel.js', () => {
- it('initial state', () => {
- const store = reduceChannel(undefined, {type: ''});
-
- const expected = {
- channels: {},
- channelIdsByTeamId: {},
- channelMembers: {}
- };
-
- assert.deepEqual(store, expected, 'initial state of store');
- });
-
- describe('CHANNEL_RECEIVED', () => {
- let store = deepFreeze(reduceChannel(undefined, {type: ''}));
-
- const teamId = TestHelper.generateId();
- const channel1 = {
- id: TestHelper.generateId(),
- ...TestHelper.fakeChannel(teamId)
- };
- const channel2 = {
- id: TestHelper.generateId(),
- ...TestHelper.fakeChannel(teamId)
- };
-
- it('first channel received', () => {
- store = reduceChannel(store, {
- type: ChannelTypes.CHANNEL_RECEIVED,
- channel: channel1
- });
-
- assert.deepEqual(store.channels, {
- [channel1.id]: channel1
- });
- assert.deepEqual(store.channelIdsByTeamId, {
- [teamId]: {
- [channel1.id]: channel1.id
- }
- });
- assert.deepEqual(store.channelMembers, {});
- });
-
- store = deepFreeze(store);
-
- it('second channel received', () => {
- store = reduceChannel(store, {
- type: ChannelTypes.CHANNEL_RECEIVED,
- channel: channel2
- });
-
- assert.deepEqual(store.channels, {
- [channel1.id]: channel1,
- [channel2.id]: channel2
- });
- assert.deepEqual(store.channelIdsByTeamId, {
- [teamId]: {
- [channel1.id]: channel1.id,
- [channel2.id]: channel2.id
- }
- });
- assert.deepEqual(store.channelMembers, {});
- });
-
- store = deepFreeze(store);
-
- const channel1a = {
- ...channel1,
- name: channel1.name + 'test'
- };
-
- it('first channel received again', () => {
- store = reduceChannel(store, {
- type: ChannelTypes.CHANNEL_RECEIVED,
- channel: channel1a
- });
-
- assert.deepEqual(store.channels, {
- [channel1.id]: channel1a,
- [channel2.id]: channel2
- });
- assert.deepEqual(store.channelIdsByTeamId, {
- [teamId]: {
- [channel1.id]: channel1.id,
- [channel2.id]: channel2.id
- }
- });
- assert.deepEqual(store.channelMembers, {});
- });
- });
-
- describe('CHANNELS_RECEIVED', () => {
- let store = deepFreeze(reduceChannel(undefined, {type: ''}));
-
- const teamId = TestHelper.generateId();
- const channel1 = {
- id: TestHelper.generateId(),
- ...TestHelper.fakeChannel(teamId)
- };
- const channel2 = {
- id: TestHelper.generateId(),
- ...TestHelper.fakeChannel(teamId)
- };
-
- it('first set of channels received', () => {
- store = reduceChannel(store, {
- type: ChannelTypes.CHANNELS_RECEIVED,
- channels: [channel1, channel2]
- });
-
- assert.deepEqual(store.channels, {
- [channel1.id]: channel1,
- [channel2.id]: channel2
- });
- assert.deepEqual(store.channelIdsByTeamId, {
- [teamId]: {
- [channel1.id]: channel1.id,
- [channel2.id]: channel2.id
- }
- });
- assert.deepEqual(store.channelMembers, {});
- });
-
- store = deepFreeze(store);
-
- const channel2a = {
- ...channel2,
- name: channel2.name + 'test'
- };
- const channel3 = {
- id: TestHelper.generateId(),
- ...TestHelper.fakeChannel(teamId)
- };
-
- it('second set of channels received', () => {
- store = reduceChannel(store, {
- type: ChannelTypes.CHANNELS_RECEIVED,
- channels: [channel2a, channel3]
- });
-
- assert.deepEqual(store.channels, {
- [channel1.id]: channel1,
- [channel2.id]: channel2a,
- [channel3.id]: channel3
- });
- assert.deepEqual(store.channelIdsByTeamId, {
- [teamId]: {
- [channel1.id]: channel1.id,
- [channel2.id]: channel2.id,
- [channel3.id]: channel3.id
- }
- });
- assert.deepEqual(store.channelMembers, {});
- });
- });
-
- describe('CHANNEL_MEMBER_RECEIVED', () => {
- let store = deepFreeze(reduceChannel(undefined, {type: ''}));
-
- const member1 = TestHelper.fakeChannelMember(TestHelper.generateId(), TestHelper.generateId());
- const member2 = TestHelper.fakeChannelMember(TestHelper.generateId(), TestHelper.generateId());
-
- it('first channel member received', () => {
- store = reduceChannel(store, {
- type: ChannelTypes.CHANNEL_MEMBER_RECEIVED,
- channelMember: member1
- });
-
- assert.deepEqual(store.channels, {});
- assert.deepEqual(store.channelIdsByTeamId, {});
- assert.deepEqual(store.channelMembers, {
- [`${member1.channel_id}-${member1.user_id}`]: member1
- });
- });
-
- store = deepFreeze(store);
-
- it('second channel member received', () => {
- store = reduceChannel(store, {
- type: ChannelTypes.CHANNEL_MEMBER_RECEIVED,
- channelMember: member2
- });
-
- assert.deepEqual(store.channels, {});
- assert.deepEqual(store.channelIdsByTeamId, {});
- assert.deepEqual(store.channelMembers, {
- [`${member1.channel_id}-${member1.user_id}`]: member1,
- [`${member2.channel_id}-${member2.user_id}`]: member2
- });
- });
-
- store = deepFreeze(store);
-
- const member1a = {
- ...member1,
- roles: 'system_admin system_user'
- };
-
- it('first channel member received again', () => {
- store = reduceChannel(store, {
- type: ChannelTypes.CHANNEL_MEMBER_RECEIVED,
- channelMember: member1a
- });
-
- assert.deepEqual(store.channels, {});
- assert.deepEqual(store.channelIdsByTeamId, {});
- assert.deepEqual(store.channelMembers, {
- [`${member1.channel_id}-${member1.user_id}`]: member1a,
- [`${member2.channel_id}-${member2.user_id}`]: member2
- });
- });
- });
-
- describe('CHANNEL_MEMBERS_RECEIVED', () => {
- let store = deepFreeze(reduceChannel(undefined, {type: ''}));
-
- const member1 = TestHelper.fakeChannelMember(TestHelper.generateId(), TestHelper.generateId());
- const member2 = TestHelper.fakeChannelMember(TestHelper.generateId(), TestHelper.generateId());
-
- it('first set of channel members received', () => {
- store = reduceChannel(store, {
- type: ChannelTypes.CHANNEL_MEMBERS_RECEIVED,
- channelMembers: [member1, member2]
- });
-
- assert.deepEqual(store.channels, {});
- assert.deepEqual(store.channelIdsByTeamId, {});
- assert.deepEqual(store.channelMembers, {
- [`${member1.channel_id}-${member1.user_id}`]: member1,
- [`${member2.channel_id}-${member2.user_id}`]: member2
- });
- });
-
- store = deepFreeze(store);
-
- const member2a = {
- ...member2,
- roles: 'system_admin system_user'
- };
- const member3 = TestHelper.fakeChannelMember(TestHelper.generateId(), TestHelper.generateId());
-
- it('second set of channel members received', () => {
- store = reduceChannel(store, {
- type: ChannelTypes.CHANNEL_MEMBERS_RECEIVED,
- channelMembers: [member2a, member3]
- });
-
- assert.deepEqual(store.channels, {});
- assert.deepEqual(store.channelIdsByTeamId, {});
- assert.deepEqual(store.channelMembers, {
- [`${member1.channel_id}-${member1.user_id}`]: member1,
- [`${member2.channel_id}-${member2.user_id}`]: member2a,
- [`${member3.channel_id}-${member3.user_id}`]: member3
- });
- });
- });
-});
diff --git a/test/reducers/device.test.js b/test/reducers/device.test.js
deleted file mode 100644
index 45b41e594..000000000
--- a/test/reducers/device.test.js
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import assert from 'assert';
-import reduceDevice, {initState} from 'reducers/device';
-import {DeviceTypes} from 'constants';
-
-describe('device reducer', () => {
- describe('Init', () => {
- let store;
- let expectedStore;
- before(() => {
- store = reduceDevice(store, {type: ''});
- expectedStore = {...initState};
- });
- it('should be initial state', () => {
- assert.equal(typeof store, 'object');
- });
- it('have a specifc initial state', () => {
- assert.deepEqual(store, expectedStore);
- });
- });
- describe(`when ${DeviceTypes.DEVICE_REQUEST}`, () => {
- let store;
- let expectedStore;
- before(() => {
- store = reduceDevice(store, {
- type: DeviceTypes.DEVICE_REQUEST
- });
- expectedStore = {
- ...initState,
- loading: true
- };
- });
- it('should set status to fetching', () => {
- assert.deepEqual(store, expectedStore);
- });
- });
- describe(`when ${DeviceTypes.DEVICE_SUCCESS}`, () => {
- let store;
- let expectedStore;
- const data = {some: 'data'};
- before(() => {
- store = reduceDevice(store, {
- type: DeviceTypes.DEVICE_SUCCESS,
- data
- });
- expectedStore = {
- ...initState,
- loading: false,
- data
- };
- });
- it('should set status to fetched and data', () => {
- assert.deepEqual(store, expectedStore);
- });
- });
- describe(`when ${DeviceTypes.DEVICE_FAILURE}`, () => {
- let store;
- let error;
- let expectedStore;
- before(() => {
- error = {id: 'the.error.id', message: 'Something went wrong'};
- store = reduceDevice(store, {
- type: DeviceTypes.DEVICE_FAILURE,
- error
- });
- expectedStore = {
- ...initState,
- loading: false,
- error
- };
- });
- it('should set status to failed and error', () => {
- assert.deepEqual(store, expectedStore);
- });
- });
-});
diff --git a/test/reducers/general.test.js b/test/reducers/general.test.js
deleted file mode 100644
index 23a602fec..000000000
--- a/test/reducers/general.test.js
+++ /dev/null
@@ -1,170 +0,0 @@
-// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-// import assert from 'assert';
-// import reduceGeneral, {initState} from 'reducers/general';
-// import {GeneralTypes} from 'constants';
-
-// const combinedState = {
-// clientConfig: {...initState},
-// ping: {...initState}
-// };
-
-// describe('general reducer', () => {
-// describe('PING', () => {
-// describe('Init', () => {
-// let store;
-// let expectedStore;
-// before(() => {
-// store = reduceGeneral(store, {type: ''});
-// expectedStore = {...combinedState};
-// });
-// it('should be initial state', () => {
-// assert.equal(typeof store, 'object');
-// });
-// it('have a specifc initial state', () => {
-// assert.deepEqual(store, expectedStore);
-// });
-// });
-// describe(`when ${GeneralTypes.PING_REQUEST}`, () => {
-// let store;
-// let expectedStore;
-// before(() => {
-// store = reduceGeneral(store, {
-// type: GeneralTypes.PING_REQUEST
-// });
-// expectedStore = {
-// ...combinedState,
-// ping: {
-// ...combinedState.ping,
-// loading: true
-// }
-// };
-// });
-// it('should set status to fetching', () => {
-// assert.deepEqual(store, expectedStore);
-// });
-// });
-// describe(`when ${GeneralTypes.PING_SUCCESS}`, () => {
-// let store;
-// let expectedStore;
-// const data = {some: 'data'};
-// before(() => {
-// store = reduceGeneral(store, {
-// type: GeneralTypes.PING_SUCCESS,
-// data
-// });
-// expectedStore = {
-// ...combinedState,
-// ping: {
-// ...combinedState.ping,
-// data
-// }
-// };
-// });
-// it('should set status to fetched and data', () => {
-// assert.deepEqual(store, expectedStore);
-// });
-// });
-// describe(`when ${GeneralTypes.PING_FAILURE}`, () => {
-// let store;
-// let error;
-// let expectedStore;
-// before(() => {
-// error = {id: 'the.error.id', message: 'Something went wrong'};
-// store = reduceGeneral(store, {
-// type: GeneralTypes.PING_FAILURE,
-// error
-// });
-// expectedStore = {
-// ...combinedState,
-// ping: {
-// ...combinedState.ping,
-// error
-// }
-// };
-// });
-// it('should set status to failed and error', () => {
-// assert.deepEqual(store, expectedStore);
-// });
-// });
-// });
-// describe('CLIENT_CONFIG', () => {
-// describe('Init', () => {
-// let store;
-// let expectedStore;
-// before(() => {
-// store = reduceGeneral(store, {type: ''});
-// expectedStore = {...combinedState};
-// });
-// it('should be initial state', () => {
-// assert.equal(typeof store, 'object');
-// });
-// it('have a specifc initial state', () => {
-// assert.deepEqual(store, expectedStore);
-// });
-// });
-// describe(`when ${GeneralTypes.CLIENT_CONFIG_REQUEST}`, () => {
-// let store;
-// let expectedStore;
-// before(() => {
-// store = reduceGeneral(store, {
-// type: GeneralTypes.CLIENT_CONFIG_REQUEST
-// });
-// expectedStore = {
-// ...combinedState,
-// clientConfig: {
-// ...combinedState.clientConfig,
-// loading: true
-// }
-// };
-// });
-// it('should set status to fetching', () => {
-// assert.deepEqual(store, expectedStore);
-// });
-// });
-// describe(`when ${GeneralTypes.CLIENT_CONFIG_SUCCESS}`, () => {
-// let store;
-// let expectedStore;
-// const data = {some: 'data'};
-// before(() => {
-// store = reduceGeneral(store, {
-// type: GeneralTypes.CLIENT_CONFIG_SUCCESS,
-// data
-// });
-// expectedStore = {
-// ...combinedState,
-// clientConfig: {
-// ...combinedState.clientConfig,
-// data
-// }
-// };
-// });
-// it('should set status to fetched and data', () => {
-// assert.deepEqual(store, expectedStore);
-// });
-// });
-// describe(`when ${GeneralTypes.CLIENT_CONFIG_FAILURE}`, () => {
-// let store;
-// let error;
-// let expectedStore;
-// before(() => {
-// error = {id: 'the.error.id', message: 'Something went wrong'};
-// store = reduceGeneral(store, {
-// type: GeneralTypes.CLIENT_CONFIG_FAILURE,
-// error
-// });
-// expectedStore = {
-// ...combinedState,
-// clientConfig: {
-// ...combinedState.clientConfig,
-// error
-// }
-// };
-// });
-// it('should set status to failed and error', () => {
-// assert.deepEqual(store, expectedStore);
-// });
-// });
-// });
-// });
diff --git a/test/reducers/login.test.js b/test/reducers/login.test.js
deleted file mode 100644
index 1094bb443..000000000
--- a/test/reducers/login.test.js
+++ /dev/null
@@ -1,75 +0,0 @@
-// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import assert from 'assert';
-import reduceLogin, {initState} from 'reducers/login';
-import {LoginTypes} from 'constants';
-
-describe('login reducer', () => {
- describe('Init', () => {
- let store;
- let expectedStore;
- before(() => {
- store = reduceLogin(store, {type: ''});
- expectedStore = {...initState};
- });
- it('should be initial state', () => {
- assert.equal(typeof store, 'object');
- });
- it('have a specifc initial state', () => {
- assert.deepEqual(store, expectedStore);
- });
- });
- describe(`when ${LoginTypes.LOGIN_REQUEST}`, () => {
- let store;
- let expectedStore;
- before(() => {
- store = reduceLogin(store, {
- type: LoginTypes.LOGIN_REQUEST
- });
- expectedStore = {
- ...initState,
- status: 'fetching'
- };
- });
- it('should set status to fetching', () => {
- assert.deepEqual(store, expectedStore);
- });
- });
- describe(`when ${LoginTypes.LOGIN_SUCCESS}`, () => {
- let store;
- let expectedStore;
- before(() => {
- store = reduceLogin(store, {
- type: LoginTypes.LOGIN_SUCCESS
- });
- expectedStore = {
- ...initState,
- status: 'fetched'
- };
- });
- it('should set status to fetched and data', () => {
- assert.deepEqual(store, expectedStore);
- });
- });
- describe(`when ${LoginTypes.LOGIN_FAILURE}`, () => {
- let store;
- let error;
- let expectedStore;
- before(() => {
- error = {id: 'the.error.id', message: 'Something went wrong'};
- store = reduceLogin(store, {
- type: LoginTypes.LOGIN_FAILURE,
- error
- });
- expectedStore = {
- ...initState,
- status: 'failed',
- error
- };
- });
- it('should set status to failed and error', () => {
- assert.deepEqual(store, expectedStore);
- });
- });
-});
diff --git a/test/reducers/logout.test.js b/test/reducers/logout.test.js
deleted file mode 100644
index 6bedeb180..000000000
--- a/test/reducers/logout.test.js
+++ /dev/null
@@ -1,75 +0,0 @@
-// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import assert from 'assert';
-import reduceLogout, {initState} from 'reducers/logout';
-import {LogoutTypes} from 'constants';
-
-describe('logout reducer', () => {
- describe('Init', () => {
- let store;
- let expectedStore;
- before(() => {
- store = reduceLogout(store, {type: ''});
- expectedStore = {...initState};
- });
- it('should be initial state', () => {
- assert.equal(typeof store, 'object');
- });
- it('have a specifc initial state', () => {
- assert.deepEqual(store, expectedStore);
- });
- });
- describe(`when ${LogoutTypes.LOGOUT_REQUEST}`, () => {
- let store;
- let expectedStore;
- before(() => {
- store = reduceLogout(store, {
- type: LogoutTypes.LOGOUT_REQUEST
- });
- expectedStore = {
- ...initState,
- status: 'fetching'
- };
- });
- it('should set status to fetching', () => {
- assert.deepEqual(store, expectedStore);
- });
- });
- describe(`when ${LogoutTypes.LOGOUT_SUCCESS}`, () => {
- let store;
- let expectedStore;
- before(() => {
- store = reduceLogout(store, {
- type: LogoutTypes.LOGOUT_SUCCESS
- });
- expectedStore = {
- ...initState,
- status: 'fetched'
- };
- });
- it('should set status to fetched and data', () => {
- assert.deepEqual(store, expectedStore);
- });
- });
- describe(`when ${LogoutTypes.LOGOUT_FAILURE}`, () => {
- let store;
- let error;
- let expectedStore;
- before(() => {
- error = {id: 'the.error.id', message: 'Something went wrong'};
- store = reduceLogout(store, {
- type: LogoutTypes.LOGOUT_FAILURE,
- error
- });
- expectedStore = {
- ...initState,
- status: 'failed',
- error
- };
- });
- it('should set status to failed and error', () => {
- assert.deepEqual(store, expectedStore);
- });
- });
-});
diff --git a/test/reducers/posts.test.js b/test/reducers/posts.test.js
deleted file mode 100644
index 2d8d32e60..000000000
--- a/test/reducers/posts.test.js
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import assert from 'assert';
-import reducePosts, {initState} from 'reducers/posts';
-import {PostsTypes} from 'constants';
-
-describe('posts reducer', () => {
- describe('Init', () => {
- let store;
- let expectedStore;
- before(() => {
- store = reducePosts(store, {type: ''});
- expectedStore = {...initState};
- });
- it('should be initial state', () => {
- assert.equal(typeof store, 'object');
- });
- it('have a specifc initial state', () => {
- assert.deepEqual(store, expectedStore);
- });
- });
- describe(`when ${PostsTypes.FETCH_POSTS_REQUEST}`, () => {
- let store;
- let expectedStore;
- before(() => {
- store = reducePosts(store, {
- type: PostsTypes.FETCH_POSTS_REQUEST
- });
- expectedStore = {
- ...initState,
- status: 'fetching'
- };
- });
- it('should set status to fetching', () => {
- assert.deepEqual(store, expectedStore);
- });
- });
- describe(`when ${PostsTypes.FETCH_POSTS_SUCCESS}`, () => {
- let store;
- let expectedStore;
- const data = {id: '1', attrs: 'attrs'};
- before(() => {
- store = reducePosts(store, {
- type: PostsTypes.FETCH_POSTS_SUCCESS,
- data: {
- posts: data
- }
- });
- expectedStore = {
- ...initState,
- status: 'fetched',
- data
- };
- });
- it('should set status to fetched and data', () => {
- assert.deepEqual(store, expectedStore);
- });
- });
- describe(`when ${PostsTypes.FETCH_POSTS_FAILURE}`, () => {
- let store;
- let error;
- let expectedStore;
- before(() => {
- error = {id: 'the.error.id', message: 'Something went wrong'};
- store = reducePosts(store, {
- type: PostsTypes.FETCH_POSTS_FAILURE,
- error
- });
- expectedStore = {
- ...initState,
- status: 'failed',
- error
- };
- });
- it('should set status to failed and error', () => {
- assert.deepEqual(store, expectedStore);
- });
- });
-});
diff --git a/test/reducers/teams.test.js b/test/reducers/teams.test.js
deleted file mode 100644
index b874deff4..000000000
--- a/test/reducers/teams.test.js
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import assert from 'assert';
-import reduceTeams, {initState} from 'reducers/teams';
-import {TeamsTypes} from 'constants';
-
-describe('teams reducer', () => {
- describe('Init', () => {
- let store;
- let expectedStore;
- before(() => {
- store = reduceTeams(store, {type: ''});
- expectedStore = {...initState};
- });
- it('should be initial state', () => {
- assert.equal(typeof store, 'object');
- });
- it('have a specifc initial state', () => {
- assert.deepEqual(store, expectedStore);
- });
- });
- describe(`when ${TeamsTypes.SELECT_TEAM}`, () => {
- let store;
- let expectedStore;
- before(() => {
- store = reduceTeams(store, {
- type: TeamsTypes.SELECT_TEAM,
- teamId: '1'
- });
- expectedStore = {
- ...initState,
- currentTeamId: '1'
- };
- });
- it('should set status to fetching', () => {
- assert.deepEqual(store, expectedStore);
- });
- });
- describe(`when ${TeamsTypes.FETCH_TEAMS_REQUEST}`, () => {
- let store;
- let expectedStore;
- before(() => {
- store = reduceTeams(store, {
- type: TeamsTypes.FETCH_TEAMS_REQUEST
- });
- expectedStore = {
- ...initState,
- status: 'fetching'
- };
- });
- it('should set status to fetching', () => {
- assert.deepEqual(store, expectedStore);
- });
- });
- describe(`when ${TeamsTypes.FETCH_TEAMS_SUCCESS}`, () => {
- let store;
- const data = {some: 'thing'};
- let expectedStore;
- before(() => {
- store = reduceTeams(store, {
- type: TeamsTypes.FETCH_TEAMS_SUCCESS,
- data
- });
- expectedStore = {
- ...initState,
- status: 'fetched',
- data
- };
- });
- it('should set status to fetched and data', () => {
- assert.deepEqual(store, expectedStore);
- });
- });
- describe(`when ${TeamsTypes.FETCH_TEAMS_FAILURE}`, () => {
- let store;
- let expectedStore;
- let error;
- before(() => {
- error = {id: 'the.error.id', message: 'Something went wrong'};
- store = reduceTeams(store, {
- type: TeamsTypes.FETCH_TEAMS_FAILURE,
- error
- });
- expectedStore = {
- ...initState,
- status: 'failed',
- error
- };
- });
- it('should set status to failed and error', () => {
- assert.deepEqual(store, expectedStore);
- });
- });
-});
diff --git a/test/test_helper.js b/test/test_helper.js
index 9e3fc4ce7..911530334 100644
--- a/test/test_helper.js
+++ b/test/test_helper.js
@@ -2,8 +2,8 @@
// See License.txt for license information.
import assert from 'assert';
-import Client from 'client/client.js';
-import Config from 'config/config.js';
+import Client from 'client/client';
+import Config from 'config';
const PASSWORD = 'password1';
@@ -20,7 +20,7 @@ class TestHelper {
assertStatusOkay = (data) => {
assert(data);
assert(data.status === 'OK');
- }
+ };
generateId = () => {
// Implementation taken from http://stackoverflow.com/a/2117523
@@ -40,7 +40,7 @@ class TestHelper {
});
return 'uid' + id;
- }
+ };
createClient = () => {
const client = new Client();
@@ -48,11 +48,11 @@ class TestHelper {
client.setUrl(Config.DefaultServerUrl);
return client;
- }
+ };
fakeEmail = () => {
return 'success' + this.generateId() + '@simulator.amazonses.com';
- }
+ };
fakeUser = () => {
return {
@@ -61,19 +61,24 @@ class TestHelper {
password: PASSWORD,
username: this.generateId()
};
- }
+ };
fakeTeam = () => {
const name = this.generateId();
+ let inviteId = this.generateId();
+ if (inviteId.length > 32) {
+ inviteId = inviteId.substring(0, 32);
+ }
return {
name,
display_name: `Unit Test ${name}`,
type: 'O',
email: this.fakeEmail(),
- allowed_domains: ''
+ allowed_domains: '',
+ invite_id: inviteId
};
- }
+ };
fakeChannel = (teamId) => {
const name = this.generateId();
@@ -84,7 +89,7 @@ class TestHelper {
display_name: `Unit Test ${name}`,
type: 'O'
};
- }
+ };
fakeChannelMember = (userId, channelId) => {
return {
@@ -93,14 +98,14 @@ class TestHelper {
notify_props: {},
roles: 'system_user'
};
- }
+ };
fakePost = (channelId) => {
return {
channel_id: channelId,
message: `Unit Test ${this.generateId()}`
};
- }
+ };
initBasic = async (client = this.createClient()) => {
client.setUrl(Config.DefaultServerUrl);
@@ -121,7 +126,7 @@ class TestHelper {
channel: this.basicChannel,
post: this.basicPost
};
- }
+ };
}
export default new TestHelper();