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)} /> - - + ); } diff --git a/src/routes/login/login_container.js b/src/routes/login/login_container.js index 887b6e9e7..e06fe5381 100644 --- a/src/routes/login/login_container.js +++ b/src/routes/login/login_container.js @@ -3,22 +3,31 @@ import {bindActionCreators} from 'redux'; import {connect} from 'react-redux'; -import * as loginActions from 'actions/login'; -import {getClientConfig} from 'actions/general'; + +import {getClientConfig, getLicenseConfig} from 'actions/general'; +import * as LoginActions from 'actions/views/login'; +import {login} from 'actions/users'; + import Login from './login.js'; function mapStateToProps(state) { return { - clientConfig: state.entities.general.clientConfig, - login: state.views.login + ...state.views.login, + loginRequest: state.requests.users.login, + configRequest: state.requests.general.config, + licenseRequest: state.requests.general.license, + config: state.entities.general.config, + license: state.entities.general.license }; } function mapDispatchToProps(dispatch) { return { actions: bindActionCreators({ - ...loginActions, - getClientConfig + ...LoginActions, + login, + getClientConfig, + getLicenseConfig }, dispatch) }; } diff --git a/src/routes/select_server/select_server.js b/src/routes/select_server/select_server.js index d9fbcbb78..aefb102a3 100644 --- a/src/routes/select_server/select_server.js +++ b/src/routes/select_server/select_server.js @@ -5,10 +5,8 @@ import React, {Component} from 'react'; import {View, TextInput, Image} from 'react-native'; import KeyboardSpacer from 'react-native-keyboard-spacer'; import {Actions as Routes} from 'react-native-router-flux'; -import _ from 'lodash'; -import Client from 'client/client_instance.js'; -import Config from 'config/config.js'; +import Client from 'client'; import Button from 'components/button'; import ErrorText from 'components/error_text'; import FormattedText from 'components/formatted_text'; @@ -17,31 +15,25 @@ import logo from 'images/logo.png'; import {injectIntl, intlShape} from 'react-intl'; +import RequestStatus from 'constants/request_status'; + class SelectServer extends Component { static propTypes = { intl: intlShape.isRequired, - ping: React.PropTypes.object.isRequired, - device: React.PropTypes.object.isRequired, + serverUrl: React.PropTypes.string.isRequired, + server: React.PropTypes.object.isRequired, actions: React.PropTypes.object.isRequired - } - - constructor(props) { - super(props); - - this.state = { - serverUrl: Config.DefaultServerUrl - }; - } + }; onClick = () => { - Client.setUrl(this.state.serverUrl); + Client.setUrl(this.props.serverUrl); this.props.actions.getPing().then(() => { - if (!this.props.ping.loading && !this.props.ping.error) { + if (this.props.server.status === RequestStatus.SUCCESS) { Routes.goToLogin(); } }); - } + }; render() { const {formatMessage} = this.props.intl; @@ -53,13 +45,13 @@ class SelectServer extends Component { source={logo} /> this.setState({serverUrl})} + value={this.props.serverUrl} + onChangeText={this.props.actions.handleServerUrlChanged} onSubmitEditing={this.onClick} style={GlobalStyles.inputBox} autoCapitalize='none' @@ -71,14 +63,14 @@ class SelectServer extends Component { /> - + ); diff --git a/src/routes/select_server/select_server_container.js b/src/routes/select_server/select_server_container.js index 0250e8ff2..2e759f5c1 100644 --- a/src/routes/select_server/select_server_container.js +++ b/src/routes/select_server/select_server_container.js @@ -3,20 +3,26 @@ import {bindActionCreators} from 'redux'; import {connect} from 'react-redux'; + import {getPing} from 'actions/general'; -import SelectServerView from './select_server.js'; +import * as SelectServerActions from 'actions/views/select_server'; + +import SelectServer from './select_server'; function mapStateToProps(state) { return { - ping: state.entities.general.ping, - device: state.views.device + ...state.views.selectServer, + server: state.requests.general.server }; } function mapDispatchToProps(dispatch) { return { - actions: bindActionCreators({getPing}, dispatch) + actions: bindActionCreators({ + ...SelectServerActions, + getPing + }, dispatch) }; } -export default connect(mapStateToProps, mapDispatchToProps)(SelectServerView); +export default connect(mapStateToProps, mapDispatchToProps)(SelectServer); diff --git a/src/routes/select_team/select_team.js b/src/routes/select_team/select_team.js index 01993e500..c62d4c0a8 100644 --- a/src/routes/select_team/select_team.js +++ b/src/routes/select_team/select_team.js @@ -6,18 +6,21 @@ import React, {Component, PropTypes} from 'react'; import {View, Image, Text} from 'react-native'; import {Actions as Routes} from 'react-native-router-flux'; import Button from 'react-native-button'; -import Loading from 'components/loading.js'; +import Loading from 'components/loading'; import Icon from 'react-native-vector-icons/MaterialIcons'; import ErrorText from 'components/error_text'; import {GlobalStyles} from 'styles'; import logo from 'images/logo.png'; import FormattedText from 'components/formatted_text'; +import {RequestStatus} from 'constants'; export default class SelectTeam extends Component { static propTypes = { - clientConfig: PropTypes.object.isRequired, + config: PropTypes.object.isRequired, teams: PropTypes.object.isRequired, + myMembers: PropTypes.object.isRequired, + teamsRequest: PropTypes.object.isRequired, actions: PropTypes.object.isRequired }; @@ -26,18 +29,21 @@ export default class SelectTeam extends Component { } onSelectTeam(team) { + this.props.actions.selectTeam(team); Routes.goToMain({currentTeamId: team.id}); } render() { - if (this.props.teams.status !== 'fetched') { + if (this.props.teamsRequest.status === RequestStatus.STARTED) { return ; } const teams = []; - for (const id of Object.keys(this.props.teams.data)) { - const team = this.props.teams.data[id]; + for (const id of Object.keys(this.props.teams)) { + const team = this.props.teams[id]; + // TODO: uncomment when PLT-4167 is merged + // if (this.props.myMembers.hasOwnProperty(id)) { teams.push( ); + + // } } 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();