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 411f7c4 commit 52e5f18
Show file tree
Hide file tree
Showing 5 changed files with 293 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
46 changes: 46 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,43 @@ if (EMBER_ROUTING_ROUTER_SERVICE) {
this.trigger('routeDidChange', transition);
});
},

// Uncomment this when we go the feature
// /**
// 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
// @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);
},

// Uncomment this when we go the feature
// /**
// 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
// */
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 +367,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 52e5f18

Please sign in to comment.