diff --git a/src/reducers/project-state.js b/src/reducers/project-state.js index 91e51bbe48e6b8fa1c0cb1b254cbce7350f4a246..76955d589d561df8d7710167aaf9fc954e21a213 100644 --- a/src/reducers/project-state.js +++ b/src/reducers/project-state.js @@ -17,7 +17,7 @@ const START_AUTO_UPDATING = 'scratch-gui/project-state/START_AUTO_UPDATING'; 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_LOADING_VM_FILE_UPLOAD = 'scratch-gui/project-state/START_LOADING_VM_FILE_UPLOAD'; const START_MANUAL_UPDATING = 'scratch-gui/project-state/START_MANUAL_UPDATING'; const START_REMIXING = 'scratch-gui/project-state/START_REMIXING'; const START_UPDATING_BEFORE_CREATING_COPY = 'scratch-gui/project-state/START_UPDATING_BEFORE_CREATING_COPY'; @@ -413,32 +413,31 @@ const onFetchedProjectData = (projectData, loadingState) => { }; const onLoadedProject = (loadingState, canSave, success) => { - if (success) { - switch (loadingState) { - case LoadingState.LOADING_VM_WITH_ID: - return { - type: DONE_LOADING_VM_WITH_ID - }; - case LoadingState.LOADING_VM_FILE_UPLOAD: + switch (loadingState) { + case LoadingState.LOADING_VM_WITH_ID: + if (success) { + return {type: DONE_LOADING_VM_WITH_ID}; + } + // failed to load project; just keep showing current project + return {type: RETURN_TO_SHOWING}; + case LoadingState.LOADING_VM_FILE_UPLOAD: + if (success) { if (canSave) { - return { - type: DONE_LOADING_VM_TO_SAVE - }; + return {type: DONE_LOADING_VM_TO_SAVE}; } - return { - type: DONE_LOADING_VM_WITHOUT_ID - }; - case LoadingState.LOADING_VM_NEW_DEFAULT: - return { - type: DONE_LOADING_VM_WITHOUT_ID - }; - default: - return; + return {type: DONE_LOADING_VM_WITHOUT_ID}; } + // failed to load project; just keep showing current project + return {type: RETURN_TO_SHOWING}; + case LoadingState.LOADING_VM_NEW_DEFAULT: + if (success) { + return {type: DONE_LOADING_VM_WITHOUT_ID}; + } + // failed to load default project; show error + return {type: START_ERROR}; + default: + return; } - return { - type: RETURN_TO_SHOWING - }; }; const doneUpdatingProject = loadingState => { diff --git a/test/unit/reducers/project-state-reducer.test.js b/test/unit/reducers/project-state-reducer.test.js index 2683c5beec159d4a08a86b003f15905071b82648..faf208bf4341b8114df38fd8e6543ecb430b8724 100644 --- a/test/unit/reducers/project-state-reducer.test.js +++ b/test/unit/reducers/project-state-reducer.test.js @@ -92,99 +92,181 @@ test('onFetchedProjectData new loads project data into vm', () => { expect(resultState.projectData).toBe('1010101'); }); -test('onLoadedProject upload, with canSave false, shows without id', () => { +// onLoadedProject: LOADING_VM_WITH_ID + +test('onLoadedProject(LOADING_VM_WITH_ID, true, true) results in state SHOWING_WITH_ID', () => { const initialState = { - loadingState: LoadingState.LOADING_VM_FILE_UPLOAD + projectId: '100', + loadingState: LoadingState.LOADING_VM_WITH_ID + }; + const action = onLoadedProject(initialState.loadingState, true, true); + const resultState = projectStateReducer(initialState, action); + expect(resultState.loadingState).toBe(LoadingState.SHOWING_WITH_ID); + expect(resultState.projectId).toBe('100'); +}); + +test('onLoadedProject(LOADING_VM_WITH_ID, false, true) results in state SHOWING_WITH_ID', () => { + const initialState = { + projectId: '100', + loadingState: LoadingState.LOADING_VM_WITH_ID }; const action = onLoadedProject(initialState.loadingState, false, true); const resultState = projectStateReducer(initialState, action); + expect(resultState.loadingState).toBe(LoadingState.SHOWING_WITH_ID); + expect(resultState.projectId).toBe('100'); +}); + +// case where we started out viewing a project with a projectId, then +// started to load another project; but loading fails. We go back to +// showing the original project. +test('onLoadedProject(LOADING_VM_WITH_ID, false, false), with project id, ' + + 'results in state SHOWING_WITH_ID', () => { + const initialState = { + projectId: '100', + loadingState: LoadingState.LOADING_VM_WITH_ID + }; + const action = onLoadedProject(initialState.loadingState, false, false); + const resultState = projectStateReducer(initialState, action); + expect(resultState.loadingState).toBe(LoadingState.SHOWING_WITH_ID); + expect(resultState.projectId).toBe('100'); +}); + +// case where we started out viewing a project with default projectId, then +// started to load one with an id, such as in standalone mode when user adds +// '#PROJECT_ID_NUMBER' to the URI; but loading fails. We go back to +// showing the original project. +test('onLoadedProject(LOADING_VM_WITH_ID, false, false), with no project id, ' + + 'results in state SHOWING_WITHOUT_ID', () => { + const initialState = { + projectId: '0', + loadingState: LoadingState.LOADING_VM_WITH_ID + }; + const action = onLoadedProject(initialState.loadingState, false, false); + const resultState = projectStateReducer(initialState, action); expect(resultState.loadingState).toBe(LoadingState.SHOWING_WITHOUT_ID); + expect(resultState.projectId).toBe('0'); }); -test('onLoadedProject upload, with canSave true, prepares to save', () => { +// onLoadedProject: LOADING_VM_FILE_UPLOAD + +test('onLoadedProject(LOADING_VM_FILE_UPLOAD, true, true) prepares to save', () => { const initialState = { + projectId: '100', loadingState: LoadingState.LOADING_VM_FILE_UPLOAD }; const action = onLoadedProject(initialState.loadingState, true, true); const resultState = projectStateReducer(initialState, action); expect(resultState.loadingState).toBe(LoadingState.AUTO_UPDATING); + expect(resultState.projectId).toBe('100'); }); -test('onLoadedProject with id shows with id', () => { +test('onLoadedProject(LOADING_VM_FILE_UPLOAD, false, true) results in state SHOWING_WITHOUT_ID', () => { const initialState = { - loadingState: LoadingState.LOADING_VM_WITH_ID + projectId: '0', + loadingState: LoadingState.LOADING_VM_FILE_UPLOAD }; - const action = onLoadedProject(initialState.loadingState, true, true); + const action = onLoadedProject(initialState.loadingState, false, true); + const resultState = projectStateReducer(initialState, action); + expect(resultState.loadingState).toBe(LoadingState.SHOWING_WITHOUT_ID); + expect(resultState.projectId).toBe('0'); +}); + +test('onLoadedProject(LOADING_VM_FILE_UPLOAD, false, false), when we know project id, ' + + 'results in state SHOWING_WITH_ID', () => { + const initialState = { + projectId: '100', + loadingState: LoadingState.LOADING_VM_FILE_UPLOAD + }; + const action = onLoadedProject(initialState.loadingState, false, false); const resultState = projectStateReducer(initialState, action); expect(resultState.loadingState).toBe(LoadingState.SHOWING_WITH_ID); + expect(resultState.projectId).toBe('100'); }); -test('onLoadedProject new shows without id', () => { +test('onLoadedProject(LOADING_VM_FILE_UPLOAD, false, false), when we ' + + 'don\'t know project id, results in state SHOWING_WITHOUT_ID', () => { const initialState = { - loadingState: LoadingState.LOADING_VM_NEW_DEFAULT + projectId: '0', + loadingState: LoadingState.LOADING_VM_FILE_UPLOAD }; - const action = onLoadedProject(initialState.loadingState, false, true); + const action = onLoadedProject(initialState.loadingState, false, false); const resultState = projectStateReducer(initialState, action); expect(resultState.loadingState).toBe(LoadingState.SHOWING_WITHOUT_ID); + expect(resultState.projectId).toBe('0'); }); -test('onLoadedProject new, to save shows without id', () => { +// onLoadedProject: LOADING_VM_NEW_DEFAULT + +test('onLoadedProject(LOADING_VM_NEW_DEFAULT, true, true) results in state SHOWING_WITHOUT_ID', () => { const initialState = { + projectId: '0', loadingState: LoadingState.LOADING_VM_NEW_DEFAULT }; const action = onLoadedProject(initialState.loadingState, true, true); const resultState = projectStateReducer(initialState, action); expect(resultState.loadingState).toBe(LoadingState.SHOWING_WITHOUT_ID); + expect(resultState.projectId).toBe('0'); }); -test('onLoadedProject with success false, no project id, shows without id', () => { +test('onLoadedProject(LOADING_VM_NEW_DEFAULT, false, true) results in state SHOWING_WITHOUT_ID', () => { const initialState = { - loadingState: LoadingState.LOADING_VM_WITH_ID, - projectId: null + projectId: '0', + loadingState: LoadingState.LOADING_VM_NEW_DEFAULT }; - const action = onLoadedProject(initialState.loadingState, false, false); + const action = onLoadedProject(initialState.loadingState, false, true); const resultState = projectStateReducer(initialState, action); expect(resultState.loadingState).toBe(LoadingState.SHOWING_WITHOUT_ID); + expect(resultState.projectId).toBe('0'); }); -test('onLoadedProject with success false, valid project id, shows with id', () => { +test('onLoadedProject(LOADING_VM_NEW_DEFAULT, false, false) results in ERROR state', () => { const initialState = { - loadingState: LoadingState.LOADING_VM_WITH_ID, - projectId: '12345' + projectId: '0', + loadingState: LoadingState.LOADING_VM_NEW_DEFAULT }; const action = onLoadedProject(initialState.loadingState, false, false); const resultState = projectStateReducer(initialState, action); - expect(resultState.loadingState).toBe(LoadingState.SHOWING_WITH_ID); + expect(resultState.loadingState).toBe(LoadingState.ERROR); + expect(resultState.projectId).toBe('0'); }); -test('doneUpdatingProject with id shows with id', () => { +// doneUpdatingProject + +test('doneUpdatingProject with id results in state SHOWING_WITH_ID', () => { const initialState = { + projectId: '100', loadingState: LoadingState.MANUAL_UPDATING }; const action = doneUpdatingProject(initialState.loadingState); const resultState = projectStateReducer(initialState, action); expect(resultState.loadingState).toBe(LoadingState.SHOWING_WITH_ID); + expect(resultState.projectId).toBe('100'); }); -test('doneUpdatingProject with id, before copy, creates copy', () => { +test('doneUpdatingProject with id, before copy occurs, results in state CREATING_COPY', () => { const initialState = { + projectId: '100', loadingState: LoadingState.UPDATING_BEFORE_COPY }; const action = doneUpdatingProject(initialState.loadingState); const resultState = projectStateReducer(initialState, action); expect(resultState.loadingState).toBe(LoadingState.CREATING_COPY); + expect(resultState.projectId).toBe('100'); }); -test('doneUpdatingProject with id, before new, fetches default project', () => { +test('doneUpdatingProject with id, before new, results in state FETCHING_NEW_DEFAULT', () => { const initialState = { + projectId: '100', loadingState: LoadingState.UPDATING_BEFORE_NEW }; const action = doneUpdatingProject(initialState.loadingState); const resultState = projectStateReducer(initialState, action); expect(resultState.loadingState).toBe(LoadingState.FETCHING_NEW_DEFAULT); + expect(resultState.projectId).toBe('0'); // resets id }); -test('setProjectId, with same id as before, should show with id, not fetch', () => { +test('calling setProjectId, using with same id as already showing, ' + + 'results in state SHOWING_WITH_ID', () => { const initialState = { projectId: '100', loadingState: LoadingState.SHOWING_WITH_ID @@ -195,7 +277,8 @@ test('setProjectId, with same id as before, should show with id, not fetch', () expect(resultState.projectId).toBe('100'); }); -test('setProjectId, with different id as before, should fetch with id, not show with id', () => { +test('calling setProjectId, using different id from project already showing, ' + + 'results in state FETCHING_WITH_ID', () => { const initialState = { projectId: 99, loadingState: LoadingState.SHOWING_WITH_ID @@ -206,7 +289,8 @@ test('setProjectId, with different id as before, should fetch with id, not show expect(resultState.projectId).toBe('100'); }); -test('setProjectId, with same id as before, but not same type, should fetch because not ===', () => { +test('setProjectId, with same id as before, but not same type, ' + + 'results in FETCHING_WITH_ID because the two projectIds are not strictly equal', () => { const initialState = { projectId: '100', loadingState: LoadingState.SHOWING_WITH_ID @@ -217,85 +301,104 @@ test('setProjectId, with same id as before, but not same type, should fetch beca expect(resultState.projectId).toBe(100); }); -test('requestNewProject, when can\'t create new, should fetch default project without id', () => { +test('requestNewProject, when can\'t create/save, results in FETCHING_NEW_DEFAULT', () => { const initialState = { + projectId: '0', loadingState: LoadingState.SHOWING_WITHOUT_ID }; const action = requestNewProject(false); const resultState = projectStateReducer(initialState, action); expect(resultState.loadingState).toBe(LoadingState.FETCHING_NEW_DEFAULT); + expect(resultState.projectId).toBe('0'); }); -test('requestNewProject, when can create new, should save and prepare to fetch default project', () => { +test('requestNewProject, when can create/save, results in UPDATING_BEFORE_NEW ' + + '(in order to save before fetching default project)', () => { const initialState = { + projectId: '100', loadingState: LoadingState.SHOWING_WITH_ID }; const action = requestNewProject(true); const resultState = projectStateReducer(initialState, action); expect(resultState.loadingState).toBe(LoadingState.UPDATING_BEFORE_NEW); + expect(resultState.projectId).toBe('100'); }); -test('requestProjectUpload when project not loaded should load', () => { +test('requestProjectUpload when project not loaded results in state LOADING_VM_FILE_UPLOAD', () => { const initialState = { + projectId: null, loadingState: LoadingState.NOT_LOADED }; const action = requestProjectUpload(initialState.loadingState); const resultState = projectStateReducer(initialState, action); expect(resultState.loadingState).toBe(LoadingState.LOADING_VM_FILE_UPLOAD); + expect(resultState.projectId).toBe(null); }); -test('requestProjectUpload when showing project with id should load', () => { +test('requestProjectUpload when showing project with id results in state LOADING_VM_FILE_UPLOAD', () => { const initialState = { + projectId: '100', loadingState: LoadingState.SHOWING_WITH_ID }; const action = requestProjectUpload(initialState.loadingState); const resultState = projectStateReducer(initialState, action); expect(resultState.loadingState).toBe(LoadingState.LOADING_VM_FILE_UPLOAD); + expect(resultState.projectId).toBe('100'); }); -test('requestProjectUpload when showing project without id should load', () => { +test('requestProjectUpload when showing project without id results in state LOADING_VM_FILE_UPLOAD', () => { const initialState = { + projectId: null, loadingState: LoadingState.SHOWING_WITHOUT_ID }; const action = requestProjectUpload(initialState.loadingState); const resultState = projectStateReducer(initialState, action); expect(resultState.loadingState).toBe(LoadingState.LOADING_VM_FILE_UPLOAD); + expect(resultState.projectId).toBe(null); }); test('manualUpdateProject should prepare to update', () => { const initialState = { + projectId: '100', loadingState: LoadingState.SHOWING_WITH_ID }; const action = manualUpdateProject(); const resultState = projectStateReducer(initialState, action); expect(resultState.loadingState).toBe(LoadingState.MANUAL_UPDATING); + expect(resultState.projectId).toBe('100'); }); test('autoUpdateProject should prepare to update', () => { const initialState = { + projectId: '100', loadingState: LoadingState.SHOWING_WITH_ID }; const action = autoUpdateProject(); const resultState = projectStateReducer(initialState, action); expect(resultState.loadingState).toBe(LoadingState.AUTO_UPDATING); + expect(resultState.projectId).toBe('100'); }); test('saveProjectAsCopy should save, before preparing to save as a copy', () => { const initialState = { + projectId: '100', loadingState: LoadingState.SHOWING_WITH_ID }; const action = saveProjectAsCopy(); const resultState = projectStateReducer(initialState, action); expect(resultState.loadingState).toBe(LoadingState.UPDATING_BEFORE_COPY); + expect(resultState.projectId).toBe('100'); }); test('remixProject should prepare to remix', () => { const initialState = { + projectId: '100', loadingState: LoadingState.SHOWING_WITH_ID }; const action = remixProject(); const resultState = projectStateReducer(initialState, action); expect(resultState.loadingState).toBe(LoadingState.REMIXING); + expect(resultState.projectId).toBe('100'); }); test('projectError from various states should show error', () => { @@ -314,11 +417,13 @@ test('projectError from various states should show error', () => { for (const startState of startStates) { const initialState = { error: null, + projectId: '100', loadingState: startState }; const action = projectError('Error string'); const resultState = projectStateReducer(initialState, action); expect(resultState.error).toEqual('Error string'); + expect(resultState.projectId).toBe('100'); } }); @@ -332,11 +437,13 @@ test('fatal projectError should show error state', () => { for (const startState of startStates) { const initialState = { error: null, + projectId: '100', loadingState: startState }; const action = projectError('Error string'); const resultState = projectStateReducer(initialState, action); expect(resultState.loadingState).toBe(LoadingState.ERROR); + expect(resultState.projectId).toBe('100'); } }); @@ -351,15 +458,18 @@ test('non-fatal projectError should show normal state', () => { for (const startState of startStates) { const initialState = { error: null, + projectId: '100', loadingState: startState }; const action = projectError('Error string'); const resultState = projectStateReducer(initialState, action); expect(resultState.loadingState).toBe(LoadingState.SHOWING_WITH_ID); + expect(resultState.projectId).toBe('100'); } }); -test('projectError when creating new while viewing project with id should show project with id', () => { +test('projectError when creating new while viewing project with id should ' + + 'go back to state SHOWING_WITH_ID', () => { const initialState = { error: null, loadingState: LoadingState.CREATING_NEW, @@ -368,9 +478,11 @@ test('projectError when creating new while viewing project with id should show p const action = projectError('Error string'); const resultState = projectStateReducer(initialState, action); expect(resultState.loadingState).toBe(LoadingState.SHOWING_WITH_ID); + expect(resultState.projectId).toBe('12345'); }); -test('projectError when creating new while logged out, looking at default project should show default project', () => { +test('projectError when creating new while logged out, looking at default project ' + + 'should go back to state SHOWING_WITHOUT_ID', () => { const initialState = { error: null, loadingState: LoadingState.CREATING_NEW, @@ -379,15 +491,19 @@ test('projectError when creating new while logged out, looking at default projec const action = projectError('Error string'); const resultState = projectStateReducer(initialState, action); expect(resultState.loadingState).toBe(LoadingState.SHOWING_WITHOUT_ID); + expect(resultState.projectId).toBe('0'); }); -test('projectError from showing project should show error', () => { +test('projectError encountered while in state FETCHING_WITH_ID results in ' + + 'ERROR state', () => { const initialState = { error: null, + projectId: null, loadingState: LoadingState.FETCHING_WITH_ID }; const action = projectError('Error string'); const resultState = projectStateReducer(initialState, action); expect(resultState.loadingState).toBe(LoadingState.ERROR); + expect(resultState.projectId).toBe(null); expect(resultState.error).toEqual('Error string'); }); diff --git a/test/unit/util/project-saver-hoc.test.jsx b/test/unit/util/project-saver-hoc.test.jsx index 0ec3190bf773fdf74aa6cc39c671b27e0df32beb..f5ac7b680e669c758c6af021643be10fc4d35692 100644 --- a/test/unit/util/project-saver-hoc.test.jsx +++ b/test/unit/util/project-saver-hoc.test.jsx @@ -56,7 +56,7 @@ describe('projectSaverHOC', () => { expect(mockedUpdateProject).toHaveBeenCalled(); }); - test('if canSave is alreatdy true and we show a project with an id, project will NOT be saved', () => { + test('if canSave is already true and we show a project with an id, project will NOT be saved', () => { const mockedSaveProject = jest.fn(); const Component = () => <div />; const WrappedComponent = projectSaverHOC(Component);