Skip to content

Commit 8036082

Browse files
committed
[DURACOM-327] Decoupling state management from data service WIP
1 parent f3982cb commit 8036082

24 files changed

Lines changed: 430 additions & 357 deletions

src/app/access-control/epeople-registry/epeople-registry.component.spec.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ import {
5757
import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub';
5858
import { PaginationServiceStub } from '../../shared/testing/pagination-service.stub';
5959
import { EPeopleRegistryComponent } from './epeople-registry.component';
60+
import { EpeopleRegistryService } from './epeople-registry.service';
6061
import { EPersonFormComponent } from './eperson-form/eperson-form.component';
6162

6263
describe('EPeopleRegistryComponent', () => {
@@ -66,13 +67,17 @@ describe('EPeopleRegistryComponent', () => {
6667

6768
let mockEPeople: EPerson[];
6869
let ePersonDataServiceStub: any;
70+
let epeopleRegistryServiceStub: any;
6971
let authorizationService: AuthorizationDataService;
7072
let modalService: NgbModal;
7173
let paginationService: PaginationServiceStub;
7274

7375
beforeEach(waitForAsync(async () => {
7476
jasmine.getEnv().allowRespy(true);
7577
mockEPeople = [EPersonMock, EPersonMock2];
78+
epeopleRegistryServiceStub = jasmine.createSpyObj('', {
79+
getActiveEPerson: of(null),
80+
});
7681
ePersonDataServiceStub = {
7782
activeEPerson: null,
7883
allEpeople: mockEPeople,
@@ -84,9 +89,6 @@ describe('EPeopleRegistryComponent', () => {
8489
currentPage: 1,
8590
}), this.allEpeople));
8691
},
87-
getActiveEPerson(): Observable<EPerson> {
88-
return of(this.activeEPerson);
89-
},
9092
searchByScope(scope: string, query: string, options: FindListOptions = {}): Observable<RemoteData<PaginatedList<EPerson>>> {
9193
if (scope === 'email') {
9294
const result = this.allEpeople.find((ePerson: EPerson) => {
@@ -155,6 +157,7 @@ describe('EPeopleRegistryComponent', () => {
155157
TranslateModule.forRoot(), EPeopleRegistryComponent, BtnDisabledDirective],
156158
providers: [
157159
{ provide: EPersonDataService, useValue: ePersonDataServiceStub },
160+
{ provide: EpeopleRegistryService, useValue: epeopleRegistryServiceStub },
158161
{ provide: NotificationsService, useValue: new NotificationsServiceStub() },
159162
{ provide: AuthorizationDataService, useValue: authorizationService },
160163
{ provide: FormBuilderService, useValue: builderService },

src/app/access-control/epeople-registry/epeople-registry.component.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,13 @@ import {
6161
getEPersonEditRoute,
6262
getEPersonsRoute,
6363
} from '../access-control-routing-paths';
64-
import { EPersonFormComponent } from './eperson-form/eperson-form.component';
64+
import { EpeopleRegistryService } from './epeople-registry.service';
6565

6666
@Component({
6767
selector: 'ds-epeople-registry',
6868
templateUrl: './epeople-registry.component.html',
6969
imports: [
7070
AsyncPipe,
71-
EPersonFormComponent,
7271
NgClass,
7372
PaginationComponent,
7473
ReactiveFormsModule,
@@ -135,6 +134,7 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy {
135134
subs: Subscription[] = [];
136135

137136
constructor(private epersonService: EPersonDataService,
137+
private epeopleRegistryService: EpeopleRegistryService,
138138
private translateService: TranslateService,
139139
private notificationsService: NotificationsService,
140140
private authorizationService: AuthorizationDataService,
@@ -163,7 +163,7 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy {
163163
initialisePage() {
164164
this.searching$.next(true);
165165
this.search({ scope: this.currentSearchScope, query: this.currentSearchQuery });
166-
this.activeEPerson$ = this.epersonService.getActiveEPerson();
166+
this.activeEPerson$ = this.epeopleRegistryService.getActiveEPerson();
167167
this.subs.push(this.ePeople$.pipe(
168168
switchMap((epeople: PaginatedList<EPerson>) => {
169169
if (epeople.pageInfo.totalElements > 0) {
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { TestBed } from '@angular/core/testing';
2+
import { EPerson } from '@core/eperson/models/eperson.model';
3+
import {
4+
MockStore,
5+
provideMockStore,
6+
} from '@ngrx/store/testing';
7+
import { cold } from 'jasmine-marbles';
8+
9+
import { AppState } from '../../app.reducer';
10+
import {
11+
EPeopleRegistryCancelEPersonAction,
12+
EPeopleRegistryEditEPersonAction,
13+
} from './epeople-registry.actions';
14+
import {
15+
editEPersonSelector,
16+
EpeopleRegistryService,
17+
} from './epeople-registry.service';
18+
19+
describe('EpeopleRegistryService', () => {
20+
let service: EpeopleRegistryService;
21+
let store: MockStore<AppState>;
22+
const initialState = {};
23+
24+
beforeEach(() => {
25+
TestBed.configureTestingModule({
26+
providers: [
27+
EpeopleRegistryService,
28+
provideMockStore({ initialState }),
29+
],
30+
});
31+
32+
service = TestBed.inject(EpeopleRegistryService);
33+
store = TestBed.inject(MockStore);
34+
});
35+
36+
it('should be created', () => {
37+
expect(service).toBeTruthy();
38+
});
39+
40+
describe('#getActiveEPerson', () => {
41+
it('should select the editEPersonSelector from the store', () => {
42+
const mockEPerson: EPerson = { id: '123', name: 'Test User' } as EPerson;
43+
store.overrideSelector(editEPersonSelector, mockEPerson);
44+
45+
const expected = cold('a', { a: mockEPerson });
46+
expect(service.getActiveEPerson()).toBeObservable(expected);
47+
});
48+
});
49+
50+
describe('#cancelEditEPerson', () => {
51+
it('should dispatch EPeopleRegistryCancelEPersonAction', () => {
52+
const dispatchSpy = spyOn(store, 'dispatch');
53+
service.cancelEditEPerson();
54+
expect(dispatchSpy).toHaveBeenCalledWith(new EPeopleRegistryCancelEPersonAction());
55+
});
56+
});
57+
58+
describe('#editEPerson', () => {
59+
it('should dispatch EPeopleRegistryEditEPersonAction with the given EPerson', () => {
60+
const dispatchSpy = spyOn(store, 'dispatch');
61+
const mockEPerson: EPerson = { id: '456', name: 'Another User' } as EPerson;
62+
service.editEPerson(mockEPerson);
63+
expect(dispatchSpy).toHaveBeenCalledWith(new EPeopleRegistryEditEPersonAction(mockEPerson));
64+
});
65+
});
66+
});
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { Injectable } from '@angular/core';
2+
import { EPerson } from '@core/eperson/models/eperson.model';
3+
import {
4+
createSelector,
5+
select,
6+
Store,
7+
} from '@ngrx/store';
8+
import { Observable } from 'rxjs';
9+
import { take } from 'rxjs/operators';
10+
11+
import { AppState } from '../../app.reducer';
12+
import { getEPersonEditRoute } from '../access-control-routing-paths';
13+
import {
14+
EPeopleRegistryCancelEPersonAction,
15+
EPeopleRegistryEditEPersonAction,
16+
} from './epeople-registry.actions';
17+
import { EPeopleRegistryState } from './epeople-registry.reducers';
18+
19+
const ePeopleRegistryStateSelector = (state: AppState) => state.epeopleRegistry;
20+
export const editEPersonSelector = createSelector(ePeopleRegistryStateSelector, (ePeopleRegistryState: EPeopleRegistryState) => ePeopleRegistryState.editEPerson);
21+
22+
@Injectable({
23+
providedIn: 'root',
24+
})
25+
export class EpeopleRegistryService {
26+
27+
constructor(protected store: Store<AppState>) {
28+
}
29+
30+
/**
31+
* Method to retrieve the eperson that is currently being edited
32+
*/
33+
public getActiveEPerson(): Observable<EPerson> {
34+
return this.store.pipe(select(editEPersonSelector));
35+
}
36+
37+
/**
38+
* Method to cancel editing an EPerson, dispatches a cancel EPerson action
39+
*/
40+
public cancelEditEPerson() {
41+
this.store.dispatch(new EPeopleRegistryCancelEPersonAction());
42+
}
43+
44+
/**
45+
* Method to set the EPerson being edited, dispatches an edit EPerson action
46+
* @param ePerson The EPerson to edit
47+
*/
48+
public editEPerson(ePerson: EPerson) {
49+
this.store.dispatch(new EPeopleRegistryEditEPersonAction(ePerson));
50+
}
51+
52+
/**
53+
* Change which ePerson is being edited and return the link for EPeople edit page
54+
* @param ePerson New EPerson to edit
55+
*/
56+
public startEditingNewEPerson(ePerson: EPerson): string {
57+
this.getActiveEPerson().pipe(take(1)).subscribe((activeEPerson: EPerson) => {
58+
if (ePerson === activeEPerson) {
59+
this.cancelEditEPerson();
60+
} else {
61+
this.editEPerson(ePerson);
62+
}
63+
});
64+
return getEPersonEditRoute(ePerson.id);
65+
}
66+
}

src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ import { PaginationComponentOptions } from '../../../shared/pagination/paginatio
7272
import { followLink } from '../../../shared/utils/follow-link-config.model';
7373
import { HasNoValuePipe } from '../../../shared/utils/has-no-value.pipe';
7474
import { getEPersonsRoute } from '../../access-control-routing-paths';
75+
import { EpeopleRegistryService } from '../epeople-registry.service';
7576
import { ValidateEmailNotTaken } from './validators/email-taken.validator';
7677

7778
@Component({
@@ -239,6 +240,7 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
239240
constructor(
240241
protected changeDetectorRef: ChangeDetectorRef,
241242
public epersonService: EPersonDataService,
243+
public epeopleRegistryService: EpeopleRegistryService,
242244
public groupsDataService: GroupDataService,
243245
private formBuilderService: FormBuilderService,
244246
private translateService: TranslateService,
@@ -256,7 +258,7 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
256258
}
257259

258260
ngOnInit() {
259-
this.activeEPerson$ = this.epersonService.getActiveEPerson();
261+
this.activeEPerson$ = this.epeopleRegistryService.getActiveEPerson();
260262
this.subs.push(this.activeEPerson$.subscribe((eperson: EPerson) => {
261263
this.epersonInitial = eperson;
262264
if (hasValue(eperson)) {
@@ -274,7 +276,7 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
274276
initialisePage() {
275277
if (this.route.snapshot.params.id) {
276278
this.subs.push(this.epersonService.findById(this.route.snapshot.params.id).subscribe((ePersonRD: RemoteData<EPerson>) => {
277-
this.epersonService.editEPerson(ePersonRD.payload);
279+
this.epeopleRegistryService.editEPerson(ePersonRD.payload);
278280
}));
279281
}
280282
this.firstName = new DynamicInputModel({
@@ -393,7 +395,7 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
393395
* Stop editing the currently selected eperson
394396
*/
395397
onCancel() {
396-
this.epersonService.cancelEditEPerson();
398+
this.epeopleRegistryService.cancelEditEPerson();
397399
this.cancelForm.emit();
398400
void this.router.navigate([getEPersonsRoute()]);
399401
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { waitForAsync } from '@angular/core/testing';
2+
import { BitstreamFormat } from '@core/shared/bitstream-format.model';
3+
import { createMockStore } from '@ngrx/store/testing';
4+
5+
import {
6+
BitstreamFormatsRegistryDeselectAction,
7+
BitstreamFormatsRegistryDeselectAllAction,
8+
BitstreamFormatsRegistrySelectAction,
9+
} from './bitstream-format.actions';
10+
import { BitstreamFormatService } from './bitstream-format.service';
11+
12+
describe('BitstreamFormatDataService', () => {
13+
let service: BitstreamFormatService;
14+
15+
const store: any = createMockStore({});
16+
17+
function initTestService() {
18+
return new BitstreamFormatService(store);
19+
}
20+
21+
describe('selectBitstreamFormat', () => {
22+
beforeEach(waitForAsync(() => {
23+
service = initTestService();
24+
spyOn(store, 'dispatch');
25+
}));
26+
it('should add a selected bitstream to the store', () => {
27+
const format = new BitstreamFormat();
28+
format.uuid = 'uuid';
29+
30+
service.selectBitstreamFormat(format);
31+
expect(store.dispatch).toHaveBeenCalledWith(new BitstreamFormatsRegistrySelectAction(format));
32+
});
33+
});
34+
35+
describe('deselectBitstreamFormat', () => {
36+
beforeEach(waitForAsync(() => {
37+
service = initTestService();
38+
spyOn(store, 'dispatch');
39+
}));
40+
it('should remove a bitstream from the store', () => {
41+
const format = new BitstreamFormat();
42+
format.uuid = 'uuid';
43+
44+
service.deselectBitstreamFormat(format);
45+
expect(store.dispatch).toHaveBeenCalledWith(new BitstreamFormatsRegistryDeselectAction(format));
46+
});
47+
});
48+
49+
describe('deselectAllBitstreamFormats', () => {
50+
beforeEach(waitForAsync(() => {
51+
service = initTestService();
52+
spyOn(store, 'dispatch');
53+
54+
}));
55+
it('should remove all bitstreamFormats from the store', () => {
56+
service.deselectAllBitstreamFormats();
57+
expect(store.dispatch).toHaveBeenCalledWith(new BitstreamFormatsRegistryDeselectAllAction());
58+
});
59+
});
60+
61+
});
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { Injectable } from '@angular/core';
2+
import { BitstreamFormat } from '@core/shared/bitstream-format.model';
3+
import {
4+
createSelector,
5+
select,
6+
Store,
7+
} from '@ngrx/store';
8+
import { Observable } from 'rxjs';
9+
10+
import { AppState } from '../../../app.reducer';
11+
import {
12+
BitstreamFormatsRegistryDeselectAction,
13+
BitstreamFormatsRegistryDeselectAllAction,
14+
BitstreamFormatsRegistrySelectAction,
15+
} from './bitstream-format.actions';
16+
import { BitstreamFormatRegistryState } from './bitstream-format.reducers';
17+
18+
19+
export const bitstreamFormatsStateSelector = (state: AppState) => state.bitstreamFormats;
20+
const selectedBitstreamFormatSelector = createSelector(
21+
bitstreamFormatsStateSelector,
22+
(bitstreamFormatRegistryState: BitstreamFormatRegistryState) => bitstreamFormatRegistryState.selectedBitstreamFormats,
23+
);
24+
25+
/**
26+
* A service responsible for fetching/sending data from/to the REST API on the bitstreamformats endpoint
27+
*/
28+
@Injectable({ providedIn: 'root' })
29+
export class BitstreamFormatService {
30+
31+
constructor(
32+
protected store: Store<AppState>,
33+
) {
34+
}
35+
36+
/**
37+
* Gets all the selected BitstreamFormats from the store
38+
*/
39+
public getSelectedBitstreamFormats(): Observable<BitstreamFormat[]> {
40+
return this.store.pipe(select(selectedBitstreamFormatSelector));
41+
}
42+
43+
/**
44+
* Adds a BistreamFormat to the selected BitstreamFormats in the store
45+
* @param bitstreamFormat
46+
*/
47+
public selectBitstreamFormat(bitstreamFormat: BitstreamFormat) {
48+
this.store.dispatch(new BitstreamFormatsRegistrySelectAction(bitstreamFormat));
49+
}
50+
51+
/**
52+
* Removes a BistreamFormat from the list of selected BitstreamFormats in the store
53+
* @param bitstreamFormat
54+
*/
55+
public deselectBitstreamFormat(bitstreamFormat: BitstreamFormat) {
56+
this.store.dispatch(new BitstreamFormatsRegistryDeselectAction(bitstreamFormat));
57+
}
58+
59+
/**
60+
* Removes all BitstreamFormats from the list of selected BitstreamFormats in the store
61+
*/
62+
public deselectAllBitstreamFormats() {
63+
this.store.dispatch(new BitstreamFormatsRegistryDeselectAllAction());
64+
}
65+
}

0 commit comments

Comments
 (0)