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}
- +