Skip to content

Commit

Permalink
Added resumeOrder to education entries
Browse files Browse the repository at this point in the history
This comprises mainly some changes to `EducationForm` and
`EducationDisplay`, but also some refactoring and some new tests to
catch regressions on `EmploymentForm`: a bug relating to edit indices
was found and fixed here.
  • Loading branch information
alicewriteswrongs committed Jun 27, 2016
1 parent 11299d2 commit 1e4646a
Show file tree
Hide file tree
Showing 9 changed files with 259 additions and 60 deletions.
2 changes: 2 additions & 0 deletions .flowconfig
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

[libs]
./static/js/flow/declarations.js
node_modules/iflow-lodash/index.js.flow
node_modules/iflow-moment/index.js.flow

[options]
esproposal.class_static_fields=enable
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
"file-loader": "0.8.5",
"flow-bin": "^0.27.0",
"history": "2.1.1",
"iflow-lodash": "^1.1.16",
"iflow-moment": "^1.1.13",
"imports-loader": "^0.6.5",
"iso-3166-2": "0.4.0",
"isomorphic-fetch": "2.2.1",
Expand Down
6 changes: 4 additions & 2 deletions static/js/components/EducationDisplay.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
} from '../util/editEducation';
import { userPrivilegeCheck } from '../util/util';
import { HIGH_SCHOOL } from '../constants';
import { educationEntriesByDate } from '../util/sorting';
import type { EducationEntry } from '../flow/profileTypes';

export default class EducationDisplay extends ProfileFormFields {
Expand All @@ -44,7 +45,7 @@ export default class EducationDisplay extends ProfileFormFields {
let deleteEntry = () => this.openEducationDeleteDialog(index);
let editEntry = () => this.openEditEducationForm(index);
let validationAlert = () => {
if (_.get(errors, ['education', index])) {
if (_.get(errors, ['education', String(index)])) {
return <IconButton name="error" onClick={editEntry} />;
}
};
Expand Down Expand Up @@ -77,7 +78,8 @@ export default class EducationDisplay extends ProfileFormFields {
const { profile, profile: { education }} = this.props;
let rows = [];
if (education !== undefined) {
rows = education.map( (entry, index) => this.educationRow(entry, index));
let sorted = educationEntriesByDate(education);
rows = sorted.map( ([index, entry]) => this.educationRow(entry, index));
}
userPrivilegeCheck(profile, () => {
rows.push(
Expand Down
10 changes: 6 additions & 4 deletions static/js/components/EducationForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
deleteEducationEntry,
} from '../util/editEducation';
import { educationValidation } from '../util/validation';
import { educationEntriesByDate } from '../util/sorting';
import type { Option } from '../flow/generalTypes';
import type {
EducationEntry,
Expand Down Expand Up @@ -61,9 +62,10 @@ class EducationForm extends ProfileFormFields {
if (educationDegreeInclusions[level.value]) {
let rows: Array<React$Element|void> = [];
if (education !== undefined) {
rows = education.map((entry, index) => (
entry.degree_name === level.value ? this.educationRow(entry, index) : undefined
));
let sorted = educationEntriesByDate(education);
rows = sorted.filter(([,entry]) => (
entry.degree_name === level.value
)).map(([index, entry]) => this.educationRow(entry, index));
}
rows.push(
<FABButton
Expand All @@ -89,7 +91,7 @@ class EducationForm extends ProfileFormFields {
let deleteEntry = () => this.openEducationDeleteDialog(index);
let editEntry = () => this.openEditEducationForm(index);
let validationAlert = () => {
if (_.get(errors, ['education', index])) {
if (_.get(errors, ['education', String(index)])) {
return <IconButton name="error" onClick={editEntry} />;
}
};
Expand Down
8 changes: 4 additions & 4 deletions static/js/components/EmploymentForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import _ from 'lodash';
import moment from 'moment';

import { generateNewWorkHistory, userPrivilegeCheck } from '../util/util';
import { sortWorkEntriesByDate } from '../util/sorting';
import { workEntriesByDate } from '../util/sorting';
import { employmentValidation } from '../util/validation';
import ProfileFormFields from '../util/ProfileFormFields';
import ConfirmDeletion from './ConfirmDeletion';
Expand Down Expand Up @@ -138,8 +138,8 @@ class EmploymentForm extends ProfileFormFields {
if ( ui.workHistoryEdit === true ) {
let workHistoryRows = [];
if ( !_.isUndefined(work_history) ) {
let sorted = sortWorkEntriesByDate(work_history);
workHistoryRows = sorted.map((entry, index) => (
let sorted = workEntriesByDate(work_history);
workHistoryRows = sorted.map(([index, entry]) => (
entry.id === undefined ? undefined : this.jobRow(entry, index)
));
}
Expand Down Expand Up @@ -170,7 +170,7 @@ class EmploymentForm extends ProfileFormFields {
setWorkDialogVisibility(true);
};
let validationAlert = () => {
if (_.get(errors, ['work_history', index])) {
if (_.get(errors, ['work_history', String(index)])) {
return <IconButton name="error" onClick={editCallback} />;
}
};
Expand Down
97 changes: 96 additions & 1 deletion static/js/containers/UserPage_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ import {
} from '../util/util';
import IntegrationTestHelper from '../util/integration_test_helper';
import * as api from '../util/api';
import { USER_PROFILE_RESPONSE, HIGH_SCHOOL } from '../constants';
import { USER_PROFILE_RESPONSE, HIGH_SCHOOL, DOCTORATE } from '../constants';
import { workEntriesByDate, educationEntriesByDate } from '../util/sorting';

describe("UserPage", function() {
this.timeout(5000);
Expand All @@ -50,6 +51,42 @@ describe("UserPage", function() {
));
};

const confirmResumeOrder = (
editButton,
profileProperty,
sortFunc,
editActions,
dialogIndexProperty
) => {
let state = helper.store.getState();
let sorted = sortFunc(state.profiles[SETTINGS.username].profile[profileProperty]);

// sorted entries should not equal unsorted entries
assert.notDeepEqual(
state.profiles[SETTINGS.username].profile[profileProperty],
sorted.map(([,entry]) => entry)
);

return listenForActions(editActions, () => {
TestUtils.Simulate.click(editButton);
}).then(() => {
state = helper.store.getState();
let stateIndex = state.ui[dialogIndexProperty];
let [sortIndex, sortEntry] = sorted[0];
// the dialog index dispatched to the store should be the same index
// as the index (into the unsorted list) of the first element in our sorted list
// since we clicked on the first entry in the UI
assert.equal(stateIndex, sortIndex);
// this index should not be equal to 0, since the first element in the sorted list
// should not be the first item in the unsorted list
assert.notEqual(stateIndex, 0);
// the entry the index in the state points to should be the same element
// in the unsorted array that we have in the sorted array
let entries = state.profiles[SETTINGS.username].profile[profileProperty];
assert.deepEqual(sortEntry, entries[stateIndex]);
});
};

describe ("Authenticated user page", () => {
beforeEach(() => {
helper = new IntegrationTestHelper();
Expand Down Expand Up @@ -77,13 +114,52 @@ describe("UserPage", function() {
getElementsByClassName('delete-button')[0];
};

beforeEach(() => {
let userProfile = Object.assign({}, USER_PROFILE_RESPONSE, {
username: SETTINGS.username
});
userProfile.education.push({
"id": 3,
"degree_name": DOCTORATE,
"graduation_date": "2015-12-01",
"field_of_study": "Philosophy",
"school_name": "Harvard",
"school_city": "Cambridge",
"school_state_or_territory": "US-MA",
"school_country": "US",
"online_degree": false
});
helper.profileGetStub.
withArgs(SETTINGS.username).
returns(Promise.resolve(userProfile));
});

it('shows the education component', () => {
return renderComponent(`/users/${SETTINGS.username}`, userActions).then(([, div]) => {
let title = div.getElementsByClassName('profile-card-title')[1];
assert.equal(title.textContent, 'Education');
});
});

it('should show the entries in resume order', () => {
return renderComponent(`/users/${SETTINGS.username}`, userActions).then(([, div]) => {
let editButton = div.querySelector('#education-card').
querySelector('.edit-button');

return confirmResumeOrder(
editButton,
'education',
educationEntriesByDate,
[
SET_EDUCATION_DIALOG_INDEX,
SET_EDUCATION_DIALOG_VISIBILITY,
SET_EDUCATION_DEGREE_LEVEL,
],
'educationDialogIndex'
);
});
});

it('should confirm deletion and let you cancel', () => {
return renderComponent(`/users/${SETTINGS.username}`, userActions).then(([, div]) => {
let button = deleteButton(div);
Expand Down Expand Up @@ -233,6 +309,25 @@ describe("UserPage", function() {
});
});

it('should show the entries in resume order', () => {
return renderComponent(`/users/${SETTINGS.username}`, userActions).then(([, div]) => {
let editButton = div.getElementsByClassName('profile-tab-card')[0].
getElementsByClassName('profile-row-icons')[0].
getElementsByClassName('mdl-button')[0];

return confirmResumeOrder(
editButton,
'work_history',
workEntriesByDate,
[
SET_WORK_DIALOG_INDEX,
SET_WORK_DIALOG_VISIBILITY
],
'workDialogIndex'
);
});
});

it('should confirm deletion and let you cancel', () => {
return renderComponent(`/users/${SETTINGS.username}`, userActions).then(([, div]) => {
let button = deleteButton(div);
Expand Down
45 changes: 27 additions & 18 deletions static/js/util/sorting.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,35 @@
// @flow
import moment from 'moment';
import _ from 'lodash';

import type { WorkHistoryEntry } from '../flow/profileTypes';
import type { WorkHistoryEntry, EducationEntry } from '../flow/profileTypes';

export function resumeOrder(entries: WorkHistoryEntry[], dateFieldName: string): WorkHistoryEntry[] {
let sortFunc = (a, b) => {
let adate = moment(a[dateFieldName]);
let bdate = moment(b[dateFieldName]);
if ( adate.isBefore(bdate) ) {
return 1;
}
if ( adate.isAfter(bdate) ) {
return -1;
}
return 0;
export function momentCompareDesc(a: moment$Moment, b: moment$Moment): number {
if ( a.isBefore(b) ) {
return 1;
}
if ( a.isAfter(b) ) {
return -1;
}
return 0;
}

export function dateOrderDesc(entries: [number, Object][], dateFieldName: string): any {
let clone = _.clone(entries);
let sortFunc = ([, a], [, b]) => {
return momentCompareDesc(moment(a[dateFieldName]), moment(b[dateFieldName]));
};
return entries.sort(sortFunc);
return clone.sort(sortFunc);
}

export function workEntriesByDate(entries: Array<WorkHistoryEntry>): Array<WorkHistoryEntry> {
let tuples = entries.map((entry, index) => [index, entry]);
let out = [];
out.push(...dateOrderDesc(tuples.filter(([,entry]) => entry.end_date === null), 'start_date'));
out.push(...dateOrderDesc(tuples.filter(([,entry]) => entry.end_date !== null), 'end_date'));
return out;
}

export function sortWorkEntriesByDate(entries: Array<WorkHistoryEntry>): Array<WorkHistoryEntry> {
let sorted = [];
sorted.push(...resumeOrder(entries.filter(entry => entry.end_date === null), 'start_date'));
sorted.push(...resumeOrder(entries.filter(entry => entry.end_date !== null), 'end_date'));
return sorted;
export function educationEntriesByDate(entries: Array<EducationEntry>): Array<EducationEntry> {
return dateOrderDesc(entries.map((entry, index) => [index, entry]), 'graduation_date');
}
Loading

0 comments on commit 1e4646a

Please sign in to comment.