diff --git a/src/app/modules/time-clock/components/entry-fields/entry-fields.component.spec.ts b/src/app/modules/time-clock/components/entry-fields/entry-fields.component.spec.ts index 4764ac6a8..aa588c670 100644 --- a/src/app/modules/time-clock/components/entry-fields/entry-fields.component.spec.ts +++ b/src/app/modules/time-clock/components/entry-fields/entry-fields.component.spec.ts @@ -1,9 +1,9 @@ +import { Subscription } from 'rxjs'; import { LoadActiveEntry, EntryActionTypes, UpdateEntry } from './../../store/entry.actions'; import { ActivityManagementActionTypes } from './../../../activities-management/store/activity-management.actions'; import {waitForAsync, ComponentFixture, TestBed} from '@angular/core/testing'; import {MockStore, provideMockStore} from '@ngrx/store/testing'; import { FormsModule, ReactiveFormsModule, FormBuilder } from '@angular/forms'; - import {TechnologyState} from '../../../shared/store/technology.reducers'; import {allTechnologies} from '../../../shared/store/technology.selectors'; import {EntryFieldsComponent} from './entry-fields.component'; @@ -135,9 +135,7 @@ describe('EntryFieldsComponent', () => { }; spyOn(component.entryForm, 'patchValue'); - component.setDataToUpdate(entry); - expect(component.entryForm.patchValue).toHaveBeenCalledTimes(1); expect(component.entryForm.patchValue).toHaveBeenCalledWith( { @@ -156,6 +154,7 @@ describe('EntryFieldsComponent', () => { start_date : moment().format(DATE_FORMAT_YEAR), start_hour : moment().format('HH:mm') }; + component.newData = mockEntry; component.activeEntry = mockEntry ; component.setDataToUpdate(mockEntry); @@ -412,4 +411,18 @@ describe('EntryFieldsComponent', () => { expect(component.selectedTechnologies).toBe(initialTechnologies); }); + it('calls unsubscribe on ngDestroy', () => { + component.loadActivitiesSubscription = new Subscription(); + component.loadActiveEntrySubscription = new Subscription(); + component.actionSetDateSubscription = new Subscription(); + spyOn(component.loadActivitiesSubscription, 'unsubscribe'); + spyOn(component.loadActiveEntrySubscription, 'unsubscribe'); + spyOn(component.actionSetDateSubscription, 'unsubscribe'); + + component.ngOnDestroy(); + + expect(component.loadActivitiesSubscription.unsubscribe).toHaveBeenCalled(); + expect(component.loadActiveEntrySubscription.unsubscribe).toHaveBeenCalled(); + expect(component.actionSetDateSubscription.unsubscribe).toHaveBeenCalled(); + }); }); diff --git a/src/app/modules/time-clock/components/entry-fields/entry-fields.component.ts b/src/app/modules/time-clock/components/entry-fields/entry-fields.component.ts index f6160a48f..a5481281e 100644 --- a/src/app/modules/time-clock/components/entry-fields/entry-fields.component.ts +++ b/src/app/modules/time-clock/components/entry-fields/entry-fields.component.ts @@ -1,14 +1,13 @@ import { ActivityManagementActionTypes } from './../../../activities-management/store/activity-management.actions'; import { EntryActionTypes, LoadActiveEntry } from './../../store/entry.actions'; import { filter } from 'rxjs/operators'; -import { Component, OnInit } from '@angular/core'; +import { Component, OnDestroy, OnInit } from '@angular/core'; import { FormBuilder, FormGroup } from '@angular/forms'; import { Store, ActionsSubject, select } from '@ngrx/store'; import { Activity, NewEntry } from '../../../shared/models'; import { ProjectState } from '../../../customer-management/components/projects/components/store/project.reducer'; import { TechnologyState } from '../../../shared/store/technology.reducers'; import { ActivityState, LoadActivities } from '../../../activities-management/store'; - import * as entryActions from '../../store/entry.actions'; import { get } from 'lodash'; import * as moment from 'moment'; @@ -16,6 +15,7 @@ import { ToastrService } from 'ngx-toastr'; import { formatDate } from '@angular/common'; import { getTimeEntriesDataSource } from '../../store/entry.selectors'; import { DATE_FORMAT } from 'src/environments/environment'; +import { Subscription } from 'rxjs'; type Merged = TechnologyState & ProjectState & ActivityState; @@ -24,7 +24,7 @@ type Merged = TechnologyState & ProjectState & ActivityState; templateUrl: './entry-fields.component.html', styleUrls: ['./entry-fields.component.scss'], }) -export class EntryFieldsComponent implements OnInit { +export class EntryFieldsComponent implements OnInit, OnDestroy { entryForm: FormGroup; selectedTechnologies: string[] = []; activities: Activity[] = []; @@ -32,6 +32,9 @@ export class EntryFieldsComponent implements OnInit { newData; lastEntry; showTimeInbuttons = false; + loadActivitiesSubscription: Subscription; + loadActiveEntrySubscription: Subscription; + actionSetDateSubscription: Subscription; constructor( private formBuilder: FormBuilder, @@ -48,17 +51,16 @@ export class EntryFieldsComponent implements OnInit { }); } - ngOnInit(): void { + ngOnInit(): void { this.store.dispatch(new LoadActivities()); this.store.dispatch(new entryActions.LoadEntries(new Date().getMonth() + 1, new Date().getFullYear())); - this.actionsSubject$ + this.loadActivitiesSubscription = this.actionsSubject$ .pipe(filter((action: any) => action.type === ActivityManagementActionTypes.LOAD_ACTIVITIES_SUCCESS)) .subscribe((action) => { this.activities = action.payload; this.store.dispatch(new LoadActiveEntry()); }); - - this.actionsSubject$ + this.loadActiveEntrySubscription = this.actionsSubject$ .pipe( filter( (action: any) => @@ -76,8 +78,7 @@ export class EntryFieldsComponent implements OnInit { this.store.dispatch(new entryActions.LoadEntriesSummary()); } }); - - this.actionsSubject$ + this.actionSetDateSubscription = this.actionsSubject$ .pipe(filter((action: any) => action.type === EntryActionTypes.LOAD_ACTIVE_ENTRY_SUCCESS)) .subscribe((action) => { this.activeEntry = action.payload; @@ -90,17 +91,14 @@ export class EntryFieldsComponent implements OnInit { start_date: this.activeEntry.start_date, start_hour: formatDate(this.activeEntry.start_date, 'HH:mm', 'en'), }; - }); + }); } - get activity_id() { return this.entryForm.get('activity_id'); } - get start_hour() { return this.entryForm.get('start_hour'); } - setDataToUpdate(entryData: NewEntry) { if (entryData) { this.entryForm.patchValue({ @@ -174,4 +172,10 @@ export class EntryFieldsComponent implements OnInit { onTechnologyRemoved($event: string[]) { this.store.dispatch(new entryActions.UpdateEntryRunning({ ...this.newData, technologies: $event })); } + + ngOnDestroy(): void { + this.loadActivitiesSubscription.unsubscribe(); + this.loadActiveEntrySubscription.unsubscribe(); + this.actionSetDateSubscription.unsubscribe(); + } } diff --git a/src/app/modules/time-clock/components/project-list-hover/project-list-hover.component.spec.ts b/src/app/modules/time-clock/components/project-list-hover/project-list-hover.component.spec.ts index c7dd88ef4..e81dd76dc 100644 --- a/src/app/modules/time-clock/components/project-list-hover/project-list-hover.component.spec.ts +++ b/src/app/modules/time-clock/components/project-list-hover/project-list-hover.component.spec.ts @@ -5,7 +5,7 @@ import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; import { provideMockStore, MockStore } from '@ngrx/store/testing'; import { HttpClientTestingModule } from '@angular/common/http/testing'; import { AutocompleteLibModule } from 'angular-ng-autocomplete'; -import { Subscription } from 'rxjs'; +import { Subscription, of } from 'rxjs'; import { ProjectState } from '../../../customer-management/components/projects/components/store/project.reducer'; import { getCustomerProjects } from '../../../customer-management/components/projects/components/store/project.selectors'; import { FilterProjectPipe } from '../../../shared/pipes'; @@ -100,11 +100,17 @@ describe('ProjectListHoverComponent', () => { it('calls unsubscribe on ngDestroy', () => { component.updateEntrySubscription = new Subscription(); + component.projectsSubscription = new Subscription(); + component.activeEntrySubscription = new Subscription(); spyOn(component.updateEntrySubscription, 'unsubscribe'); + spyOn(component.projectsSubscription, 'unsubscribe'); + spyOn(component.activeEntrySubscription, 'unsubscribe'); component.ngOnDestroy(); expect(component.updateEntrySubscription.unsubscribe).toHaveBeenCalled(); + expect(component.projectsSubscription.unsubscribe).toHaveBeenCalled(); + expect(component.activeEntrySubscription.unsubscribe).toHaveBeenCalled(); }); it('sets customer name and project name on setSelectedProject', () => { @@ -115,10 +121,9 @@ describe('ProjectListHoverComponent', () => { component.setSelectedProject(); expect(component.projectsForm.setValue) - .toHaveBeenCalledWith({ project_id: 'customer - xyz'}); + .toHaveBeenCalledWith({ project_id: 'customer - xyz'}); }); - // TODO Fix this test since it is throwing this error // Expected spy dispatch to have been called with: // [CreateEntry({ payload: Object({ project_id: '1', start_date: '2020-07-27T22:30:26.743Z', timezone_offset: 300 }), @@ -140,5 +145,4 @@ describe('ProjectListHoverComponent', () => { // }) // ); // }); - }); diff --git a/src/app/modules/time-clock/components/project-list-hover/project-list-hover.component.ts b/src/app/modules/time-clock/components/project-list-hover/project-list-hover.component.ts index 6213e3ca7..c238d63bf 100644 --- a/src/app/modules/time-clock/components/project-list-hover/project-list-hover.component.ts +++ b/src/app/modules/time-clock/components/project-list-hover/project-list-hover.component.ts @@ -3,7 +3,7 @@ import { FormBuilder, FormGroup } from '@angular/forms'; import { ActionsSubject, select, Store } from '@ngrx/store'; import { ToastrService } from 'ngx-toastr'; import { Observable, Subscription } from 'rxjs'; -import { delay, filter } from 'rxjs/operators'; +import { delay, filter, map } from 'rxjs/operators'; import { Project } from 'src/app/modules/shared/models'; import * as actions from '../../../customer-management/components/projects/components/store/project.actions'; import { ProjectState } from '../../../customer-management/components/projects/components/store/project.reducer'; @@ -17,7 +17,6 @@ import { getActiveTimeEntry } from './../../store/entry.selectors'; styleUrls: ['./project-list-hover.component.scss'], }) export class ProjectListHoverComponent implements OnInit, OnDestroy { - keyword = 'search_field'; listProjects: Project[] = []; activeEntry; @@ -25,6 +24,8 @@ export class ProjectListHoverComponent implements OnInit, OnDestroy { showClockIn: boolean; updateEntrySubscription: Subscription; isLoading$: Observable; + projectsSubscription: Subscription; + activeEntrySubscription: Subscription; constructor(private formBuilder: FormBuilder, private store: Store, private actionsSubject$: ActionsSubject, private toastrService: ToastrService) { @@ -32,10 +33,10 @@ export class ProjectListHoverComponent implements OnInit, OnDestroy { this.isLoading$ = this.store.pipe(delay(0), select(getIsLoading)); } - ngOnInit(): void { + ngOnInit(): void { this.store.dispatch(new actions.LoadProjects()); const projects$ = this.store.pipe(select(getProjects)); - projects$.subscribe((projects) => { + this.projectsSubscription = projects$.subscribe((projects) => { this.listProjects = []; projects.forEach((project) => { const projectWithSearchField = {...project}; @@ -45,7 +46,6 @@ export class ProjectListHoverComponent implements OnInit, OnDestroy { ); this.loadActiveTimeEntry(); }); - this.updateEntrySubscription = this.actionsSubject$.pipe( filter((action: any) => ( action.type === EntryActionTypes.UPDATE_ENTRY_SUCCESS @@ -55,13 +55,12 @@ export class ProjectListHoverComponent implements OnInit, OnDestroy { this.activeEntry = action.payload; this.setSelectedProject(); }); - } loadActiveTimeEntry() { this.store.dispatch(new entryActions.LoadActiveEntry()); const activeEntry$ = this.store.pipe(select(getActiveTimeEntry)); - activeEntry$.subscribe((activeEntry) => { + this.activeEntrySubscription = activeEntry$.subscribe((activeEntry) => { this.activeEntry = activeEntry; if (activeEntry) { this.showClockIn = false; @@ -72,13 +71,12 @@ export class ProjectListHoverComponent implements OnInit, OnDestroy { } }); } - setSelectedProject() { this.listProjects.forEach( (project) => { if (project.id === this.activeEntry.project_id) { this.projectsForm.setValue( { project_id: `${project.customer_name} - ${project.name}`, } - ); + ); } }); } @@ -110,6 +108,8 @@ export class ProjectListHoverComponent implements OnInit, OnDestroy { } ngOnDestroy(): void { + this.projectsSubscription.unsubscribe(); + this.activeEntrySubscription.unsubscribe(); this.updateEntrySubscription.unsubscribe(); } } diff --git a/src/app/modules/time-clock/pages/time-clock.component.spec.ts b/src/app/modules/time-clock/pages/time-clock.component.spec.ts index 1fa2476ee..10a4b5959 100644 --- a/src/app/modules/time-clock/pages/time-clock.component.spec.ts +++ b/src/app/modules/time-clock/pages/time-clock.component.spec.ts @@ -1,3 +1,4 @@ +import { of } from 'rxjs'; import { FormBuilder } from '@angular/forms'; import { StopTimeEntryRunning, EntryActionTypes, LoadEntriesSummary } from './../store/entry.actions'; import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; @@ -95,12 +96,14 @@ describe('TimeClockComponent', () => { expect(component.reloadSummariesOnClockOut).toHaveBeenCalled(); }); - it('unsubscribe clockOutSubscription onDestroy', () => { + it('unsubscribe clockOutSubscription, storeSubscription onDestroy', () => { spyOn(component.clockOutSubscription, 'unsubscribe'); + spyOn(component.storeSubscription, 'unsubscribe'); component.ngOnDestroy(); expect(component.clockOutSubscription.unsubscribe).toHaveBeenCalled(); + expect(component.storeSubscription.unsubscribe).toHaveBeenCalled(); }); it('onInit checks if isLogin and gets the userName', () => { diff --git a/src/app/modules/time-clock/pages/time-clock.component.ts b/src/app/modules/time-clock/pages/time-clock.component.ts index ac57d88aa..30cbbb994 100644 --- a/src/app/modules/time-clock/pages/time-clock.component.ts +++ b/src/app/modules/time-clock/pages/time-clock.component.ts @@ -2,7 +2,9 @@ import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { ActionsSubject, select, Store } from '@ngrx/store'; import { ToastrService } from 'ngx-toastr'; import { Subscription } from 'rxjs'; -import { filter } from 'rxjs/operators'; + +import { filter, map } from 'rxjs/operators'; +import { threadId } from 'worker_threads'; import { AzureAdB2CService } from '../../login/services/azure.ad.b2c.service'; import { EntryFieldsComponent } from '../components/entry-fields/entry-fields.component'; import { Entry } from './../../shared/models/entry.model'; @@ -20,7 +22,7 @@ export class TimeClockComponent implements OnInit, OnDestroy { areFieldsVisible = false; activeTimeEntry: Entry; clockOutSubscription: Subscription; - + storeSubscription: Subscription; constructor( private azureAdB2CService: AzureAdB2CService, @@ -29,13 +31,9 @@ export class TimeClockComponent implements OnInit, OnDestroy { private actionsSubject$: ActionsSubject ) {} - ngOnDestroy(): void { - this.clockOutSubscription.unsubscribe(); - } - - ngOnInit() { + ngOnInit(): void { this.username = this.azureAdB2CService.isLogin() ? this.azureAdB2CService.getName() : ''; - this.store.pipe(select(getActiveTimeEntry)).subscribe((activeTimeEntry) => { + this.storeSubscription = this.store.pipe(select(getActiveTimeEntry)).subscribe((activeTimeEntry) => { this.activeTimeEntry = activeTimeEntry; if (this.activeTimeEntry) { this.areFieldsVisible = true; @@ -43,9 +41,7 @@ export class TimeClockComponent implements OnInit, OnDestroy { this.areFieldsVisible = false; } }); - this.reloadSummariesOnClockOut(); - } reloadSummariesOnClockOut() { @@ -72,4 +68,12 @@ export class TimeClockComponent implements OnInit, OnDestroy { this.toastrService.error('Activity is required'); } } + + ngOnDestroy(): void { + this.clockOutSubscription.unsubscribe(); + this.storeSubscription.unsubscribe(); + } + } + +