Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUGFIX release] Sticky query params for nested and for dynamic routes. #11636

Merged
merged 1 commit into from
Jul 2, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 31 additions & 3 deletions packages/ember-routing/lib/ext/controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,11 +147,18 @@ ControllerMixin.reopen({
*/
_calculateCacheKey(prefix, _parts, values) {
var parts = _parts || [];
var suffixes = "";
var suffixes = '';
for (var i = 0, len = parts.length; i < len; ++i) {
var part = parts[i];
var value = get(values, part);
suffixes += "::" + part + ":" + value;
var cacheValuePrefix = _calculateCacheValuePrefix(prefix, part);
var value;
if (cacheValuePrefix && cacheValuePrefix in values) {
var partRemovedPrefix = (part.indexOf(cacheValuePrefix) === 0) ? part.substr(cacheValuePrefix.length + 1) : part;
value = get(values[cacheValuePrefix], partRemovedPrefix);
} else {
value = get(values, part);
}
suffixes += '::' + part + ':' + value;
}
return prefix + suffixes.replace(ALL_PERIODS_REGEX, '-');
},
Expand Down Expand Up @@ -355,5 +362,26 @@ function listenForQueryParamChanges(controller) {
}
}

function _calculateCacheValuePrefix(prefix, part) {
// calculates the dot seperated sections from prefix that are also
// at the start of part - which gives us the route name

// given : prefix = site.article.comments, part = site.article.id
// - returns: site.article (use get(values[site.article], 'id') to get the dynamic part - used below)

// given : prefix = site.article, part = site.article.id
// - returns: site.article. (use get(values[site.article], 'id') to get the dynamic part - used below)

var prefixParts = prefix.split('.');
var currPrefix = '';
for (var i = 0, len = prefixParts.length; i < len; i++) {
var currPart = prefixParts.slice(0, i+1).join('.');
if (part.indexOf(currPart) !== 0) {
break;
}
currPrefix = currPart;
}
return currPrefix;
}

export default ControllerMixin;
286 changes: 0 additions & 286 deletions packages/ember/tests/routing/query_params_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1250,292 +1250,6 @@ var testParamlessLinks = function(routeName) {
testParamlessLinks('application');
testParamlessLinks('index');

QUnit.module("Model Dep Query Params", {
setup() {
sharedSetup();

App.Router.map(function() {
this.resource('article', { path: '/a/:id' }, function() {
this.resource('comments');
});
});

var articles = this.articles = Ember.A([{ id: 'a-1' }, { id: 'a-2' }, { id: 'a-3' }]);

App.ApplicationController = Ember.Controller.extend({
articles: this.articles
});

var self = this;
App.ArticleRoute = Ember.Route.extend({
queryParams: {},
model(params) {
if (self.expectedModelHookParams) {
deepEqual(params, self.expectedModelHookParams, "the ArticleRoute model hook received the expected merged dynamic segment + query params hash");
self.expectedModelHookParams = null;
}
return articles.findProperty('id', params.id);
}
});

App.ArticleController = Ember.Controller.extend({
queryParams: ['q', 'z'],
q: 'wat',
z: 0
});

App.CommentsController = Ember.Controller.extend({
queryParams: 'page',
page: 1
});

Ember.TEMPLATES.application = compile("{{#each articles as |a|}} {{link-to 'Article' 'article' a id=a.id}} {{/each}} {{outlet}}");

this.boot = function() {
bootApplication();

self.$link1 = Ember.$('#a-1');
self.$link2 = Ember.$('#a-2');
self.$link3 = Ember.$('#a-3');

equal(self.$link1.attr('href'), '/a/a-1');
equal(self.$link2.attr('href'), '/a/a-2');
equal(self.$link3.attr('href'), '/a/a-3');

self.controller = container.lookup('controller:article');
};
},

teardown() {
sharedTeardown();
ok(!this.expectedModelHookParams, "there should be no pending expectation of expected model hook params");
}
});

QUnit.test("query params have 'model' stickiness by default", function() {
this.boot();

Ember.run(this.$link1, 'click');
equal(router.get('location.path'), '/a/a-1');

setAndFlush(this.controller, 'q', 'lol');

equal(this.$link1.attr('href'), '/a/a-1?q=lol');
equal(this.$link2.attr('href'), '/a/a-2');
equal(this.$link3.attr('href'), '/a/a-3');

Ember.run(this.$link2, 'click');

equal(this.controller.get('q'), 'wat');
equal(this.controller.get('z'), 0);
deepEqual(withoutMeta(this.controller.get('model')), { id: 'a-2' });
equal(this.$link1.attr('href'), '/a/a-1?q=lol');
equal(this.$link2.attr('href'), '/a/a-2');
equal(this.$link3.attr('href'), '/a/a-3');
});

QUnit.test("query params have 'model' stickiness by default (url changes)", function() {

this.boot();

this.expectedModelHookParams = { id: 'a-1', q: 'lol', z: 0 };
handleURL('/a/a-1?q=lol');

deepEqual(withoutMeta(this.controller.get('model')), { id: 'a-1' });
equal(this.controller.get('q'), 'lol');
equal(this.controller.get('z'), 0);
equal(this.$link1.attr('href'), '/a/a-1?q=lol');
equal(this.$link2.attr('href'), '/a/a-2');
equal(this.$link3.attr('href'), '/a/a-3');

this.expectedModelHookParams = { id: 'a-2', q: 'lol', z: 0 };
handleURL('/a/a-2?q=lol');

deepEqual(withoutMeta(this.controller.get('model')), { id: 'a-2' }, "controller's model changed to a-2");
equal(this.controller.get('q'), 'lol');
equal(this.controller.get('z'), 0);
equal(this.$link1.attr('href'), '/a/a-1?q=lol');
equal(this.$link2.attr('href'), '/a/a-2?q=lol'); // fail
equal(this.$link3.attr('href'), '/a/a-3');

this.expectedModelHookParams = { id: 'a-3', q: 'lol', z: 123 };
handleURL('/a/a-3?q=lol&z=123');

equal(this.controller.get('q'), 'lol');
equal(this.controller.get('z'), 123);
equal(this.$link1.attr('href'), '/a/a-1?q=lol');
equal(this.$link2.attr('href'), '/a/a-2?q=lol');
equal(this.$link3.attr('href'), '/a/a-3?q=lol&z=123');
});


QUnit.test("query params have 'model' stickiness by default (params-based transitions)", function() {
Ember.TEMPLATES.application = compile("{{#each articles as |a|}} {{link-to 'Article' 'article' a.id id=a.id}} {{/each}}");

this.boot();

this.expectedModelHookParams = { id: 'a-1', q: 'wat', z: 0 };
Ember.run(router, 'transitionTo', 'article', 'a-1');

deepEqual(withoutMeta(this.controller.get('model')), { id: 'a-1' });
equal(this.controller.get('q'), 'wat');
equal(this.controller.get('z'), 0);
equal(this.$link1.attr('href'), '/a/a-1');
equal(this.$link2.attr('href'), '/a/a-2');
equal(this.$link3.attr('href'), '/a/a-3');

this.expectedModelHookParams = { id: 'a-2', q: 'lol', z: 0 };
Ember.run(router, 'transitionTo', 'article', 'a-2', { queryParams: { q: 'lol' } });

deepEqual(withoutMeta(this.controller.get('model')), { id: 'a-2' });
equal(this.controller.get('q'), 'lol');
equal(this.controller.get('z'), 0);
equal(this.$link1.attr('href'), '/a/a-1');
equal(this.$link2.attr('href'), '/a/a-2?q=lol');
equal(this.$link3.attr('href'), '/a/a-3');

this.expectedModelHookParams = { id: 'a-3', q: 'hay', z: 0 };
Ember.run(router, 'transitionTo', 'article', 'a-3', { queryParams: { q: 'hay' } });

deepEqual(withoutMeta(this.controller.get('model')), { id: 'a-3' });
equal(this.controller.get('q'), 'hay');
equal(this.controller.get('z'), 0);
equal(this.$link1.attr('href'), '/a/a-1');
equal(this.$link2.attr('href'), '/a/a-2?q=lol');
equal(this.$link3.attr('href'), '/a/a-3?q=hay');

this.expectedModelHookParams = { id: 'a-2', q: 'lol', z: 1 };
Ember.run(router, 'transitionTo', 'article', 'a-2', { queryParams: { z: 1 } });

deepEqual(withoutMeta(this.controller.get('model')), { id: 'a-2' });
equal(this.controller.get('q'), 'lol');
equal(this.controller.get('z'), 1);
equal(this.$link1.attr('href'), '/a/a-1');
equal(this.$link2.attr('href'), '/a/a-2?q=lol&z=1');
equal(this.$link3.attr('href'), '/a/a-3?q=hay');
});

QUnit.test("'controller' stickiness shares QP state between models", function() {
App.ArticleController.reopen({
queryParams: { q: { scope: 'controller' } }
});

this.boot();

Ember.run(this.$link1, 'click');
equal(router.get('location.path'), '/a/a-1');

setAndFlush(this.controller, 'q', 'lol');

equal(this.$link1.attr('href'), '/a/a-1?q=lol');
equal(this.$link2.attr('href'), '/a/a-2?q=lol');
equal(this.$link3.attr('href'), '/a/a-3?q=lol');

Ember.run(this.$link2, 'click');

equal(this.controller.get('q'), 'lol');
equal(this.controller.get('z'), 0);
deepEqual(withoutMeta(this.controller.get('model')), { id: 'a-2' });

equal(this.$link1.attr('href'), '/a/a-1?q=lol');
equal(this.$link2.attr('href'), '/a/a-2?q=lol');
equal(this.$link3.attr('href'), '/a/a-3?q=lol');

this.expectedModelHookParams = { id: 'a-3', q: 'haha', z: 123 };
handleURL('/a/a-3?q=haha&z=123');

deepEqual(withoutMeta(this.controller.get('model')), { id: 'a-3' });
equal(this.controller.get('q'), 'haha');
equal(this.controller.get('z'), 123);

equal(this.$link1.attr('href'), '/a/a-1?q=haha');
equal(this.$link2.attr('href'), '/a/a-2?q=haha');
equal(this.$link3.attr('href'), '/a/a-3?q=haha&z=123');

setAndFlush(this.controller, 'q', 'woot');

equal(this.$link1.attr('href'), '/a/a-1?q=woot');
equal(this.$link2.attr('href'), '/a/a-2?q=woot');
equal(this.$link3.attr('href'), '/a/a-3?q=woot&z=123');
});

QUnit.test("'model' stickiness is scoped to current or first dynamic parent route", function() {
this.boot();

Ember.run(router, 'transitionTo', 'comments', 'a-1');

var commentsCtrl = container.lookup('controller:comments');
equal(commentsCtrl.get('page'), 1);
equal(router.get('location.path'), '/a/a-1/comments');

setAndFlush(commentsCtrl, 'page', 2);
equal(router.get('location.path'), '/a/a-1/comments?page=2');

setAndFlush(commentsCtrl, 'page', 3);
equal(router.get('location.path'), '/a/a-1/comments?page=3');

Ember.run(router, 'transitionTo', 'comments', 'a-2');
equal(commentsCtrl.get('page'), 1);
equal(router.get('location.path'), '/a/a-2/comments');

Ember.run(router, 'transitionTo', 'comments', 'a-1');
equal(commentsCtrl.get('page'), 3);
equal(router.get('location.path'), '/a/a-1/comments?page=3');
});

QUnit.test("can reset query params using the resetController hook", function() {
App.Router.map(function() {
this.resource('article', { path: '/a/:id' }, function() {
this.resource('comments');
});
this.route('about');
});

App.ArticleRoute.reopen({
resetController(controller, isExiting) {
this.controllerFor('comments').set('page', 1);
if (isExiting) {
controller.set('q', 'imdone');
}
}
});

Ember.TEMPLATES.about = compile("{{link-to 'A' 'comments' 'a-1' id='one'}} {{link-to 'B' 'comments' 'a-2' id='two'}}");

this.boot();

Ember.run(router, 'transitionTo', 'comments', 'a-1');

var commentsCtrl = container.lookup('controller:comments');
equal(commentsCtrl.get('page'), 1);
equal(router.get('location.path'), '/a/a-1/comments');

setAndFlush(commentsCtrl, 'page', 2);
equal(router.get('location.path'), '/a/a-1/comments?page=2');

Ember.run(router, 'transitionTo', 'comments', 'a-2');
equal(commentsCtrl.get('page'), 1);
equal(this.controller.get('q'), 'wat');

Ember.run(router, 'transitionTo', 'comments', 'a-1');

equal(router.get('location.path'), '/a/a-1/comments');
equal(commentsCtrl.get('page'), 1);

Ember.run(router, 'transitionTo', 'about');

equal(Ember.$('#one').attr('href'), "/a/a-1/comments?q=imdone");
equal(Ember.$('#two').attr('href'), "/a/a-2/comments");
});

QUnit.test("can unit test without bucket cache", function() {
var controller = container.lookup('controller:article');
controller._bucketCache = null;

controller.set('q', "i used to break");
equal(controller.get('q'), "i used to break");
});

QUnit.module("Query Params - overlapping query param property names", {
setup() {
sharedSetup();
Expand Down
Loading