Why isn't this rejected RSVP promise not calling catch()? - ember.js

In my Ember RSVP code, I'm not able to get any error handlers called, even though I see that my 'reject' function is executing successfully. Code:
var promise = new Promise(function(resolve, reject) {
doStuff().then(function() {
resolve(1);
}, function() {
console.log('rejected!');
reject('This has been rejected!');
});
});
var allPromises = [promise]; // Real code has more than one; just one for demonstration.
Ember.RSVP.all(allPromises)
.then(
function() {
console.log('I am called!');
},
function() { console.log('I am never called!');})
.catch(function(err) {
console.log('I am never called, either.');
});
However, I do see the 'rejected!' message in the console. What have I done wrong here? Shouldn't the catch() fire since the reject() worked properly?
DEBUG: Ember : 1.9.0-beta.1
DEBUG: Ember Data : 1.0.0-beta.12
DEBUG: jQuery : 1.9.1
Docs: https://github.com/tildeio/rsvp.js
They state 'Sometimes you might want to work with many promises at once. If you pass an array of promises to the all() method it will return a new promise that will be fulfilled when all of the promises in the array have been fulfilled; or rejected immediately if any promise in the array is rejected.'

You are catching the error in your onReject handler. If you want your catch to run you will have to either not supply a onReject handler or throw an error in the reject handler:
var promise = new Promise(function(resolve, reject) {
reject("")
});
var allPromises = [promise];
// Alt 1 - Dont supply a onRejected handler
Ember.RSVP.all(allPromises)
.then(function() {
console.log('I am called!');
}).catch(function(err) {
console.log('I am never called, either.');
});
// Alt 2 - Throw error in onRejected handler
Ember.RSVP.all(allPromises)
.then(function() {
console.log('I am called!');
}, function () {
console.log('I am never called!');
throw new Error("boom");
}).catch(function(err) {
console.log('I am never called, either.');
});
Alt 1 will print:
I am never called, either.
Alt 2 will print:
I am never called!
I am never called, either.

Related

How to find out why a function passed the test in Jest?

Is there a way to show why tested function can pass?
When I follow Jest test Async Code section
It says:
Be sure to return the promise - if you omit this return statement,
your test will complete before fetchData completes.
And my code is:
function add1(n) {
return new Promise((res, rej)=>{
res(n+1)
})
}
test('should add 1', function() {
expect.assertions(1)
//////////////////////////// I did not use RETURN here
add1(10).then((n11)=>{
expect(n11).toBe(11)
})
});
This still passed, I want to know how this can pass?
The Promise resolves immediately and synchronously so the then gets called immediately and the expect has run before the test finishes. (then callbacks run immediately if the Promise has already resolved)
If you use setTimeout to keep the Promise from resolving immediately and synchronously then the test fails unless you return the Promise:
function add1(n) {
return new Promise((res, rej) => {
setTimeout(() => { res(n + 1) }, 0); // use setTimeout
})
}
test('should add 1', function () {
expect.assertions(1)
// PASSES only if Promise is returned
return add1(10).then((n11) => {
expect(n11).toBe(11);
})
});

Ember.js Testing async action in controller

I've a controller with an action that invoke a method that do some async stuff and return a promise.
export default Ember.Controller.extend({
_upload: function() {
// return a promise
},
actions: {
save: function(item) {
this._upload(item).then(function(response) {
// Handle success
}, function(error) {
// Handle error
}
}
}
});
I would like to unit test the code under Handle success and Handle error.
In my unit test I've mocked the _uploadMethod using
controller.set("_upload", function() {
return new Ember.RSVP.Promise(function(resolve) {
resolve({name: "image1"});
});
});
And then I invoke the action and assert that the success handler has done is job
controller.send("save", "item");
assert.equal(controller.get("selected.item"), "item");
The problem is that the assertion fails because it's run before the promise is resolved and all the stuff in success handler is completed.
How can I wait the promise to resolve before the assertion is checked?
What if you try this instead:
controller.set("_upload", function() {
const promise = new Ember.RSVP.Promise(function(resolve) {
resolve({name: "image1"});
});
promise.then(() => Ember.run.next(() => {
assert.equal(controller.get("selected.item"), "item");
}));
return promise;
});
controller.send("save", "item");
A bit hacky way, but it might work.
To test async methods, you can use the test helper waitUntil to wait for the expected return of the method, like the code below.
controller.send('changeStepAsyncActionExample');
await waitUntil(() => {
return 'what you expect to the Promise resolve';
}, { timeout: 4000, timeoutMessage: 'Your timeout message' });
// If not timeout, the helper below will be executed
assert.ok(true, 'The promise was executed correctly');

Ember promise isPending not firing if thenable

I want to show a loading spinner in an ember component when I load some data from the store. If there is an error from the store, I want to handle it appropriately.
Here is a snippet of my current code:
cachedCampaignDataIsPending: computed.readOnly('fetchCachedCampaignData.isPending'),
fetchCachedCampaignData: computed('campaign', function() {
return this.get('store').findRecord('cachedCampaignMetric').then( (cachedMetrics) => {
this.set('cachedCampaignData', cachedMetrics);
}, () => {
this.set('errors', true);
});
}),
This will properly set the errors to true if the server responds with an error, however, the cachedCampaignDataIsPending is not acting properly. It does not get set to true.
However, if I rewrite my code to make the computed property not be thenable, then it does fire propertly.
fetchCachedCampaignData: computed('campaign', function() {
return this.get('store').findRecord('cachedCampaignMetric');
}),
Anyone know the reason why, or how to make it fire properly, using the then/catch logic?
Thanks.
Update
I found that this works for me and gives me what I need.
cachedCampaignDataIsPending: computed.readOnly('fetchCachedCampaignData.isPending'),
fetchCachedCampaignData: computed('campaign', function() {
let promise = this.get('store').findRecord('cachedCampaignMetric');
promise.then( (cachedMetrics) => {
this.set('cachedCampaignData', cachedMetrics);
}, () => {
this.set('errors', true);
});
return promise;
}),

Backbone tests returning timeout

I am getting a timeout of 2000ms exceeded message when running the following tests for my Backbone application.
How can I get this test to pass?
I am also trying to listen for the event's being triggered when calling more. How could this be tested?
describe("Foo.Collection.Items/Discover", function () {
describe("More method", function () {
beforeEach(function () {
this.server = sinon.fakeServer.create();
this.hasLength = sinon.spy(Foo.View.ItemFeed.prototype, "toggleFeedback");
this.noLength = sinon.spy(Foo.View.ItemFeed.prototype, "stopLazyload");
this.item1 = new Foo.Model.item({
description: "A swell minions movie!",
for_sale: false,
title: "Minions: Goldfinger",
link: "/discover",
'private': false,
'main_image': false
});
this.item2 = new Foo.Model.item({
description: "A round pot",
for_sale: true,
title: "Pot",
link: "/discover",
'private': true,
'main_image': true
});
this.itemsDiscover = new Foo.Collection.Items([this.item1, this.item2], {url: "/discover"});
});
afterEach(function () {
this.server.restore();
this.hasLength.restore();
this.noLength.restore();
});
it("should fetch items and trigger moreFetched", function (done) {
this.server.respondWith('GET', "/discover", [
200,
{"Content-type": "application/json"},
JSON.stringify([this.item1, this.item2])
]);
this.itemsDiscover.once("add", function () {
expect(this.itemsDiscover).to.have.length(2);
expect(this.hasLength).to.be.calledOnce();
done();
});
this.itemsDiscover.more();
});
});
});
The part of the Backbone Collection I am trying to test:
more: function () {
var collection = this;
this.fetch({
success: function (collection, response) {
if (response.length) {
collection.trigger('moreFetched');
} else {
collection.trigger('emptyFetched');
}
},
reset: false,
remove: false
});
}
I guess that you have an exception that avoids to call done, try
this.itemsDiscover.once("add", function () {
try {
expect(this.itemsDiscover).to.have.length(2);
expect(this.hasLength).to.be.calledOnce();
done();
} catch(e) {
done(e);
}
});
If you get an error then post it, because I think what it is.
I think I see what's going on: your test method is dependent on the add event being triggered in order to get into the callback that verifies the data. However, the way you've set up this.itemsDiscover, it contains this.item1 and this.item2 already:
....
this.itemsDiscover = new Foo.Collection.Items([this.item1, this.item2], {url: "/discover"});
Then, your mocking out of the URL will also return the string version of an array containing this.item1 and this.item2:
...
this.server.respondWith('GET', "/discover", [
200,
{"Content-type": "application/json"},
JSON.stringify([this.item1, this.item2])
]);
...
Here's the kicker: the add event will never be fired by the collection, because the two items returned already exist in the collection. D'oh.
I suggest changing the test to wait for a sync event instead, which is always fired after a successful fetch:
this.itemsDiscover.once("sync", function () {
expect(this.itemsDiscover).to.have.length(2);
expect(this.hasLength).to.be.calledOnce();
done();
});
I expect that will get your test code running.

Rejecting / Resolving a promise from outside its body

I have a need to reject a promise from outside its body, to handle the case of the user that wanted to cancel the action.
Here, I need to start several uploads at the same time, by calling #start on every queued uploads.
The class that manages the uploads queue then stores all the promises and uses Ember.RSVP.all to handle when all the promises have resolved or one has rejected. This works fine.
Now, I would like to cancel the upload
App.Upload = Ember.Object.extend({
start: function() {
var self = this;
return new Ember.RSVP.Promise(function(resolve, reject) {
self.startUpload() // Async upload with jQuery
.then(
function(resp) { resolve(resp) },
function(error) { reject(error) }
);
});
},
cancel: function() {
this.get('currentUpload').cancel() // Works, and cancels the upload
// Would like to reject the promise here
},
startUpload: function() {
return this.set('currentUpload', /* some jqXHR that i build to upload */)
}
});
I have thought of many ways to handle it, but I don't found any method like myPromise.reject(reason).
So what I did, is to store the reject function in the Upload instance and call it from my cancel method, like this :
App.Upload = Ember.Object.extend({
start: function() {
var self = this;
return new Ember.RSVP.Promise(function(resolve, reject) {
/* Store it here */
self.set('rejectUpload', reject);
/* ------------- */
self.startUpload() // Async upload with jQuery
.then(
function(resp) { resolve(resp) },
function(error) { reject(error) }
);
});
},
cancel: function() {
this.get('currentUpload').cancel() // Works, and cancels the upload
/* Reject the promise here */
var reject;
if (reject = this.get('rejectUpload')) reject();
/* ----------------------- */
},
startUpload: function() {
return this.set('currentUpload', /* some jqXHR that i build to upload */)
}
});
This sound a bit dirty to me, and I'd like to know if there was a better way to make this.
Thanks for your time !
var deferred = Ember.RSVP.defer();
deferred.resolve("Success!");
deferred.reject("End of the world");
To access the promise (for thening etc)
deferred.promise.then(function(){
console.log('all good');
},function(){
console.log('all bad');
});