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 7b00f7b commit 2dabdd5
Show file tree
Hide file tree
Showing 5 changed files with 294 additions and 5 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
47 changes: 47 additions & 0 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 @@ -266,6 +267,44 @@ 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`.
@param {String} url
@category ember-routing-router-service
*/
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);
},
// Uncomment this when we go the feature
// /**
// The `routeWillChange` event is fired at the beginning of any
Expand Down Expand Up @@ -329,4 +368,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 2dabdd5

Please sign in to comment.