Skip to content

Commit f933cea

Browse files
authored
Merge pull request #80 from ioet/64/save-projects-API
closes #64 #66
2 parents 71b59cf + 306aa9e commit f933cea

32 files changed

+704
-431
lines changed

src/app/app.module.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ import { SearchProjectComponent } from './modules/shared/components/search-proje
3232
import { HomeComponent } from './modules/home/home.component';
3333
import { LoginComponent } from './modules/login/login.component';
3434
import { ActivityEffects } from './modules/activities-management/store/activity-management.effects';
35-
import { activityManagementReducer } from './modules/activities-management/store';
35+
import { ProjectEffects } from './modules/project-management/store/project.effects';
36+
import { reducers, metaReducers } from './reducers';
37+
import { environment } from '../environments/environment';
3638

3739
@NgModule({
3840
declarations: [
@@ -68,11 +70,15 @@ import { activityManagementReducer } from './modules/activities-management/store
6870
FormsModule,
6971
ReactiveFormsModule,
7072
HttpClientModule,
71-
StoreModule.forRoot({ activities: activityManagementReducer }),
72-
EffectsModule.forRoot([ActivityEffects]),
73-
StoreDevtoolsModule.instrument({
74-
maxAge: 15, // Retains last 15 states
73+
StoreModule.forRoot(reducers, {
74+
metaReducers,
7575
}),
76+
!environment.production
77+
? StoreDevtoolsModule.instrument({
78+
maxAge: 15, // Retains last 15 states
79+
})
80+
: [],
81+
EffectsModule.forRoot([ProjectEffects, ActivityEffects]),
7682
],
7783
providers: [],
7884
bootstrap: [AppComponent],

src/app/modules/project-management/components/create-project/create-project.component.html

Lines changed: 9 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -18,40 +18,23 @@ <h1 class="card-title">Project</h1>
1818
</div>
1919

2020
<div class="form-group">
21-
<label for="details">Details:</label>
21+
<label for="description">Description:</label>
2222
<textarea
2323
class="form-control"
2424
rows="3"
25-
id="details"
25+
id="description"
2626
type="text"
27-
formControlName="details"
28-
[class.is-invalid]="details.invalid && details.touched"
27+
formControlName="description"
28+
[class.is-invalid]="description.invalid && description.touched"
2929
required
3030
></textarea>
31-
<p class="text-danger" *ngIf="(details.dirty || details.touched) && details.invalid && details.errors.required">
32-
Project details are required.
31+
<p
32+
class="text-danger"
33+
*ngIf="(description.dirty || description.touched) && description.invalid && description.errors.required"
34+
>
35+
Project description is required.
3336
</p>
3437
</div>
35-
36-
<div class="form-group">
37-
<label for="status">Status:</label>
38-
<select class="form-control" formControlName="status">
39-
<option
40-
*ngFor="let status of projectStatus"
41-
[class.is-invalid]="status.invalid && status.touched"
42-
[value]="status"
43-
>{{ status }}</option
44-
>
45-
</select>
46-
<p class="text-danger" *ngIf="(status.dirty || status.touched) && status.invalid && status.errors.required">
47-
Project status is required.
48-
</p>
49-
</div>
50-
51-
<div class="form-group form-check" [hidden]="!projectToEdit">
52-
<input type="checkbox" class="form-check-input" id="completedProject" formControlName="completed" />
53-
<label class="form-check-label" for="completedProject">Completed project</label>
54-
</div>
5538
<div class="btn-toolbar" role="toolbar">
5639
<div class="btn-group mr-2" role="group">
5740
<button class="btn save-button-style mb-2" type="submit" [disabled]="!projectForm.valid">Save</button>
Lines changed: 69 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,114 @@
11
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2-
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
2+
import { provideMockStore, MockStore } from '@ngrx/store/testing';
3+
import { FormsModule, ReactiveFormsModule, FormBuilder } from '@angular/forms';
34

5+
import { ProjectState } from '../../store/project.reducer';
46
import { CreateProjectComponent } from './create-project.component';
7+
import * as actions from '../../store/project.actions';
58

69
describe('CreateProjectComponent', () => {
710
let component: CreateProjectComponent;
811
let fixture: ComponentFixture<CreateProjectComponent>;
12+
let store: MockStore<ProjectState>;
13+
14+
const state = {
15+
projectList: [{ id: 'id', name: 'name', description: 'description' }],
16+
isLoading: false,
17+
};
918

1019
beforeEach(async(() => {
1120
TestBed.configureTestingModule({
12-
declarations: [ CreateProjectComponent ],
13-
imports: [
14-
FormsModule,
15-
ReactiveFormsModule
16-
]
17-
})
18-
.compileComponents();
21+
declarations: [CreateProjectComponent],
22+
imports: [FormsModule, ReactiveFormsModule],
23+
providers: [FormBuilder, provideMockStore({ initialState: state })],
24+
}).compileComponents();
1925
}));
2026

2127
beforeEach(() => {
2228
fixture = TestBed.createComponent(CreateProjectComponent);
2329
component = fixture.componentInstance;
2430
fixture.detectChanges();
31+
store = TestBed.inject(MockStore);
32+
store.setState(state);
2533
});
2634

2735
it('should create', () => {
2836
expect(component).toBeTruthy();
2937
});
3038

31-
it('should send the form data on onSubmit buton #onSubmit', () => {
39+
it('should emit ngOnChange with projectToEdit', () => {
40+
const newData = {
41+
id: 'acf6a6c7-a24e-423c-8d1d-08505a82feae',
42+
name: 'Project Test 13',
43+
description: 'description',
44+
};
45+
component.projectToEdit = newData;
46+
47+
component.ngOnChanges();
48+
49+
expect(component.projectForm.value.name).toEqual(newData.name);
50+
expect(component.projectForm.value.description).toEqual(newData.description);
51+
expect(component.isUpdating).toEqual(true);
52+
});
53+
54+
it('on ngOnChange with projectToEdit=null should reset projectForm', () => {
55+
component.projectToEdit = null;
56+
57+
component.ngOnChanges();
58+
59+
expect(component.projectForm.value.name).toEqual(null);
60+
expect(component.projectForm.value.description).toEqual(null);
61+
expect(component.isUpdating).toEqual(false);
62+
});
63+
64+
it('should dispatch CreateProject action #onSubmit if isUpdating=false', () => {
3265
const project = {
66+
id: '1',
3367
name: 'app 4',
34-
details: 'It is a good app',
35-
status: 'inactive',
36-
completed: true
68+
description: 'It is a good app',
3769
};
70+
component.isUpdating = false;
71+
spyOn(store, 'dispatch');
3872

39-
spyOn(component.savedProject, 'emit');
4073
component.onSubmit(project);
41-
expect(component.savedProject.emit).toHaveBeenCalled();
74+
75+
expect(store.dispatch).toHaveBeenCalledWith(new actions.CreateProject(project));
4276
});
4377

44-
it('should clean the form and send a cancelForm event #reset', () => {
78+
it('should dispatch UpdateProject action #onSubmit if isUpdating=true', () => {
4579
const project = {
80+
id: '1',
4681
name: 'app 4',
47-
details: 'It is a good app',
48-
status: 'inactive',
49-
completed: true
82+
description: 'It is a good app',
5083
};
84+
component.isUpdating = true;
85+
spyOn(store, 'dispatch');
86+
87+
component.onSubmit(project);
5188

89+
expect(store.dispatch).toHaveBeenCalledWith(new actions.UpdateProject(project));
90+
});
91+
92+
it('should clean the form and emmit a cancelForm event on #reset', () => {
93+
const project = {
94+
name: 'app 4',
95+
description: 'It is a good app',
96+
};
5297
component.projectForm.setValue(project);
5398
spyOn(component.cancelForm, 'emit');
99+
54100
component.reset();
55-
expect(component.projectForm.value.name).toEqual(null);
56-
expect(component.projectForm.value.details).toEqual(null);
57-
expect(component.projectForm.value.status).toEqual(null);
58-
expect(component.projectForm.value.completed).toEqual(null);
59101

102+
expect(component.projectForm.value.name).toEqual(null);
103+
expect(component.projectForm.value.description).toEqual(null);
60104
expect(component.cancelForm.emit).toHaveBeenCalled();
61105
});
62106

63107
it('form invalid when empty', () => {
64108
expect(component.projectForm.valid).toBeFalsy();
65109
});
66110

67-
it('name field validity', () => {
111+
it('checks if name field is valid', () => {
68112
const name = component.projectForm.controls.name;
69113
expect(name.valid).toBeFalsy();
70114

@@ -75,8 +119,8 @@ describe('CreateProjectComponent', () => {
75119
expect(name.hasError('required')).toBeFalsy();
76120
});
77121

78-
it('details field validity', () => {
79-
const details = component.projectForm.controls.details;
122+
it('checks if description field is valid', () => {
123+
const details = component.projectForm.controls.description;
80124
expect(details.valid).toBeFalsy();
81125

82126
details.setValue('');
@@ -85,16 +129,4 @@ describe('CreateProjectComponent', () => {
85129
details.setValue('A');
86130
expect(details.hasError('required')).toBeFalsy();
87131
});
88-
89-
it('status field validity', () => {
90-
const status = component.projectForm.controls.status;
91-
expect(status.valid).toBeFalsy();
92-
93-
status.setValue('');
94-
expect(status.hasError('required')).toBeTruthy();
95-
96-
status.setValue('A');
97-
expect(status.hasError('required')).toBeFalsy();
98-
});
99-
100132
});
Lines changed: 26 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,62 @@
1-
import {
2-
Component,
3-
Input,
4-
OnChanges,
5-
Output,
6-
EventEmitter
7-
} from '@angular/core';
1+
import { Component, Input, OnChanges, OnInit, Output, EventEmitter } from '@angular/core';
82
import { FormBuilder, Validators } from '@angular/forms';
3+
import { Store } from '@ngrx/store';
94
import { Project } from '../../../shared/models';
5+
import { ProjectState } from '../../store/project.reducer';
6+
import * as actions from '../../store/project.actions';
107

118
@Component({
129
selector: 'app-create-project',
1310
templateUrl: './create-project.component.html',
14-
styleUrls: ['./create-project.component.scss']
11+
styleUrls: ['./create-project.component.scss'],
1512
})
16-
export class CreateProjectComponent implements OnChanges {
17-
13+
export class CreateProjectComponent implements OnChanges, OnInit {
1814
projectForm;
1915
editProjectId;
20-
projectStatus = ['', 'Active', 'Inactive'];
16+
isUpdating = false;
2117

2218
@Input() projectToEdit: Project;
23-
@Output() savedProject = new EventEmitter();
2419
@Output() cancelForm = new EventEmitter();
2520

26-
constructor(private formBuilder: FormBuilder) {
21+
constructor(private formBuilder: FormBuilder, private store: Store<ProjectState>) {
2722
this.projectForm = this.formBuilder.group({
2823
name: ['', Validators.required],
29-
details: ['', Validators.required],
30-
status: ['', Validators.required],
31-
completed: [false]
24+
description: ['', Validators.required],
3225
});
3326
}
3427

28+
ngOnInit(): void {}
29+
3530
ngOnChanges(): void {
3631
if (this.projectToEdit) {
37-
this.editProjectId = this.projectToEdit.id;
38-
this.projectForm.setValue({
39-
name: this.projectToEdit.name, details: this.projectToEdit.details,
40-
status: this.projectToEdit.status, completed: this.projectToEdit.completed
41-
});
32+
this.projectForm.patchValue(this.projectToEdit);
33+
this.isUpdating = true;
34+
} else {
35+
this.projectForm.reset();
36+
this.isUpdating = false;
4237
}
4338
}
4439

4540
get name() {
4641
return this.projectForm.get('name');
4742
}
4843

49-
get details() {
50-
return this.projectForm.get('details');
51-
}
52-
53-
get status() {
54-
return this.projectForm.get('status');
44+
get description() {
45+
return this.projectForm.get('description');
5546
}
5647

57-
onSubmit(projectData) {
48+
onSubmit(formData) {
49+
const projectData = { ...this.projectToEdit, ...formData };
50+
if (this.isUpdating) {
51+
this.store.dispatch(new actions.UpdateProject(projectData));
52+
} else {
53+
this.store.dispatch(new actions.CreateProject(projectData));
54+
}
5855
this.reset();
59-
this.savedProject.emit(projectData);
6056
}
57+
6158
reset() {
6259
this.projectForm.reset();
6360
this.cancelForm.emit();
6461
}
65-
6662
}

src/app/modules/project-management/components/project-list/project-list.component.html

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ <h2 class="mb-0">
1616
{{ project.name }}
1717
</a>
1818
<div class="btn-group float-right" role="group">
19-
<i (click)="editProject.emit(project.id)" class="far fa-edit btn btn-link text-white"></i>
19+
<i (click)="editProject.emit(project)" class="far fa-edit btn btn-link text-white"></i>
2020
<i
2121
(click)="openModal(project)"
2222
data-toggle="modal"
@@ -29,12 +29,8 @@ <h2 class="mb-0">
2929

3030
<div [id]="'row' + rowIndex" class="collapse" data-parent="#accordionProject">
3131
<div class="card-body">
32-
<h5>Details:</h5>
33-
<p>{{ project.details }}</p>
34-
<h5>Status:</h5>
35-
<p>{{ project.status }}</p>
36-
<h5>Completed project:</h5>
37-
<p>{{ project.completed ? 'Yes' : 'No' }}</p>
32+
<h5>Description:</h5>
33+
<p>{{ project.description }}</p>
3834
</div>
3935
</div>
4036
</div>

0 commit comments

Comments
 (0)