Skip to content

Commit

Permalink
feat(vanilla): Implement in-memory-only location api
Browse files Browse the repository at this point in the history
test(resolve): Put beforeEach router init inside describe block
test(stateService): re-implement url-based tests using in-memory location API
  • Loading branch information
christopherthielen committed Dec 2, 2016
1 parent 0690917 commit f64aace
Show file tree
Hide file tree
Showing 10 changed files with 264 additions and 187 deletions.
16 changes: 12 additions & 4 deletions src/vanilla/hashLocation.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import {services, isDefined} from '../common/module';
import {LocationConfig, LocationServices} from '../common/coreservices';
import {splitHash, splitQuery, trimHashVal, getParams} from './utils';
import { isDefined } from '../common/module';
import { LocationConfig, LocationServices } from '../common/coreservices';
import { splitHash, splitQuery, trimHashVal, getParams, locationPluginFactory } from './utils';
import { UIRouter } from '../router';
import { LocationPlugin } from "./interface";

let hashPrefix: string = '';
let baseHref: string = '';
Expand Down Expand Up @@ -34,5 +36,11 @@ export const hashLocationService: LocationServices = {
setUrl: (url: string, replace: boolean = true) => {
if (url) location.hash = url;
},
onChange: (cb: EventListener) => window.addEventListener("hashchange", cb, false) as any
onChange: (cb: EventListener) => {
window.addEventListener('hashchange', cb, false);
return () => window.removeEventListener('hashchange', cb);
}
};

export const hashLocationPlugin: (router: UIRouter) => LocationPlugin =
locationPluginFactory('vanilla.hashBangLocation', hashLocationService, hashLocationConfig);
27 changes: 7 additions & 20 deletions src/vanilla/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,8 @@
*/ /** */
import { UIRouter } from "../router";

import { services, LocationServices, LocationConfig } from "../common/coreservices";
import { LocationPlugin, ServicesPlugin } from "./interface";
import { extend } from "../common/common";
import { hashLocationService, hashLocationConfig } from "./hashLocation";
import { pushStateLocationService, pushStateLocationConfig } from "./pushStateLocation";
import { services } from "../common/coreservices";
import { ServicesPlugin } from "./interface";
import { $q } from "./$q";
import { $injector } from "./$injector";

Expand All @@ -18,20 +15,10 @@ export { $q, $injector };
export function servicesPlugin(router: UIRouter): ServicesPlugin {
services.$injector = $injector;
services.$q = $q;

return { name: "vanilla.services", $q, $injector };
}

const locationPluginFactory = (name: string, service: LocationServices, configuration: LocationConfig) =>
(router: UIRouter) => {
extend(services.location, service);
extend(services.locationConfig, configuration);
return { name, service, configuration };
};

export const hashLocationPlugin: (router: UIRouter) => LocationPlugin =
locationPluginFactory("vanilla.hashBangLocation", hashLocationService, hashLocationConfig);

export const pushStateLocationPlugin: (router: UIRouter) => LocationPlugin =
locationPluginFactory("vanilla.pushStateLocation", pushStateLocationService, pushStateLocationConfig);
return { name: "vanilla.services", $q, $injector, dispose: () => null };
}

export * from "./hashLocation";
export * from "./memoryLocation";
export * from "./pushStateLocation";
75 changes: 75 additions & 0 deletions src/vanilla/memoryLocation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { services, isDefined } from '../common/module';
import { LocationConfig, LocationServices } from '../common/coreservices';
import { splitQuery, trimHashVal, getParams, splitHash, locationPluginFactory } from './utils';
import { removeFrom, unnestR } from "../common/common";
import { UIRouter } from "../router";
import { LocationPlugin } from "./interface";
import { isArray } from "../common/predicates";

var mlc;
export const memoryLocationConfig: LocationConfig = mlc = {
_hashPrefix: '',
_baseHref: '',
_port: 80,
_protocol: "http",
_host: "localhost",

port: () => mlc._port,
protocol: () => mlc._protocol,
host: () => mlc._host,
baseHref: () => mlc._baseHref,
html5Mode: () => false,
hashPrefix: (newprefix?: string): string => {
if (isDefined(newprefix)) {
mlc._hashPrefix = newprefix;
}
return mlc._hashPrefix;
}
};

var mls;
export const memoryLocationService: LocationServices = mls = {
_listeners: [],
_url: {
path: '',
search: {},
hash: ''
},
_changed: (newval, oldval) => {
if (newval === oldval) return;
let evt = new Event("locationchange");
evt['url'] = newval;
mls._listeners.forEach(cb => cb(evt));
},

url: () => {
let s = mls._url.search;
let hash = mls._url.hash;
let query = Object.keys(s).map(key => (isArray(s[key]) ? s[key] : [s[key]]) .map(val => key + "=" + val))
.reduce(unnestR, [])
.join("&");

return mls._url.path +
(query ? "?" + query : "") +
(hash ? "#" + hash : "");
},
hash: () => mls._url.hash,
path: () => mls._url.path,
search: () => mls._url.search,
setUrl: (url: string, replace: boolean = false) => {
if (isDefined(url)) {
let path = splitHash(splitQuery(url)[0])[0];
let hash = splitHash(url)[1];
let search = getParams(splitQuery(splitHash(url)[0])[1]);

let oldval = mls.url();
mls._url = { path, search, hash };
let newval = mls.url();
mls._changed(newval, oldval);
}
},
onChange: (cb: EventListener) => (mls._listeners.push(cb), () => removeFrom(mls._listeners, cb))
};

export const memoryLocationPlugin: (router: UIRouter) => LocationPlugin =
locationPluginFactory("vanilla.memoryLocation", memoryLocationService, memoryLocationConfig);
13 changes: 11 additions & 2 deletions src/vanilla/pushStateLocation.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { services, isDefined } from '../common/module';
import { LocationConfig, LocationServices } from '../common/coreservices';
import { splitQuery, trimHashVal, getParams } from './utils';
import { splitQuery, trimHashVal, getParams, locationPluginFactory } from './utils';
import { LocationPlugin } from "./interface";
import { UIRouter } from "../router";

let hashPrefix: string = '';
let baseHref: string = '';
Expand Down Expand Up @@ -42,5 +44,12 @@ export const pushStateLocationService: LocationServices = {
else history.pushState(null, null, services.locationConfig.baseHref() + url);
}
},
onChange: (cb: EventListener) => window.addEventListener("popstate", cb, false) as any
onChange: (cb: EventListener) => {
window.addEventListener("popstate", cb, false);
return () => window.removeEventListener("popstate", cb);
}
};

export const pushStateLocationPlugin: (router: UIRouter) => LocationPlugin =
locationPluginFactory("vanilla.pushStateLocation", pushStateLocationService, pushStateLocationConfig);

23 changes: 22 additions & 1 deletion src/vanilla/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import {isArray} from "../common/module";
import { LocationServices, LocationConfig, services } from "../common/coreservices";
import { UIRouter } from "../router";
import { extend, pushTo, removeFrom } from "../common/common";

const beforeAfterSubstr = (char: string) => (str: string): string[] => {
if (!str) return ["", ""];
Expand All @@ -24,4 +27,22 @@ export const keyValsToObjectR = (accum, [key, val]) => {
};

export const getParams = (queryString: string): any =>
queryString.split("&").map(splitEqual).reduce(keyValsToObjectR, {});
queryString.split("&").map(splitEqual).reduce(keyValsToObjectR, {});

export function locationPluginFactory(name: string, service: LocationServices, configuration: LocationConfig) {
let deregFns: Function[] = [];
function dispose() {
deregFns.forEach(fn => {
typeof fn === 'function' && fn();
removeFrom(deregFns, fn);
});
}

return function(router: UIRouter) {
extend(services.locationConfig, configuration);
extend(services.location, service);
services.location.onChange = (cb: Function) =>
pushTo(deregFns, service.onChange(cb));
return { name, service, configuration, dispose };
};
}
11 changes: 8 additions & 3 deletions test/_testUtils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {pick, forEach, omit} from "../src/index";
import {map} from "../src/common/common";
import { pick, forEach, omit } from "../src/index";
import { map } from "../src/common/common";

let stateProps = ["resolve", "resolvePolicy", "data", "template", "templateUrl", "url", "name", "params"];

Expand Down Expand Up @@ -59,4 +59,9 @@ export function PromiseResult(promise?) {
}
}


export const awaitTransition = (router) => new Promise(resolve => {
let dereg = router.transitionService.onSuccess({}, (trans) => {
dereg();
resolve(trans);
});
});
34 changes: 34 additions & 0 deletions test/_testingPlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { UIRouter } from "../src/router";
import { UIRouterPluginBase } from "../src/interface";
import * as vanilla from "../src/vanilla";

export class TestingPlugin extends UIRouterPluginBase {
name: string = 'testing';
errorsCount: number = 0;
errorsThreshold: number = 1000;

constructor(public router: UIRouter) {
super();
router.plugin(vanilla.servicesPlugin);
router.plugin(vanilla.memoryLocationPlugin);

this.addErrorLoopHandler();

this.startRouter();
}

startRouter() {
this.router.stateRegistry.stateQueue.autoFlush(this.router.stateService);
this.router.urlRouter.listen();
}

addErrorLoopHandler() {
let $transitions = this.router.transitionService;
$transitions.onCreate({}, trans => {
trans.promise.catch(() => this.errorsCount++);
if (this.errorsCount > this.errorsThreshold) {
throw new Error(`Over ${this.errorsThreshold} failures; creation of new transitions disabled`);
}
});
}
}
1 change: 1 addition & 0 deletions test/pluginSpec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ describe('plugin api', function () {
class FancyPluginClass implements UIRouterPlugin {
name = "fancypluginclass";
constructor(public router: UIRouter) { }
dispose() {}
}

function FancyPluginConstructor(router: UIRouter, options: any) {
Expand Down
32 changes: 16 additions & 16 deletions test/resolveSpec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { tree2Array } from "./_testUtils";
import { UIRouter } from "../src/router";

import Spy = jasmine.Spy;
import { TestingPlugin } from "./_testingPlugin";

///////////////////////////////////////////////

Expand Down Expand Up @@ -61,21 +62,6 @@ function getStates() {
};
}

beforeEach(function () {
router = new UIRouter();
router.plugin(vanilla.servicesPlugin);
router.plugin(vanilla.hashLocationPlugin);
router.stateRegistry.stateQueue.autoFlush(router.stateService);

counts = { _J: 0, _J2: 0, _K: 0, _L: 0, _M: 0, _Q: 0 };
vals = { _Q: null };
expectCounts = copy(counts);

tree2Array(getStates(), false).forEach(state => router.stateRegistry.register(state));
statesMap = router.stateRegistry.get()
.reduce((acc, state) => (acc[state.name] = state.$$state(), acc), statesMap);
});

function makePath(names: string[]): PathNode[] {
return names.map(name => new PathNode(statesMap[name]));
}
Expand All @@ -86,8 +72,22 @@ function getResolvedData(pathContext: ResolveContext) {
.reduce((acc, resolvable) => { acc[resolvable.token] = resolvable.data; return acc; }, {});
}


describe('Resolvables system:', function () {

afterEach(() => router.dispose());
beforeEach(function () {
router = new UIRouter();
router.plugin(TestingPlugin);

counts = { _J: 0, _J2: 0, _K: 0, _L: 0, _M: 0, _Q: 0 };
vals = { _Q: null };
expectCounts = copy(counts);

tree2Array(getStates(), false).forEach(state => router.stateRegistry.register(state));
statesMap = router.stateRegistry.get()
.reduce((acc, state) => (acc[state.name] = state.$$state(), acc), statesMap);
});

describe('Path.getResolvables', function () {
it('should return Resolvables from the deepest element and all ancestors', () => {
let path = makePath([ "A", "B", "C" ]);
Expand Down
Loading

0 comments on commit f64aace

Please sign in to comment.