Skip to content

Commit

Permalink
[FEATURE Router Service] recognize and recognizeAndLoad
Browse files Browse the repository at this point in the history
  • Loading branch information
chadhietala committed Oct 3, 2018
1 parent c3e41a0 commit d6e899c
Show file tree
Hide file tree
Showing 5 changed files with 297 additions and 10 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@
"puppeteer": "^1.3.0",
"qunit": "^2.5.0",
"route-recognizer": "^0.3.4",
"router_js": "^5.1.1",
"router_js": "^5.2.0",
"rsvp": "^4.8.2",
"semver": "^5.5.0",
"serve-static": "^1.12.2",
Expand Down
55 changes: 50 additions & 5 deletions packages/@ember/-internals/routing/lib/services/router.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Evented } from '@ember/-internals/runtime';
import { EMBER_ROUTING_ROUTER_SERVICE } from '@ember/canary-features';
import { assert } from '@ember/debug';
import { readOnly } from '@ember/object/computed';
import Service from '@ember/service';
import { Transition } from 'router_js';
Expand Down Expand Up @@ -261,6 +262,46 @@ if (EMBER_ROUTING_ROUTER_SERVICE) {
this.trigger('routeDidChange', transition);
});
},

/**
Takes a string URL and returns a `RouteInfo` for the leafmost route represented
by the URL. Returns `null` if the URL is not recognized. This method expects to
receive the actual URL as seen by the browser including the app's `rootURL`.
@method recognize
@param {String} url
@category ember-routing-router-service
@public
*/
recognize(url: string) {
assert(
`You must pass a url that begins with the application's rootURL "${this.rootURL}"`,
url.indexOf(this.rootURL) === 0
);
let internalURL = cleanURL(url, this.rootURL);
return this._router._routerMicrolib.recognize(internalURL);
},

/**
Takes a string URL and returns a promise that resolves to a
`RouteInfoWithAttributes` for the leafmost route represented by the URL.
The promise rejects if the URL is not recognized or an unhandled exception
is encountered. This method expects to receive the actual URL as seen by
the browser including the app's `rootURL`.
@method recognizeAndLoad
@param {String} url
@category ember-routing-router-service
@public
*/
recognizeAndLoad(url: string) {
assert(
`You must pass a url that begins with the application's rootURL "${this.rootURL}"`,
url.indexOf(this.rootURL) === 0
);
let internalURL = cleanURL(url, this.rootURL);
return this._router._routerMicrolib.recognizeAndLoad(internalURL);
},
/**
The `routeWillChange` event is fired at the beginning of any
attempted transition with a `Transition` object as the sole
Expand All @@ -287,11 +328,7 @@ if (EMBER_ROUTING_ROUTER_SERVICE) {
});
```
The `routeWillChange` event fires whenever a new route is chosen
as the desired target of a transition. This includes `transitionTo`,
`replaceWith`, all redirection for any reason including error handling,
and abort. Aborting implies changing the desired target back to where
you already were. Once a transition has completed, `routeDidChange` fires.
The `routeWillChange` event fires whenever a new route is chosen as the desired target of a transition. This includes `transitionTo`, `replaceWith`, all redirection for any reason including error handling, and abort. Aborting implies changing the desired target back to where you already were. Once a transition has completed, `routeDidChange` fires.
@event routeWillChange
@param {Transition} transition
Expand Down Expand Up @@ -329,4 +366,12 @@ if (EMBER_ROUTING_ROUTER_SERVICE) {
@public
*/
});

function cleanURL(url: string, rootURL: string) {
if (rootURL === '/') {
return url;
}

return url.substr(rootURL.length, url.length);
}
}
14 changes: 13 additions & 1 deletion packages/@ember/-internals/routing/lib/system/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1169,6 +1169,9 @@ class Route extends EmberObject implements IRoute {
Router.js hook.
*/
deserialize(_params: {}, transition: Transition) {
if (EMBER_ROUTING_ROUTER_SERVICE) {
return this.model(this._paramsFor(this.routeName, _params), transition);
}
return this.model(this.paramsFor(this.routeName), transition);
}

Expand Down Expand Up @@ -2544,7 +2547,16 @@ if (EMBER_ROUTING_ROUTER_SERVICE && ROUTER_EVENTS) {
},
};

Route.reopen(ROUTER_EVENT_DEPRECATIONS);
Route.reopen(ROUTER_EVENT_DEPRECATIONS, {
_paramsFor(routeName: string, params: {}) {
let transition = this._router._routerMicrolib.activeTransition;
if (transition !== undefined) {
return this.paramsFor(routeName);
}

return params;
},
});
}

export default Route;
229 changes: 229 additions & 0 deletions packages/ember/tests/routing/router_service_test/recognize_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
import { RouterTestCase, moduleFor } from 'internal-test-helpers';
import { Route } from '@ember/-internals/routing';
import { EMBER_ROUTING_ROUTER_SERVICE } from '@ember/canary-features';

if (EMBER_ROUTING_ROUTER_SERVICE) {
moduleFor(
'Router Service - recognize',
class extends RouterTestCase {
'@test returns a RouteInfo for recognized URL'(assert) {
return this.visit('/').then(() => {
let routeInfo = this.routerService.recognize('/dynamic-with-child/123/1?a=b');
assert.ok(routeInfo);
let { name, localName, parent, child, params, queryParams, paramNames } = routeInfo;
assert.equal(name, 'dynamicWithChild.child');
assert.equal(localName, 'child');
assert.ok(parent);
assert.equal(parent.name, 'dynamicWithChild');
assert.notOk(child);
assert.deepEqual(params, { child_id: '1' });
assert.deepEqual(queryParams, { a: 'b' });
assert.deepEqual(paramNames, ['child_id']);
});
}

'@test does not transition'(assert) {
this.addTemplate('parent', 'Parent');
this.addTemplate('dynamic-with-child.child', 'Dynamic Child');

return this.visit('/').then(() => {
this.routerService.recognize('/dynamic-with-child/123/1?a=b');
this.assertText('Parent', 'Did not transition and cause render');
assert.equal(this.routerService.currentURL, '/', 'Did not transition');
});
}

'@test respects the usage of a different rootURL'(assert) {
this.router.reopen({
rootURL: '/app/',
});

return this.visit('/app').then(() => {
let routeInfo = this.routerService.recognize('/app/child/');
assert.ok(routeInfo);
let { name, localName, parent } = routeInfo;
assert.equal(name, 'parent.child');
assert.equal(localName, 'child');
assert.equal(parent.name, 'parent');
});
}

'@test must include rootURL'() {
this.addTemplate('parent', 'Parent');
this.addTemplate('dynamic-with-child.child', 'Dynamic Child');

this.router.reopen({
rootURL: '/app/',
});

return this.visit('/app').then(() => {
expectAssertion(() => {
this.routerService.recognize('/dynamic-with-child/123/1?a=b');
}, 'You must pass a url that begins with the application\'s rootURL "/app/"');
});
}

'@test returns `null` if URL is not recognized'(assert) {
return this.visit('/').then(() => {
let routeInfo = this.routerService.recognize('/foo');
assert.equal(routeInfo, null);
});
}
}
);

moduleFor(
'Router Service - recognizeAndLoad',
class extends RouterTestCase {
'@test returns a RouteInfoWithAttributes for recognized URL'(assert) {
this.add(
'route:dynamicWithChild',
Route.extend({
model(params) {
return { name: 'dynamicWithChild', data: params.dynamic_id };
},
})
);
this.add(
'route:dynamicWithChild.child',
Route.extend({
model(params) {
return { name: 'dynamicWithChild.child', data: params.child_id };
},
})
);

return this.visit('/')
.then(() => {
return this.routerService.recognizeAndLoad('/dynamic-with-child/123/1?a=b');
})
.then(routeInfoWithAttributes => {
assert.ok(routeInfoWithAttributes);
let {
name,
localName,
parent,
attributes,
paramNames,
params,
queryParams,
} = routeInfoWithAttributes;
assert.equal(name, 'dynamicWithChild.child');
assert.equal(localName, 'child');
assert.equal(parent.name, 'dynamicWithChild');
assert.deepEqual(params, { child_id: '1' });
assert.deepEqual(queryParams, { a: 'b' });
assert.deepEqual(paramNames, ['child_id']);
assert.deepEqual(attributes, { name: 'dynamicWithChild.child', data: '1' });
assert.deepEqual(parent.attributes, { name: 'dynamicWithChild', data: '123' });
assert.deepEqual(parent.paramNames, ['dynamic_id']);
assert.deepEqual(parent.params, { dynamic_id: '123' });
});
}

'@test does not transition'(assert) {
this.addTemplate('parent', 'Parent{{outlet}}');
this.addTemplate('parent.child', 'Child');

this.add(
'route:parent.child',
Route.extend({
model() {
return { name: 'child', data: ['stuff'] };
},
})
);
return this.visit('/')
.then(() => {
return this.routerService.recognizeAndLoad('/child');
})
.then(() => {
assert.equal(this.routerService.currentURL, '/');
this.assertText('Parent');
});
}

'@test respects the usage of a different rootURL'(assert) {
this.router.reopen({
rootURL: '/app/',
});

return this.visit('/app')
.then(() => {
return this.routerService.recognizeAndLoad('/app/child/');
})
.then(routeInfoWithAttributes => {
assert.ok(routeInfoWithAttributes);
let { name, localName, parent } = routeInfoWithAttributes;
assert.equal(name, 'parent.child');
assert.equal(localName, 'child');
assert.equal(parent.name, 'parent');
});
}

'@test must include rootURL'() {
this.router.reopen({
rootURL: '/app/',
});

return this.visit('/app').then(() => {
expectAssertion(() => {
this.routerService.recognizeAndLoad('/dynamic-with-child/123/1?a=b');
}, 'You must pass a url that begins with the application\'s rootURL "/app/"');
});
}

'@test rejects if url is not recognized'(assert) {
this.addTemplate('parent', 'Parent{{outlet}}');
this.addTemplate('parent.child', 'Child');

this.add(
'route:parent.child',
Route.extend({
model() {
return { name: 'child', data: ['stuff'] };
},
})
);
return this.visit('/')
.then(() => {
return this.routerService.recognizeAndLoad('/foo');
})
.then(
() => {
assert.ok(false, 'never');
},
reason => {
assert.equal(reason, 'URL /foo was not recognized');
}
);
}

'@test rejects if there is an unhandled error'(assert) {
this.addTemplate('parent', 'Parent{{outlet}}');
this.addTemplate('parent.child', 'Child');

this.add(
'route:parent.child',
Route.extend({
model() {
throw Error('Unhandled');
},
})
);
return this.visit('/')
.then(() => {
return this.routerService.recognizeAndLoad('/child');
})
.then(
() => {
assert.ok(false, 'never');
},
err => {
assert.equal(err.message, 'Unhandled');
}
);
}
}
);
}
7 changes: 4 additions & 3 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7385,9 +7385,10 @@ route-recognizer@^0.3.4:
resolved "https://registry.yarnpkg.com/route-recognizer/-/route-recognizer-0.3.4.tgz#39ab1ffbce1c59e6d2bdca416f0932611e4f3ca3"
integrity sha512-2+MhsfPhvauN1O8KaXpXAOfR/fwe8dnUXVM+xw7yt40lJRfPVQxV6yryZm0cgRvAj5fMF/mdRZbL2ptwbs5i2g==

router_js@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/router_js/-/router_js-5.1.1.tgz#3a285264132040ea4aa4a6ce2d9b7a05d40176fb"
router_js@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/router_js/-/router_js-5.2.0.tgz#8796d0ad7ab8a9d0ffbf5b02e5e00d2472a53e7d"
integrity sha512-v+gjYRwDWJpJW0jPB9tFphbcp0pD7R/ZRqu/tno9TXgQxanRArw/weyGFZnbpR95tY9B5SpFonAZk5opPNQUvQ==
dependencies:
"@types/node" "^10.5.5"

Expand Down

0 comments on commit d6e899c

Please sign in to comment.