
Adds tests and fixes corners cases for both `search()` and `setSearc()` for things like empty queries and param keys that need encoding. This commit refactors the `LocationService` to rely upon the `PlatformLocation` rather than using `window.history` directly. This makes testing easier but also makes the code simpler since `PlatformLocation` deals with platforms that do not support history for us.
225 lines
7.7 KiB
TypeScript
225 lines
7.7 KiB
TypeScript
import { ReflectiveInjector } from '@angular/core';
|
|
import { Location, LocationStrategy, PlatformLocation } from '@angular/common';
|
|
import { MockLocationStrategy } from '@angular/common/testing';
|
|
import { LocationService } from './location.service';
|
|
|
|
class MockPlatformLocation {
|
|
pathname = 'a/b/c';
|
|
replaceState = jasmine.createSpy('PlatformLocation.replaceState');
|
|
}
|
|
|
|
describe('LocationService', () => {
|
|
|
|
let injector: ReflectiveInjector;
|
|
|
|
beforeEach(() => {
|
|
injector = ReflectiveInjector.resolveAndCreate([
|
|
LocationService,
|
|
Location,
|
|
{ provide: LocationStrategy, useClass: MockLocationStrategy },
|
|
{ provide: PlatformLocation, useClass: MockPlatformLocation }
|
|
]);
|
|
});
|
|
|
|
describe('urlStream', () => {
|
|
it('should emit the latest url at the time it is subscribed to', () => {
|
|
|
|
const location: MockLocationStrategy = injector.get(LocationStrategy);
|
|
|
|
location.simulatePopState('/initial-url1');
|
|
location.simulatePopState('/initial-url2');
|
|
location.simulatePopState('/initial-url3');
|
|
|
|
const service: LocationService = injector.get(LocationService);
|
|
|
|
location.simulatePopState('/next-url1');
|
|
location.simulatePopState('/next-url2');
|
|
location.simulatePopState('/next-url3');
|
|
|
|
let initialUrl;
|
|
service.currentUrl.subscribe(url => initialUrl = url);
|
|
expect(initialUrl).toEqual('next-url3');
|
|
});
|
|
|
|
it('should emit all location changes after it has been subscribed to', () => {
|
|
const location: MockLocationStrategy = injector.get(LocationStrategy);
|
|
const service: LocationService = injector.get(LocationService);
|
|
|
|
location.simulatePopState('/initial-url1');
|
|
location.simulatePopState('/initial-url2');
|
|
location.simulatePopState('/initial-url3');
|
|
|
|
const urls = [];
|
|
service.currentUrl.subscribe(url => urls.push(url));
|
|
|
|
location.simulatePopState('/next-url1');
|
|
location.simulatePopState('/next-url2');
|
|
location.simulatePopState('/next-url3');
|
|
|
|
expect(urls).toEqual([
|
|
'initial-url3',
|
|
'next-url1',
|
|
'next-url2',
|
|
'next-url3'
|
|
]);
|
|
});
|
|
|
|
it('should pass only the latest and later urls to each subscriber', () => {
|
|
const location: MockLocationStrategy = injector.get(LocationStrategy);
|
|
const service: LocationService = injector.get(LocationService);
|
|
|
|
location.simulatePopState('/initial-url1');
|
|
location.simulatePopState('/initial-url2');
|
|
location.simulatePopState('/initial-url3');
|
|
|
|
const urls1 = [];
|
|
service.currentUrl.subscribe(url => urls1.push(url));
|
|
|
|
location.simulatePopState('/next-url1');
|
|
location.simulatePopState('/next-url2');
|
|
|
|
const urls2 = [];
|
|
service.currentUrl.subscribe(url => urls2.push(url));
|
|
|
|
location.simulatePopState('/next-url3');
|
|
|
|
expect(urls1).toEqual([
|
|
'initial-url3',
|
|
'next-url1',
|
|
'next-url2',
|
|
'next-url3'
|
|
]);
|
|
|
|
expect(urls2).toEqual([
|
|
'next-url2',
|
|
'next-url3'
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe('go', () => {
|
|
it('should update the location', () => {
|
|
const location: MockLocationStrategy = injector.get(LocationStrategy);
|
|
const service: LocationService = injector.get(LocationService);
|
|
|
|
service.go('some-new-url');
|
|
|
|
expect(location.internalPath).toEqual('some-new-url');
|
|
expect(location.path(true)).toEqual('some-new-url');
|
|
});
|
|
|
|
it('should emit the new url', () => {
|
|
const location: MockLocationStrategy = injector.get(LocationStrategy);
|
|
const service: LocationService = injector.get(LocationService);
|
|
|
|
service.go('some-initial-url');
|
|
|
|
const urls = [];
|
|
service.currentUrl.subscribe(url => urls.push(url));
|
|
|
|
service.go('some-new-url');
|
|
|
|
expect(urls).toEqual([
|
|
'some-initial-url',
|
|
'some-new-url'
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe('search', () => {
|
|
it('should read the query from the current location.path', () => {
|
|
const location: MockLocationStrategy = injector.get(LocationStrategy);
|
|
const service: LocationService = injector.get(LocationService);
|
|
|
|
location.simulatePopState('a/b/c?foo=bar&moo=car');
|
|
expect(service.search()).toEqual({ foo: 'bar', moo: 'car' });
|
|
});
|
|
|
|
it('should cope with an empty query', () => {
|
|
const location: MockLocationStrategy = injector.get(LocationStrategy);
|
|
const service: LocationService = injector.get(LocationService);
|
|
|
|
location.simulatePopState('a/b/c');
|
|
expect(service.search()).toEqual({ });
|
|
|
|
location.simulatePopState('x/y/z?');
|
|
expect(service.search()).toEqual({ });
|
|
|
|
location.simulatePopState('x/y/z?x=');
|
|
expect(service.search()).toEqual({ x: '' });
|
|
|
|
location.simulatePopState('x/y/z?x');
|
|
expect(service.search()).toEqual({ x: undefined });
|
|
});
|
|
|
|
it('should URL decode query values', () => {
|
|
const location: MockLocationStrategy = injector.get(LocationStrategy);
|
|
const service: LocationService = injector.get(LocationService);
|
|
|
|
location.simulatePopState('a/b/c?query=a%26b%2Bc%20d');
|
|
expect(service.search()).toEqual({ query: 'a&b+c d' });
|
|
});
|
|
|
|
it('should URL decode query keys', () => {
|
|
const location: MockLocationStrategy = injector.get(LocationStrategy);
|
|
const service: LocationService = injector.get(LocationService);
|
|
|
|
location.simulatePopState('a/b/c?a%26b%2Bc%20d=value');
|
|
expect(service.search()).toEqual({ 'a&b+c d': 'value' });
|
|
});
|
|
|
|
it('should cope with a hash on the URL', () => {
|
|
const location: MockLocationStrategy = injector.get(LocationStrategy);
|
|
const service: LocationService = injector.get(LocationService);
|
|
|
|
spyOn(location, 'path').and.callThrough();
|
|
service.search();
|
|
expect(location.path).toHaveBeenCalledWith(false);
|
|
});
|
|
});
|
|
|
|
describe('setSearch', () => {
|
|
it('should call replaceState on PlatformLocation', () => {
|
|
const location: MockPlatformLocation = injector.get(PlatformLocation);
|
|
const service: LocationService = injector.get(LocationService);
|
|
|
|
const params = {};
|
|
service.setSearch('Some label', params);
|
|
expect(location.replaceState).toHaveBeenCalledWith(jasmine.any(Object), 'Some label', 'a/b/c');
|
|
});
|
|
|
|
it('should convert the params to a query string', () => {
|
|
const location: MockPlatformLocation = injector.get(PlatformLocation);
|
|
const service: LocationService = injector.get(LocationService);
|
|
|
|
const params = { foo: 'bar', moo: 'car' };
|
|
service.setSearch('Some label', params);
|
|
expect(location.replaceState).toHaveBeenCalledWith(jasmine.any(Object), 'Some label', jasmine.any(String));
|
|
const [path, query] = location.replaceState.calls.mostRecent().args[2].split('?');
|
|
expect(path).toEqual('a/b/c');
|
|
expect(query).toContain('foo=bar');
|
|
expect(query).toContain('moo=car');
|
|
});
|
|
|
|
it('should URL encode param values', () => {
|
|
const location: MockPlatformLocation = injector.get(PlatformLocation);
|
|
const service: LocationService = injector.get(LocationService);
|
|
|
|
const params = { query: 'a&b+c d' };
|
|
service.setSearch('', params);
|
|
const [, query] = location.replaceState.calls.mostRecent().args[2].split('?');
|
|
expect(query).toContain('query=a%26b%2Bc%20d');
|
|
});
|
|
|
|
it('should URL encode param keys', () => {
|
|
const location: MockPlatformLocation = injector.get(PlatformLocation);
|
|
const service: LocationService = injector.get(LocationService);
|
|
|
|
const params = { 'a&b+c d': 'value' };
|
|
service.setSearch('', params);
|
|
const [, query] = location.replaceState.calls.mostRecent().args[2].split('?');
|
|
expect(query).toContain('a%26b%2Bc%20d=value');
|
|
});
|
|
});
|
|
});
|