import Controller from '@ember/controller'; import { dasherize } from '@ember/string'; import { RSVP, Object as EmberObject, A as emberA } from 'ember-runtime'; import { run } from '@ember/runloop'; import { peekMeta } from 'ember-meta'; import { get, computed } from 'ember-metal'; import { Route } from 'ember-routing'; import { QueryParamTestCase, moduleFor, getTextOf } from 'internal-test-helpers'; moduleFor( 'Query Params - main', class extends QueryParamTestCase { refreshModelWhileLoadingTest(loadingReturn) { let assert = this.assert; assert.expect(9); let appModelCount = 0; let promiseResolve; this.add( 'route:application', Route.extend({ queryParams: { appomg: { defaultValue: 'applol', }, }, model(/* params */) { appModelCount++; }, }) ); this.setSingleQPController('index', 'omg', undefined, { omg: undefined, }); let actionName = typeof loadingReturn !== 'undefined' ? 'loading' : 'ignore'; let indexModelCount = 0; this.add( 'route:index', Route.extend({ queryParams: { omg: { refreshModel: true, }, }, actions: { [actionName]: function() { return loadingReturn; }, }, model(params) { indexModelCount++; if (indexModelCount === 2) { assert.deepEqual(params, { omg: 'lex' }); return new RSVP.Promise(function(resolve) { promiseResolve = resolve; return; }); } else if (indexModelCount === 3) { assert.deepEqual( params, { omg: 'hello' }, "Model hook reruns even if the previous one didn't finish" ); } }, }) ); return this.visit('/').then(() => { assert.equal(appModelCount, 1, 'appModelCount is 1'); assert.equal(indexModelCount, 1); let indexController = this.getController('index'); this.setAndFlush(indexController, 'omg', 'lex'); assert.equal(appModelCount, 1, 'appModelCount is 1'); assert.equal(indexModelCount, 2); this.setAndFlush(indexController, 'omg', 'hello'); assert.equal(appModelCount, 1, 'appModelCount is 1'); assert.equal(indexModelCount, 3); run(function() { promiseResolve(); }); assert.equal(get(indexController, 'omg'), 'hello', 'At the end last value prevails'); }); } ["@test No replaceURL occurs on startup because default values don't show up in URL"](assert) { assert.expect(1); this.setSingleQPController('index'); return this.visitAndAssert('/'); } ['@test Calling transitionTo does not lose query params already on the activeTransition']( assert ) { assert.expect(2); this.router.map(function() { this.route('parent', function() { this.route('child'); this.route('sibling'); }); }); this.add( 'route:parent.child', Route.extend({ afterModel() { this.transitionTo('parent.sibling'); }, }) ); this.setSingleQPController('parent'); return this.visit('/parent/child?foo=lol').then(() => { this.assertCurrentPath( '/parent/sibling?foo=lol', 'redirected to the sibling route, instead of child route' ); assert.equal( this.getController('parent').get('foo'), 'lol', 'controller has value from the active transition' ); }); } ['@test Single query params can be set on the controller and reflected in the url'](assert) { assert.expect(3); this.router.map(function() { this.route('home', { path: '/' }); }); this.setSingleQPController('home'); return this.visitAndAssert('/').then(() => { let controller = this.getController('home'); this.setAndFlush(controller, 'foo', '456'); this.assertCurrentPath('/?foo=456'); this.setAndFlush(controller, 'foo', '987'); this.assertCurrentPath('/?foo=987'); }); } ['@test Query params can map to different url keys configured on the controller'](assert) { assert.expect(6); this.add( 'controller:index', Controller.extend({ queryParams: [{ foo: 'other_foo', bar: { as: 'other_bar' } }], foo: 'FOO', bar: 'BAR', }) ); return this.visitAndAssert('/').then(() => { let controller = this.getController('index'); this.setAndFlush(controller, 'foo', 'LEX'); this.assertCurrentPath('/?other_foo=LEX', "QP mapped correctly without 'as'"); this.setAndFlush(controller, 'foo', 'WOO'); this.assertCurrentPath('/?other_foo=WOO', "QP updated correctly without 'as'"); this.transitionTo('/?other_foo=NAW'); assert.equal(controller.get('foo'), 'NAW', 'QP managed correctly on URL transition'); this.setAndFlush(controller, 'bar', 'NERK'); this.assertCurrentPath('/?other_bar=NERK&other_foo=NAW', "QP mapped correctly with 'as'"); this.setAndFlush(controller, 'bar', 'NUKE'); this.assertCurrentPath('/?other_bar=NUKE&other_foo=NAW', "QP updated correctly with 'as'"); }); } ['@test Routes have a private overridable serializeQueryParamKey hook'](assert) { assert.expect(2); this.add( 'route:index', Route.extend({ serializeQueryParamKey: dasherize, }) ); this.setSingleQPController('index', 'funTimes', ''); return this.visitAndAssert('/').then(() => { let controller = this.getController('index'); this.setAndFlush(controller, 'funTimes', 'woot'); this.assertCurrentPath('/?fun-times=woot'); }); } ['@test Can override inherited QP behavior by specifying queryParams as a computed property']( assert ) { assert.expect(3); this.setSingleQPController('index', 'a', 0, { queryParams: computed(function() { return ['c']; }), c: true, }); return this.visitAndAssert('/').then(() => { let indexController = this.getController('index'); this.setAndFlush(indexController, 'a', 1); this.assertCurrentPath('/', 'QP did not update due to being overriden'); this.setAndFlush(indexController, 'c', false); this.assertCurrentPath('/?c=false', 'QP updated with overridden param'); }); } ['@test Can concatenate inherited QP behavior by specifying queryParams as an array'](assert) { assert.expect(3); this.setSingleQPController('index', 'a', 0, { queryParams: ['c'], c: true, }); return this.visitAndAssert('/').then(() => { let indexController = this.getController('index'); this.setAndFlush(indexController, 'a', 1); this.assertCurrentPath('/?a=1', 'Inherited QP did update'); this.setAndFlush(indexController, 'c', false); this.assertCurrentPath('/?a=1&c=false', 'New QP did update'); }); } ['@test model hooks receives query params'](assert) { assert.expect(2); this.setSingleQPController('index'); this.add( 'route:index', Route.extend({ model(params) { assert.deepEqual(params, { foo: 'bar' }); }, }) ); return this.visitAndAssert('/'); } ['@test model hooks receives query params with dynamic segment params'](assert) { assert.expect(2); this.router.map(function() { this.route('index', { path: '/:id' }); }); this.setSingleQPController('index'); this.add( 'route:index', Route.extend({ model(params) { assert.deepEqual(params, { foo: 'bar', id: 'baz' }); }, }) ); return this.visitAndAssert('/baz'); } ['@test model hooks receives query params (overridden by incoming url value)'](assert) { assert.expect(2); this.router.map(function() { this.route('index', { path: '/:id' }); }); this.setSingleQPController('index'); this.add( 'route:index', Route.extend({ model(params) { assert.deepEqual(params, { foo: 'baz', id: 'boo' }); }, }) ); return this.visitAndAssert('/boo?foo=baz'); } ['@test error is thrown if dynamic segment and query param have same name'](assert) { assert.expect(1); this.router.map(function() { this.route('index', { path: '/:foo' }); }); this.setSingleQPController('index'); expectAssertion(() => { this.visitAndAssert('/boo?foo=baz'); }, `The route 'index' has both a dynamic segment and query param with name 'foo'. Please rename one to avoid collisions.`); } ['@test query params have been set by the time setupController is called'](assert) { assert.expect(2); this.setSingleQPController('application'); this.add( 'route:application', Route.extend({ setupController(controller) { assert.equal( controller.get('foo'), 'YEAH', "controller's foo QP property set before setupController called" ); }, }) ); return this.visitAndAssert('/?foo=YEAH'); } ['@test mapped query params have been set by the time setupController is called'](assert) { assert.expect(2); this.setSingleQPController('application', { faz: 'foo' }); this.add( 'route:application', Route.extend({ setupController(controller) { assert.equal( controller.get('faz'), 'YEAH', "controller's foo QP property set before setupController called" ); }, }) ); return this.visitAndAssert('/?foo=YEAH'); } ['@test Route#paramsFor fetches query params with default value'](assert) { assert.expect(2); this.router.map(function() { this.route('index', { path: '/:something' }); }); this.setSingleQPController('index'); this.add( 'route:index', Route.extend({ model(/* params, transition */) { assert.deepEqual( this.paramsFor('index'), { something: 'baz', foo: 'bar' }, 'could retrieve params for index' ); }, }) ); return this.visitAndAssert('/baz'); } ['@test Route#paramsFor fetches query params with non-default value'](assert) { assert.expect(2); this.router.map(function() { this.route('index', { path: '/:something' }); }); this.setSingleQPController('index'); this.add( 'route:index', Route.extend({ model(/* params, transition */) { assert.deepEqual( this.paramsFor('index'), { something: 'baz', foo: 'boo' }, 'could retrieve params for index' ); }, }) ); return this.visitAndAssert('/baz?foo=boo'); } ['@test Route#paramsFor fetches default falsy query params'](assert) { assert.expect(2); this.router.map(function() { this.route('index', { path: '/:something' }); }); this.setSingleQPController('index', 'foo', false); this.add( 'route:index', Route.extend({ model(/* params, transition */) { assert.deepEqual( this.paramsFor('index'), { something: 'baz', foo: false }, 'could retrieve params for index' ); }, }) ); return this.visitAndAssert('/baz'); } ['@test Route#paramsFor fetches non-default falsy query params'](assert) { assert.expect(2); this.router.map(function() { this.route('index', { path: '/:something' }); }); this.setSingleQPController('index', 'foo', true); this.add( 'route:index', Route.extend({ model(/* params, transition */) { assert.deepEqual( this.paramsFor('index'), { something: 'baz', foo: false }, 'could retrieve params for index' ); }, }) ); return this.visitAndAssert('/baz?foo=false'); } ['@test model hook can query prefix-less application params'](assert) { assert.expect(4); this.setSingleQPController('application', 'appomg', 'applol'); this.setSingleQPController('index', 'omg', 'lol'); this.add( 'route:application', Route.extend({ model(params) { assert.deepEqual(params, { appomg: 'applol' }); }, }) ); this.add( 'route:index', Route.extend({ model(params) { assert.deepEqual(params, { omg: 'lol' }); assert.deepEqual(this.paramsFor('application'), { appomg: 'applol', }); }, }) ); return this.visitAndAssert('/'); } ['@test model hook can query prefix-less application params (overridden by incoming url value)']( assert ) { assert.expect(4); this.setSingleQPController('application', 'appomg', 'applol'); this.setSingleQPController('index', 'omg', 'lol'); this.add( 'route:application', Route.extend({ model(params) { assert.deepEqual(params, { appomg: 'appyes' }); }, }) ); this.add( 'route:index', Route.extend({ model(params) { assert.deepEqual(params, { omg: 'yes' }); assert.deepEqual(this.paramsFor('application'), { appomg: 'appyes', }); }, }) ); return this.visitAndAssert('/?appomg=appyes&omg=yes'); } ['@test can opt into full transition by setting refreshModel in route queryParams'](assert) { assert.expect(7); this.setSingleQPController('application', 'appomg', 'applol'); this.setSingleQPController('index', 'omg', 'lol'); let appModelCount = 0; this.add( 'route:application', Route.extend({ model(/* params, transition */) { appModelCount++; }, }) ); let indexModelCount = 0; this.add( 'route:index', Route.extend({ queryParams: { omg: { refreshModel: true, }, }, model(params) { indexModelCount++; if (indexModelCount === 1) { assert.deepEqual(params, { omg: 'lol' }, 'params are correct on first pass'); } else if (indexModelCount === 2) { assert.deepEqual(params, { omg: 'lex' }, 'params are correct on second pass'); } }, }) ); return this.visitAndAssert('/').then(() => { assert.equal(appModelCount, 1, 'app model hook ran'); assert.equal(indexModelCount, 1, 'index model hook ran'); let indexController = this.getController('index'); this.setAndFlush(indexController, 'omg', 'lex'); assert.equal(appModelCount, 1, 'app model hook did not run again'); assert.equal(indexModelCount, 2, 'index model hook ran again due to refreshModel'); }); } ['@test refreshModel and replace work together'](assert) { assert.expect(8); this.setSingleQPController('application', 'appomg', 'applol'); this.setSingleQPController('index', 'omg', 'lol'); let appModelCount = 0; this.add( 'route:application', Route.extend({ model(/* params */) { appModelCount++; }, }) ); let indexModelCount = 0; this.add( 'route:index', Route.extend({ queryParams: { omg: { refreshModel: true, replace: true, }, }, model(params) { indexModelCount++; if (indexModelCount === 1) { assert.deepEqual(params, { omg: 'lol' }, 'params are correct on first pass'); } else if (indexModelCount === 2) { assert.deepEqual(params, { omg: 'lex' }, 'params are correct on second pass'); } }, }) ); return this.visitAndAssert('/').then(() => { assert.equal(appModelCount, 1, 'app model hook ran'); assert.equal(indexModelCount, 1, 'index model hook ran'); let indexController = this.getController('index'); this.expectedReplaceURL = '/?omg=lex'; this.setAndFlush(indexController, 'omg', 'lex'); assert.equal(appModelCount, 1, 'app model hook did not run again'); assert.equal(indexModelCount, 2, 'index model hook ran again due to refreshModel'); }); } ['@test multiple QP value changes only cause a single model refresh'](assert) { assert.expect(2); this.setSingleQPController('index', 'alex', 'lol'); this.setSingleQPController('index', 'steely', 'lel'); let refreshCount = 0; this.add( 'route:index', Route.extend({ queryParams: { alex: { refreshModel: true, }, steely: { refreshModel: true, }, }, refresh() { refreshCount++; }, }) ); return this.visitAndAssert('/').then(() => { let indexController = this.getController('index'); run(indexController, 'setProperties', { alex: 'fran', steely: 'david', }); assert.equal(refreshCount, 1, 'index refresh hook only run once'); }); } ['@test refreshModel does not cause a second transition during app boot '](assert) { assert.expect(1); this.setSingleQPController('application', 'appomg', 'applol'); this.setSingleQPController('index', 'omg', 'lol'); this.add( 'route:index', Route.extend({ queryParams: { omg: { refreshModel: true, }, }, refresh() { assert.ok(false); }, }) ); return this.visitAndAssert('/?appomg=hello&omg=world'); } ['@test queryParams are updated when a controller property is set and the route is refreshed. Issue #13263 ']( assert ) { this.addTemplate( 'application', '{{foo}}{{outlet}}' ); this.setSingleQPController('application', 'foo', 1, { actions: { increment() { this.incrementProperty('foo'); this.send('refreshRoute'); }, }, }); this.add( 'route:application', Route.extend({ actions: { refreshRoute() { this.refresh(); }, }, }) ); return this.visitAndAssert('/').then(() => { assert.equal(getTextOf(document.getElementById('test-value')), '1'); run(document.getElementById('test-button'), 'click'); assert.equal(getTextOf(document.getElementById('test-value')), '2'); this.assertCurrentPath('/?foo=2'); run(document.getElementById('test-button'), 'click'); assert.equal(getTextOf(document.getElementById('test-value')), '3'); this.assertCurrentPath('/?foo=3'); }); } ["@test Use Ember.get to retrieve query params 'refreshModel' configuration"](assert) { assert.expect(7); this.setSingleQPController('application', 'appomg', 'applol'); this.setSingleQPController('index', 'omg', 'lol'); let appModelCount = 0; this.add( 'route:application', Route.extend({ model(/* params */) { appModelCount++; }, }) ); let indexModelCount = 0; this.add( 'route:index', Route.extend({ queryParams: EmberObject.create({ unknownProperty() { return { refreshModel: true }; }, }), model(params) { indexModelCount++; if (indexModelCount === 1) { assert.deepEqual(params, { omg: 'lol' }); } else if (indexModelCount === 2) { assert.deepEqual(params, { omg: 'lex' }); } }, }) ); return this.visitAndAssert('/').then(() => { assert.equal(appModelCount, 1); assert.equal(indexModelCount, 1); let indexController = this.getController('index'); this.setAndFlush(indexController, 'omg', 'lex'); assert.equal(appModelCount, 1); assert.equal(indexModelCount, 2); }); } ['@test can use refreshModel even with URL changes that remove QPs from address bar'](assert) { assert.expect(4); this.setSingleQPController('index', 'omg', 'lol'); let indexModelCount = 0; this.add( 'route:index', Route.extend({ queryParams: { omg: { refreshModel: true, }, }, model(params) { indexModelCount++; let data; if (indexModelCount === 1) { data = 'foo'; } else if (indexModelCount === 2) { data = 'lol'; } assert.deepEqual(params, { omg: data }, 'index#model receives right data'); }, }) ); return this.visitAndAssert('/?omg=foo').then(() => { this.transitionTo('/'); let indexController = this.getController('index'); assert.equal(indexController.get('omg'), 'lol'); }); } ['@test can opt into a replace query by specifying replace:true in the Route config hash']( assert ) { assert.expect(2); this.setSingleQPController('application', 'alex', 'matchneer'); this.add( 'route:application', Route.extend({ queryParams: { alex: { replace: true, }, }, }) ); return this.visitAndAssert('/').then(() => { let appController = this.getController('application'); this.expectedReplaceURL = '/?alex=wallace'; this.setAndFlush(appController, 'alex', 'wallace'); }); } ['@test Route query params config can be configured using property name instead of URL key']( assert ) { assert.expect(2); this.add( 'controller:application', Controller.extend({ queryParams: [{ commitBy: 'commit_by' }], }) ); this.add( 'route:application', Route.extend({ queryParams: { commitBy: { replace: true, }, }, }) ); return this.visitAndAssert('/').then(() => { let appController = this.getController('application'); this.expectedReplaceURL = '/?commit_by=igor_seb'; this.setAndFlush(appController, 'commitBy', 'igor_seb'); }); } ['@test An explicit replace:false on a changed QP always wins and causes a pushState'](assert) { assert.expect(3); this.add( 'controller:application', Controller.extend({ queryParams: ['alex', 'steely'], alex: 'matchneer', steely: 'dan', }) ); this.add( 'route:application', Route.extend({ queryParams: { alex: { replace: true, }, steely: { replace: false, }, }, }) ); return this.visit('/').then(() => { let appController = this.getController('application'); this.expectedPushURL = '/?alex=wallace&steely=jan'; run(appController, 'setProperties', { alex: 'wallace', steely: 'jan' }); this.expectedPushURL = '/?alex=wallace&steely=fran'; run(appController, 'setProperties', { steely: 'fran' }); this.expectedReplaceURL = '/?alex=sriracha&steely=fran'; run(appController, 'setProperties', { alex: 'sriracha' }); }); } ['@test can opt into full transition by setting refreshModel in route queryParams when transitioning from child to parent']( assert ) { this.addTemplate('parent', '{{outlet}}'); this.addTemplate( 'parent.child', "{{link-to 'Parent' 'parent' (query-params foo='change') id='parent-link'}}" ); this.router.map(function() { this.route('parent', function() { this.route('child'); }); }); let parentModelCount = 0; this.add( 'route:parent', Route.extend({ model() { parentModelCount++; }, queryParams: { foo: { refreshModel: true, }, }, }) ); this.setSingleQPController('parent', 'foo', 'abc'); return this.visit('/parent/child?foo=lol').then(() => { assert.equal(parentModelCount, 1); run(document.getElementById('parent-link'), 'click'); assert.equal(parentModelCount, 2); }); } ["@test Use Ember.get to retrieve query params 'replace' configuration"](assert) { assert.expect(2); this.setSingleQPController('application', 'alex', 'matchneer'); this.add( 'route:application', Route.extend({ queryParams: EmberObject.create({ unknownProperty(/* keyName */) { // We are simulating all qps requiring refresh return { replace: true }; }, }), }) ); return this.visitAndAssert('/').then(() => { let appController = this.getController('application'); this.expectedReplaceURL = '/?alex=wallace'; this.setAndFlush(appController, 'alex', 'wallace'); }); } ['@test can override incoming QP values in setupController'](assert) { assert.expect(3); this.router.map(function() { this.route('about'); }); this.setSingleQPController('index', 'omg', 'lol'); this.add( 'route:index', Route.extend({ setupController(controller) { assert.ok(true, 'setupController called'); controller.set('omg', 'OVERRIDE'); }, actions: { queryParamsDidChange() { assert.ok(false, "queryParamsDidChange shouldn't fire"); }, }, }) ); return this.visitAndAssert('/about').then(() => { this.transitionTo('index'); this.assertCurrentPath('/?omg=OVERRIDE'); }); } ['@test can override incoming QP array values in setupController'](assert) { assert.expect(3); this.router.map(function() { this.route('about'); }); this.setSingleQPController('index', 'omg', ['lol']); this.add( 'route:index', Route.extend({ setupController(controller) { assert.ok(true, 'setupController called'); controller.set('omg', ['OVERRIDE']); }, actions: { queryParamsDidChange() { assert.ok(false, "queryParamsDidChange shouldn't fire"); }, }, }) ); return this.visitAndAssert('/about').then(() => { this.transitionTo('index'); this.assertCurrentPath('/?omg=' + encodeURIComponent(JSON.stringify(['OVERRIDE']))); }); } ['@test URL transitions that remove QPs still register as QP changes'](assert) { assert.expect(2); this.setSingleQPController('index', 'omg', 'lol'); return this.visit('/?omg=borf').then(() => { let indexController = this.getController('index'); assert.equal(indexController.get('omg'), 'borf'); this.transitionTo('/'); assert.equal(indexController.get('omg'), 'lol'); }); } ['@test Subresource naming style is supported'](assert) { assert.expect(5); this.router.map(function() { this.route('abc.def', { path: '/abcdef' }, function() { this.route('zoo'); }); }); this.addTemplate( 'application', "{{link-to 'A' 'abc.def' (query-params foo='123') id='one'}}{{link-to 'B' 'abc.def.zoo' (query-params foo='123' bar='456') id='two'}}{{outlet}}" ); this.setSingleQPController('abc.def', 'foo', 'lol'); this.setSingleQPController('abc.def.zoo', 'bar', 'haha'); return this.visitAndAssert('/').then(() => { assert.equal(this.$('#one').attr('href'), '/abcdef?foo=123'); assert.equal(this.$('#two').attr('href'), '/abcdef/zoo?bar=456&foo=123'); run(this.$('#one'), 'click'); this.assertCurrentPath('/abcdef?foo=123'); run(this.$('#two'), 'click'); this.assertCurrentPath('/abcdef/zoo?bar=456&foo=123'); }); } ['@test transitionTo supports query params']() { this.setSingleQPController('index', 'foo', 'lol'); return this.visitAndAssert('/').then(() => { this.transitionTo({ queryParams: { foo: 'borf' } }); this.assertCurrentPath('/?foo=borf', 'shorthand supported'); this.transitionTo({ queryParams: { 'index:foo': 'blaf' } }); this.assertCurrentPath('/?foo=blaf', 'longform supported'); this.transitionTo({ queryParams: { 'index:foo': false } }); this.assertCurrentPath('/?foo=false', 'longform supported (bool)'); this.transitionTo({ queryParams: { foo: false } }); this.assertCurrentPath('/?foo=false', 'shorhand supported (bool)'); }); } ['@test transitionTo supports query params (multiple)']() { this.add( 'controller:index', Controller.extend({ queryParams: ['foo', 'bar'], foo: 'lol', bar: 'wat', }) ); return this.visitAndAssert('/').then(() => { this.transitionTo({ queryParams: { foo: 'borf' } }); this.assertCurrentPath('/?foo=borf', 'shorthand supported'); this.transitionTo({ queryParams: { 'index:foo': 'blaf' } }); this.assertCurrentPath('/?foo=blaf', 'longform supported'); this.transitionTo({ queryParams: { 'index:foo': false } }); this.assertCurrentPath('/?foo=false', 'longform supported (bool)'); this.transitionTo({ queryParams: { foo: false } }); this.assertCurrentPath('/?foo=false', 'shorhand supported (bool)'); }); } ["@test setting controller QP to empty string doesn't generate null in URL"](assert) { assert.expect(1); this.setSingleQPController('index', 'foo', '123'); return this.visit('/').then(() => { let controller = this.getController('index'); this.expectedPushURL = '/?foo='; this.setAndFlush(controller, 'foo', ''); }); } ["@test setting QP to empty string doesn't generate null in URL"](assert) { assert.expect(1); this.add( 'route:index', Route.extend({ queryParams: { foo: { defaultValue: '123', }, }, }) ); return this.visit('/').then(() => { let controller = this.getController('index'); this.expectedPushURL = '/?foo='; this.setAndFlush(controller, 'foo', ''); }); } ['@test A default boolean value deserializes QPs as booleans rather than strings'](assert) { assert.expect(3); this.setSingleQPController('index', 'foo', false); this.add( 'route:index', Route.extend({ model(params) { assert.equal(params.foo, true, 'model hook received foo as boolean true'); }, }) ); return this.visit('/?foo=true').then(() => { let controller = this.getController('index'); assert.equal(controller.get('foo'), true); this.transitionTo('/?foo=false'); assert.equal(controller.get('foo'), false); }); } ['@test Query param without value are empty string'](assert) { assert.expect(1); this.add( 'controller:index', Controller.extend({ queryParams: ['foo'], foo: '', }) ); return this.visit('/?foo=').then(() => { let controller = this.getController('index'); assert.equal(controller.get('foo'), ''); }); } ['@test Array query params can be set'](assert) { assert.expect(2); this.router.map(function() { this.route('home', { path: '/' }); }); this.setSingleQPController('home', 'foo', []); return this.visit('/').then(() => { let controller = this.getController('home'); this.setAndFlush(controller, 'foo', [1, 2]); this.assertCurrentPath('/?foo=%5B1%2C2%5D'); this.setAndFlush(controller, 'foo', [3, 4]); this.assertCurrentPath('/?foo=%5B3%2C4%5D'); }); } ['@test (de)serialization: arrays'](assert) { assert.expect(4); this.setSingleQPController('index', 'foo', [1]); return this.visitAndAssert('/').then(() => { this.transitionTo({ queryParams: { foo: [2, 3] } }); this.assertCurrentPath('/?foo=%5B2%2C3%5D', 'shorthand supported'); this.transitionTo({ queryParams: { 'index:foo': [4, 5] } }); this.assertCurrentPath('/?foo=%5B4%2C5%5D', 'longform supported'); this.transitionTo({ queryParams: { foo: [] } }); this.assertCurrentPath('/?foo=%5B%5D', 'longform supported'); }); } ['@test Url with array query param sets controller property to array'](assert) { assert.expect(1); this.setSingleQPController('index', 'foo', ''); return this.visit('/?foo[]=1&foo[]=2&foo[]=3').then(() => { let controller = this.getController('index'); assert.deepEqual(controller.get('foo'), ['1', '2', '3']); }); } ['@test Array query params can be pushed/popped'](assert) { assert.expect(17); this.router.map(function() { this.route('home', { path: '/' }); }); this.setSingleQPController('home', 'foo', emberA()); return this.visitAndAssert('/').then(() => { let controller = this.getController('home'); run(controller.foo, 'pushObject', 1); this.assertCurrentPath('/?foo=%5B1%5D'); assert.deepEqual(controller.foo, [1]); run(controller.foo, 'popObject'); this.assertCurrentPath('/'); assert.deepEqual(controller.foo, []); run(controller.foo, 'pushObject', 1); this.assertCurrentPath('/?foo=%5B1%5D'); assert.deepEqual(controller.foo, [1]); run(controller.foo, 'popObject'); this.assertCurrentPath('/'); assert.deepEqual(controller.foo, []); run(controller.foo, 'pushObject', 1); this.assertCurrentPath('/?foo=%5B1%5D'); assert.deepEqual(controller.foo, [1]); run(controller.foo, 'pushObject', 2); this.assertCurrentPath('/?foo=%5B1%2C2%5D'); assert.deepEqual(controller.foo, [1, 2]); run(controller.foo, 'popObject'); this.assertCurrentPath('/?foo=%5B1%5D'); assert.deepEqual(controller.foo, [1]); run(controller.foo, 'unshiftObject', 'lol'); this.assertCurrentPath('/?foo=%5B%22lol%22%2C1%5D'); assert.deepEqual(controller.foo, ['lol', 1]); }); } ["@test Overwriting with array with same content shouldn't refire update"](assert) { assert.expect(4); this.router.map(function() { this.route('home', { path: '/' }); }); let modelCount = 0; this.add( 'route:home', Route.extend({ model() { modelCount++; }, }) ); this.setSingleQPController('home', 'foo', emberA([1])); return this.visitAndAssert('/').then(() => { assert.equal(modelCount, 1); let controller = this.getController('home'); this.setAndFlush(controller, 'model', emberA([1])); assert.equal(modelCount, 1); this.assertCurrentPath('/'); }); } ['@test Defaulting to params hash as the model should not result in that params object being watched']( assert ) { assert.expect(1); this.router.map(function() { this.route('other'); }); // This causes the params hash, which is returned as a route's // model if no other model could be resolved given the provided // params (and no custom model hook was defined), to be watched, // unless we return a copy of the params hash. this.setSingleQPController('application', 'woot', 'wat'); this.add( 'route:other', Route.extend({ model(p, trans) { let m = peekMeta(trans.params.application); assert.ok(m === undefined, "A meta object isn't constructed for this params POJO"); }, }) ); return this.visit('/').then(() => { this.transitionTo('other'); }); } ['@test Setting bound query param property to null or undefined does not serialize to url']( assert ) { assert.expect(9); this.router.map(function() { this.route('home'); }); this.setSingleQPController('home', 'foo', [1, 2]); return this.visitAndAssert('/home').then(() => { var controller = this.getController('home'); assert.deepEqual(controller.get('foo'), [1, 2]); this.assertCurrentPath('/home'); this.setAndFlush(controller, 'foo', emberA([1, 3])); this.assertCurrentPath('/home?foo=%5B1%2C3%5D'); return this.transitionTo('/home').then(() => { assert.deepEqual(controller.get('foo'), [1, 2]); this.assertCurrentPath('/home'); this.setAndFlush(controller, 'foo', null); this.assertCurrentPath('/home', 'Setting property to null'); this.setAndFlush(controller, 'foo', emberA([1, 3])); this.assertCurrentPath('/home?foo=%5B1%2C3%5D'); this.setAndFlush(controller, 'foo', undefined); this.assertCurrentPath('/home', 'Setting property to undefined'); }); }); } ['@test {{link-to}} with null or undefined QPs does not get serialized into url'](assert) { assert.expect(3); this.addTemplate( 'home', "{{link-to 'Home' 'home' (query-params foo=nullValue) id='null-link'}}{{link-to 'Home' 'home' (query-params foo=undefinedValue) id='undefined-link'}}" ); this.router.map(function() { this.route('home'); }); this.setSingleQPController('home', 'foo', [], { nullValue: null, undefinedValue: undefined, }); return this.visitAndAssert('/home').then(() => { assert.equal(this.$('#null-link').attr('href'), '/home'); assert.equal(this.$('#undefined-link').attr('href'), '/home'); }); } ["@test A child of a resource route still defaults to parent route's model even if the child route has a query param"]( assert ) { assert.expect(2); this.setSingleQPController('index', 'woot', undefined, { woot: undefined, }); this.add( 'route:application', Route.extend({ model(/* p, trans */) { return { woot: true }; }, }) ); this.add( 'route:index', Route.extend({ setupController(controller, model) { assert.deepEqual( model, { woot: true }, 'index route inherited model route from parent route' ); }, }) ); return this.visitAndAssert('/'); } ['@test opting into replace does not affect transitions between routes'](assert) { assert.expect(5); this.addTemplate( 'application', "{{link-to 'Foo' 'foo' id='foo-link'}}{{link-to 'Bar' 'bar' id='bar-no-qp-link'}}{{link-to 'Bar' 'bar' (query-params raytiley='isthebest') id='bar-link'}}{{outlet}}" ); this.router.map(function() { this.route('foo'); this.route('bar'); }); this.setSingleQPController('bar', 'raytiley', 'israd'); this.add( 'route:bar', Route.extend({ queryParams: { raytiley: { replace: true, }, }, }) ); return this.visit('/').then(() => { let controller = this.getController('bar'); this.expectedPushURL = '/foo'; run(document.getElementById('foo-link'), 'click'); this.expectedPushURL = '/bar'; run(document.getElementById('bar-no-qp-link'), 'click'); this.expectedReplaceURL = '/bar?raytiley=woot'; this.setAndFlush(controller, 'raytiley', 'woot'); this.expectedPushURL = '/foo'; run(document.getElementById('foo-link'), 'click'); this.expectedPushURL = '/bar?raytiley=isthebest'; run(document.getElementById('bar-link'), 'click'); }); } ["@test undefined isn't serialized or deserialized into a string"](assert) { assert.expect(4); this.router.map(function() { this.route('example'); }); this.addTemplate( 'application', "{{link-to 'Example' 'example' (query-params foo=undefined) id='the-link'}}" ); this.setSingleQPController('example', 'foo', undefined, { foo: undefined, }); this.add( 'route:example', Route.extend({ model(params) { assert.deepEqual(params, { foo: undefined }); }, }) ); return this.visitAndAssert('/').then(() => { assert.equal( this.$('#the-link').attr('href'), '/example', 'renders without undefined qp serialized' ); return this.transitionTo('example', { queryParams: { foo: undefined }, }).then(() => { this.assertCurrentPath('/example'); }); }); } ['@test when refreshModel is true and loading hook is undefined, model hook will rerun when QPs change even if previous did not finish']() { return this.refreshModelWhileLoadingTest(); } ['@test when refreshModel is true and loading hook returns false, model hook will rerun when QPs change even if previous did not finish']() { return this.refreshModelWhileLoadingTest(false); } ['@test when refreshModel is true and loading hook returns true, model hook will rerun when QPs change even if previous did not finish']() { return this.refreshModelWhileLoadingTest(true); } ["@test warn user that Route's queryParams configuration must be an Object, not an Array"]( assert ) { assert.expect(1); this.add( 'route:application', Route.extend({ queryParams: [{ commitBy: { replace: true } }], }) ); expectAssertion(() => { this.visit('/'); }, 'You passed in `[{"commitBy":{"replace":true}}]` as the value for `queryParams` but `queryParams` cannot be an Array'); } ['@test handle route names that clash with Object.prototype properties'](assert) { assert.expect(1); this.router.map(function() { this.route('constructor'); }); this.add( 'route:constructor', Route.extend({ queryParams: { foo: { defaultValue: '123', }, }, }) ); return this.visit('/').then(() => { this.transitionTo('constructor', { queryParams: { foo: '999' } }); let controller = this.getController('constructor'); assert.equal(get(controller, 'foo'), '999'); }); } } );