Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Redux logic errors #1954

Merged
merged 28 commits into from
Jan 31, 2020
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
4168920
Start migration updateProjectSource saga
joshling1919 Nov 13, 2019
c26af06
Update updateProjectSource
joshling1919 Nov 15, 2019
fe8d719
Use redux-logic for errors
joshling1919 Nov 16, 2019
9bf3a0d
Remove errors sagas unit tests
joshling1919 Nov 16, 2019
1227492
Reorganize errors logic
joshling1919 Dec 2, 2019
e40ff0f
Start validateProjectOnChange tests
joshling1919 Dec 3, 2019
982f9d4
Finish validateProjectOnChange specs
joshling1919 Dec 5, 2019
e429d3b
Write test for validateSource helper
joshling1919 Dec 6, 2019
7a6faef
Start validateCurrentProject test
joshling1919 Dec 7, 2019
4e4e64c
Finish validateProjectOnChange tests
joshling1919 Dec 10, 2019
adfd45b
Merge branch 'master' of https://github.com/popcodeorg/popcode into r…
joshling1919 Jan 9, 2020
9d7a55c
Refactor validateSource logic tests
joshling1919 Jan 13, 2020
1080acb
Lint validateCurrentProject and validateProjectOnChange
joshling1919 Jan 13, 2020
33eb932
Improve validateCurrentProject and validateProjectOnChange tests
joshling1919 Jan 13, 2020
80cf62f
Ensure that only current validations are dispatched
joshling1919 Jan 13, 2020
9a6a751
Merge branch 'master' of https://github.com/popcodeorg/popcode into r…
joshling1919 Jan 14, 2020
12b20a0
Merge branch 'master' of https://github.com/popcodeorg/popcode into r…
joshling1919 Jan 15, 2020
99c37ca
Lint errors logic
joshling1919 Jan 15, 2020
c2be8db
Merge branch 'master' of https://github.com/popcodeorg/popcode into r…
joshling1919 Jan 23, 2020
5520295
Start validateProject logic
joshling1919 Jan 23, 2020
23c2613
Refactor errors logic to use validateProject
joshling1919 Jan 25, 2020
c81b993
Merge branch 'master' of https://github.com/popcodeorg/popcode into r…
joshling1919 Jan 25, 2020
c12991d
Run lint
joshling1919 Jan 25, 2020
5a567a8
Merge branch 'master' into redux-logic-errors
outoftime Jan 25, 2020
3b666dd
Refactor validateProject
joshling1919 Jan 27, 2020
fbe3d0c
Merge branch 'redux-logic-errors' of https://github.com/joshling1919/…
joshling1919 Jan 27, 2020
8826d1e
Merge branch 'master' of https://github.com/popcodeorg/popcode into r…
joshling1919 Jan 27, 2020
43505c8
Merge branch 'master' into redux-logic-errors
outoftime Jan 31, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions src/logic/__tests__/validateCurrentProject.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import {fromJS} from 'immutable';

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,
} from '../../actions/projects';
import validateCurrentProject from '../validateCurrentProject';

import {makeTestLogic} from './helpers';

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

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

const mockHtmlValidationErrors = [
{
text: 'Closing tag missing',
},
];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a pretty good candidate for a factory? Generally any time we need sample data to use in a test that conforms to a particular well-defined shape, a factory is the move (since it’s then reusable and rosie makes it super-easy to customize the generated objects for the needs of particular tests)


jest.mock('../../util/retryingFailedImports', () =>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we mock the validations module itself rather than the retryingFailedImports utility that we use to load it?

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

test('should validate current project', async () => {
const state = fromJS({
currentProject: {projectKey: '123'},
projects: {
123: {
sources: {
html: '',
css: '',
javascript: '',
},
},
},
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally when we’re testing against a particular starting state, I think it’s best to generate that state by running a series of actions through a reducer, rather than hand-rolling it—that way if we make changes to the state shape, updating the reducers will automatically update all the tests. Here’s a good example from another logic test.

const testLogic = makeTestLogic(validateCurrentProject);

[
changeCurrentProjectAction,
gistImportedAction,
snapshotImportedAction,
projectRestoredFromLastSessionAction,
toggleLibraryAction,
].forEach(async action => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EXTREME NITPICK: Why not for…of? 🙂

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry, totally missed this comment

const dispatch = await testLogic(action('123'), {
state,
});

expect(dispatch).toHaveBeenCalledWith(
validatedSource('html', mockHtmlValidationErrors),
);
expect(dispatch).toHaveBeenCalledWith(
validatedSource('css', mockCssValidationErrors),
);
});
});
48 changes: 48 additions & 0 deletions src/logic/__tests__/validateProjectOnChange.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import {fromJS} from 'immutable';

import {validatedSource} from '../../actions/errors';
import {updateProjectSource as updateProjectSourceAction} from '../../actions/projects';
import validateProjectOnChange from '../validateProjectOnChange';

import {makeTestLogic} from './helpers';

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

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

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

test('should validate project on change', async () => {
const state = fromJS({
currentProject: {projectKey: '123'},
projects: {
123: {
sources: {
html: '',
css: 'div {',
javascript: '',
},
},
},
});

const testLogic = makeTestLogic(validateProjectOnChange);
const dispatch = await testLogic(
updateProjectSourceAction('123', 'css', 'div {'),
{state},
);

expect(dispatch).toHaveBeenCalledWith(
validatedSource('css', mockCssValidationErrors),
);
});
27 changes: 27 additions & 0 deletions src/logic/helpers/validateSource.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import {validatedSource} from '../../actions/errors';
import {getCurrentProject} from '../../selectors';
import retryingFailedImports from '../../util/retryingFailedImports';

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

export default async function validateSource(
{language, source, projectAttributes},
dispatch,
getState,
) {
const validations = await importValidations();
const validate = validations[language];
const validationErrors = await validate(source, projectAttributes);
const state = getState();
const currentProject = getCurrentProject(state);
if (currentProject.sources[language] === source) {
dispatch(validatedSource(language, validationErrors));
}
}
8 changes: 6 additions & 2 deletions src/logic/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,18 @@ import projectSuccessfullySaved from './projectSuccessfullySaved';
import saveProject from './saveProject';
import startAccountMigration from './startAccountMigration';
import unlinkGithubIdentity from './unlinkGithubIdentity';
import validateCurrentProject from './validateCurrentProject';
import validateProjectOnChange from './validateProjectOnChange';

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

import Analyzer from '../analyzers';
import {getCurrentProject} from '../selectors';

import validateSource from './helpers/validateSource';

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

const validatePromises = map(
Reflect.ownKeys(currentProject.sources),
language =>
validateSource(
{
language,
source: currentProject.sources[language],
projectAttributes: analyzer,
},
dispatch,
getState,
),
);

await Promise.all(validatePromises);
done();
},
});
31 changes: 31 additions & 0 deletions src/logic/validateProjectOnChange.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import {createLogic} from 'redux-logic';

import Analyzer from '../analyzers';
import {getCurrentProject} from '../selectors';

import validateSource from './helpers/validateSource';

export default createLogic({
type: 'UPDATE_PROJECT_SOURCE',
latest: true,
async process(
{
getState,
action: {
payload: {language, newValue},
},
},
dispatch,
done,
) {
const state = getState();
const analyzer = new Analyzer(getCurrentProject(state));

await validateSource(
{language, source: newValue, projectAttributes: analyzer},
dispatch,
getState,
);
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 @@ -11,7 +10,6 @@ import watchAssignments from './assignments';
export default function* rootSaga() {
yield all([
manageUserState(),
watchErrors(),
watchProjects(),
watchUi(),
watchClients(),
Expand Down
Loading