fix(aio): intercept all clicks on anchors
Previously we had the `LinkDirective` which intercepted clicks on anchors outside the doc viewer. Now we intercept "all" link clicks within the app.
This commit is contained in:
parent
3f7cfde476
commit
eaa04354d5
@ -5,10 +5,13 @@ import { AppModule } from './app.module';
|
|||||||
import { GaService } from 'app/shared/ga.service';
|
import { GaService } from 'app/shared/ga.service';
|
||||||
import { SearchService } from 'app/search/search.service';
|
import { SearchService } from 'app/search/search.service';
|
||||||
import { MockSearchService } from 'testing/search.service';
|
import { MockSearchService } from 'testing/search.service';
|
||||||
|
import { LocationService } from 'app/shared/location.service';
|
||||||
|
import { MockLocationService } from 'testing/location.service';
|
||||||
|
|
||||||
describe('AppComponent', () => {
|
describe('AppComponent', () => {
|
||||||
let component: AppComponent;
|
let component: AppComponent;
|
||||||
let fixture: ComponentFixture<AppComponent>;
|
let fixture: ComponentFixture<AppComponent>;
|
||||||
|
const initialUrl = 'a/b';
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
@ -16,7 +19,8 @@ describe('AppComponent', () => {
|
|||||||
providers: [
|
providers: [
|
||||||
{ provide: APP_BASE_HREF, useValue: '/' },
|
{ provide: APP_BASE_HREF, useValue: '/' },
|
||||||
{ provide: SearchService, useClass: MockSearchService },
|
{ provide: SearchService, useClass: MockSearchService },
|
||||||
{ provide: GaService, useClass: TestGaService }
|
{ provide: GaService, useClass: TestGaService },
|
||||||
|
{ provide: LocationService, useFactory: () => new MockLocationService(initialUrl) }
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
TestBed.compileComponents();
|
TestBed.compileComponents();
|
||||||
@ -33,11 +37,10 @@ describe('AppComponent', () => {
|
|||||||
|
|
||||||
describe('google analytics', () => {
|
describe('google analytics', () => {
|
||||||
it('should call gaService.locationChanged with initial URL', () => {
|
it('should call gaService.locationChanged with initial URL', () => {
|
||||||
const url = window.location.pathname.substr(1); // strip leading '/'
|
|
||||||
const { locationChanged } = TestBed.get(GaService) as TestGaService;
|
const { locationChanged } = TestBed.get(GaService) as TestGaService;
|
||||||
expect(locationChanged.calls.count()).toBe(1, 'gaService.locationChanged');
|
expect(locationChanged.calls.count()).toBe(1, 'gaService.locationChanged');
|
||||||
const args = locationChanged.calls.first().args;
|
const args = locationChanged.calls.first().args;
|
||||||
expect(args[0]).toBe(url);
|
expect(args[0]).toBe(initialUrl);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Todo: add test to confirm tracking URL when navigate.
|
// Todo: add test to confirm tracking URL when navigate.
|
||||||
@ -70,6 +73,17 @@ describe('AppComponent', () => {
|
|||||||
expect(searchService.loadIndex).toHaveBeenCalled();
|
expect(searchService.loadIndex).toHaveBeenCalled();
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('click intercepting', () => {
|
||||||
|
it('should intercept clicks on anchors and call `location.handleAnchorClick()`',
|
||||||
|
inject([LocationService], (location: LocationService) => {
|
||||||
|
const anchorElement: HTMLAnchorElement = document.createElement('a');
|
||||||
|
anchorElement.href = 'some/local/url';
|
||||||
|
fixture.nativeElement.append(anchorElement);
|
||||||
|
anchorElement.click();
|
||||||
|
expect(location.handleAnchorClick).toHaveBeenCalledWith(anchorElement, 0, false, false);
|
||||||
|
}));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
class TestGaService {
|
class TestGaService {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Component, ViewChild, OnInit } from '@angular/core';
|
import { Component, HostListener, ViewChild, OnInit } from '@angular/core';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
|
||||||
import { GaService } from 'app/shared/ga.service';
|
import { GaService } from 'app/shared/ga.service';
|
||||||
@ -26,7 +26,7 @@ export class AppComponent implements OnInit {
|
|||||||
constructor(
|
constructor(
|
||||||
documentService: DocumentService,
|
documentService: DocumentService,
|
||||||
gaService: GaService,
|
gaService: GaService,
|
||||||
locationService: LocationService,
|
private locationService: LocationService,
|
||||||
navigationService: NavigationService,
|
navigationService: NavigationService,
|
||||||
private searchService: SearchService) {
|
private searchService: SearchService) {
|
||||||
|
|
||||||
@ -46,4 +46,12 @@ export class AppComponent implements OnInit {
|
|||||||
onResize(width) {
|
onResize(width) {
|
||||||
this.isSideBySide = width > this.sideBySideWidth;
|
this.isSideBySide = width > this.sideBySideWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@HostListener('click', ['$event.target', '$event.button', '$event.ctrlKey', '$event.metaKey'])
|
||||||
|
onClick(eventTarget: HTMLElement, button: number, ctrlKey: boolean, metaKey: boolean): boolean {
|
||||||
|
if (eventTarget instanceof HTMLAnchorElement) {
|
||||||
|
return this.locationService.handleAnchorClick(eventTarget, button, ctrlKey, metaKey);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,6 @@ import { SearchService } from 'app/search/search.service';
|
|||||||
import { TopMenuComponent } from 'app/layout/top-menu/top-menu.component';
|
import { TopMenuComponent } from 'app/layout/top-menu/top-menu.component';
|
||||||
import { NavMenuComponent } from 'app/layout/nav-menu/nav-menu.component';
|
import { NavMenuComponent } from 'app/layout/nav-menu/nav-menu.component';
|
||||||
import { NavItemComponent } from 'app/layout/nav-item/nav-item.component';
|
import { NavItemComponent } from 'app/layout/nav-item/nav-item.component';
|
||||||
import { LinkDirective } from 'app/shared/link.directive';
|
|
||||||
import { SearchResultsComponent } from './search/search-results/search-results.component';
|
import { SearchResultsComponent } from './search/search-results/search-results.component';
|
||||||
import { SearchBoxComponent } from './search/search-box/search-box.component';
|
import { SearchBoxComponent } from './search/search-box/search-box.component';
|
||||||
|
|
||||||
@ -49,7 +48,6 @@ import { SearchBoxComponent } from './search/search-box/search-box.component';
|
|||||||
TopMenuComponent,
|
TopMenuComponent,
|
||||||
NavMenuComponent,
|
NavMenuComponent,
|
||||||
NavItemComponent,
|
NavItemComponent,
|
||||||
LinkDirective,
|
|
||||||
SearchResultsComponent,
|
SearchResultsComponent,
|
||||||
SearchBoxComponent,
|
SearchBoxComponent,
|
||||||
],
|
],
|
||||||
|
@ -1,93 +0,0 @@
|
|||||||
import { async, inject, ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
import { By } from '@angular/platform-browser';
|
|
||||||
import { Component } from '@angular/core';
|
|
||||||
import { LocationService } from 'app/shared/location.service';
|
|
||||||
import { MockLocationService } from 'testing/location.service';
|
|
||||||
import { LinkDirective } from './link.directive';
|
|
||||||
|
|
||||||
describe('LinkDirective', () => {
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
template: '<a href="{{ url }}">Test Link</a>'
|
|
||||||
})
|
|
||||||
class TestComponent {
|
|
||||||
url: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeEach(async(() => {
|
|
||||||
TestBed.configureTestingModule({
|
|
||||||
declarations: [
|
|
||||||
LinkDirective,
|
|
||||||
TestComponent
|
|
||||||
],
|
|
||||||
providers: [
|
|
||||||
{ provide: LocationService, useFactory: () => new MockLocationService('initial/url') }
|
|
||||||
]
|
|
||||||
})
|
|
||||||
.compileComponents();
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should attach to all anchor elements', () => {
|
|
||||||
const fixture = TestBed.createComponent(TestComponent);
|
|
||||||
const directiveElement = fixture.debugElement.query(By.directive(LinkDirective));
|
|
||||||
expect(directiveElement.name).toEqual('a');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should bind a property to the "href" attribute', () => {
|
|
||||||
const fixture = TestBed.createComponent(TestComponent);
|
|
||||||
const directiveElement = fixture.debugElement.query(By.directive(LinkDirective));
|
|
||||||
|
|
||||||
fixture.componentInstance.url = 'test/url';
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(directiveElement.properties['href']).toEqual('test/url');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should set the "target" attribute to "_blank" if the href is absolute, otherwise "_self"', () => {
|
|
||||||
const fixture = TestBed.createComponent(TestComponent);
|
|
||||||
const directiveElement = fixture.debugElement.query(By.directive(LinkDirective));
|
|
||||||
|
|
||||||
fixture.componentInstance.url = 'http://test/url';
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(directiveElement.properties['target']).toEqual('_blank');
|
|
||||||
|
|
||||||
fixture.componentInstance.url = 'https://test/url';
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(directiveElement.properties['target']).toEqual('_blank');
|
|
||||||
|
|
||||||
fixture.componentInstance.url = 'ftp://test/url';
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(directiveElement.properties['target']).toEqual('_blank');
|
|
||||||
|
|
||||||
fixture.componentInstance.url = '//test/url';
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(directiveElement.properties['target']).toEqual('_blank');
|
|
||||||
|
|
||||||
fixture.componentInstance.url = 'test/url';
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(directiveElement.properties['target']).toEqual('_self');
|
|
||||||
|
|
||||||
fixture.componentInstance.url = '/test/url';
|
|
||||||
fixture.detectChanges();
|
|
||||||
expect(directiveElement.properties['target']).toEqual('_self');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should intercept clicks for local urls and call `location.go()`', inject([LocationService], (location: LocationService) => {
|
|
||||||
const fixture = TestBed.createComponent(TestComponent);
|
|
||||||
const directiveElement = fixture.debugElement.query(By.directive(LinkDirective));
|
|
||||||
fixture.componentInstance.url = 'some/local/url';
|
|
||||||
fixture.detectChanges();
|
|
||||||
location.go = jasmine.createSpy('Location.go');
|
|
||||||
directiveElement.triggerEventHandler('click', null);
|
|
||||||
expect(location.go).toHaveBeenCalledWith('some/local/url');
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should not intercept clicks for absolute urls', inject([LocationService], (location: LocationService) => {
|
|
||||||
const fixture = TestBed.createComponent(TestComponent);
|
|
||||||
const directiveElement = fixture.debugElement.query(By.directive(LinkDirective));
|
|
||||||
fixture.componentInstance.url = 'https://some/absolute/url';
|
|
||||||
fixture.detectChanges();
|
|
||||||
location.go = jasmine.createSpy('Location.go');
|
|
||||||
directiveElement.triggerEventHandler('click', null);
|
|
||||||
expect(location.go).not.toHaveBeenCalled();
|
|
||||||
}));
|
|
||||||
});
|
|
@ -1,39 +0,0 @@
|
|||||||
import { Directive, HostListener, HostBinding, Input, OnChanges } from '@angular/core';
|
|
||||||
import { LocationService } from 'app/shared/location.service';
|
|
||||||
@Directive({
|
|
||||||
/* tslint:disable-next-line:directive-selector */
|
|
||||||
selector: 'a[href]'
|
|
||||||
})
|
|
||||||
export class LinkDirective implements OnChanges {
|
|
||||||
|
|
||||||
// We need both these decorators to ensure that we can access
|
|
||||||
// the href programmatically, and that it appears as a real
|
|
||||||
// attribute on the element.
|
|
||||||
@Input()
|
|
||||||
@HostBinding()
|
|
||||||
href: string;
|
|
||||||
|
|
||||||
@HostBinding()
|
|
||||||
target: string;
|
|
||||||
|
|
||||||
@HostListener('click', ['$event'])
|
|
||||||
onClick($event) {
|
|
||||||
if (this.isAbsolute(this.href)) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
this.location.go(this.href);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private isAbsolute(url) {
|
|
||||||
return /^[a-z]+:\/\/|\/\//i.test(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(private location: LocationService) { }
|
|
||||||
|
|
||||||
ngOnChanges() {
|
|
||||||
this.target = this.isAbsolute(this.href) ? '_blank' : '_self';
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -221,4 +221,124 @@ describe('LocationService', () => {
|
|||||||
expect(query).toContain('a%26b%2Bc%20d=value');
|
expect(query).toContain('a%26b%2Bc%20d=value');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('handleAnchorClick', () => {
|
||||||
|
let service: LocationService, anchor: HTMLAnchorElement;
|
||||||
|
beforeEach(() => {
|
||||||
|
service = injector.get(LocationService);
|
||||||
|
anchor = document.createElement('a');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('intercepting', () => {
|
||||||
|
it('should intercept clicks on anchors for relative local urls', () => {
|
||||||
|
anchor.href = 'some/local/url';
|
||||||
|
spyOn(service, 'go');
|
||||||
|
const result = service.handleAnchorClick(anchor, 0, false, false);
|
||||||
|
expect(service.go).toHaveBeenCalledWith('some/local/url');
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should intercept clicks on anchors for absolute local urls', () => {
|
||||||
|
anchor.href = '/some/local/url';
|
||||||
|
spyOn(service, 'go');
|
||||||
|
const result = service.handleAnchorClick(anchor, 0, false, false);
|
||||||
|
expect(service.go).toHaveBeenCalledWith('some/local/url');
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should intercept clicks on anchors for local urls, with query params', () => {
|
||||||
|
anchor.href = 'some/local/url?query=xxx&other=yyy';
|
||||||
|
spyOn(service, 'go');
|
||||||
|
const result = service.handleAnchorClick(anchor, 0, false, false);
|
||||||
|
expect(service.go).toHaveBeenCalledWith('some/local/url?query=xxx&other=yyy');
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should intercept clicks on anchors for local urls, with hash fragment', () => {
|
||||||
|
anchor.href = 'some/local/url#somefragment';
|
||||||
|
spyOn(service, 'go');
|
||||||
|
const result = service.handleAnchorClick(anchor, 0, false, false);
|
||||||
|
expect(service.go).toHaveBeenCalledWith('some/local/url#somefragment');
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should intercept clicks on anchors for local urls, with query params and hash fragment', () => {
|
||||||
|
anchor.href = 'some/local/url?query=xxx&other=yyy#somefragment';
|
||||||
|
spyOn(service, 'go');
|
||||||
|
const result = service.handleAnchorClick(anchor, 0, false, false);
|
||||||
|
expect(service.go).toHaveBeenCalledWith('some/local/url?query=xxx&other=yyy#somefragment');
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('not intercepting', () => {
|
||||||
|
it('should not intercept clicks on anchors for external urls', () => {
|
||||||
|
anchor.href = 'http://other.com/some/local/url?query=xxx&other=yyy#somefragment';
|
||||||
|
spyOn(service, 'go');
|
||||||
|
let result = service.handleAnchorClick(anchor, 0, false, false);
|
||||||
|
expect(service.go).not.toHaveBeenCalled();
|
||||||
|
expect(result).toBe(true);
|
||||||
|
|
||||||
|
anchor.href = 'some/local/url.pdf';
|
||||||
|
anchor.protocol = 'ftp';
|
||||||
|
result = service.handleAnchorClick(anchor, 0, false, false);
|
||||||
|
expect(service.go).not.toHaveBeenCalled();
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not intercept clicks on anchors if button is not zero', () => {
|
||||||
|
anchor.href = 'some/local/url';
|
||||||
|
spyOn(service, 'go');
|
||||||
|
const result = service.handleAnchorClick(anchor, 1, false, false);
|
||||||
|
expect(service.go).not.toHaveBeenCalled();
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not intercept clicks on anchors if ctrl key is pressed', () => {
|
||||||
|
anchor.href = 'some/local/url';
|
||||||
|
spyOn(service, 'go');
|
||||||
|
const result = service.handleAnchorClick(anchor, 0, true, false);
|
||||||
|
expect(service.go).not.toHaveBeenCalled();
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not intercept clicks on anchors if meta key is pressed', () => {
|
||||||
|
anchor.href = 'some/local/url';
|
||||||
|
spyOn(service, 'go');
|
||||||
|
const result = service.handleAnchorClick(anchor, 0, false, true);
|
||||||
|
expect(service.go).not.toHaveBeenCalled();
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not intercept clicks on links with (non-_self) targets', () => {
|
||||||
|
anchor.href = 'some/local/url';
|
||||||
|
spyOn(service, 'go');
|
||||||
|
|
||||||
|
anchor.target = '_blank';
|
||||||
|
let result = service.handleAnchorClick(anchor, 0, false, false);
|
||||||
|
expect(service.go).not.toHaveBeenCalled();
|
||||||
|
expect(result).toBe(true);
|
||||||
|
|
||||||
|
anchor.target = '_parent';
|
||||||
|
result = service.handleAnchorClick(anchor, 0, false, false);
|
||||||
|
expect(service.go).not.toHaveBeenCalled();
|
||||||
|
expect(result).toBe(true);
|
||||||
|
|
||||||
|
anchor.target = '_top';
|
||||||
|
result = service.handleAnchorClick(anchor, 0, false, false);
|
||||||
|
expect(service.go).not.toHaveBeenCalled();
|
||||||
|
expect(result).toBe(true);
|
||||||
|
|
||||||
|
anchor.target = 'other-frame';
|
||||||
|
result = service.handleAnchorClick(anchor, 0, false, false);
|
||||||
|
expect(service.go).not.toHaveBeenCalled();
|
||||||
|
expect(result).toBe(true);
|
||||||
|
|
||||||
|
anchor.target = '_self';
|
||||||
|
result = service.handleAnchorClick(anchor, 0, false, false);
|
||||||
|
expect(service.go).toHaveBeenCalledWith('some/local/url');
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -6,6 +6,7 @@ import { ReplaySubject } from 'rxjs/ReplaySubject';
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class LocationService {
|
export class LocationService {
|
||||||
|
|
||||||
|
private readonly urlParser = document.createElement('a');
|
||||||
private urlSubject = new ReplaySubject<string>(1);
|
private urlSubject = new ReplaySubject<string>(1);
|
||||||
currentUrl = this.urlSubject.asObservable();
|
currentUrl = this.urlSubject.asObservable();
|
||||||
|
|
||||||
@ -60,4 +61,44 @@ export class LocationService {
|
|||||||
|
|
||||||
this.platformLocation.replaceState({}, label, this.platformLocation.pathname + search);
|
this.platformLocation.replaceState({}, label, this.platformLocation.pathname + search);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Since we are using `LocationService` to navigate between docs, without the browser
|
||||||
|
* reloading the page, we must intercept clicks on links.
|
||||||
|
* If the link is to a document that we will render, then we navigate using `Location.go()`
|
||||||
|
* and tell the browser not to handle the event.
|
||||||
|
*
|
||||||
|
* In most apps you might do this in a `LinkDirective` attached to anchors but in this app
|
||||||
|
* we have a special situation where the `DocViewerComponent` is displaying semi-static
|
||||||
|
* content that cannot contain directives. So all the links in that content would not be
|
||||||
|
* able to use such a `LinkDirective`. Instead we are adding a click handler to the
|
||||||
|
* `AppComponent`, whose element contains all the of the application and so captures all
|
||||||
|
* link clicks both inside and outside the `DocViewerComponent`.
|
||||||
|
*/
|
||||||
|
handleAnchorClick(anchor: HTMLAnchorElement, button: number, ctrlKey: boolean, metaKey: boolean) {
|
||||||
|
|
||||||
|
// Check for modifier keys, which indicate the user wants to control navigation
|
||||||
|
if (button !== 0 || ctrlKey || metaKey) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is a target and it is not `_self` then we take this
|
||||||
|
// as a signal that it doesn't want to be intercepted.
|
||||||
|
// TODO: should we also allow an explicit `_self` target to opt-out?
|
||||||
|
const anchorTarget = anchor.target;
|
||||||
|
if (anchorTarget && anchorTarget !== '_self') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for external link
|
||||||
|
const { pathname, search, hash } = anchor;
|
||||||
|
const relativeUrl = pathname + search + hash;
|
||||||
|
this.urlParser.href = relativeUrl;
|
||||||
|
if (anchor.href !== this.urlParser.href) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.go(this.stripLeadingSlashes(relativeUrl));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,9 @@ export class MockLocationService {
|
|||||||
currentUrl = this.urlSubject.asObservable();
|
currentUrl = this.urlSubject.asObservable();
|
||||||
search = jasmine.createSpy('search').and.returnValue({});
|
search = jasmine.createSpy('search').and.returnValue({});
|
||||||
setSearch = jasmine.createSpy('setSearch');
|
setSearch = jasmine.createSpy('setSearch');
|
||||||
|
go = jasmine.createSpy('Location.go');
|
||||||
|
handleAnchorClick = jasmine.createSpy('Location.handleAnchorClick')
|
||||||
|
.and.returnValue(false); // prevent click from causing a browser navigation
|
||||||
constructor(private initialUrl) {}
|
constructor(private initialUrl) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user