|
|
@@ -0,0 +1,427 @@
|
|
|
+import { Component, DebugElement } from '@angular/core';
|
|
|
+import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
|
|
|
+import { By } from '@angular/platform-browser';
|
|
|
+import { Resolve, Router, Routes } from '@angular/router';
|
|
|
+import { RouterTestingModule } from '@angular/router/testing';
|
|
|
+import { Observable, of as observableOf } from 'rxjs';
|
|
|
+import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
|
|
+
|
|
|
+import { StateStore } from '../../../state/state-store.service';
|
|
|
+import { BreadcrumbComponent, BreadcrumbLabelLinkPair } from './breadcrumb.component';
|
|
|
+
|
|
|
+describe('BeadcrumbsComponent', () => {
|
|
|
+
|
|
|
+ let baseRouteConfig: Routes;
|
|
|
+ let router: Router;
|
|
|
+ let breadcrumbSubject: BehaviorSubject<string>;
|
|
|
+
|
|
|
+ beforeEach(() => {
|
|
|
+ breadcrumbSubject = new BehaviorSubject<string>('Initial Value');
|
|
|
+
|
|
|
+ const leafRoute = {
|
|
|
+ path: 'string-grandchild',
|
|
|
+ data: { breadcrumb: 'Grandchild' },
|
|
|
+ component: TestChildComponent,
|
|
|
+ };
|
|
|
+
|
|
|
+ baseRouteConfig = [
|
|
|
+ {
|
|
|
+ path: '',
|
|
|
+ component: TestParentComponent,
|
|
|
+ data: { breadcrumb: 'Root' },
|
|
|
+ children: [
|
|
|
+ {
|
|
|
+ path: 'string-child',
|
|
|
+ component: TestParentComponent,
|
|
|
+ data: { breadcrumb: 'Child' },
|
|
|
+ children: [leafRoute],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ path: 'no-breadcrumb-child',
|
|
|
+ component: TestParentComponent,
|
|
|
+ children: [leafRoute],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ path: 'simple-function-child',
|
|
|
+ component: TestParentComponent,
|
|
|
+ data: {
|
|
|
+ breadcrumb: () => 'String From Function',
|
|
|
+ },
|
|
|
+ children: [leafRoute],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ path: 'resolved-function-child',
|
|
|
+ component: TestParentComponent,
|
|
|
+ data: {
|
|
|
+ breadcrumb: data => data.foo,
|
|
|
+ },
|
|
|
+ resolve: { foo: FooResolver },
|
|
|
+ children: [leafRoute],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ path: 'params-child/:name',
|
|
|
+ component: TestParentComponent,
|
|
|
+ data: {
|
|
|
+ breadcrumb: (data, params) => params['name'],
|
|
|
+ },
|
|
|
+ children: [leafRoute],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ path: 'single-pair-child',
|
|
|
+ component: TestParentComponent,
|
|
|
+ data: {
|
|
|
+ breadcrumb: {
|
|
|
+ label: 'Pair',
|
|
|
+ link: ['foo', 'bar', { p: 1 }],
|
|
|
+ } as BreadcrumbLabelLinkPair,
|
|
|
+ },
|
|
|
+ children: [leafRoute],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ path: 'array-pair-child',
|
|
|
+ component: TestParentComponent,
|
|
|
+ data: {
|
|
|
+ breadcrumb: [
|
|
|
+ {
|
|
|
+ label: 'PairA',
|
|
|
+ link: ['foo', 'bar'],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: 'PairB',
|
|
|
+ link: ['baz', 'quux'],
|
|
|
+ },
|
|
|
+ ] as BreadcrumbLabelLinkPair[],
|
|
|
+ },
|
|
|
+ children: [leafRoute],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ path: 'pair-function-child',
|
|
|
+ component: TestParentComponent,
|
|
|
+ data: {
|
|
|
+ breadcrumb: () => [
|
|
|
+ {
|
|
|
+ label: 'PairA',
|
|
|
+ link: ['foo', 'bar'],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: 'PairB',
|
|
|
+ link: ['baz', 'quux'],
|
|
|
+ },
|
|
|
+ ] as BreadcrumbLabelLinkPair[],
|
|
|
+ },
|
|
|
+ children: [leafRoute],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ path: 'relative-parent',
|
|
|
+ component: TestParentComponent,
|
|
|
+ data: { breadcrumb: 'Parent' },
|
|
|
+ children: [
|
|
|
+ {
|
|
|
+ path: 'relative-child',
|
|
|
+ component: TestParentComponent,
|
|
|
+ data: {
|
|
|
+ breadcrumb: {
|
|
|
+ label: 'Child',
|
|
|
+ link: ['./', 'foo', { p: 1 }],
|
|
|
+ } as BreadcrumbLabelLinkPair,
|
|
|
+ },
|
|
|
+ children: [leafRoute],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ path: 'relative-sibling',
|
|
|
+ component: TestParentComponent,
|
|
|
+ data: {
|
|
|
+ breadcrumb: {
|
|
|
+ label: 'Sibling',
|
|
|
+ link: ['../', 'foo', { p: 1 }],
|
|
|
+ } as BreadcrumbLabelLinkPair,
|
|
|
+ },
|
|
|
+ children: [leafRoute],
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ path: 'deep-pair-child-1',
|
|
|
+ component: TestParentComponent,
|
|
|
+ data: {
|
|
|
+ breadcrumb: 'Child 1',
|
|
|
+ },
|
|
|
+ children: [
|
|
|
+ {
|
|
|
+ path: 'deep-pair-child-2',
|
|
|
+ component: TestParentComponent,
|
|
|
+ data: {
|
|
|
+ breadcrumb: () => [
|
|
|
+ {
|
|
|
+ label: 'PairA',
|
|
|
+ link: ['./', 'child', 'path'],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: 'PairB',
|
|
|
+ link: ['../', 'sibling', 'path'],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: 'PairC',
|
|
|
+ link: ['absolute', 'path'],
|
|
|
+ },
|
|
|
+ ] as BreadcrumbLabelLinkPair[],
|
|
|
+ },
|
|
|
+ children: [leafRoute],
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ path: 'observable-string',
|
|
|
+ component: TestParentComponent,
|
|
|
+ data: {
|
|
|
+ breadcrumb: observableOf('Observable String'),
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ path: 'observable-pair',
|
|
|
+ component: TestParentComponent,
|
|
|
+ data: {
|
|
|
+ breadcrumb: observableOf({
|
|
|
+ label: 'Observable Pair',
|
|
|
+ link: ['foo', 'bar', { p: 1 }],
|
|
|
+ } as BreadcrumbLabelLinkPair),
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ path: 'observable-array-pair',
|
|
|
+ component: TestParentComponent,
|
|
|
+ data: {
|
|
|
+ breadcrumb: [
|
|
|
+ {
|
|
|
+ label: 'Observable PairA',
|
|
|
+ link: ['foo', 'bar'],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: 'Observable PairB',
|
|
|
+ link: ['baz', 'quux'],
|
|
|
+ },
|
|
|
+ ] as BreadcrumbLabelLinkPair[],
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ path: 'function-observable',
|
|
|
+ component: TestParentComponent,
|
|
|
+ data: {
|
|
|
+ breadcrumb: () => observableOf('Observable String From Function'),
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ path: 'observable-string-subject',
|
|
|
+ component: TestParentComponent,
|
|
|
+ data: {
|
|
|
+ breadcrumb: breadcrumbSubject.asObservable(),
|
|
|
+ },
|
|
|
+ children: [leafRoute],
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ ];
|
|
|
+
|
|
|
+ TestBed.configureTestingModule({
|
|
|
+ imports: [
|
|
|
+ RouterTestingModule.withRoutes(baseRouteConfig),
|
|
|
+ ],
|
|
|
+ declarations: [
|
|
|
+ BreadcrumbComponent,
|
|
|
+ TestParentComponent,
|
|
|
+ TestChildComponent,
|
|
|
+ ],
|
|
|
+ providers: [
|
|
|
+ FooResolver,
|
|
|
+ { provide: StateStore, useClass: class {} },
|
|
|
+ ],
|
|
|
+ }).compileComponents();
|
|
|
+
|
|
|
+ router = TestBed.get(Router);
|
|
|
+ });
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Navigates to the provided route and returns the fixture for the TestChildComponent at that route.
|
|
|
+ */
|
|
|
+ function getFixtureForRoute(route: string[], testFn: (fixture: ComponentFixture<TestComponent>) => void): () => void {
|
|
|
+ return fakeAsync(() => {
|
|
|
+ const fixture = TestBed.createComponent(TestChildComponent);
|
|
|
+ router.navigate(route);
|
|
|
+ fixture.detectChanges();
|
|
|
+ tick();
|
|
|
+ fixture.detectChanges();
|
|
|
+ testFn(fixture);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ it('shows correct labels for string breadcrumbs',
|
|
|
+ getFixtureForRoute(['', 'string-child', 'string-grandchild'], fixture => {
|
|
|
+ const labels = getBreadcrumbLabels(fixture);
|
|
|
+ expect(labels).toEqual(['Root', 'Child', 'Grandchild']);
|
|
|
+ }));
|
|
|
+
|
|
|
+ it('links have correct href',
|
|
|
+ getFixtureForRoute(['', 'string-child', 'string-grandchild'], fixture => {
|
|
|
+ const links = getBreadcrumbLinks(fixture);
|
|
|
+ expect(links).toEqual(['/', '/string-child']);
|
|
|
+ }));
|
|
|
+
|
|
|
+ it('skips a route with no breadcrumbs configured',
|
|
|
+ getFixtureForRoute(['', 'no-breadcrumb-child', 'string-grandchild'], fixture => {
|
|
|
+ const labels = getBreadcrumbLabels(fixture);
|
|
|
+ expect(labels).toEqual(['Root', 'Grandchild']);
|
|
|
+ }));
|
|
|
+
|
|
|
+ it('shows correct label for function breadcrumb',
|
|
|
+ getFixtureForRoute(['', 'simple-function-child', 'string-grandchild'], fixture => {
|
|
|
+ const labels = getBreadcrumbLabels(fixture);
|
|
|
+ expect(labels).toEqual(['Root', 'String From Function', 'Grandchild']);
|
|
|
+ }));
|
|
|
+
|
|
|
+ it('works with resolved data',
|
|
|
+ getFixtureForRoute(['', 'resolved-function-child', 'string-grandchild'], fixture => {
|
|
|
+ const labels = getBreadcrumbLabels(fixture);
|
|
|
+ expect(labels).toEqual(['Root', 'Foo', 'Grandchild']);
|
|
|
+ }));
|
|
|
+
|
|
|
+ it('works with data from parameters',
|
|
|
+ getFixtureForRoute(['', 'params-child', 'Bar', 'string-grandchild'], fixture => {
|
|
|
+ const labels = getBreadcrumbLabels(fixture);
|
|
|
+ expect(labels).toEqual(['Root', 'Bar', 'Grandchild']);
|
|
|
+ }));
|
|
|
+
|
|
|
+ it('works with a BreadcrumbLabelLinkPair',
|
|
|
+ getFixtureForRoute(['', 'single-pair-child', 'string-grandchild'], fixture => {
|
|
|
+ const labels = getBreadcrumbLabels(fixture);
|
|
|
+ const links = getBreadcrumbLinks(fixture);
|
|
|
+ expect(labels).toEqual(['Root', 'Pair', 'Grandchild']);
|
|
|
+ expect(links).toEqual(['/', '/foo/bar;p=1']);
|
|
|
+ }));
|
|
|
+
|
|
|
+ it('works with array of BreadcrumbLabelLinkPairs',
|
|
|
+ getFixtureForRoute(['', 'array-pair-child', 'string-grandchild'], fixture => {
|
|
|
+ const labels = getBreadcrumbLabels(fixture);
|
|
|
+ const links = getBreadcrumbLinks(fixture);
|
|
|
+ expect(labels).toEqual(['Root', 'PairA', 'PairB', 'Grandchild']);
|
|
|
+ expect(links).toEqual(['/', '/foo/bar', '/baz/quux']);
|
|
|
+ }));
|
|
|
+
|
|
|
+ it('works with function returning BreadcrumbLabelLinkPairs',
|
|
|
+ getFixtureForRoute(['', 'pair-function-child', 'string-grandchild'], fixture => {
|
|
|
+ const labels = getBreadcrumbLabels(fixture);
|
|
|
+ const links = getBreadcrumbLinks(fixture);
|
|
|
+ expect(labels).toEqual(['Root', 'PairA', 'PairB', 'Grandchild']);
|
|
|
+ expect(links).toEqual(['/', '/foo/bar', '/baz/quux']);
|
|
|
+ }));
|
|
|
+
|
|
|
+ it('works with relative child paths in a BreadcrumbLabelLinkPair',
|
|
|
+ getFixtureForRoute(['', 'relative-parent', 'relative-child', 'string-grandchild'], fixture => {
|
|
|
+ const labels = getBreadcrumbLabels(fixture);
|
|
|
+ const links = getBreadcrumbLinks(fixture);
|
|
|
+ expect(labels).toEqual(['Root', 'Parent', 'Child', 'Grandchild']);
|
|
|
+ expect(links).toEqual(['/', '/relative-parent', '/relative-parent/relative-child/foo;p=1']);
|
|
|
+ }));
|
|
|
+
|
|
|
+ it('works with relative sibling paths in a BreadcrumbLabelLinkPair',
|
|
|
+ getFixtureForRoute(['', 'relative-parent', 'relative-sibling', 'string-grandchild'], fixture => {
|
|
|
+ const labels = getBreadcrumbLabels(fixture);
|
|
|
+ const links = getBreadcrumbLinks(fixture);
|
|
|
+ expect(labels).toEqual(['Root', 'Parent', 'Sibling', 'Grandchild']);
|
|
|
+ expect(links).toEqual(['/', '/relative-parent', '/relative-parent/foo;p=1']);
|
|
|
+ }));
|
|
|
+
|
|
|
+ it('array of BreadcrumbLabelLinkPairs paths compose correctly',
|
|
|
+ getFixtureForRoute(['', 'deep-pair-child-1', 'deep-pair-child-2', 'string-grandchild'], fixture => {
|
|
|
+ const labels = getBreadcrumbLabels(fixture);
|
|
|
+ const links = getBreadcrumbLinks(fixture);
|
|
|
+ expect(labels).toEqual(['Root', 'Child 1', 'PairA', 'PairB', 'PairC', 'Grandchild']);
|
|
|
+ expect(links).toEqual([
|
|
|
+ '/',
|
|
|
+ '/deep-pair-child-1',
|
|
|
+ '/deep-pair-child-1/deep-pair-child-2/child/path',
|
|
|
+ '/deep-pair-child-1/sibling/path',
|
|
|
+ '/absolute/path',
|
|
|
+ ]);
|
|
|
+ }));
|
|
|
+
|
|
|
+ it('shows correct labels for observable of string',
|
|
|
+ getFixtureForRoute(['', 'observable-string'], fixture => {
|
|
|
+ const labels = getBreadcrumbLabels(fixture);
|
|
|
+ const links = getBreadcrumbLinks(fixture);
|
|
|
+ expect(labels).toEqual(['Root', 'Observable String']);
|
|
|
+ }));
|
|
|
+
|
|
|
+ it('shows correct labels for observable of BreadcrumbLabelLinkPair',
|
|
|
+ getFixtureForRoute(['', 'observable-pair'], fixture => {
|
|
|
+ const labels = getBreadcrumbLabels(fixture);
|
|
|
+ const links = getBreadcrumbLinks(fixture);
|
|
|
+ expect(labels).toEqual(['Root', 'Observable Pair']);
|
|
|
+ }));
|
|
|
+
|
|
|
+ it('shows correct labels for observable of BreadcrumbLabelLinkPair array',
|
|
|
+ getFixtureForRoute(['', 'observable-array-pair'], fixture => {
|
|
|
+ const labels = getBreadcrumbLabels(fixture);
|
|
|
+ const links = getBreadcrumbLinks(fixture);
|
|
|
+ expect(labels).toEqual(['Root', 'Observable PairA', 'Observable PairB']);
|
|
|
+ expect(links).toEqual(['/', '/foo/bar']);
|
|
|
+ }));
|
|
|
+
|
|
|
+ it('shows correct labels for function returning observable string',
|
|
|
+ getFixtureForRoute(['', 'function-observable'], fixture => {
|
|
|
+ const labels = getBreadcrumbLabels(fixture);
|
|
|
+ const links = getBreadcrumbLinks(fixture);
|
|
|
+ expect(labels).toEqual(['Root', 'Observable String From Function']);
|
|
|
+ }));
|
|
|
+
|
|
|
+ it('labels update when observables emit new values',
|
|
|
+ getFixtureForRoute(['', 'observable-string-subject', 'string-grandchild'], fixture => {
|
|
|
+ expect(getBreadcrumbLabels(fixture)).toEqual(['Root', 'Initial Value', 'Grandchild']);
|
|
|
+ breadcrumbSubject.next('New Value');
|
|
|
+ fixture.detectChanges();
|
|
|
+ expect(getBreadcrumbLabels(fixture)).toEqual(['Root', 'New Value', 'Grandchild']);
|
|
|
+ }));
|
|
|
+});
|
|
|
+
|
|
|
+function getBreadcrumbsElement(fixture: ComponentFixture<TestComponent>): DebugElement {
|
|
|
+ return fixture.debugElement.query(By.directive(BreadcrumbComponent));
|
|
|
+}
|
|
|
+
|
|
|
+function getBreadcrumbListItems(fixture: ComponentFixture<TestComponent>): HTMLLIElement[] {
|
|
|
+ return fixture.debugElement.queryAll(By.css('.breadcrumbs li'))
|
|
|
+ .map(de => de.nativeElement);
|
|
|
+}
|
|
|
+
|
|
|
+function getBreadcrumbLabels(fixture: ComponentFixture<TestComponent>): string[] {
|
|
|
+ return getBreadcrumbListItems(fixture).map(item => item.innerText.trim());
|
|
|
+}
|
|
|
+
|
|
|
+function getBreadcrumbLinks(fixture: ComponentFixture<TestComponent>): string[] {
|
|
|
+ return getBreadcrumbListItems(fixture)
|
|
|
+ .map(el => el.querySelector('a'))
|
|
|
+ .filter(el => !!el)
|
|
|
+ .map(a => a.getAttribute('href'));
|
|
|
+}
|
|
|
+
|
|
|
+// tslint:disable component-selector
|
|
|
+@Component({
|
|
|
+ selector: 'test-root-component',
|
|
|
+ template: `
|
|
|
+ <vdr-breadcrumb></vdr-breadcrumb>
|
|
|
+ <router-outlet></router-outlet>`,
|
|
|
+})
|
|
|
+class TestParentComponent {}
|
|
|
+
|
|
|
+@Component({
|
|
|
+ selector: 'test-child-component',
|
|
|
+ template: `
|
|
|
+ <vdr-breadcrumb></vdr-breadcrumb>`,
|
|
|
+})
|
|
|
+class TestChildComponent {}
|
|
|
+
|
|
|
+type TestComponent = TestParentComponent | TestChildComponent;
|
|
|
+
|
|
|
+class FooResolver implements Resolve<string> {
|
|
|
+ resolve(): Observable<string> {
|
|
|
+ return observableOf('Foo');
|
|
|
+ }
|
|
|
+}
|