diff --git a/src/common/actionEnums/admin.ts b/src/common/actionEnums/admin.ts
index 2568f71..c36d0bd 100644
--- a/src/common/actionEnums/admin.ts
+++ b/src/common/actionEnums/admin.ts
@@ -2,4 +2,5 @@
export const adminActionEnums = {
GET_SUMMARY_STUDENT_REQUEST_COMPLETED: 'GET_SUMMARY_STUDENT_REQUEST_COMPLETED',
GET_SUMMARY_TRAINING_REQUEST_COMPLETED: 'GET_SUMMARY_TRAINING_REQUEST_COMPLETED',
+ GET_SUMMARY_STUDENT_BY_ID_REQUEST_COMPLETED: 'GET_SUMMARY_STUDENT_BY_ID_REQUEST_COMPLETED',
};
diff --git a/src/common/routeEnums/admin/index.ts b/src/common/routeEnums/admin/index.ts
index 0acd850..55dd623 100644
--- a/src/common/routeEnums/admin/index.ts
+++ b/src/common/routeEnums/admin/index.ts
@@ -1,12 +1,14 @@
const defaultRoute = '/admin';
const studentRoute = `${defaultRoute}/student`;
const trainingRoute = `${defaultRoute}/training`;
+const studentByIdRoute = `${studentRoute}/:id`;
export const adminRouteEnums = {
default: defaultRoute,
student: {
+ base: studentRoute,
list: `${studentRoute}/list`,
- edit: `${studentRoute}/edit`,
+ edit: `${studentByIdRoute}/edit`,
},
training: {
list: `${trainingRoute}/list`,
diff --git a/src/common/routeEnums/admin/spec/index.spec.ts b/src/common/routeEnums/admin/spec/index.spec.ts
index a7a2b72..993cecd 100644
--- a/src/common/routeEnums/admin/spec/index.spec.ts
+++ b/src/common/routeEnums/admin/spec/index.spec.ts
@@ -8,7 +8,7 @@ describe('adminRouteEnums', () => {
it('should have keys defined', () => {
expect(adminRouteEnums.default).to.be.equals('/admin');
expect(adminRouteEnums.student.list).to.be.equals('/admin/student/list');
- expect(adminRouteEnums.student.edit).to.be.equals('/admin/student/edit');
+ expect(adminRouteEnums.student.edit).to.be.equals('/admin/student/:id/edit');
expect(adminRouteEnums.training.list).to.be.equals('/admin/training/list');
expect(adminRouteEnums.training.edit).to.be.equals('/admin/training/edit');
});
diff --git a/src/pages/admin/routes.tsx b/src/pages/admin/routes.tsx
index 9ba73bf..ab8a41d 100644
--- a/src/pages/admin/routes.tsx
+++ b/src/pages/admin/routes.tsx
@@ -1,7 +1,7 @@
import * as React from 'react';
import { Route } from 'react-router';
import { DashboardPage} from './dashboard/page';
-import { EditStudentPage } from './student/edit/page';
+import { EditStudentPageContainer } from './student/edit/pageContainer';
import { ListStudentPageContainer } from './student/list/pageContainer';
import { EditTrainingPage } from './training/edit/page';
import { ListTrainingPageContainer } from './training/list/pageContainer';
@@ -14,7 +14,7 @@ export const AdminRoutes = (
-
+
diff --git a/src/pages/admin/student/edit/actions/spec/summaryStudentRequest.spec.ts b/src/pages/admin/student/edit/actions/spec/summaryStudentRequest.spec.ts
new file mode 100644
index 0000000..2a4dd4e
--- /dev/null
+++ b/src/pages/admin/student/edit/actions/spec/summaryStudentRequest.spec.ts
@@ -0,0 +1,100 @@
+import thunk from 'redux-thunk';
+import configureStore from 'redux-mock-store';
+
+import { adminActionEnums } from '../../../../../../common/actionEnums/admin';
+import { summaryStudentByIdRequestCompleted, summaryStudentByIdRequestStarted } from '../summaryStudentRequest';
+import { StudentSummary } from '../../../../../../model/studentSummary';
+import { studentApi } from '../../../../../../rest-api';
+
+const mockStore = configureStore([thunk]);
+
+describe('summaryStudentByIdRequestCompleted', () => {
+ it('should be a function', () => {
+ // Assert
+ expect(summaryStudentByIdRequestCompleted).to.be.a('function');
+ });
+
+ it('contains the expected type GET_SUMMARY_STUDENT_BY_ID_REQUEST_COMPLETED', () => {
+ // Arrange
+ const student = new StudentSummary();
+
+ // Act
+ const action = summaryStudentByIdRequestCompleted(student);
+
+ // Assert
+ expect(action.type).to.be.equals(adminActionEnums.GET_SUMMARY_STUDENT_BY_ID_REQUEST_COMPLETED);
+ });
+
+ it('contains the expected payload including the student summary', () => {
+ // Arrange
+ const student: StudentSummary = {
+ id: '2',
+ fullname: 'John Doe',
+ email: 'test@test.com',
+ isActive: true,
+ };
+
+ // Act
+ const actionResult = summaryStudentByIdRequestCompleted(student);
+
+ // Assert
+ expect(actionResult.payload.fullname).to.be.equal(student.fullname);
+ expect(actionResult.payload.email).to.be.equal(student.email);
+ expect(actionResult.payload.isActive).to.be.equal(student.isActive);
+ expect(actionResult.payload).eql(student);
+ });
+});
+
+describe('summaryStudentByIdRequestStarted', () => {
+ it('should return a function', () => {
+ // Arrange
+ const studentId = '1';
+
+ // Assert
+ expect(summaryStudentByIdRequestStarted(studentId)).to.be.a('function');
+ });
+
+ it('should return request action type completed', sinon.test(function(done) {
+ // Arrange
+ const sinon: sinon.SinonStatic = this;
+ const studentId = '1';
+
+ // Act
+ const store = mockStore([]);
+ store.dispatch(summaryStudentByIdRequestStarted(studentId)).then(() => {
+
+ // Assert
+ expect(store.getActions()[0].type).to.be.equal(adminActionEnums.GET_SUMMARY_STUDENT_BY_ID_REQUEST_COMPLETED);
+ done();
+ });
+ }));
+
+ it('should return expected student summary data', sinon.test(function(done) {
+ // Arrange
+ const sinon: sinon.SinonStatic = this;
+
+ const student: StudentSummary = {
+ id: '2',
+ fullname: 'John Doe',
+ email: 'test@test.com',
+ isActive: true,
+ };
+
+ const getSummaryStudentListStub = sinon.stub(studentApi, 'getStudentById');
+
+ getSummaryStudentListStub.returns({
+ then: (callback) => {
+ callback(student);
+ },
+ });
+
+ // Act
+ const store = mockStore([]);
+ store.dispatch(summaryStudentByIdRequestStarted(student.id)).then(() => {
+ // Assert
+ expect(store.getActions()[0].payload).to.be.equal(student);
+ expect(getSummaryStudentListStub.called).to.be.true;
+ done();
+ });
+ }));
+});
diff --git a/src/pages/admin/student/edit/actions/summaryStudentRequest.ts b/src/pages/admin/student/edit/actions/summaryStudentRequest.ts
new file mode 100644
index 0000000..3e5981a
--- /dev/null
+++ b/src/pages/admin/student/edit/actions/summaryStudentRequest.ts
@@ -0,0 +1,24 @@
+import { adminActionEnums } from '../../../../../common/actionEnums/admin';
+import { StudentSummary } from '../../../../../model/studentSummary';
+import { studentApi } from '../../../../../rest-api';
+
+export const summaryStudentByIdRequestStarted = (studentId: string) => {
+ return function(dispatcher) {
+ const promise = studentApi.getStudentById(studentId);
+
+ promise.then(
+ (data) => {
+ dispatcher(summaryStudentByIdRequestCompleted(data));
+ },
+ );
+
+ return promise;
+ };
+};
+
+export const summaryStudentByIdRequestCompleted = (student: StudentSummary) => {
+ return {
+ payload: student,
+ type: adminActionEnums.GET_SUMMARY_STUDENT_BY_ID_REQUEST_COMPLETED,
+ };
+};
diff --git a/src/pages/admin/student/edit/index.ts b/src/pages/admin/student/edit/index.ts
new file mode 100644
index 0000000..be97e50
--- /dev/null
+++ b/src/pages/admin/student/edit/index.ts
@@ -0,0 +1 @@
+export { EditStudentPageContainer } from './pageContainer';
diff --git a/src/pages/admin/student/edit/page.tsx b/src/pages/admin/student/edit/page.tsx
index 658d917..64337be 100644
--- a/src/pages/admin/student/edit/page.tsx
+++ b/src/pages/admin/student/edit/page.tsx
@@ -1,17 +1,33 @@
import * as React from 'react';
-import {Link} from 'react-router';
-import {adminRouteEnums} from '../../../../common/routeEnums/admin';
+import { Link } from 'react-router';
+import { adminRouteEnums } from '../../../../common/routeEnums/admin';
+import { StudentSummary } from '../../../../model/studentSummary';
-export class EditStudentPage extends React.Component<{}, {}> {
- public render() {
+interface Props {
+ student: StudentSummary;
+ getStudent: (id: string) => void;
+ studentId: string;
+}
+
+export class EditStudentPage extends React.Component {
+ public componentDidMount() {
+ const studentId: string = this.props.studentId;
+ this.props.getStudent(studentId);
+ }
+
+ public render() {
return (
-
-
Edit Student Page:
-
+
+ Student name: {this.props.student.fullname}
+
+ Email: {this.props.student.email}
+
+ Is Active?: {this.props.student.isActive ? 'Yes' : 'No'}
Back to student list
Back to Dashboard
-
+
+
);
}
}
diff --git a/src/pages/admin/student/edit/pageContainer.tsx b/src/pages/admin/student/edit/pageContainer.tsx
new file mode 100644
index 0000000..42bfe9e
--- /dev/null
+++ b/src/pages/admin/student/edit/pageContainer.tsx
@@ -0,0 +1,18 @@
+import { connect } from 'react-redux';
+import { IAppState } from '../../../../reducers';
+import { EditStudentPage } from './page';
+import { summaryStudentByIdRequestStarted } from './actions/summaryStudentRequest';
+
+const mapStateToProps = (state: IAppState, ownProps) => ({
+ studentId: ownProps.params.id.toString(),
+ student: state.adminStudent.editingStudentSummary,
+});
+
+const mapDispatchToProps = (dispatch) => ({
+ getStudent: (studentId: string) => dispatch(summaryStudentByIdRequestStarted(studentId)),
+});
+
+export const EditStudentPageContainer = connect(
+ mapStateToProps,
+ mapDispatchToProps,
+)(EditStudentPage);
diff --git a/src/pages/admin/student/edit/spec/pageContainer.spec.tsx b/src/pages/admin/student/edit/spec/pageContainer.spec.tsx
new file mode 100644
index 0000000..c2cc819
--- /dev/null
+++ b/src/pages/admin/student/edit/spec/pageContainer.spec.tsx
@@ -0,0 +1,111 @@
+import * as React from 'react';
+import { mount } from 'enzyme';
+import configureStore from 'redux-mock-store';
+import { Provider } from 'react-redux';
+import * as getStudent from '../actions/summaryStudentRequest';
+import { EditStudentPageContainer } from '../pageContainer';
+
+const createStore = configureStore();
+
+describe('EditStudentPageContainer', () => {
+ it('should be defined', sinon.test(() => {
+ // Arrange
+ const sinon: sinon.SinonStatic = this;
+
+ const mockStore: any = createStore({
+ adminStudent: {
+ editingStudentSummary: {},
+ },
+ });
+
+ const getStudentStub = sinon.stub(getStudent,
+ 'summaryStudentByIdRequestStarted', () => ({ type: 'dummy' }));
+
+ // Act
+ const container = mount(
+
+
+ ,
+ );
+
+ // Assert
+ expect(container).not.to.be.undefined;
+ }).bind(this));
+
+ it('should contain a studentId property equals to the conversion to string when params is not a string', sinon.test(() => {
+ // Arrange
+ const sinon: sinon.SinonStatic = this;
+
+ const mockStore: any = createStore({
+ adminStudent: {
+ editingStudentSummary: {},
+ },
+ });
+
+ const getStudentStub = sinon.stub(getStudent,
+ 'summaryStudentByIdRequestStarted', () => ({ type: 'dummy' }));
+
+ // Act
+ const container = mount(
+
+
+ ,
+ );
+
+ // Assert
+ const presentational = container.find('EditStudentPage');
+ expect(presentational).not.to.be.undefined;
+ expect(presentational.prop('studentId')).to.equal('2');
+ }).bind(this));
+
+ it('should contain a studentId property equals 2 when params equals 2', sinon.test(() => {
+ // Arrange
+ const sinon: sinon.SinonStatic = this;
+
+ const mockStore: any = createStore({
+ adminStudent: {
+ editingStudentSummary: {},
+ },
+ });
+
+ const getStudentStub = sinon.stub(getStudent,
+ 'summaryStudentByIdRequestStarted', () => ({ type: 'dummy' }));
+
+ // Act
+ const container = mount(
+
+
+ ,
+ );
+
+ // Assert
+ const presentational = container.find('EditStudentPage');
+ expect(presentational).not.to.be.undefined;
+ expect(presentational.prop('studentId')).to.equal('2');
+ }).bind(this));
+
+ it('should call to getStudent with expected studentId', sinon.test(() => {
+ // Arrange
+ const sinon: sinon.SinonStatic = this;
+
+ const mockStore: any = createStore({
+ adminStudent: {
+ editingStudentSummary: {},
+ },
+ });
+
+ const getStudentStub = sinon.stub( getStudent ,
+ 'summaryStudentByIdRequestStarted', () => ({ type: 'dummy' }));
+
+ // Act
+ const container = mount(
+
+
+ ,
+ );
+
+ // Assert
+ expect(getStudentStub.calledOnce).to.be.true;
+ expect(getStudentStub.calledWith('2')).to.be.true;
+ }).bind(this));
+});
diff --git a/src/pages/admin/student/list/components/spec/studentRow.spec.tsx b/src/pages/admin/student/list/components/spec/studentRow.spec.tsx
index c057dbe..18c2cb6 100644
--- a/src/pages/admin/student/list/components/spec/studentRow.spec.tsx
+++ b/src/pages/admin/student/list/components/spec/studentRow.spec.tsx
@@ -194,7 +194,7 @@ describe('StudentRowComponent', () => {
// Assert
expect(link.type()).to.be.equals(Link);
expect(link.prop('className')).to.be.equals('btn btn-primary');
- expect(link.prop('to')).to.be.equals(`${adminRouteEnums.student.edit}/32`);
+ expect(link.prop('to')).to.be.equals(`${adminRouteEnums.student.base}/32/edit`);
expect(icon.type()).to.be.equals('i');
expect(icon.prop('className')).to.be.equals('glyphicon glyphicon-pencil');
});
diff --git a/src/pages/admin/student/list/components/studentRow.tsx b/src/pages/admin/student/list/components/studentRow.tsx
index a5af323..62eaf0d 100644
--- a/src/pages/admin/student/list/components/studentRow.tsx
+++ b/src/pages/admin/student/list/components/studentRow.tsx
@@ -23,7 +23,7 @@ export const StudentRowComponent: React.StatelessComponent = (props) => {
{props.rowData.fullname}
{props.rowData.email}
-
+
diff --git a/src/reducers/adminStudent.ts b/src/reducers/adminStudent.ts
index 1c0ae06..9191471 100644
--- a/src/reducers/adminStudent.ts
+++ b/src/reducers/adminStudent.ts
@@ -3,9 +3,11 @@ import { StudentSummary } from '../model/studentSummary';
export class AdminStudentState {
public studentSummaryList: StudentSummary[];
+ public editingStudentSummary: StudentSummary;
public constructor() {
this.studentSummaryList = [];
+ this.editingStudentSummary = new StudentSummary();
}
}
@@ -13,6 +15,8 @@ export const adminStudentReducer = (state: AdminStudentState = new AdminStudentS
switch (action.type) {
case adminActionEnums.GET_SUMMARY_STUDENT_REQUEST_COMPLETED:
return handleGetSummaryStudentRequestCompleted(state, action.payload);
+ case adminActionEnums.GET_SUMMARY_STUDENT_BY_ID_REQUEST_COMPLETED:
+ return handleGetSummaryStudentByIdRequestCompleted(state, action.payload);
default:
return state;
}
@@ -24,3 +28,10 @@ const handleGetSummaryStudentRequestCompleted = (state: AdminStudentState, paylo
studentSummaryList: payload,
};
};
+
+const handleGetSummaryStudentByIdRequestCompleted = (state: AdminStudentState, payload: StudentSummary) => {
+ return {
+ ...state,
+ editingStudentSummary: payload,
+ };
+};
diff --git a/src/reducers/spec/adminStudent.spec.ts b/src/reducers/spec/adminStudent.spec.ts
index 303b5eb..53f4e98 100644
--- a/src/reducers/spec/adminStudent.spec.ts
+++ b/src/reducers/spec/adminStudent.spec.ts
@@ -65,4 +65,30 @@ describe('adminStudentReducer', () => {
// Assert
expect(newState.studentSummaryList).to.eql(students);
});
+
+ it(`should return a new state including a specific student when
+ passing a GET_SUMMARY_STUDENT_BY_ID_REQUEST_COMPLETED`, () => {
+ // Arrange
+ const originalState = new AdminStudentState();
+
+ deepFreeze(originalState);
+
+ const student: StudentSummary = {
+ id: '2',
+ fullname: 'John Doe',
+ email: 'test@test.com',
+ isActive: true,
+ };
+
+ const actionResult = {
+ type: adminActionEnums.GET_SUMMARY_STUDENT_BY_ID_REQUEST_COMPLETED,
+ payload: student,
+ };
+
+ // Act
+ const newState = adminStudentReducer(originalState, actionResult);
+
+ // Assert
+ expect(newState.editingStudentSummary).to.eql(student);
+ });
});
diff --git a/src/rest-api/student.ts b/src/rest-api/student.ts
index 26a26a4..9ff0298 100644
--- a/src/rest-api/student.ts
+++ b/src/rest-api/student.ts
@@ -26,6 +26,11 @@ class StudentApi {
return Promise.resolve(studentSummaryList);
}
+
+ public getStudentById(id: string): Promise {
+ const studentSummary: StudentSummary = this.studentList.find((st) => st.id === id);
+ return Promise.resolve(studentSummary);
+ }
}
export const studentApi = new StudentApi();