I can't get mocha working with Ember due to the fact that it fails when a test of the following nature is executed:
describe('Location Panel', function () {
beforeEach(function () {
App.reset();
visit('/map/41.76721,-72.66907');
});
it('Have proper address', function () {
var $title = find('.panel-header h2');
expect($title).to.have.text('476 Columbus Blvd, Hartford');
});
});
Basically it can't find any of the DOM elements, because it runs the test before the route has finished loading.. The same happens if I visit from within the test, and use andThen, etc..
Here's a jsbin for debugging.
Edit
In the jsbin, I'm using a mocked ajax call, but in my tests the ajax calls are real. I am using Ember.$.ajax wrapped in the following:
function ajax (url, options) {
return new Ember.RSVP.Promise(function (resolve, reject) {
options = options || {};
options.url = url;
options.success = function (data) {
Ember.run(null, resolve, data);
};
options.error = function (jqxhr, status, something) {
Ember.run(null, reject, arguments);
};
Ember.$.ajax(options);
});
}
Should I be using Ember.run.later as well?
You should use Ember.run.later instead of setTimeout so that the wait helper knows that it should wait.
Alternatively you can use Ember.test.registerWaiter though I don't think you need it here.
Updated JSBIN: http://emberjs.jsbin.com/gahe/1/edit
Related
Quick context:
The user has a problem with the network and I need to repeat requests to the server to continue loading app instead of showing an error.
Existing code in the app:
ajaxPostWithRootData = function (url, rootData) {
//prepare request
var request = decoratorFunction(
$.ajax({
//standard data required to send request
})
)
.done(function (result) {
// normal flow
})
.fail(function (e) {
//I want to repeat call after 1 second
});
return request;
};
Example call:
return ajaxPostWithRootData(url, data)
.done(function (result) {
//do some stuff
})
.fail(function (e) {
//hide loading, show error message
});
};
I thought to write in fail() something like that:
//repeatOnFail is a new function's param to prevent infinite loop
if (shouldRepeatOnConnectionProblem(repeatOnFail, e)) {
setTimeout(() => ajaxPostWithRootData(url, rootData, false), 1000);
}
Unfortunately JS works asynchronously here and even I get success response in second request, it's too late because app executes code from second fail().
Do you know how to change this code to "back" to the correct flow when I got a success response after the second call?
My knowledge about JS is poor, so even I know have an idea how to fix it, I don't know how to implement a solution ;/
If I understand correctly, you would like to invoke $.ajax({..}) with a fixed configuration and, if that first invocation fails, immediately retry the $.ajax({..}) request with the same configuration, in which case the following changes to ajaxPostWithRootData should achieve what you require:
var ajaxPostWithRootData = function(url, rootData) {
// Define reusable function that sets up and invokes
// ajax request
var doRequest = function() {
return decoratorFunction(
$.ajax({
//standard data required to send request
}))
}
// Return a promise that is controlled by this deferred behavior
return $.Deferred(function(deferred) {
doRequest()
.fail(function (){
doRequest ()
.fail(function () {
// On fail after second attempt, throw error
// to external app flow
deferred.reject();
}).done(function (result){
// If second attempt succeed, continue external
// app flow
deferred.resolve(result);
});
})
.done(function (result){
// If first attempt succeed, continue external
// app flow
deferred.resolve(result);
});
}).promise();
};
There updates should also work nicely with your external code and preserve the overall flow of events that you're after.
Hope that helps!
I'm facing an issue with deferred usage where 2 nested function that should wait for each other actually run in the wrong order silently.
I cant' figure out where I mix return promise.
So here is what I try to achieve. In a mobile Cordova app, when user enter the Game view, I got a function that download question in WebSql, and I want then to retrieve one question, and then my slider function load the content.
So I nested the getQuestion function in the .done() event.
router.addRoute('game', function () {
'use strict';
//Reload Question List when User enter the Game view.
questionService.initialize().done(
//Now we got question, initialize the Game View
questionService.getQuestion().done(
function (data) {
console.log(data);
slider.slidePage(new GameView(data).render().$el);
})
);
Here is how I use the $.Deferred() in both function. First I declare my $.Deferred() and at the end of the function I return the promise.
But my getQuestion() in code below does not wait for the initialize() function to end before start.
Where did I mixed up my promise return?
var getQuestions = function(param) {
var deferred = $.Deferred();
param = param;
$.ajax({
type: 'POST',
url: 'myserver',
data: {
region: uRegion
},
success: function(value, status) {
//do something with value
this.db = window.openDatabase('database details');
this.db.transaction(function(tx) {
storeQuestion(tx);
}, function(error) {
deferred.reject('Transaction error: ' + error);
}, function() {
//Transaction success
deferred.resolve();
});
},
error: function(textStatus, exception) {}
});
return deferred.promise();
};
You are not passing the success callback handler. As per current implementation your are invoking getQuestion() immediately.
Use anonymous function, rest its fine
//Reload Question List when User enter the Game view.
questionService.initialize().done(function(){
//Now we got question, initialize the Game View
questionService.getQuestion().done(
function (data) {
console.log(data);
slider.slidePage(new GameView(data).render().$el);
})
});
I am just getting into unit testing and currently exploring the Mocha, Chai, Sinon setup, testing this in a browser.
I have a javascript block that makes an ajax call to the server like so
PromptBase.prototype.fetchAndSetTemplateString = function() {
var deferred = $.Deferred();
$.ajax({
url: "/someurl",
type: "GET"
})
.done(function(response) {
if (response.status.toLowerCase() === "success") {
deferred.resolve(response.templateString);
}
});
return deferred.promise();
};
PromptBase.prototype.fetchPromptTemplate = function() {
var promise = this.fetchAndSetTemplateString();
$.when(promise).done(function(promptHtml) {
//set the templateString to promptHtml here
});
};
//Register a custom event on document to fire ajax request.
$(document).on("customevent", function(event) {
promptInstance.fetchPromptTemplate(promptParams.fetchTemplateOnEvent);
});
Now I have a test case that looks like this using fakeServer
describe("TemplateFetch", function() {
before(function() {
this.server = sinon.fakeServer.create(); // and other setup
});
after(function() {
this.server.restore(); // and other clean up
});
it("should fetch template string from server, when fetchTemplateEvent is fired", function() {
var expectedTemplateString = "templateStringFromServer";
var templateAjaxUrl = '/someurl';
//faking a server response
this.server.respondWith("GET", templateAjaxUrl,
[200, {"Content-Type": "application/json"},
'{"status": "success", "templateString": "'+ expectedTemplateString +'"}']);
// Now trigger even that fetches the template
$(document).trigger("customevent");
// This calls the ajax done function, which resolves the promise
this.server.respond();
//debugger shows that templateString is set here
expect(this.instance.templateString).to.eqaul("something");
});
});
However my test outputs the timeout error
Error: timeout of 2000ms exceeded. Ensure the done() callback is being called in this test.
So the fakeServer.respond() call ensures that the ajax->done function is called resulting in the promise being resolved, And when I debug I can see that the expect test should match the two strings. How can I fix the time out issue here? I have also tried adding a done callback but to no avail.
Any input is appreciated. Thanks for reading.
I am facing the following synchronization issue. I wouldn't be surprised if it has a simple solution/workaround. The BuildMenu() function is called from another block of code and it calls the CreateMenuData() which makes a request to a service which return some data. The problem is that since it is an async call to the service when the data variable is being used it is undefined. I have provided the js log that also shows my point.
BuildMenu: function () {
console.log("before call");
var data=this.CreateMenuData();
console.log("after call");
//Doing more stuff with data that fail.
}
CreateMenuData: function () {
console.log("func starts");
data = [];
dojo.forEach(config.layerlist, function (collection, colindex) {
var layersRequest = esriRequest({
url: collection.url,
handleAs: "json",
});
layersRequest.then(
function (response) {
dojo.forEach(response.records, function (value, key) {
console.log(key);
data.push(key);
});
}, function (error) {
});
});
console.log("func ends");
return data;
}
Console log writes:
before call
func starts
func ends
after call
0
1
2
3
4
FYI: using anything "dojo." is deprecated. Make sure you are pulling all the modules you need in "require".
Ken has pointed you the right direction, go through the link and get familiarized with the asynchronous requests.
However, I'd like to point out that you are not handling only one async request, but potentionally there might be more of them of which you are trying to fill the "data" with. To make sure you handle the results only when all of the requests are finished, you should use "dojo/promise/all".
CreateMenuData: function (callback) {
console.log("func starts");
requests = [];
data = [];
var scope = this;
require(["dojo/_base/lang", "dojo/base/array", "dojo/promise/all"], function(lang, array, all){
array.forEach(config.layerlist, function (collection, colindex) {
var promise = esriRequest({
url: collection.url,
handleAs: "json",
});
requests.push(promise);
});
// Now use the dojo/promise/all object
all(requests).then(function(responses){
// Check for all the responses and add whatever you need to the data object.
...
// once it's all done, apply the callback. watch the scope!
if (typeof callback == "function")
callback.apply(scope, data);
});
});
}
so now you have that method ready, call it
BuildMenu: function () {
console.log("before call");
var dataCallback = function(data){
// do whatever you need to do with the data or call other function that handles them.
}
this.CreateMenuData(dataCallback);
}
I've followed this answer on how to abort all AJAX requests on page redirection. It basically uses ajaxSend to add the xhr request object to an array and then ajaxComplete to remove that object from the array and then if the page is redirected it will loop through that array and abort each request that the browser is still waiting for.
This works fine for all normal AJAX requests using $.ajax({}). However, if a Backbone AJAX request is called like model.save() then the window.onbeforeunload function gets executed before anything happens and causes the model.save() to be aborted before it is event sent to the server.
Anyone have any ideas on how to fix this or why the window.onbeforeunload function is getting called?
Here is the code that I got from the linked answer:
var xhrPool = [];
$.xhrPool = {};
$.xhrPool.abortAll = function () {
$.each(xhrPool, function (idx, jqXHR) {
jqXHR.abort();
});
xhrPool.length = 0;
};
$(document).ajaxSend(function (e, jqXHR, options) {
xhrPool.push(jqXHR);
});
$(document).ajaxComplete(function (e, jqXHR, options) {
xhrPool = $.grep(xhrPool, function (x) {
return x != jqXHR;
});
});
window.onbeforeunload = function () {
$.xhrPool.abortAll();
};
did you tried using the wait flag at the time to save?
like this
model.save(data,{wait:true});
Hope this helps.