mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 18:14:17 +00:00
77643: WIP: Refactor BreadcrumbsComponent into service
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<ng-container *ngVar="(breadcrumbs$ | async) as breadcrumbs">
|
||||
<nav *ngIf="showBreadcrumbs" aria-label="breadcrumb">
|
||||
<nav *ngIf="(showBreadcrumbs$ | async)" aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<ng-container
|
||||
*ngTemplateOutlet="breadcrumbs?.length > 0 ? breadcrumb : activeBreadcrumb; context: {text: 'home.breadcrumbs', url: '/'}"></ng-container>
|
||||
|
@@ -12,7 +12,6 @@ import { Breadcrumb } from './breadcrumb/breadcrumb.model';
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { VarDirective } from '../shared/utils/var.directive';
|
||||
import { getTestScheduler } from 'jasmine-marbles';
|
||||
|
||||
class TestBreadcrumbsService implements BreadcrumbsProviderService<string> {
|
||||
getBreadcrumbs(key: string, url: string): Observable<Breadcrumb[]> {
|
||||
@@ -92,24 +91,24 @@ describe('BreadcrumbsComponent', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('ngOnInit', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(component, 'resolveBreadcrumbs').and.returnValue(observableOf([]));
|
||||
});
|
||||
|
||||
it('should call resolveBreadcrumb on init', () => {
|
||||
router.events = observableOf(new NavigationEnd(0, '', ''));
|
||||
component.ngOnInit();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.resolveBreadcrumbs).toHaveBeenCalledWith(route.root);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveBreadcrumbs', () => {
|
||||
it('should return the correct breadcrumbs', () => {
|
||||
const breadcrumbs = component.resolveBreadcrumbs(route.root);
|
||||
getTestScheduler().expectObservable(breadcrumbs).toBe('(a|)', { a: expectedBreadcrumbs });
|
||||
});
|
||||
});
|
||||
// describe('ngOnInit', () => {
|
||||
// beforeEach(() => {
|
||||
// spyOn(component, 'resolveBreadcrumbs').and.returnValue(observableOf([]));
|
||||
// });
|
||||
//
|
||||
// it('should call resolveBreadcrumb on init', () => {
|
||||
// router.events = observableOf(new NavigationEnd(0, '', ''));
|
||||
// component.ngOnInit();
|
||||
// fixture.detectChanges();
|
||||
//
|
||||
// expect(component.resolveBreadcrumbs).toHaveBeenCalledWith(route.root);
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// describe('resolveBreadcrumbs', () => {
|
||||
// it('should return the correct breadcrumbs', () => {
|
||||
// const breadcrumbs = component.resolveBreadcrumbs(route.root);
|
||||
// getTestScheduler().expectObservable(breadcrumbs).toBe('(a|)', { a: expectedBreadcrumbs });
|
||||
// });
|
||||
// });
|
||||
});
|
||||
|
@@ -1,9 +1,8 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
|
||||
import { Component } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { Breadcrumb } from './breadcrumb/breadcrumb.model';
|
||||
import { hasNoValue, hasValue, isUndefined } from '../shared/empty.util';
|
||||
import { filter, map, switchMap, tap } from 'rxjs/operators';
|
||||
import { combineLatest, Observable, of as observableOf } from 'rxjs';
|
||||
import { BreadcrumbsService } from './breadcrumbs.service';
|
||||
import { Observable } from 'rxjs/internal/Observable';
|
||||
|
||||
/**
|
||||
* Component representing the breadcrumbs of a page
|
||||
@@ -13,7 +12,8 @@ import { combineLatest, Observable, of as observableOf } from 'rxjs';
|
||||
templateUrl: './breadcrumbs.component.html',
|
||||
styleUrls: ['./breadcrumbs.component.scss']
|
||||
})
|
||||
export class BreadcrumbsComponent implements OnInit {
|
||||
export class BreadcrumbsComponent {
|
||||
|
||||
/**
|
||||
* Observable of the list of breadcrumbs for this page
|
||||
*/
|
||||
@@ -22,61 +22,15 @@ export class BreadcrumbsComponent implements OnInit {
|
||||
/**
|
||||
* Whether or not to show breadcrumbs on this page
|
||||
*/
|
||||
showBreadcrumbs: boolean;
|
||||
showBreadcrumbs$: Observable<boolean>;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private router: Router
|
||||
private router: Router,
|
||||
private breadcrumbsService: BreadcrumbsService,
|
||||
) {
|
||||
this.breadcrumbs$ = breadcrumbsService.breadcrumbs$;
|
||||
this.showBreadcrumbs$ = breadcrumbsService.showBreadcrumbs$;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the breadcrumbs on init for this page
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.breadcrumbs$ = this.router.events.pipe(
|
||||
filter((e): e is NavigationEnd => e instanceof NavigationEnd),
|
||||
tap(() => this.reset()),
|
||||
switchMap(() => this.resolveBreadcrumbs(this.route.root)),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method that recursively resolves breadcrumbs
|
||||
* @param route The route to get the breadcrumb from
|
||||
*/
|
||||
resolveBreadcrumbs(route: ActivatedRoute): Observable<Breadcrumb[]> {
|
||||
const data = route.snapshot.data;
|
||||
const routeConfig = route.snapshot.routeConfig;
|
||||
|
||||
const last: boolean = hasNoValue(route.firstChild);
|
||||
if (last) {
|
||||
if (hasValue(data.showBreadcrumbs)) {
|
||||
this.showBreadcrumbs = data.showBreadcrumbs;
|
||||
} else if (isUndefined(data.breadcrumb)) {
|
||||
this.showBreadcrumbs = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
hasValue(data) && hasValue(data.breadcrumb) &&
|
||||
hasValue(routeConfig) && hasValue(routeConfig.resolve) && hasValue(routeConfig.resolve.breadcrumb)
|
||||
) {
|
||||
const { provider, key, url } = data.breadcrumb;
|
||||
if (!last) {
|
||||
return combineLatest(provider.getBreadcrumbs(key, url), this.resolveBreadcrumbs(route.firstChild))
|
||||
.pipe(map((crumbs) => [].concat.apply([], crumbs)));
|
||||
} else {
|
||||
return provider.getBreadcrumbs(key, url);
|
||||
}
|
||||
}
|
||||
return !last ? this.resolveBreadcrumbs(route.firstChild) : observableOf([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the state of the breadcrumbs
|
||||
*/
|
||||
reset() {
|
||||
this.showBreadcrumbs = true;
|
||||
}
|
||||
}
|
||||
|
116
src/app/breadcrumbs/breadcrumbs.service.spec.ts
Normal file
116
src/app/breadcrumbs/breadcrumbs.service.spec.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
/* tslint:disable:max-classes-per-file */
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { BreadcrumbsService } from './breadcrumbs.service';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { ActivatedRouteSnapshot, Resolve, Route, Router } from '@angular/router';
|
||||
import { Component } from '@angular/core';
|
||||
import { BreadcrumbConfig } from './breadcrumb/breadcrumb-config.model';
|
||||
import { BreadcrumbsProviderService } from '../core/breadcrumbs/breadcrumbsProviderService';
|
||||
import { Observable, of as observableOf } from 'rxjs';
|
||||
import { Breadcrumb } from './breadcrumb/breadcrumb.model';
|
||||
|
||||
/**
|
||||
* Create the breadcrumbs
|
||||
*/
|
||||
class TestBreadcrumbsProviderService implements BreadcrumbsProviderService<string> {
|
||||
getBreadcrumbs(key: string, url: string): Observable<Breadcrumb[]> {
|
||||
return observableOf([new Breadcrumb(key, url)]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty component used for every route
|
||||
*/
|
||||
@Component({ template: '' })
|
||||
class DummyComponent {}
|
||||
|
||||
/**
|
||||
* {@link BreadcrumbsService#resolveBreadcrumbs} requires that a breadcrumb resolver is present,
|
||||
* or data.breadcrumb will be ignored.
|
||||
* This class satisfies the requirement and sets data.breadcrumb.
|
||||
*/
|
||||
class TestBreadcrumbResolver implements Resolve<BreadcrumbConfig<string>> {
|
||||
resolve(route: ActivatedRouteSnapshot): BreadcrumbConfig<string> {
|
||||
console.log('route:', route);
|
||||
return route.data.returnValueForTestBreadcrumbResolver;
|
||||
}
|
||||
}
|
||||
|
||||
describe('BreadcrumbsService', () => {
|
||||
let service: BreadcrumbsService;
|
||||
let router: any;
|
||||
let breadcrumbProvider;
|
||||
let breadcrumbConfigA: BreadcrumbConfig<string>;
|
||||
let breadcrumbConfigB: BreadcrumbConfig<string>;
|
||||
|
||||
const initRoute = (path: string, showBreadcrumbs: boolean, breadcrumbConfig: BreadcrumbConfig<string>): Route => ({
|
||||
path: path,
|
||||
component: DummyComponent,
|
||||
data: {
|
||||
showBreadcrumbs: showBreadcrumbs,
|
||||
returnValueForTestBreadcrumbResolver: breadcrumbConfig,
|
||||
},
|
||||
resolve: {
|
||||
breadcrumb: TestBreadcrumbResolver,
|
||||
}
|
||||
});
|
||||
|
||||
const initBreadcrumbs = () => {
|
||||
breadcrumbProvider = new TestBreadcrumbsProviderService();
|
||||
breadcrumbConfigA = { provider: breadcrumbProvider, key: 'example.path', url: 'example.com' };
|
||||
breadcrumbConfigB = { provider: breadcrumbProvider, key: 'another.path', url: 'another.com' };
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
initBreadcrumbs();
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
TestBreadcrumbResolver,
|
||||
],
|
||||
imports: [
|
||||
RouterTestingModule.withRoutes([
|
||||
initRoute('route-1', undefined, undefined),
|
||||
initRoute('route-2', false, breadcrumbConfigA),
|
||||
initRoute('route-3', true, breadcrumbConfigB),
|
||||
]),
|
||||
],
|
||||
});
|
||||
router = TestBed.inject(Router);
|
||||
service = TestBed.inject(BreadcrumbsService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('breadcrumbs$', () => {
|
||||
it('should return a breadcrumb corresponding to the current route', () => {
|
||||
// TODO
|
||||
service.breadcrumbs$.subscribe((value) => {
|
||||
console.log('TEST');
|
||||
console.log(value);
|
||||
});
|
||||
router.navigate(['route-3']);
|
||||
});
|
||||
|
||||
it('should change when the route changes', () => {
|
||||
// TODO
|
||||
});
|
||||
});
|
||||
|
||||
describe('showBreadcrumbs$', () => {
|
||||
describe('when the last part of the route has showBreadcrumbs in its data', () => {
|
||||
it('should return that value', () => {
|
||||
// TODO
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the last part of the route has no breadcrumb in its data', () => {
|
||||
it('should return false', () => {
|
||||
// TODO
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
74
src/app/breadcrumbs/breadcrumbs.service.ts
Normal file
74
src/app/breadcrumbs/breadcrumbs.service.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {combineLatest, Observable, of as observableOf, ReplaySubject} from 'rxjs';
|
||||
import {Breadcrumb} from './breadcrumb/breadcrumb.model';
|
||||
import {ActivatedRoute, NavigationEnd, Router} from '@angular/router';
|
||||
import {filter, map, switchMap, tap} from 'rxjs/operators';
|
||||
import {hasNoValue, hasValue, isUndefined} from '../shared/empty.util';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class BreadcrumbsService {
|
||||
|
||||
/**
|
||||
* Observable of the list of breadcrumbs for this page
|
||||
*/
|
||||
breadcrumbs$: ReplaySubject<Breadcrumb[]> = new ReplaySubject(1);
|
||||
|
||||
/**
|
||||
* Whether or not to show breadcrumbs on this page
|
||||
*/
|
||||
showBreadcrumbs$: ReplaySubject<boolean> = new ReplaySubject(1);
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
) {
|
||||
// supply events to this.breadcrumbs$
|
||||
this.router.events.pipe(
|
||||
filter((e): e is NavigationEnd => e instanceof NavigationEnd),
|
||||
tap(() => this.reset()),
|
||||
switchMap(() => this.resolveBreadcrumbs(this.route.root)),
|
||||
).subscribe(this.breadcrumbs$);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method that recursively resolves breadcrumbs
|
||||
* @param route The route to get the breadcrumb from
|
||||
*/
|
||||
private resolveBreadcrumbs(route: ActivatedRoute): Observable<Breadcrumb[]> {
|
||||
const data = route.snapshot.data;
|
||||
const routeConfig = route.snapshot.routeConfig;
|
||||
|
||||
const last: boolean = hasNoValue(route.firstChild);
|
||||
if (last) {
|
||||
if (hasValue(data.showBreadcrumbs)) {
|
||||
this.showBreadcrumbs$.next(data.showBreadcrumbs);
|
||||
} else if (isUndefined(data.breadcrumb)) {
|
||||
this.showBreadcrumbs$.next(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
hasValue(data) && hasValue(data.breadcrumb) &&
|
||||
hasValue(routeConfig) && hasValue(routeConfig.resolve) && hasValue(routeConfig.resolve.breadcrumb)
|
||||
) {
|
||||
const { provider, key, url } = data.breadcrumb;
|
||||
if (!last) {
|
||||
return combineLatest(provider.getBreadcrumbs(key, url), this.resolveBreadcrumbs(route.firstChild))
|
||||
.pipe(map((crumbs) => [].concat.apply([], crumbs)));
|
||||
} else {
|
||||
return provider.getBreadcrumbs(key, url);
|
||||
}
|
||||
}
|
||||
return !last ? this.resolveBreadcrumbs(route.firstChild) : observableOf([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the state of the breadcrumbs
|
||||
*/
|
||||
private reset() {
|
||||
this.showBreadcrumbs$.next(true);
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user