diff --git a/package.json b/package.json index d9bac43f90..588ab27f88 100644 --- a/package.json +++ b/package.json @@ -84,6 +84,7 @@ "react-dom": "^15.4.1", "react-ga": "^2.1.2", "react-redux": "^5.0.3", + "reduce-reducers": "^0.1.2", "redux": "^3.6.0", "redux-actions": "^1.2.1", "redux-immutable": "^3.0.11", diff --git a/spec/examples/actions/user.spec.js b/spec/examples/actions/user.spec.js deleted file mode 100644 index e6a7c06e79..0000000000 --- a/spec/examples/actions/user.spec.js +++ /dev/null @@ -1,56 +0,0 @@ -/* eslint-env mocha */ -/* global sinon */ - -import {assert} from 'chai'; -import '../../helper'; -import MockFirebase, - {createUser} from '../../helpers/MockFirebase'; -import dispatchAndWait from '../../helpers/dispatchAndWait'; -import createAndMutateProject from '../../helpers/createAndMutateProject'; -import {getCurrentProject} from '../../../src/util/projectUtils'; -import {applicationLoaded, logOut} from '../../../src/actions'; -import createApplicationStore from '../../../src/createApplicationStore'; - -describe('user actions', () => { - let store, sandbox, mockFirebase; - - beforeEach(() => { - sandbox = sinon.sandbox.create(); - mockFirebase = new MockFirebase(sandbox); - store = createApplicationStore(); - }); - - afterEach(() => { - sandbox.restore(); - }); - - const user = createUser(); - - describe('logOut', () => { - let loggedInProjectKey; - - beforeEach(async () => { - mockFirebase.logIn(user.uid); - mockFirebase.setCurrentProject(null); - await dispatchAndWait(store, applicationLoaded()); - createAndMutateProject(store); - loggedInProjectKey = getCurrentProject(store.getState()).projectKey; - return dispatchAndWait(store, logOut()); - }); - - it('should set authenticated to false', () => { - assert.isFalse(store.getState().getIn(['user', 'authenticated'])); - }); - - it('should set user id to null', () => { - assert.isUndefined(store.getState().getIn(['user', 'id'])); - }); - - it('should retain current project', () => { - assert.equal( - getCurrentProject(store.getState()).projectKey, - loggedInProjectKey, - ); - }); - }); -}); diff --git a/src/actions/index.js b/src/actions/index.js index d3ba5fab32..8535d98c00 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -29,7 +29,7 @@ import { import { userAuthenticated, - logOut, + userLoggedOut, } from './user'; function getCurrentPersistor(state) { @@ -173,7 +173,7 @@ export { updateProjectSource, toggleLibrary, userAuthenticated, - logOut, + userLoggedOut, addRuntimeError, clearRuntimeErrors, minimizeComponent, diff --git a/src/actions/user.js b/src/actions/user.js index 9080c3f359..e24c9db97f 100644 --- a/src/actions/user.js +++ b/src/actions/user.js @@ -1,20 +1,6 @@ import {createAction} from 'redux-actions'; import identity from 'lodash/identity'; -export const userAuthenticated = createAction( - 'USER_AUTHENTICATED', - identity, -); +export const userAuthenticated = createAction('USER_AUTHENTICATED', identity); -const resetWorkspace = createAction('RESET_WORKSPACE', identity); - -const userLoggedOut = createAction('USER_LOGGED_OUT'); - -export function logOut() { - return (dispatch, getState) => { - const currentProjectKey = - getState().getIn(['currentProject', 'projectKey']); - dispatch(resetWorkspace({currentProjectKey})); - dispatch(userLoggedOut()); - }; -} +export const userLoggedOut = createAction('USER_LOGGED_OUT'); diff --git a/src/components/Workspace.jsx b/src/components/Workspace.jsx index 41066317bf..f4e892574e 100644 --- a/src/components/Workspace.jsx +++ b/src/components/Workspace.jsx @@ -34,7 +34,7 @@ import { createProject, updateProjectSource, userAuthenticated, - logOut, + userLoggedOut, toggleLibrary, minimizeComponent, maximizeComponent, @@ -291,7 +291,7 @@ class Workspace extends React.Component { onSignedIn(userCredential => this.props.dispatch(userAuthenticated(userCredential)), ); - onSignedOut(() => this.props.dispatch(logOut())); + onSignedOut(() => this.props.dispatch(userLoggedOut())); } _handleStartLogIn() { diff --git a/src/reducers/index.js b/src/reducers/index.js index 3bc85182f7..116098e452 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -1,13 +1,14 @@ import {combineReducers} from 'redux-immutable'; +import reduceReducers from 'reduce-reducers'; import user from './user'; -import projects from './projects'; +import projects, {reduceRoot as reduceRootForProjects} from './projects'; import currentProject from './currentProject'; import errors from './errors'; import runtimeErrors from './runtimeErrors'; import ui from './ui'; import clients from './clients'; -const reducers = combineReducers({ +const reduceRoot = combineReducers({ user, projects, currentProject, @@ -17,4 +18,7 @@ const reducers = combineReducers({ clients, }); -export default reducers; +export default reduceReducers( + reduceRoot, + reduceRootForProjects, +); diff --git a/src/reducers/projects.js b/src/reducers/projects.js index bc308bb389..0866348bb9 100644 --- a/src/reducers/projects.js +++ b/src/reducers/projects.js @@ -64,7 +64,29 @@ function importGist(state, projectKey, gistData) { ); } -export default function projects(stateIn, action) { +export function reduceRoot(stateIn, action) { + return stateIn.update('projects', (projects) => { + switch (action.type) { + case 'USER_LOGGED_OUT': + { + const currentProjectKey = + stateIn.getIn(['currentProject', 'projectKey']); + + if (isNil(currentProjectKey)) { + return new Immutable.Map(); + } + + return new Immutable.Map().set( + currentProjectKey, + projects.get(currentProjectKey), + ); + } + } + return projects; + }); +} + +export default function reduceProjects(stateIn, action) { let state; if (stateIn === undefined) { @@ -95,16 +117,6 @@ export default function projects(stateIn, action) { case 'CHANGE_CURRENT_PROJECT': return removePristineExcept(state, action.payload.projectKey); - case 'RESET_WORKSPACE': - if (isNil(action.payload.currentProjectKey)) { - return emptyMap; - } - - return new Immutable.Map().set( - action.payload.currentProjectKey, - state.get(action.payload.currentProjectKey), - ); - case 'GIST_IMPORTED': return importGist( state, diff --git a/src/reducers/ui.js b/src/reducers/ui.js index 2756063290..a7ed71c4da 100644 --- a/src/reducers/ui.js +++ b/src/reducers/ui.js @@ -108,11 +108,14 @@ function ui(stateIn, action) { ), ); - case 'RESET_WORKSPACE': - return state.setIn( - ['dashboard', 'activeSubmenu'], - null, - ); + case 'USER_LOGGED_OUT': + if (state.getIn(['dashboard', 'activeSubmenu']) === 'projectList') { + return state.setIn( + ['dashboard', 'activeSubmenu'], + null, + ); + } + return state; default: return state; diff --git a/test/unit/reducers/projects.js b/test/unit/reducers/projects.js index b1cf9e8db5..cf2762b7b8 100644 --- a/test/unit/reducers/projects.js +++ b/test/unit/reducers/projects.js @@ -7,7 +7,9 @@ import Immutable from 'immutable'; import reducerTest from '../../helpers/reducerTest'; import {projects as states} from '../../helpers/referenceStates'; import {gistData, project} from '../../helpers/factory'; -import reducer from '../../../src/reducers/projects'; +import reducer, { + reduceRoot as rootReducer, +} from '../../../src/reducers/projects'; import { changeCurrentProject, gistImported, @@ -15,6 +17,7 @@ import { projectLoaded, projectSourceEdited, } from '../../../src/actions/projects'; +import {userLoggedOut} from '../../../src/actions/user'; const now = Date.now(); const projectKey = '12345'; @@ -136,6 +139,18 @@ tap(project(), projectIn => )), ); +tap(initProjects({1: true, 2: true}), projects => + test('userLoggedOut', reducerTest( + rootReducer, + Immutable.fromJS({currentProject: {projectKey: '1'}, projects}), + userLoggedOut, + Immutable.fromJS({ + currentProject: {projectKey: '1'}, + projects: projects.take(1), + }), + )), +); + function initProjects(map = {}) { return reduce(map, (projectsIn, modified, key) => { const projects = reducer(projectsIn, projectCreated(key)); diff --git a/test/unit/reducers/ui.js b/test/unit/reducers/ui.js index 79bfad6676..96b007244f 100644 --- a/test/unit/reducers/ui.js +++ b/test/unit/reducers/ui.js @@ -7,7 +7,7 @@ import { gistNotFound, gistImportError, } from '../../../src/actions/projects'; - +import {userLoggedOut} from '../../../src/actions/user'; const initialState = Immutable.fromJS({ editors: {typing: false}, @@ -49,3 +49,26 @@ test('gistImportError', reducerTest( })), ), )); + +test('userLoggedOut', (t) => { + const libraryPickerOpen = initialState.setIn( + ['dashboard', 'activeSubmenu'], + 'libraryPicker', + ); + t.test('with active submenu that is not projects', reducerTest( + reducer, + libraryPickerOpen, + userLoggedOut, + libraryPickerOpen, + )); + + t.test('with projectList active submenu', reducerTest( + reducer, + initialState.setIn( + ['dashboard', 'activeSubmenu'], + 'projectList', + ), + userLoggedOut, + initialState, + )); +}); diff --git a/test/unit/reducers/user.js b/test/unit/reducers/user.js index aa40af3536..6da3b9ff55 100644 --- a/test/unit/reducers/user.js +++ b/test/unit/reducers/user.js @@ -5,21 +5,30 @@ import reducerTest from '../../helpers/reducerTest'; import {user as states} from '../../helpers/referenceStates'; import {userCredential} from '../../helpers/factory'; import reducer from '../../../src/reducers/user'; -import {userAuthenticated} from '../../../src/actions/user'; +import {userAuthenticated, userLoggedOut} from '../../../src/actions/user'; const userCredentialIn = userCredential(); +const loggedInState = Immutable.fromJS({ + authenticated: true, + id: userCredentialIn.user.uid, + displayName: userCredentialIn.user.providerData[0].displayName, + avatarUrl: userCredentialIn.user.providerData[0].photoURL, + accessTokens: { + 'github.com': userCredentialIn.credential.accessToken, + }, +}); + test('userAuthenticated', reducerTest( reducer, states.initial, partial(userAuthenticated, userCredentialIn), - Immutable.fromJS({ - authenticated: true, - id: userCredentialIn.user.uid, - displayName: userCredentialIn.user.providerData[0].displayName, - avatarUrl: userCredentialIn.user.providerData[0].photoURL, - accessTokens: { - 'github.com': userCredentialIn.credential.accessToken, - }, - }), + loggedInState, +)); + +test('userLoggedOut', reducerTest( + reducer, + loggedInState, + userLoggedOut, + states.initial, )); diff --git a/yarn.lock b/yarn.lock index 741adb01c2..5010baf52a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6515,7 +6515,7 @@ reduce-function-call@^1.0.1: dependencies: balanced-match "^0.4.2" -reduce-reducers@^0.1.0: +reduce-reducers@^0.1.0, reduce-reducers@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/reduce-reducers/-/reduce-reducers-0.1.2.tgz#fa1b4718bc5292a71ddd1e5d839c9bea9770f14b"