Skip to content

Commit

Permalink
Redux logic errors (#1954)
Browse files Browse the repository at this point in the history
* Start migration updateProjectSource saga

* Update updateProjectSource

* Use redux-logic for errors

* Remove errors sagas unit tests

* Reorganize errors logic

* Start validateProjectOnChange tests

* Finish validateProjectOnChange specs

* Write test for validateSource helper

* Start validateCurrentProject test

* Finish validateProjectOnChange tests

* Refactor validateSource logic tests

* Lint validateCurrentProject and validateProjectOnChange

* Improve validateCurrentProject and validateProjectOnChange tests

* Ensure that only current validations are dispatched

* Lint errors logic

* Start validateProject logic

* Refactor errors logic to use validateProject

* Run lint

* Refactor validateProject

Co-authored-by: Mat Brown <[email protected]>
  • Loading branch information
joshling1919 and outoftime authored Jan 31, 2020
1 parent 4674d7d commit 3314044
Show file tree
Hide file tree
Showing 8 changed files with 173 additions and 233 deletions.
11 changes: 11 additions & 0 deletions src/logic/__tests__/helpers.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import noop from 'lodash-es/noop';
import reduce from 'lodash-es/reduce';
import {createLogicMiddleware} from 'redux-logic';
import configureStore from 'redux-mock-store';
import {first} from 'rxjs/operators';

import rootReducer from '../../reducers';

export function makeTestLogic(logic) {
return async (action, {state = {}, afterDispatch = noop} = {}) => {
const logicMiddleware = createLogicMiddleware([logic]);
Expand All @@ -25,3 +28,11 @@ export function makeTestLogic(logic) {
return dispatch;
};
}

export function applyActions(...actions) {
return reduce(
actions,
(state, action) => rootReducer(state, action),
undefined,
);
}
13 changes: 1 addition & 12 deletions src/logic/__tests__/startAccountMigration.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import reduce from 'lodash-es/reduce';

import {
accountMigrationComplete,
accountMigrationError,
Expand All @@ -10,11 +8,10 @@ import {
userAuthenticated,
} from '../../actions/user';
import {migrateAccount} from '../../clients/firebase';
import rootReducer from '../../reducers';
import {bugsnagClient} from '../../util/bugsnag';
import startAccountMigration from '../startAccountMigration';

import {makeTestLogic} from './helpers';
import {applyActions, makeTestLogic} from './helpers';

import {
credentialFactory,
Expand Down Expand Up @@ -111,11 +108,3 @@ describe('startAccountMigration', () => {
expect(migrateAccount).not.toHaveBeenCalledWith(mockCredential);
});
});

function applyActions(...actions) {
return reduce(
actions,
(state, action) => rootReducer(state, action),
undefined,
);
}
78 changes: 78 additions & 0 deletions src/logic/__tests__/validateProject.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import {
projectRestoredFromLastSession as projectRestoredFromLastSessionAction,
snapshotImported as snapshotImportedAction,
} from '../../actions/clients';
import {validatedSource} from '../../actions/errors';
import {
changeCurrentProject as changeCurrentProjectAction,
gistImported as gistImportedAction,
toggleLibrary as toggleLibraryAction,
updateProjectSource as updateProjectSourceAction,
} from '../../actions/projects';
import validateProject from '../validateProject';

import {applyActions, makeTestLogic} from './helpers';

import {firebaseProjectFactory} from '@factories/data/firebase';
import {consoleErrorFactory} from '@factories/validations/errors';

jest.mock('../../analyzers');

const mockCssValidationErrors = [
consoleErrorFactory.build({
text: 'You have a starting { but no ending } to go with it.',
}),
];

const mockHtmlValidationErrors = [
consoleErrorFactory.build({
text: 'Closing tag missing',
}),
];

jest.mock('../../validations', () => ({
css: jest.fn(() => mockCssValidationErrors),
html: jest.fn(() => mockHtmlValidationErrors),
javascript: jest.fn(() => []),
}));

const testLogic = makeTestLogic(validateProject);
for (const action of [
changeCurrentProjectAction,
gistImportedAction,
snapshotImportedAction,
projectRestoredFromLastSessionAction,
toggleLibraryAction,
]) {
test(`validates current project on ${action}`, async () => {
const mockProject = firebaseProjectFactory.build();
const state = applyActions(
projectRestoredFromLastSessionAction(mockProject),
);

const dispatch = await testLogic(action(mockProject.projectKey), {
state,
});

expect(dispatch).toHaveBeenCalledWith(
validatedSource('html', mockHtmlValidationErrors),
);
expect(dispatch).toHaveBeenCalledWith(
validatedSource('css', mockCssValidationErrors),
);
});
}

test('UPDATE_PROJECT_SOURCE should validate newSource', async () => {
const mockProject = firebaseProjectFactory.build();
const state = applyActions(projectRestoredFromLastSessionAction(mockProject));

const dispatch = await testLogic(
updateProjectSourceAction(mockProject.projectKey, 'css', 'div {'),
{state},
);

expect(dispatch).toHaveBeenCalledWith(
validatedSource('css', mockCssValidationErrors),
);
});
6 changes: 4 additions & 2 deletions src/logic/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@ import projectSuccessfullySaved from './projectSuccessfullySaved';
import saveProject from './saveProject';
import startAccountMigration from './startAccountMigration';
import unlinkGithubIdentity from './unlinkGithubIdentity';
import validateProject from './validateProject';

export default [
instrumentApplicationLoaded,
instrumentEnvironmentReady,
linkGithubIdentity,
logout,
startAccountMigration,
unlinkGithubIdentity,
projectSuccessfullySaved,
saveProject,
startAccountMigration,
unlinkGithubIdentity,
validateProject,
];
79 changes: 79 additions & 0 deletions src/logic/validateProject.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import map from 'lodash-es/map';
import {createLogic} from 'redux-logic';

import {validatedSource} from '../actions/errors';
import Analyzer from '../analyzers';
import {getCurrentProject} from '../selectors';
import retryingFailedImports from '../util/retryingFailedImports';

function importValidations() {
return retryingFailedImports(() =>
import(
/* webpackChunkName: "mainAsync" */
'../validations'
),
);
}

async function validateSource({language, source, projectAttributes}, dispatch) {
const validations = await importValidations();
const validate = validations[language];
const validationErrors = await validate(source, projectAttributes);
dispatch(validatedSource(language, validationErrors));
}

async function validateSources({sources, projectAttributes}, dispatch) {
const validatePromises = map(Reflect.ownKeys(sources), language =>
validateSource(
{
language,
source: sources[language],
projectAttributes,
},
dispatch,
),
);

await Promise.all(validatePromises);
}

export default createLogic({
type: [
'CHANGE_CURRENT_PROJECT',
'GIST_IMPORTED',
'SNAPSHOT_IMPORTED',
'PROJECT_RESTORED_FROM_LAST_SESSION',
'TOGGLE_LIBRARY',
'UPDATE_PROJECT_SOURCE',
],
latest: true,
async process(
{
getState,
action: {
type,
payload: {language, newValue},
},
},
dispatch,
done,
) {
const state = getState();
const currentProject = getCurrentProject(state);
const projectAttributes = new Analyzer(currentProject);

if (type === 'UPDATE_PROJECT_SOURCE') {
await validateSource(
{language, source: newValue, projectAttributes},
dispatch,
);
} else {
await validateSources(
{sources: currentProject.sources, projectAttributes},
dispatch,
);
}

done();
},
});
80 changes: 0 additions & 80 deletions src/sagas/errors.js

This file was deleted.

2 changes: 0 additions & 2 deletions src/sagas/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import {all} from 'redux-saga/effects';

import manageUserState from './manageUserState';
import watchErrors from './errors';
import watchProjects from './projects';
import watchUi from './ui';
import watchClients from './clients';
Expand All @@ -10,7 +9,6 @@ import watchCompiledProjects from './compiledProjects';
export default function* rootSaga() {
yield all([
manageUserState(),
watchErrors(),
watchProjects(),
watchUi(),
watchClients(),
Expand Down
Loading

0 comments on commit 3314044

Please sign in to comment.