diff --git a/README.md b/README.md index d5f75a62d2caa98e27e121c428e6f51de21898f3..1f49dca4f60a4fd3c0f2039944a8c81dadbf7e5d 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,12 @@ To run unit tests in watch mode (watches for code changes and continuously runs npm run test:unit -- --watch ``` +You can run a single file of integration tests (in this example, the `button` tests): + +```bash +$(npm bin)/jest --runInBand test/unit/components/button.test.jsx +``` + #### Running integration tests Integration tests use a headless browser to manipulate the actual html and javascript that the repo diff --git a/src/lib/hash-parser-hoc.jsx b/src/lib/hash-parser-hoc.jsx index 2cb90a526c5e73dfc422e813a5742584a40b1bbb..611f6769923f81e02489b1431d1b9874abdf8a1f 100644 --- a/src/lib/hash-parser-hoc.jsx +++ b/src/lib/hash-parser-hoc.jsx @@ -42,7 +42,7 @@ const HashParserHOC = function (WrappedComponent) { handleHashChange () { const hashMatch = window.location.hash.match(/#(\d+)/); const hashProjectId = hashMatch === null ? defaultProjectId : hashMatch[1]; - this.props.setProjectId(hashProjectId); + this.props.setProjectId(hashProjectId.toString()); if (hashProjectId !== defaultProjectId) { this.setState({hideIntro: true}); } diff --git a/src/lib/project-fetcher-hoc.jsx b/src/lib/project-fetcher-hoc.jsx index 4d850b68109fbff61b4842f2bae3ca396718e7f1..884a77cfc6885a6326017f28c3d63a368b64ab76 100644 --- a/src/lib/project-fetcher-hoc.jsx +++ b/src/lib/project-fetcher-hoc.jsx @@ -40,7 +40,7 @@ const ProjectFetcherHOC = function (WrappedComponent) { props.projectId !== null && typeof props.projectId !== 'undefined' ) { - this.props.setProjectId(props.projectId); + this.props.setProjectId(props.projectId.toString()); } } componentDidUpdate (prevProps) { diff --git a/src/lib/project-saver-hoc.jsx b/src/lib/project-saver-hoc.jsx index 065a0602abbe568af67d32f775db195683860b09..83ccd12c1526cab5f23fb5a91fb0939377d9c2e6 100644 --- a/src/lib/project-saver-hoc.jsx +++ b/src/lib/project-saver-hoc.jsx @@ -39,7 +39,7 @@ const ProjectSaverHOC = function (WrappedComponent) { if (this.props.isCreating && !prevProps.isCreating) { this.storeProject() .then(response => { - this.props.onCreated(response.id); + this.props.onCreated(response.id.toString()); }) .catch(err => { // NOTE: should throw up a notice for user diff --git a/src/reducers/project-state.js b/src/reducers/project-state.js index b69cfc5244d93c4fa59e493dc5d47ed61417cfba..56e5bb1284dd8a030e4de74660dc5585a7c5fd0d 100644 --- a/src/reducers/project-state.js +++ b/src/reducers/project-state.js @@ -17,7 +17,7 @@ const START_LOADING_VM_FILE_UPLOAD = 'scratch-gui/project-state/START_LOADING_FI 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 defaultProjectId = 0; // hardcoded id of default project +const defaultProjectId = '0'; // hardcoded id of default project const LoadingState = keyMirror({ NOT_LOADED: null, @@ -83,7 +83,7 @@ const reducer = function (state, action) { if (state.loadingState === LoadingState.CREATING_NEW) { return Object.assign({}, state, { loadingState: LoadingState.SHOWING_WITH_ID, - id: action.id + projectId: action.id }); } return state; @@ -160,8 +160,8 @@ const reducer = function (state, action) { } return state; case SET_PROJECT_ID: - // if we were already showing something, only fetch project if the - // project id has changed. This prevents re-fetching projects unnecessarily. + // 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) { return Object.assign({}, state, { diff --git a/test/unit/reducers/project-state-reducer.test.js b/test/unit/reducers/project-state-reducer.test.js new file mode 100644 index 0000000000000000000000000000000000000000..920999ffc45e4678731cdac028ebbd79ac1a7c2e --- /dev/null +++ b/test/unit/reducers/project-state-reducer.test.js @@ -0,0 +1,242 @@ +/* eslint-env jest */ +import projectStateReducer from '../../../src/reducers/project-state'; +import { + LoadingState, + onCreated, + onError, + onFetchedProjectData, + onLoadedProject, + onProjectUploadStarted, + onUpdated, + requestNewProject, + saveProject, + setProjectId +} from '../../../src/reducers/project-state'; + +test('initialState', () => { + let defaultState; + /* projectStateReducer(state, action) */ + expect(projectStateReducer(defaultState, {type: 'anything'})).toBeDefined(); + expect(projectStateReducer(defaultState, {type: 'anything'}).errStr).toBe(null); + expect(projectStateReducer(defaultState, {type: 'anything'}).projectData).toBe(null); + expect(projectStateReducer(defaultState, {type: 'anything'}).projectId).toBe(null); + expect(projectStateReducer(defaultState, {type: 'anything'}).loadingState).toBe(LoadingState.NOT_LOADED); +}); + +test('onCreated with projectId type string shows project with that id', () => { + const initialState = { + projectId: null, + loadingState: LoadingState.CREATING_NEW + }; + const action = onCreated('100'); + 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', () => { + const initialState = { + projectId: null, + loadingState: LoadingState.CREATING_NEW + }; + const action = onCreated(100); + 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, + loadingState: LoadingState.FETCHING_WITH_ID + }; + const action = onFetchedProjectData('1010101', initialState.loadingState); + const resultState = projectStateReducer(initialState, action); + expect(resultState.loadingState).toBe(LoadingState.LOADING_VM_WITH_ID); + expect(resultState.projectData).toBe('1010101'); +}); + +test('onFetchedProjectData new loads project data into vm', () => { + const initialState = { + projectData: null, + loadingState: LoadingState.FETCHING_NEW_DEFAULT + }; + const action = onFetchedProjectData('1010101', initialState.loadingState); + const resultState = projectStateReducer(initialState, action); + expect(resultState.loadingState).toBe(LoadingState.LOADING_VM_NEW_DEFAULT); + expect(resultState.projectData).toBe('1010101'); +}); + +test('onFetchedProjectData new, to save loads project data into vm, prepares to save next', () => { + const initialState = { + projectData: null, + loadingState: LoadingState.FETCHING_NEW_DEFAULT_TO_SAVE + }; + const action = onFetchedProjectData('1010101', initialState.loadingState); + const resultState = projectStateReducer(initialState, action); + expect(resultState.loadingState).toBe(LoadingState.LOADING_VM_NEW_DEFAULT_TO_SAVE); + expect(resultState.projectData).toBe('1010101'); +}); + +test('onLoadedProject upload shows without id', () => { + const initialState = { + loadingState: LoadingState.LOADING_VM_FILE_UPLOAD + }; + const action = onLoadedProject(initialState.loadingState); + const resultState = projectStateReducer(initialState, action); + expect(resultState.loadingState).toBe(LoadingState.SHOWING_WITHOUT_ID); +}); + +test('onLoadedProject with id shows with id', () => { + const initialState = { + loadingState: LoadingState.LOADING_VM_WITH_ID + }; + const action = onLoadedProject(initialState.loadingState); + const resultState = projectStateReducer(initialState, action); + expect(resultState.loadingState).toBe(LoadingState.SHOWING_WITH_ID); +}); + +test('onLoadedProject new shows without id', () => { + const initialState = { + loadingState: LoadingState.LOADING_VM_NEW_DEFAULT + }; + const action = onLoadedProject(initialState.loadingState); + const resultState = projectStateReducer(initialState, action); + expect(resultState.loadingState).toBe(LoadingState.SHOWING_WITHOUT_ID); +}); + +test('onLoadedProject new, to save shows with id', () => { + const initialState = { + loadingState: LoadingState.LOADING_VM_NEW_DEFAULT_TO_SAVE + }; + const action = onLoadedProject(initialState.loadingState); + const resultState = projectStateReducer(initialState, action); + expect(resultState.loadingState).toBe(LoadingState.SHOWING_WITH_ID); +}); + +test('onUpdated with id shows with id', () => { + const initialState = { + loadingState: LoadingState.SAVING_WITH_ID + }; + const action = onUpdated(initialState.loadingState); + const resultState = projectStateReducer(initialState, action); + expect(resultState.loadingState).toBe(LoadingState.SHOWING_WITH_ID); +}); + +test('onUpdated with id, before new, fetches default project', () => { + const initialState = { + loadingState: LoadingState.SAVING_WITH_ID_BEFORE_NEW + }; + const action = onUpdated(initialState.loadingState); + const resultState = projectStateReducer(initialState, action); + expect(resultState.loadingState).toBe(LoadingState.FETCHING_NEW_DEFAULT_TO_SAVE); +}); + +test('setProjectId, with same id as before, should show with id, not fetch', () => { + const initialState = { + projectId: '100', + loadingState: LoadingState.SHOWING_WITH_ID + }; + const action = setProjectId('100'); + const resultState = projectStateReducer(initialState, action); + expect(resultState.loadingState).toBe(LoadingState.SHOWING_WITH_ID); + expect(resultState.projectId).toBe('100'); +}); + +test('setProjectId, with different id as before, should fetch with id, not show with id', () => { + const initialState = { + projectId: 99, + loadingState: LoadingState.SHOWING_WITH_ID + }; + const action = setProjectId('100'); + const resultState = projectStateReducer(initialState, action); + expect(resultState.loadingState).toBe(LoadingState.FETCHING_WITH_ID); + expect(resultState.projectId).toBe('100'); +}); + +test('setProjectId, with same id as before, but not same type, should fetch because not ===', () => { + const initialState = { + projectId: '100', + loadingState: LoadingState.SHOWING_WITH_ID + }; + const action = setProjectId(100); + const resultState = projectStateReducer(initialState, action); + expect(resultState.loadingState).toBe(LoadingState.FETCHING_WITH_ID); + expect(resultState.projectId).toBe(100); +}); + +test('requestNewProject, when can\'t save, should fetch default project without id', () => { + const initialState = { + loadingState: LoadingState.SHOWING_WITHOUT_ID + }; + const action = requestNewProject(false); + const resultState = projectStateReducer(initialState, action); + expect(resultState.loadingState).toBe(LoadingState.FETCHING_NEW_DEFAULT); +}); + +test('requestNewProject, when can save, 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); +}); + +test('onProjectUploadStarted when project not loaded should load', () => { + const initialState = { + loadingState: LoadingState.NOT_LOADED + }; + const action = onProjectUploadStarted(); + const resultState = projectStateReducer(initialState, action); + expect(resultState.loadingState).toBe(LoadingState.LOADING_VM_FILE_UPLOAD); +}); + +test('onProjectUploadStarted when showing project with id should load', () => { + const initialState = { + loadingState: LoadingState.SHOWING_WITH_ID + }; + const action = onProjectUploadStarted(); + const resultState = projectStateReducer(initialState, action); + expect(resultState.loadingState).toBe(LoadingState.LOADING_VM_FILE_UPLOAD); +}); + +test('onProjectUploadStarted when showing project without id should load', () => { + const initialState = { + loadingState: LoadingState.SHOWING_WITHOUT_ID + }; + const action = onProjectUploadStarted(); + const resultState = projectStateReducer(initialState, action); + expect(resultState.loadingState).toBe(LoadingState.LOADING_VM_FILE_UPLOAD); +}); + +test('saveProject should prepare to save', () => { + const initialState = { + loadingState: LoadingState.SHOWING_WITH_ID + }; + const action = saveProject(); + const resultState = projectStateReducer(initialState, action); + expect(resultState.loadingState).toBe(LoadingState.SAVING_WITH_ID); +}); + +test('onError from unloaded state should show error', () => { + const initialState = { + errStr: null, + loadingState: LoadingState.NOT_LOADED + }; + const action = onError('Error string'); + const resultState = projectStateReducer(initialState, action); + expect(resultState.loadingState).toBe(LoadingState.ERROR); + expect(resultState.errStr).toBe('Error string'); +}); + +test('onError from showing project should show error', () => { + const initialState = { + errStr: null, + loadingState: LoadingState.FETCHING_WITH_ID + }; + const action = onError('Error string'); + const resultState = projectStateReducer(initialState, action); + expect(resultState.loadingState).toBe(LoadingState.ERROR); + expect(resultState.errStr).toBe('Error string'); +}); diff --git a/test/unit/util/hash-project-loader-hoc.test.jsx b/test/unit/util/hash-project-loader-hoc.test.jsx index a78f43475f0d416ecfd2f48efbb5108b4872c3b4..208d1db16dfef950fbea90f38479b02f1b8cad1f 100644 --- a/test/unit/util/hash-project-loader-hoc.test.jsx +++ b/test/unit/util/hash-project-loader-hoc.test.jsx @@ -43,7 +43,7 @@ describe('HashParserHOC', () => { store={store} /> ); - expect(mockSetProjectIdFunc.mock.calls[0][0]).toBe(0); + expect(mockSetProjectIdFunc.mock.calls[0][0]).toBe('0'); }); test('when the hash is not a number, it passes 0 as projectId', () => { @@ -57,7 +57,7 @@ describe('HashParserHOC', () => { store={store} /> ); - expect(mockSetProjectIdFunc.mock.calls[0][0]).toBe(0); + expect(mockSetProjectIdFunc.mock.calls[0][0]).toBe('0'); }); test('when hash change happens, the projectId state is changed', () => {