diff --git a/src/components/gui/gui.jsx b/src/components/gui/gui.jsx index 176dea6e1..e21062db1 100644 --- a/src/components/gui/gui.jsx +++ b/src/components/gui/gui.jsx @@ -66,7 +66,7 @@ const GUIComponent = props => { canCreateNew, canRemix, canSave, - canSaveAsCopy, + canCreateCopy, canShare, children, connectionModalVisible, @@ -178,10 +178,10 @@ const GUIComponent = props => { ) : null} {}, showComingSoon: false, diff --git a/src/components/menu-bar/menu-bar.jsx b/src/components/menu-bar/menu-bar.jsx index 462131b62..a67c97ceb 100644 --- a/src/components/menu-bar/menu-bar.jsx +++ b/src/components/menu-bar/menu-bar.jsx @@ -26,7 +26,9 @@ import { getIsUpdating, getIsShowingProject, requestNewProject, - saveProject + remixProject, + updateProject, + saveProjectAsCopy } from '../../reducers/project-state'; import { openAccountMenu, @@ -130,7 +132,9 @@ class MenuBar extends React.Component { super(props); bindAll(this, [ 'handleClickNew', + 'handleClickRemix', 'handleClickSave', + 'handleClickSaveAsCopy', 'handleCloseFileMenuAndThen', 'handleLanguageMouseUp', 'handleRestoreOption', @@ -145,16 +149,23 @@ class MenuBar extends React.Component { } } handleClickNew () { - // if canSave===true, it's safe to replace current project, since we will auto-save first - const readyToReplaceProject = - this.props.canSave || confirm('Replace contents of the current project?'); // eslint-disable-line no-alert + // if canCreateNew===true, it's safe to replace current project, since we will auto-save first. + // else confirm first. + const readyToReplaceProject = this.props.canCreateNew || + confirm('Replace contents of the current project?'); // eslint-disable-line no-alert if (readyToReplaceProject) { - this.props.onClickNew(this.props.canSave); + this.props.onClickNew(this.props.canCreateNew); } } + handleClickRemix () { + this.props.onClickRemix(); + } handleClickSave () { this.props.onClickSave(); } + handleClickSaveAsCopy () { + this.props.onClickSaveAsCopy(); + } handleRestoreOption (restoreFun) { return () => { restoreFun(); @@ -209,13 +220,20 @@ class MenuBar extends React.Component { id="gui.menuBar.saveNow" /> ); - const saveAsCopyMessage = ( + const createCopyMessage = ( ); + const remixMessage = ( + + ); const newProjectMessage = ( - {this.props.canCreateNew ? ( - - {newProjectMessage} - - ) : (this.props.showComingSoon ? ( - - {newProjectMessage} - - ) : [])} + + {newProjectMessage} + {this.props.canSave ? ( @@ -319,18 +328,23 @@ class MenuBar extends React.Component { {saveNowMessage} ) : [])} - {this.props.canSaveAsCopy ? ( + {this.props.canCreateCopy ? ( - {saveAsCopyMessage} + {createCopyMessage} ) : (this.props.showComingSoon ? ( - {saveAsCopyMessage} + {createCopyMessage} ) : [])} + {this.props.canRemix ? ( + + {remixMessage} + + ) : []} @@ -627,10 +641,10 @@ class MenuBar extends React.Component { MenuBar.propTypes = { accountMenuOpen: PropTypes.bool, + canCreateCopy: PropTypes.bool, canCreateNew: PropTypes.bool, canRemix: PropTypes.bool, canSave: PropTypes.bool, - canSaveAsCopy: PropTypes.bool, canShare: PropTypes.bool, className: PropTypes.string, editMenuOpen: PropTypes.bool, @@ -648,7 +662,9 @@ MenuBar.propTypes = { onClickLanguage: PropTypes.func, onClickLogin: PropTypes.func, onClickNew: PropTypes.func, + onClickRemix: PropTypes.func, onClickSave: PropTypes.func, + onClickSaveAsCopy: PropTypes.func, onLogOut: PropTypes.func, onOpenRegistration: PropTypes.func, onOpenTipLibrary: PropTypes.func, @@ -700,8 +716,10 @@ const mapDispatchToProps = dispatch => ({ onRequestCloseLanguage: () => dispatch(closeLanguageMenu()), onClickLogin: () => dispatch(openLoginMenu()), onRequestCloseLogin: () => dispatch(closeLoginMenu()), - onClickNew: canSave => dispatch(requestNewProject(canSave)), - onClickSave: () => dispatch(saveProject()), + onClickNew: canCreateNew => dispatch(requestNewProject(canCreateNew)), + onClickRemix: () => dispatch(remixProject()), + onClickSave: () => dispatch(updateProject()), + onClickSaveAsCopy: () => dispatch(saveProjectAsCopy()), onSeeCommunity: () => dispatch(setPlayer(true)) }); diff --git a/src/lib/project-saver-hoc.jsx b/src/lib/project-saver-hoc.jsx index c7df7bd2d..7039b1a41 100644 --- a/src/lib/project-saver-hoc.jsx +++ b/src/lib/project-saver-hoc.jsx @@ -7,14 +7,14 @@ import storage from '../lib/storage'; import { LoadingStates, createProject, + doneCreatingProject, + doneUpdatingProject, getIsCreating, getIsShowingProject, getIsShowingWithoutId, getIsUpdating, - onCreated, - onUpdated, - onError, - saveProject + projectError, + updateProject } from '../reducers/project-state'; /** @@ -33,34 +33,39 @@ const ProjectSaverHOC = function (WrappedComponent) { this.storeProject(this.props.reduxProjectId) .then(() => { // there is nothing we expect to find in response that we need to check here - this.props.onUpdated(this.props.loadingState); + this.props.onUpdatedProject(this.props.loadingState); }) .catch(err => { // NOTE: should throw up a notice for user - this.props.onError(`Saving the project failed with error: ${err}`); + this.props.onProjectError(`Saving the project failed with error: ${err}`); }); } + // TODO: distinguish between creating new, remixing, and saving as a copy if (this.props.isCreating && !prevProps.isCreating) { this.storeProject() .then(response => { - this.props.onCreated(response.id.toString()); + this.props.onCreatedProject(response.id.toString(), this.props.loadingState); }) .catch(err => { // NOTE: should throw up a notice for user - this.props.onError(`Creating a new project failed with error: ${err}`); + this.props.onProjectError(`Creating a new project failed with error: ${err}`); }); } - // if this is the first time we're able to create this project on the server, create it! - const showingCreateable = this.props.canSave && this.props.isShowingWithoutId; - const prevShowingCreateable = prevProps.canSave && prevProps.isShowingWithoutId; + + // check if the project state, and user capabilities, have changed so as to indicate + // that we should create or update project + // + // if we're newly able to create this project on the server, create it! + const showingCreateable = this.props.canCreateNew && this.props.isShowingWithoutId; + const prevShowingCreateable = prevProps.canCreateNew && prevProps.isShowingWithoutId; if (showingCreateable && !prevShowingCreateable) { - this.props.createProject(); + this.props.onCreateProject(); } else { - // if we're newly *able* to save this project, save it! + // if we're newly able to save this project, save it! const showingSaveable = this.props.canSave && this.props.isShowingWithId; const becameAbleToSave = this.props.canSave && !prevProps.canSave; if (showingSaveable && becameAbleToSave) { - this.props.saveProject(); + this.props.onUpdateProject(); } } } @@ -89,17 +94,17 @@ const ProjectSaverHOC = function (WrappedComponent) { render () { const { /* eslint-disable no-unused-vars */ - createProject: createProjectProp, isCreating: isCreatingProp, isShowingWithId: isShowingWithIdProp, isShowingWithoutId: isShowingWithoutIdProp, isUpdating: isUpdatingProp, loadingState, - onCreated: onCreatedProp, - onError: onErrorProp, - onUpdated: onUpdatedProp, + onCreatedProject: onCreatedProjectProp, + onCreateProject: onCreateProjectProp, + onProjectError: onProjectErrorProp, + onUpdatedProject: onUpdatedProjectProp, + onUpdateProject: onUpdateProjectProp, reduxProjectId, - saveProject: saveProjectProp, /* eslint-enable no-unused-vars */ ...componentProps } = this.props; @@ -111,18 +116,19 @@ const ProjectSaverHOC = function (WrappedComponent) { } } ProjectSaverComponent.propTypes = { + canCreateNew: PropTypes.bool, canSave: PropTypes.bool, - createProject: PropTypes.func, isCreating: PropTypes.bool, isShowingWithId: PropTypes.bool, isShowingWithoutId: PropTypes.bool, isUpdating: PropTypes.bool, loadingState: PropTypes.oneOf(LoadingStates), - onCreated: PropTypes.func, - onError: PropTypes.func, - onUpdated: PropTypes.func, + onCreateProject: PropTypes.func, + onCreatedProject: PropTypes.func, + onProjectError: PropTypes.func, + onUpdateProject: PropTypes.func, + onUpdatedProject: PropTypes.func, reduxProjectId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - saveProject: PropTypes.func, vm: PropTypes.instanceOf(VM).isRequired }; const mapStateToProps = state => { @@ -138,11 +144,11 @@ const ProjectSaverHOC = function (WrappedComponent) { }; }; const mapDispatchToProps = dispatch => ({ - createProject: () => dispatch(createProject()), - onCreated: projectId => dispatch(onCreated(projectId)), - onUpdated: (projectId, loadingState) => dispatch(onUpdated(projectId, loadingState)), - onError: error => dispatch(onError(error)), - saveProject: () => dispatch(saveProject()) + onCreatedProject: (projectId, loadingState) => dispatch(doneCreatingProject(projectId, loadingState)), + onCreateProject: () => dispatch(createProject()), + onProjectError: error => dispatch(projectError(error)), + onUpdateProject: () => dispatch(updateProject()), + onUpdatedProject: (projectId, loadingState) => dispatch(doneUpdatingProject(projectId, loadingState)) }); // Allow incoming props to override redux-provided props. Used to mock in tests. const mergeProps = (stateProps, dispatchProps, ownProps) => Object.assign( diff --git a/src/reducers/project-state.js b/src/reducers/project-state.js index b5eddde7b..553331acb 100644 --- a/src/reducers/project-state.js +++ b/src/reducers/project-state.js @@ -1,20 +1,24 @@ import keyMirror from 'keymirror'; -const START_CREATING_NEW = 'scratch-gui/project-state/START_CREATING_NEW'; +const DONE_CREATING_COPY = 'scratch-gui/project-state/DONE_CREATING_COPY'; const DONE_CREATING_NEW = 'scratch-gui/project-state/DONE_CREATING_NEW'; -const DONE_FETCHING_WITH_ID = 'scratch-gui/project-state/DONE_FETCHING_WITH_ID'; const DONE_FETCHING_DEFAULT = 'scratch-gui/project-state/DONE_FETCHING_DEFAULT'; +const DONE_FETCHING_WITH_ID = 'scratch-gui/project-state/DONE_FETCHING_WITH_ID'; +const DONE_LOADING_VM_TO_SAVE = 'scratch-gui/project-state/DONE_LOADING_VM_TO_SAVE'; const DONE_LOADING_VM_WITH_ID = 'scratch-gui/project-state/DONE_LOADING_VM_WITH_ID'; const DONE_LOADING_VM_WITHOUT_ID = 'scratch-gui/project-state/DONE_LOADING_VM_WITHOUT_ID'; -const DONE_LOADING_VM_TO_SAVE = 'scratch-gui/project-state/DONE_LOADING_VM_TO_SAVE'; -const DONE_SAVING_WITH_ID = 'scratch-gui/project-state/DONE_SAVING_WITH_ID'; -const DONE_SAVING_WITH_ID_BEFORE_NEW = 'scratch-gui/project-state/DONE_SAVING_WITH_ID_BEFORE_NEW'; -const GO_TO_ERROR_STATE = 'scratch-gui/project-state/GO_TO_ERROR_STATE'; +const DONE_REMIXING = 'scratch-gui/project-state/DONE_REMIXING'; +const DONE_UPDATING = 'scratch-gui/project-state/DONE_UPDATING'; +const DONE_UPDATING_BEFORE_NEW = 'scratch-gui/project-state/DONE_UPDATING_BEFORE_NEW'; const SET_PROJECT_ID = 'scratch-gui/project-state/SET_PROJECT_ID'; +const START_CREATING_COPY = 'scratch-gui/project-state/START_CREATING_COPY'; +const START_CREATING_NEW = 'scratch-gui/project-state/START_CREATING_NEW'; +const START_ERROR = 'scratch-gui/project-state/START_ERROR'; const START_FETCHING_NEW = 'scratch-gui/project-state/START_FETCHING_NEW'; const START_LOADING_VM_FILE_UPLOAD = 'scratch-gui/project-state/START_LOADING_FILE_UPLOAD'; -const START_SAVING = 'scratch-gui/project-state/START_SAVING'; -const START_SAVING_BEFORE_CREATING_NEW = 'scratch-gui/project-state/START_SAVING_BEFORE_CREATING_NEW'; +const START_REMIXING = 'scratch-gui/project-state/START_REMIXING'; +const START_UPDATING = 'scratch-gui/project-state/START_UPDATING'; +const START_UPDATING_BEFORE_CREATING_NEW = 'scratch-gui/project-state/START_UPDATING_BEFORE_CREATING_NEW'; const defaultProjectId = '0'; // hardcoded id of default project @@ -26,10 +30,12 @@ const LoadingState = keyMirror({ LOADING_VM_WITH_ID: null, LOADING_VM_FILE_UPLOAD: null, LOADING_VM_NEW_DEFAULT: null, + REMIXING: null, SHOWING_WITH_ID: null, SHOWING_WITHOUT_ID: null, - SAVING_WITH_ID: null, - SAVING_WITH_ID_BEFORE_NEW: null, + CREATING_COPY: null, + UPDATING: null, + UPDATING_BEFORE_NEW: null, CREATING_NEW: null }); @@ -49,11 +55,13 @@ const getIsLoadingWithId = loadingState => ( loadingState === LoadingState.LOADING_VM_NEW_DEFAULT ); const getIsCreating = loadingState => ( - loadingState === LoadingState.CREATING_NEW + loadingState === LoadingState.CREATING_NEW || + loadingState === LoadingState.REMIXING || + loadingState === LoadingState.CREATING_COPY ); const getIsUpdating = loadingState => ( - loadingState === LoadingState.SAVING_WITH_ID || - loadingState === LoadingState.SAVING_WITH_ID_BEFORE_NEW + loadingState === LoadingState.UPDATING || + loadingState === LoadingState.UPDATING_BEFORE_NEW ); const getIsShowingProject = loadingState => ( loadingState === LoadingState.SHOWING_WITH_ID || @@ -86,7 +94,7 @@ const reducer = function (state, action) { if (state.loadingState === LoadingState.CREATING_NEW) { return Object.assign({}, state, { loadingState: LoadingState.SHOWING_WITH_ID, - projectId: action.id + projectId: action.projectId }); } return state; @@ -124,19 +132,39 @@ const reducer = function (state, action) { case DONE_LOADING_VM_TO_SAVE: if (state.loadingState === LoadingState.LOADING_VM_FILE_UPLOAD) { return Object.assign({}, state, { - loadingState: LoadingState.SAVING_WITH_ID + loadingState: LoadingState.UPDATING }); } return state; - case DONE_SAVING_WITH_ID: - if (state.loadingState === LoadingState.SAVING_WITH_ID) { + case DONE_REMIXING: + // We need to set project id since we just created new project on the server. + // No need to load, we should have data already in vm. + if (state.loadingState === LoadingState.REMIXING) { + return Object.assign({}, state, { + loadingState: LoadingState.SHOWING_WITH_ID, + projectId: action.projectId + }); + } + return state; + case DONE_CREATING_COPY: + // We need to set project id since we just created new project on the server. + // No need to load, we should have data already in vm. + if (state.loadingState === LoadingState.CREATING_COPY) { + return Object.assign({}, state, { + loadingState: LoadingState.SHOWING_WITH_ID, + projectId: action.projectId + }); + } + return state; + case DONE_UPDATING: + if (state.loadingState === LoadingState.UPDATING) { return Object.assign({}, state, { loadingState: LoadingState.SHOWING_WITH_ID }); } return state; - case DONE_SAVING_WITH_ID_BEFORE_NEW: - if (state.loadingState === LoadingState.SAVING_WITH_ID_BEFORE_NEW) { + case DONE_UPDATING_BEFORE_NEW: + if (state.loadingState === LoadingState.UPDATING_BEFORE_NEW) { return Object.assign({}, state, { loadingState: LoadingState.FETCHING_NEW_DEFAULT, projectId: defaultProjectId @@ -145,7 +173,7 @@ const reducer = function (state, action) { return state; case SET_PROJECT_ID: // if setting the default project id, specifically fetch that project - if (action.id === defaultProjectId) { + if (action.projectId === defaultProjectId) { return Object.assign({}, state, { loadingState: LoadingState.FETCHING_NEW_DEFAULT, projectId: defaultProjectId @@ -154,16 +182,16 @@ const reducer = function (state, action) { // if we were already showing a project, and a different projectId is set, only fetch that project if // projectId has changed. This prevents re-fetching projects unnecessarily. if (state.loadingState === LoadingState.SHOWING_WITH_ID) { - if (state.projectId !== action.id) { + if (state.projectId !== action.projectId) { return Object.assign({}, state, { loadingState: LoadingState.FETCHING_WITH_ID, - projectId: action.id + projectId: action.projectId }); } } else { // allow any other states to transition to fetching project return Object.assign({}, state, { loadingState: LoadingState.FETCHING_WITH_ID, - projectId: action.id + projectId: action.projectId }); } return state; @@ -197,30 +225,48 @@ const reducer = function (state, action) { }); } return state; - case START_SAVING: + case START_REMIXING: + // do not set projectId to null, because we nay reference it in creating project if (state.loadingState === LoadingState.SHOWING_WITH_ID) { return Object.assign({}, state, { - loadingState: LoadingState.SAVING_WITH_ID + loadingState: LoadingState.REMIXING }); } return state; - case START_SAVING_BEFORE_CREATING_NEW: + case START_UPDATING: if (state.loadingState === LoadingState.SHOWING_WITH_ID) { return Object.assign({}, state, { - loadingState: LoadingState.SAVING_WITH_ID_BEFORE_NEW + loadingState: LoadingState.UPDATING }); } return state; - case GO_TO_ERROR_STATE: + case START_CREATING_COPY: + // do not set projectId to null, because we nay reference it in creating project + if (state.loadingState === LoadingState.SHOWING_WITH_ID) { + return Object.assign({}, state, { + loadingState: LoadingState.CREATING_COPY + }); + } + return state; + case START_UPDATING_BEFORE_CREATING_NEW: + if (state.loadingState === LoadingState.SHOWING_WITH_ID) { + return Object.assign({}, state, { + loadingState: LoadingState.UPDATING_BEFORE_NEW + }); + } + return state; + case START_ERROR: // NOTE: we should introduce handling in components for showing ERROR state if ([ + LoadingState.CREATING_NEW, + LoadingState.FETCHING_NEW_DEFAULT, + LoadingState.FETCHING_WITH_ID, LoadingState.LOADING_VM_NEW_DEFAULT, LoadingState.LOADING_VM_WITH_ID, - LoadingState.FETCHING_WITH_ID, - LoadingState.FETCHING_NEW_DEFAULT, - LoadingState.SAVING_WITH_ID, - LoadingState.SAVING_WITH_ID_BEFORE_NEW, - LoadingState.CREATING_NEW + LoadingState.REMIXING, + LoadingState.CREATING_COPY, + LoadingState.UPDATING_BEFORE_NEW, + LoadingState.UPDATING ].includes(state.loadingState)) { return Object.assign({}, state, { loadingState: LoadingState.ERROR, @@ -237,10 +283,27 @@ const createProject = () => ({ type: START_CREATING_NEW }); -const onCreated = id => ({ - type: DONE_CREATING_NEW, - id: id -}); +const doneCreatingProject = (id, loadingState) => { + switch (loadingState) { + case LoadingState.CREATING_NEW: + return { + type: DONE_CREATING_NEW, + projectId: id + }; + case LoadingState.CREATING_COPY: + return { + type: DONE_CREATING_COPY, + projectId: id + }; + case LoadingState.REMIXING: + return { + type: DONE_REMIXING, + projectId: id + }; + default: + break; + } +}; const onFetchedProjectData = (projectData, loadingState) => { switch (loadingState) { @@ -283,15 +346,15 @@ const onLoadedProject = (loadingState, canSave) => { } }; -const onUpdated = loadingState => { +const doneUpdatingProject = loadingState => { switch (loadingState) { - case LoadingState.SAVING_WITH_ID: + case LoadingState.UPDATING: return { - type: DONE_SAVING_WITH_ID + type: DONE_UPDATING }; - case LoadingState.SAVING_WITH_ID_BEFORE_NEW: + case LoadingState.UPDATING_BEFORE_NEW: return { - type: DONE_SAVING_WITH_ID_BEFORE_NEW + type: DONE_UPDATING_BEFORE_NEW }; default: break; @@ -299,17 +362,17 @@ const onUpdated = loadingState => { }; const onError = error => ({ - type: GO_TO_ERROR_STATE, + type: START_ERROR, error: error }); const setProjectId = id => ({ type: SET_PROJECT_ID, - id: id + projectId: id }); -const requestNewProject = canSave => { - if (canSave) return {type: START_SAVING_BEFORE_CREATING_NEW}; +const requestNewProject = canCreateNew => { + if (canCreateNew) return {type: START_UPDATING_BEFORE_CREATING_NEW}; return {type: START_FETCHING_NEW}; }; @@ -317,8 +380,16 @@ const onProjectUploadStarted = () => ({ type: START_LOADING_VM_FILE_UPLOAD }); -const saveProject = () => ({ - type: START_SAVING +const updateProject = () => ({ + type: START_UPDATING +}); + +const saveProjectAsCopy = () => ({ + type: START_CREATING_COPY +}); + +const remixProject = () => ({ + type: START_REMIXING }); export { @@ -337,13 +408,15 @@ export { getIsShowingProject, getIsShowingWithId, getIsShowingWithoutId, - onCreated, - onError, + doneCreatingProject, + projectError, onFetchedProjectData, onLoadedProject, onProjectUploadStarted, - onUpdated, + doneUpdatingProject, requestNewProject, - saveProject, + remixProject, + updateProject, + saveProjectAsCopy, setProjectId }; diff --git a/test/unit/reducers/project-state-reducer.test.js b/test/unit/reducers/project-state-reducer.test.js index 9fe683899..099abd418 100644 --- a/test/unit/reducers/project-state-reducer.test.js +++ b/test/unit/reducers/project-state-reducer.test.js @@ -2,15 +2,17 @@ import projectStateReducer from '../../../src/reducers/project-state'; import { LoadingState, - onCreated, - onError, + doneCreatingProject, + doneUpdatingProject, onFetchedProjectData, onLoadedProject, onProjectUploadStarted, - onUpdated, + projectError, + remixProject, requestNewProject, - saveProject, - setProjectId + saveProjectAsCopy, + setProjectId, + updateProject } from '../../../src/reducers/project-state'; test('initialState', () => { @@ -23,28 +25,50 @@ test('initialState', () => { expect(projectStateReducer(defaultState, {type: 'anything'}).loadingState).toBe(LoadingState.NOT_LOADED); }); -test('onCreated with projectId type string shows project with that id', () => { +test('doneCreatingProject for new project with projectId type string shows project with that id', () => { const initialState = { projectId: null, loadingState: LoadingState.CREATING_NEW }; - const action = onCreated('100'); + const action = doneCreatingProject('100', initialState.loadingState); const resultState = projectStateReducer(initialState, action); expect(resultState.loadingState).toBe(LoadingState.SHOWING_WITH_ID); expect(resultState.projectId).toBe('100'); }); -test('onCreated with projectId type number shows project with id of type number', () => { +test('doneCreatingProject for new project with projectId type number shows project with id of type number', () => { const initialState = { projectId: null, loadingState: LoadingState.CREATING_NEW }; - const action = onCreated(100); + const action = doneCreatingProject(100, initialState.loadingState); const resultState = projectStateReducer(initialState, action); expect(resultState.loadingState).toBe(LoadingState.SHOWING_WITH_ID); expect(resultState.projectId).toBe(100); }); +test('doneCreatingProject for remix shows project with that id', () => { + const initialState = { + projectId: null, + loadingState: LoadingState.REMIXING + }; + const action = doneCreatingProject('100', initialState.loadingState); + const resultState = projectStateReducer(initialState, action); + expect(resultState.loadingState).toBe(LoadingState.SHOWING_WITH_ID); + expect(resultState.projectId).toBe('100'); +}); + +test('doneCreatingProject for save as copy shows project with that id', () => { + const initialState = { + projectId: null, + loadingState: LoadingState.CREATING_COPY + }; + const action = doneCreatingProject('100', initialState.loadingState); + const resultState = projectStateReducer(initialState, action); + expect(resultState.loadingState).toBe(LoadingState.SHOWING_WITH_ID); + expect(resultState.projectId).toBe('100'); +}); + test('onFetchedProjectData with id loads project data into vm', () => { const initialState = { projectData: null, @@ -82,7 +106,7 @@ test('onLoadedProject upload, with canSave true, prepares to save', () => { }; const action = onLoadedProject(initialState.loadingState, true); const resultState = projectStateReducer(initialState, action); - expect(resultState.loadingState).toBe(LoadingState.SAVING_WITH_ID); + expect(resultState.loadingState).toBe(LoadingState.UPDATING); }); test('onLoadedProject with id shows with id', () => { @@ -112,20 +136,20 @@ test('onLoadedProject new, to save shows without id', () => { expect(resultState.loadingState).toBe(LoadingState.SHOWING_WITHOUT_ID); }); -test('onUpdated with id shows with id', () => { +test('doneUpdatingProject with id shows with id', () => { const initialState = { - loadingState: LoadingState.SAVING_WITH_ID + loadingState: LoadingState.UPDATING }; - const action = onUpdated(initialState.loadingState); + const action = doneUpdatingProject(initialState.loadingState); const resultState = projectStateReducer(initialState, action); expect(resultState.loadingState).toBe(LoadingState.SHOWING_WITH_ID); }); -test('onUpdated with id, before new, fetches default project', () => { +test('doneUpdatingProject with id, before new, fetches default project', () => { const initialState = { - loadingState: LoadingState.SAVING_WITH_ID_BEFORE_NEW + loadingState: LoadingState.UPDATING_BEFORE_NEW }; - const action = onUpdated(initialState.loadingState); + const action = doneUpdatingProject(initialState.loadingState); const resultState = projectStateReducer(initialState, action); expect(resultState.loadingState).toBe(LoadingState.FETCHING_NEW_DEFAULT); }); @@ -163,7 +187,7 @@ test('setProjectId, with same id as before, but not same type, should fetch beca expect(resultState.projectId).toBe(100); }); -test('requestNewProject, when can\'t save, should fetch default project without id', () => { +test('requestNewProject, when can\'t create new, should fetch default project without id', () => { const initialState = { loadingState: LoadingState.SHOWING_WITHOUT_ID }; @@ -172,13 +196,13 @@ test('requestNewProject, when can\'t save, should fetch default project without expect(resultState.loadingState).toBe(LoadingState.FETCHING_NEW_DEFAULT); }); -test('requestNewProject, when can save, should save and prepare to fetch default project', () => { +test('requestNewProject, when can create new, should save and prepare to fetch default project', () => { const initialState = { loadingState: LoadingState.SHOWING_WITH_ID }; const action = requestNewProject(true); const resultState = projectStateReducer(initialState, action); - expect(resultState.loadingState).toBe(LoadingState.SAVING_WITH_ID_BEFORE_NEW); + expect(resultState.loadingState).toBe(LoadingState.UPDATING_BEFORE_NEW); }); test('onProjectUploadStarted when project not loaded should load', () => { @@ -208,24 +232,44 @@ test('onProjectUploadStarted when showing project without id should load', () => expect(resultState.loadingState).toBe(LoadingState.LOADING_VM_FILE_UPLOAD); }); -test('saveProject should prepare to save', () => { +test('updateProject should prepare to update', () => { const initialState = { loadingState: LoadingState.SHOWING_WITH_ID }; - const action = saveProject(); + const action = updateProject(); const resultState = projectStateReducer(initialState, action); - expect(resultState.loadingState).toBe(LoadingState.SAVING_WITH_ID); + expect(resultState.loadingState).toBe(LoadingState.UPDATING); }); -test('onError from various states should show error', () => { +test('saveProjectAsCopy should prepare to save as a copy', () => { + const initialState = { + loadingState: LoadingState.SHOWING_WITH_ID + }; + const action = saveProjectAsCopy(); + const resultState = projectStateReducer(initialState, action); + expect(resultState.loadingState).toBe(LoadingState.CREATING_COPY); +}); + +test('remixProject should prepare to remix', () => { + const initialState = { + loadingState: LoadingState.SHOWING_WITH_ID + }; + const action = remixProject(); + const resultState = projectStateReducer(initialState, action); + expect(resultState.loadingState).toBe(LoadingState.REMIXING); +}); + +test('projectError from various states should show error', () => { const startStates = [ + LoadingState.CREATING_NEW, + LoadingState.FETCHING_NEW_DEFAULT, + LoadingState.FETCHING_WITH_ID, LoadingState.LOADING_VM_NEW_DEFAULT, LoadingState.LOADING_VM_WITH_ID, - LoadingState.FETCHING_WITH_ID, - LoadingState.FETCHING_NEW_DEFAULT, - LoadingState.SAVING_WITH_ID, - LoadingState.SAVING_WITH_ID_BEFORE_NEW, - LoadingState.CREATING_NEW + LoadingState.REMIXING, + LoadingState.CREATING_COPY, + LoadingState.UPDATING_BEFORE_NEW, + LoadingState.UPDATING ]; for (const startState of startStates) { const initialState = { @@ -239,7 +283,7 @@ test('onError from various states should show error', () => { } }); -test('onError from showing project should show error', () => { +test('projectError from showing project should show error', () => { const initialState = { error: null, loadingState: LoadingState.FETCHING_WITH_ID diff --git a/test/unit/util/project-saver-hoc.test.jsx b/test/unit/util/project-saver-hoc.test.jsx index d667db379..2082c0955 100644 --- a/test/unit/util/project-saver-hoc.test.jsx +++ b/test/unit/util/project-saver-hoc.test.jsx @@ -23,7 +23,7 @@ describe('projectSaverHOC', () => { }); test('if canSave becomes true when showing a project with an id, project will be saved', () => { - const mockedSaveProject = jest.fn(); + const mockedUpdateProject = jest.fn(); const Component = () =>
; const WrappedComponent = projectSaverHOC(Component); const mounted = mount( @@ -34,15 +34,15 @@ describe('projectSaverHOC', () => { isShowingWithoutId={false} isUpdating={false} loadingState={LoadingState.SHOWING_WITH_ID} - saveProject={mockedSaveProject} store={store} vm={vm} + onUpdateProject={mockedUpdateProject} /> ); mounted.setProps({ canSave: true }); - expect(mockedSaveProject).toHaveBeenCalled(); + expect(mockedUpdateProject).toHaveBeenCalled(); }); test('if canSave is alreatdy true and we show a project with an id, project will NOT be saved', () => { @@ -57,9 +57,9 @@ describe('projectSaverHOC', () => { isShowingWithoutId={false} isUpdating={false} loadingState={LoadingState.LOADING_VM_WITH_ID} - saveProject={mockedSaveProject} store={store} vm={vm} + onUpdateProject={mockedSaveProject} /> ); mounted.setProps({ @@ -78,13 +78,13 @@ describe('projectSaverHOC', () => { ); mounted.setProps({ @@ -94,37 +94,36 @@ describe('projectSaverHOC', () => { expect(mockedCreateProject).not.toHaveBeenCalled(); }); - test('if canSave becomes true when showing a project without an id, project will be created', () => { + test('if canCreateNew becomes true when showing a project without an id, project will be created', () => { const mockedCreateProject = jest.fn(); const Component = () =>
; const WrappedComponent = projectSaverHOC(Component); const mounted = mount( ); mounted.setProps({ - canSave: true + canCreateNew: true }); expect(mockedCreateProject).toHaveBeenCalled(); }); - test('if canSave is true and we transition to showing new project, project will be created', () => { + test('if canCreateNew is true and we transition to showing new project, project will be created', () => { const mockedCreateProject = jest.fn(); const Component = () =>
; const WrappedComponent = projectSaverHOC(Component); const mounted = mount( { loadingState={LoadingState.LOADING_VM_NEW_DEFAULT} store={store} vm={vm} + onCreateProject={mockedCreateProject} /> ); mounted.setProps({ @@ -184,7 +184,7 @@ describe('projectSaverHOC', () => { ); mounted.setProps({ isUpdating: true, - loadingState: LoadingState.SAVING_WITH_ID + loadingState: LoadingState.UPDATING }); expect(vm.saveProjectSb3).toHaveBeenCalled(); }); @@ -200,7 +200,7 @@ describe('projectSaverHOC', () => { isCreating={false} isShowingWithId={false} isShowingWithoutId={false} - loadingState={LoadingState.SAVING_WITH_ID} + loadingState={LoadingState.UPDATING} reduxProjectId={'100'} store={store} vm={vm} @@ -208,7 +208,7 @@ describe('projectSaverHOC', () => { ); mounted.setProps({ isUpdating: true, - loadingState: LoadingState.SAVING_WITH_ID, + loadingState: LoadingState.UPDATING, reduxProjectId: '99' // random change to force a re-render and componentDidUpdate }); expect(vm.saveProjectSb3).not.toHaveBeenCalled();