Skip to content

Commit 2ec18e2

Browse files
authored
Merge branch 'master' into 19-change-app-colors
2 parents 029ecc7 + c197040 commit 2ec18e2

19 files changed

+299
-62
lines changed

package-lock.json

Lines changed: 19 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
"@angular/platform-browser": "~9.0.3",
2121
"@angular/platform-browser-dynamic": "~9.0.3",
2222
"@angular/router": "~9.0.3",
23+
"@ngrx/effects": "^9.0.0",
24+
"@ngrx/store": "^9.0.0",
25+
"@ngrx/store-devtools": "^9.0.0",
2326
"bootstrap": "^4.4.1",
2427
"jquery": "^3.4.1",
2528
"minimist": "^1.2.5",

src/app/app.module.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ import { BrowserModule } from '@angular/platform-browser';
33
import { NgModule } from '@angular/core';
44
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
55
import { HttpClientModule } from '@angular/common/http';
6+
import { StoreModule } from '@ngrx/store';
7+
import { EffectsModule } from '@ngrx/effects';
8+
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
69

710
import { AppRoutingModule } from './app-routing.module';
811
import { AppComponent } from './app.component';
@@ -28,6 +31,8 @@ import { FilterProjectPipe } from './modules/shared/pipes/filter-project/filter-
2831
import { SearchProjectComponent } from './modules/shared/components/search-project/search-project.component';
2932
import { HomeComponent } from './modules/home/home.component';
3033
import { LoginComponent } from './modules/login/login.component';
34+
import { ActivityEffects } from './modules/activities-management/store/activity-management.effects';
35+
import { activityManagementReducer } from './modules/activities-management/store';
3136

3237
@NgModule({
3338
declarations: [
@@ -56,7 +61,19 @@ import { LoginComponent } from './modules/login/login.component';
5661
FilterProjectPipe,
5762
SearchProjectComponent,
5863
],
59-
imports: [CommonModule, BrowserModule, AppRoutingModule, FormsModule, ReactiveFormsModule, HttpClientModule],
64+
imports: [
65+
CommonModule,
66+
BrowserModule,
67+
AppRoutingModule,
68+
FormsModule,
69+
ReactiveFormsModule,
70+
HttpClientModule,
71+
StoreModule.forRoot({ activities: activityManagementReducer }),
72+
EffectsModule.forRoot([ActivityEffects]),
73+
StoreDevtoolsModule.instrument({
74+
maxAge: 15, // Retains last 15 states
75+
}),
76+
],
6077
providers: [],
6178
bootstrap: [AppComponent],
6279
})

src/app/modules/activities-management/components/activity-list/activity-list.component.spec.ts

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,29 @@
1-
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
1+
import { provideMockStore, MockStore } from '@ngrx/store/testing';
22

3+
import { allActivities } from './../../store/activity-management.selectors';
4+
import { ActivityState } from './../../store/activity-management.reducers';
5+
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
36
import { ActivityListComponent } from './activity-list.component';
47

58
describe('ActivityListComponent', () => {
69
let component: ActivityListComponent;
710
let fixture: ComponentFixture<ActivityListComponent>;
11+
let mockActivitiesSelector;
12+
13+
const state = { data: [{id: 'id', name: 'name', description: 'description'}], isLoading: false, message: '' };
14+
15+
let store: MockStore<ActivityState>;
816

917
beforeEach(async(() => {
1018
TestBed.configureTestingModule({
11-
declarations: [ ActivityListComponent ]
19+
declarations: [ ActivityListComponent ],
20+
providers: [ provideMockStore({ initialState: state }) ]
1221
})
1322
.compileComponents();
23+
24+
store = TestBed.inject(MockStore);
25+
26+
mockActivitiesSelector = store.overrideSelector( allActivities, state );
1427
}));
1528

1629
beforeEach(() => {
@@ -22,4 +35,21 @@ describe('ActivityListComponent', () => {
2235
it('should create', () => {
2336
expect(component).toBeTruthy();
2437
});
38+
39+
it('onInit, LoadActivities action is dispatched', () => {
40+
spyOn(store, 'dispatch');
41+
42+
component.ngOnInit();
43+
44+
expect(store.dispatch).toHaveBeenCalled();
45+
});
46+
47+
it('onInit, activities field is populated with data from store', () => {
48+
component.ngOnInit();
49+
50+
expect(component.activities).toBe(state.data);
51+
});
52+
53+
afterEach(() => { fixture.destroy(); });
54+
2555
});
Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,28 @@
1-
import { Input } from '@angular/core';
1+
import { Input, OnInit } from '@angular/core';
22
import { Component } from '@angular/core';
3+
import { Store, select } from '@ngrx/store';
4+
5+
import { allActivities } from '../../store';
6+
import { LoadActivities } from './../../store/activity-management.actions';
7+
import { ActivityState } from './../../store/activity-management.reducers';
38
import { Activity } from '../../../shared/models';
49

5-
@Component({
6-
selector: 'app-activity-list',
7-
templateUrl: './activity-list.component.html',
8-
styleUrls: ['./activity-list.component.scss']
9-
})
10-
export class ActivityListComponent {
10+
@Component({selector: 'app-activity-list', templateUrl: './activity-list.component.html', styleUrls: ['./activity-list.component.scss']})
11+
export class ActivityListComponent implements OnInit {
12+
13+
@Input()activities: Activity[] = [];
14+
public isLoading: boolean;
15+
16+
constructor(private store: Store<ActivityState>) { }
1117

12-
@Input() activities: Activity[] = [];
18+
ngOnInit() {
19+
this.store.dispatch(new LoadActivities());
20+
const activities$ = this.store.pipe(select(allActivities));
1321

14-
constructor() { }
22+
activities$.subscribe(response => {
23+
this.isLoading = response.isLoading;
24+
this.activities = response.data;
25+
});
26+
}
1527

1628
}

src/app/modules/activities-management/components/create-activity/create-activity.component.html

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,15 @@
33
<h1 class="card-title">Activity</h1>
44
<div class="form-group">
55
<label for="name">Name:</label>
6-
<input class="form-control" id="name" type="text" formControlName="name" required />
7-
<div class="alert alert-danger" *ngIf="(name.dirty || name.touched) && name.invalid && name.errors.required">
6+
<input
7+
class="form-control"
8+
id="name"
9+
type="text"
10+
formControlName="name"
11+
[class.is-invalid]="name.invalid && name.touched"
12+
required
13+
/>
14+
<div class="text-danger" *ngIf="(name.dirty || name.touched) && name.invalid && name.errors.required">
815
Activity name is required.
916
</div>
1017
</div>

src/app/modules/activities-management/components/create-activity/create-activity.component.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,9 @@ import { Activity } from '../../../shared/models';
55
@Component({
66
selector: 'app-create-activity',
77
templateUrl: './create-activity.component.html',
8-
styleUrls: ['./create-activity.component.scss']
8+
styleUrls: ['./create-activity.component.scss'],
99
})
1010
export class CreateActivityComponent {
11-
1211
activityForm: FormGroup;
1312

1413
@Input()
@@ -17,7 +16,7 @@ export class CreateActivityComponent {
1716
constructor(private formBuilder: FormBuilder) {
1817
this.activityForm = this.formBuilder.group({
1918
name: ['', Validators.required],
20-
description: ['']
19+
description: [''],
2120
});
2221
}
2322

@@ -35,5 +34,4 @@ export class CreateActivityComponent {
3534
get description() {
3635
return this.activityForm.get('description');
3736
}
38-
3937
}

src/app/modules/activities-management/services/activity.service.spec.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { TestBed, inject } from '@angular/core/testing';
22
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
3+
34
import { Activity } from '../../shared/models';
45
import { ActivityService } from './activity.service';
56

@@ -28,14 +29,15 @@ describe('Activity Service', () => {
2829
expect(httpClient).toBeTruthy();
2930
}));
3031

31-
it('activities are read using GET from assets/activities.json URL', () => {
32+
it('activities are read using GET from baseUrl', () => {
3233
const activitiesFoundSize = activities.length;
34+
service.baseUrl = 'foo';
3335
service
3436
.getActivities()
3537
.subscribe(activitiesInResponse => {
3638
expect(activitiesInResponse.length).toBe(activitiesFoundSize);
3739
});
38-
const getActivitiesRequest = httpMock.expectOne('assets/activities.json');
40+
const getActivitiesRequest = httpMock.expectOne(service.baseUrl);
3941
expect(getActivitiesRequest.request.method).toBe('GET');
4042
getActivitiesRequest.flush(activities);
4143
});
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
import { Injectable } from '@angular/core';
22
import { HttpClient } from '@angular/common/http';
33
import { Observable } from 'rxjs';
4+
5+
import { environment } from './../../../../environments/environment';
46
import { Activity } from '../../shared/models';
57

68
@Injectable({
79
providedIn: 'root'
810
})
911
export class ActivityService {
1012

11-
url = 'assets/activities.json';
13+
baseUrl = `${environment.timeTrackerApiUrl}/activities`;
1214

1315
constructor(private http: HttpClient) {}
1416

1517
getActivities(): Observable<Activity[]> {
16-
return this.http.get<Activity[]>(this.url);
18+
return this.http.get<Activity[]>(this.baseUrl);
1719
}
1820
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { LoadActivitiesFail } from './activity-management.actions';
2+
import { LoadActivitiesSuccess, ActivityManagementActionTypes } from './activity-management.actions';
3+
4+
describe('LoadActivitiesSuccess', () => {
5+
6+
it('LoadActivitiesSuccess type is ActivityManagementActionTypes.LoadActivitiesSuccess', () => {
7+
const loadActivitiesSuccess = new LoadActivitiesSuccess([]);
8+
expect(loadActivitiesSuccess.type).toEqual(ActivityManagementActionTypes.LoadActivitiesSuccess);
9+
});
10+
11+
it('LoadActivitiesFail type is ActivityManagementActionTypes.LoadActivitiesFail', () => {
12+
const loadActivitiesFail = new LoadActivitiesFail('error');
13+
expect(loadActivitiesFail.type).toEqual(ActivityManagementActionTypes.LoadActivitiesFail);
14+
});
15+
16+
});

0 commit comments

Comments
 (0)