mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
77643: WIP: Refactor BreadcrumbsComponent into service
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
<ng-container *ngVar="(breadcrumbs$ | async) as breadcrumbs">
|
<ng-container *ngVar="(breadcrumbs$ | async) as breadcrumbs">
|
||||||
<nav *ngIf="showBreadcrumbs" aria-label="breadcrumb">
|
<nav *ngIf="(showBreadcrumbs$ | async)" aria-label="breadcrumb">
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<ng-container
|
<ng-container
|
||||||
*ngTemplateOutlet="breadcrumbs?.length > 0 ? breadcrumb : activeBreadcrumb; context: {text: 'home.breadcrumbs', url: '/'}"></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 { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { VarDirective } from '../shared/utils/var.directive';
|
import { VarDirective } from '../shared/utils/var.directive';
|
||||||
import { getTestScheduler } from 'jasmine-marbles';
|
|
||||||
|
|
||||||
class TestBreadcrumbsService implements BreadcrumbsProviderService<string> {
|
class TestBreadcrumbsService implements BreadcrumbsProviderService<string> {
|
||||||
getBreadcrumbs(key: string, url: string): Observable<Breadcrumb[]> {
|
getBreadcrumbs(key: string, url: string): Observable<Breadcrumb[]> {
|
||||||
@@ -92,24 +91,24 @@ describe('BreadcrumbsComponent', () => {
|
|||||||
expect(component).toBeTruthy();
|
expect(component).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('ngOnInit', () => {
|
// describe('ngOnInit', () => {
|
||||||
beforeEach(() => {
|
// beforeEach(() => {
|
||||||
spyOn(component, 'resolveBreadcrumbs').and.returnValue(observableOf([]));
|
// spyOn(component, 'resolveBreadcrumbs').and.returnValue(observableOf([]));
|
||||||
});
|
// });
|
||||||
|
//
|
||||||
it('should call resolveBreadcrumb on init', () => {
|
// it('should call resolveBreadcrumb on init', () => {
|
||||||
router.events = observableOf(new NavigationEnd(0, '', ''));
|
// router.events = observableOf(new NavigationEnd(0, '', ''));
|
||||||
component.ngOnInit();
|
// component.ngOnInit();
|
||||||
fixture.detectChanges();
|
// fixture.detectChanges();
|
||||||
|
//
|
||||||
expect(component.resolveBreadcrumbs).toHaveBeenCalledWith(route.root);
|
// expect(component.resolveBreadcrumbs).toHaveBeenCalledWith(route.root);
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
|
//
|
||||||
describe('resolveBreadcrumbs', () => {
|
// describe('resolveBreadcrumbs', () => {
|
||||||
it('should return the correct breadcrumbs', () => {
|
// it('should return the correct breadcrumbs', () => {
|
||||||
const breadcrumbs = component.resolveBreadcrumbs(route.root);
|
// const breadcrumbs = component.resolveBreadcrumbs(route.root);
|
||||||
getTestScheduler().expectObservable(breadcrumbs).toBe('(a|)', { a: expectedBreadcrumbs });
|
// getTestScheduler().expectObservable(breadcrumbs).toBe('(a|)', { a: expectedBreadcrumbs });
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
});
|
});
|
||||||
|
@@ -1,9 +1,8 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { Breadcrumb } from './breadcrumb/breadcrumb.model';
|
import { Breadcrumb } from './breadcrumb/breadcrumb.model';
|
||||||
import { hasNoValue, hasValue, isUndefined } from '../shared/empty.util';
|
import { BreadcrumbsService } from './breadcrumbs.service';
|
||||||
import { filter, map, switchMap, tap } from 'rxjs/operators';
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
import { combineLatest, Observable, of as observableOf } from 'rxjs';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component representing the breadcrumbs of a page
|
* Component representing the breadcrumbs of a page
|
||||||
@@ -13,7 +12,8 @@ import { combineLatest, Observable, of as observableOf } from 'rxjs';
|
|||||||
templateUrl: './breadcrumbs.component.html',
|
templateUrl: './breadcrumbs.component.html',
|
||||||
styleUrls: ['./breadcrumbs.component.scss']
|
styleUrls: ['./breadcrumbs.component.scss']
|
||||||
})
|
})
|
||||||
export class BreadcrumbsComponent implements OnInit {
|
export class BreadcrumbsComponent {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Observable of the list of breadcrumbs for this page
|
* 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
|
* Whether or not to show breadcrumbs on this page
|
||||||
*/
|
*/
|
||||||
showBreadcrumbs: boolean;
|
showBreadcrumbs$: Observable<boolean>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private route: ActivatedRoute,
|
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