Skip to content
Open
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
11 changes: 11 additions & 0 deletions packages/@ember/routing/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2114,6 +2114,17 @@ Route.reopen({
let options = this._optionsForQueryParam(qp);
assert('options exists', options && typeof options === 'object');
if ((get(options, 'refreshModel') as boolean) && this._router.currentState) {
// `changed` is computed from router state query params, where default values
// may have been pruned during finalization. A later transition can reintroduce
// the same serialized value via sticky QP hydration or URL parsing, making the
// QP appear changed even though its finalized value is unchanged. Only refresh
// the model when the serialized value differs from the last finalized value.
if (change in changed) {
let newSerializedValue = (changed as Record<string, unknown>)[change];
if (newSerializedValue === qp.serializedValue) {
continue;
}
}
this.refresh();
break;
}
Expand Down
116 changes: 116 additions & 0 deletions packages/ember/tests/routing/router_service_test/transitionTo_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -439,5 +439,121 @@ moduleFor(
assert.equal(this.routerService.get('currentURL'), '/?url_sort=a');
});
}

async ['@test RouterService#transitionTo with route name and unchanged application query params does not abort or re-run application model hook'](
assert
) {
assert.expect(3);

let applicationModelHookCallCount = 0;

this.add(
'route:application',
class extends Route {
queryParams = {
filter: {
refreshModel: true,
},
sort: {
refreshModel: true,
},
};

model() {
applicationModelHookCallCount++;
return {};
}
}
);

this.add(
'controller:application',
Controller.extend({
queryParams: ['filter', 'sort'],
filter: '',
sort: '',
})
);

await this.visit('/?filter=&sort=');

assert.equal(
applicationModelHookCallCount,
1,
'application model hook called on initial visit'
);

await this.routerService.transitionTo('parent.child');

assert.equal(
this.routerService.get('currentRouteName'),
'parent.child',
'transitioned to child route'
);

assert.equal(
applicationModelHookCallCount,
1,
'application model hook not re-run when transitioning with unchanged sticky query params'
);
}

async ['@test RouterService#transitionTo with URL string and unchanged application query params does not abort or re-run application model hook'](
assert
) {
assert.expect(3);

let applicationModelHookCallCount = 0;

this.add(
'route:application',
class extends Route {
queryParams = {
filter: {
refreshModel: true,
},
sort: {
refreshModel: true,
},
};

model() {
applicationModelHookCallCount++;
return {};
}
}
);

this.add(
'controller:application',
Controller.extend({
queryParams: ['filter', 'sort'],
filter: '',
sort: '',
})
);

await this.visit('/?filter=&sort=');

assert.equal(
applicationModelHookCallCount,
1,
'application model hook called on initial visit'
);

await this.routerService.transitionTo('/child?filter=&sort=');

assert.equal(
this.routerService.get('currentRouteName'),
'parent.child',
'transitioned to child route'
);

assert.equal(
applicationModelHookCallCount,
1,
'application model hook not re-run when transitioning with unchanged query params'
);
}
}
);
Loading