I've searched all over and it appears this error is due to not using asyncTest properly. However, per the documentation, it appears that I am doing it correctly. I'm guessing I'm missing a small detail somewhere and need an extra pair of eyes...
I'm trying to test some code that makes an ajax request to get a page and then loads it in a lightbox. lightbox-content does not show up in the DOM until after the ajax call has completed and can be displayed. So, I can only check for it in my onComplete call back, which is where I have my test to see if it loaded it correctly.
Here is my code:
asyncTest('mytest', 1, function() {
utils.lightbox.show('/login', {
onComplete: function() {
ok($('#lighbox-content').is(':visible'), 'Lightbox loaded the /login page.');
start();
}
});
});
I get the error:
Uncaught Error: assertion outside test context, was at HTMLDivElement.window.utils
Can anyone see where I'm going wrong?
I agree that your code matches the documentation as far as I can tell.
Update
Even though the documentation doesn't show it, I wonder if you must tell QUnit to stop at some point so it knows to wait after the test function returns. I would think that QUnit assumes this since it's an async test, but it's worth a shot.
asyncTest('mytest', 1, function() {
stop();
...
});
I've been using Sinon.JS to avoid making the AJAX calls in the first place. This has three immediate benefits:
I don't depend on a server to respond to the requests.
I can specify different results for each test.
The tests run much faster.
The mocking can be done at the XMLHttpRequest level or on the jQuery method and is quite easy. Here's an example from one of my tests:
module("geo", {
setup: function () {
this.server = sinon.fakeServer.create();
},
teardown: function () {
this.server.restore();
}
}
test("returns detected ZIP code", function () {
this.server.respondWith("/geo/detect-zip-from-ip",
[ 200, { "Content-Type": "text/html" }, '90210' ]);
geo.detectZip(function (zip) {
assertThat(zip, is('90210'));
});
this.server.respond();
});
I have found a solution for my case, hope your problem has the same source.
Explaining in words:
I have a complicated asynchronous test
I have delayed events, and there are ok and equal assertions inside
Of course, all this is wrapped inside asyncTest
But, when the test is "completed" and I call start(), the event handlers remain there
After calling start(), all further calls of ok inside that asyncTest become illegal
And throw exceptions
I wonder what happens if the number in expect(in your example it's the second parameter) is exceeded. The same exception?
Explaining in code:
asyncTest('mytest', /*1,*/ function() {
function imgLoadedOrFailed (result) {
clearTimeout(imageTimeToLive);
img.off();
ok(result, 'Image in carousel pane has been loaded');
}
var imageTimeToLive = setTimeout(
imgLoadedOrFailed.bind(this, false),
5000),
img = panes[index].find('img:first');
if (img) {
img.on('load', imgLoadedOrFailed.bind(this, true));
img.on('error', imgLoadedOrFailed.bind(this, false));
}
});
// at some point I call: start();
In this example, when I "finish" the test calling start(), the onload and onerror events can still happen.
Related
I load a table into the document and want to bind a function of footbale to it. But whatever I try (ready,load), I cannot get an onload event to do the job. My workaround is a timeout. That is not pretty. But it proofs that the code has no errors.
<body>
<div id="table_wrapper"></div>
</body>
<script>
$("#stable_wrapper").load("rss2table.php"); // <table id="termine">
setTimeout(() => {
$('#termine').footable({
components: {
filtering: FooTable.MyFiltering
}
});
}, 1000); // min. timeout to run propper
</script>
When I try this
$('#termine tbody').ready(function() {
// Run code
console.log("ready");
$('#termine').footable({
components: {
filtering: FooTable.MyFiltering
}
});
});
I get "ready" in the console, but the rest won't work.
Any ideas how to fix this?
Assuming that footable() is a valid library that you're using, then to fix the problem you should use the callback of load() to execute your logic after the AJAX call completes.
In theory the setTimeout() could do this too, but is not considered the best method as you either make the user wait too long, if the request completes before 1s, or it runs before the request completes and nothing happens. The latter of these seems to be the case here, given the behaviour you describe.
To define the callback, place your logic in a function which you provide as the second argument of load():
$("#stable_wrapper").load("rss2table.php", function() {
$('#termine').footable({
components: {
filtering: FooTable.MyFiltering
}
});
});
I have a two tests that are causing side effects with each other. I understand why as I am replacing a jQuery built-in function that is being called internally in the second test. However what I don't understand is why the test alternately passes and fails.
This question is similar However, I am not doing anything directly on the qunit-fixture div.
Here are my tests
test('always passing test', function() { // Always passes
var panelId = '#PanelMyTab';
var event = {};
var ui = {
tab: {
name: 'MyTab',
},
panel: panelId,
};
$('<div id="' + panelId + '">')
.append('Test')
.append('Show Form')
.appendTo('#qunit-fixture');
jQuery.fn.on = function(event, callback) {
ok(this.selector == panelId + ' .export', 'Setting export click event');
equal(callback, tickets.search.getReport, 'Callback being set');
};
loadTab(event, ui);
});
test('alternately passing and failing', function() { // Alternates between passing and failing on page refresh
expect(5);
var testUrl = 'test';
$('<div class="ui-tabs-panel">')
.append('Get Report')
.append('<form action="notest" target="" class="ticketSearch"></form>')
.appendTo('#qunit-fixture');
// Setup form mocking
$('form.ticketSearch').submit(function() {
var urlPattern = new RegExp(testUrl + '$');
ok(urlPattern.test($(this).prop('action')), 'Form action set to link href');
equal($(this).prop('target'), '_blank', 'Open form on a new page');
});
var event = {
target: 'a#getReport',
};
var result = getReport(event);
var form = $('form.ticketSearch');
ok(/notest$/.test($(form).prop('action')), 'Making sure action is not replaced');
equal($(form).prop('target'), '', 'Making sure that target is not replaced');
ok(false === result, 'click event returns false to not refresh page');
});
The tests will start off passing but when I refresh they will alternate between passing and failing.
Why is this happening? Even adding GET parameters to the url result in the same behavior on the page.
In the failing cases, the test is failing because internal jQuery is calling .on() when the submit() handler is set. But why isn't the test always failing in that case? What is the browser doing that a state is being retained during page refresh?
Update:
Here is the code that is being tested:
var tickets = function() {
var self = {
loadTab: function(event, ui) {
$(panel).find('.export').button().on('click', this.getReport);
},
search: {
getReport: function(event) {
var button = event.target;
var form = $(button).closest('div.ui-tabs-panel').find('form.ticketSearch').clone(true);
$(form).prop('action', $(button).prop('href'));
$(form).prop('target', '_blank');
$(form).submit();
return false;
}
}
};
return self;
}();
I've modified #Ben's fiddle to include your code with both of your tests. I modified some of your code to make it run correctly. When you hit the run button all of the tests will pass. When you hit the run button again, the second test ("alternately passing and failing") will fail -- this is basically simulating your original issue.
The issue is your first test ("always passing test") alters the global state by replacing the jQuery.fn.on function with an overridden one. Because of this, when the tests are run in order, the second test ("alternately passing and failing") uses the incorrect overridden jQuery.fn.on function and fails. Each unit test should return the global state back to its pre-test state so that other tests can run based on the same assumptions.
The reason why it's alternating between pass and fail is that under the hood QUnit always runs failed tests first (it remembers this somehow via cookie or local storage, I'm not exactly sure). When it runs the failed tests first, the second test runs before the first one; as a result, the second test gets jQuery's native on function and works. When you run it a third time, the tests will run in their "original" order and the second test will use the overridden on function and fail.
Here's the working fiddle. I've add the fix to "un-override" the on function after the test by caching the original var jQueryOn = jQuery.fn.on; function and resetting it at the end of the test via: jQuery.fn.on = jQueryOn;. You can probably better implement this using QUnit's module teardown() method instead.
You can check out https://github.com/jquery/qunit/issues/74 for more info.
I'm not sure I can solve this without some more info, but I can point out some possible issues.
The first test seems to have invalid syntax on line 2
var panelId = '#PanelMyTab');
But that's probably a type mistake, seeing as you say the first always passes.
I'm assuming that for the first test to pass(and be valid) the loadTab(event,ui) must run the jQuery.fn.on(), without it no assertions have been run. Which doing some testing with jQuery UI Tabs, seems to be the case (just not sure if it was your intention).
I'm not sure it's advisable putting these assertions within that function, and you must understand that you have overwritten the jquery function with a function that doesn't do anything, so it's likely to cause issues.
You seem to be doing something similar in the second test, you are expecting 5 assertions, but I can only see how the final 3 can be run
ok(/notest$/.test($(form).prop('action')), 'Making sure action is not replaced');
equal($(form).prop('target'), '', 'Making sure that target is not replaced');
ok(false === result, 'click event returns false to not refresh page');
The other 2 are within a submit function that doesn't look like it is invoked as part of the test.
Remember these tests are synchronous so it won't wait for you to hit submit before running the test and failing.
Here is an example
test('asynchronous test', function() {
setTimeout(function() {
ok(true);
}, 100)
})
Would fail as the ok is run 100ms after the test.
test('asynchronous test', function() {
// Pause the test first
stop();
setTimeout(function() {
ok(true);
// After the assertion has been called,
// continue the test
start();
}, 100)
})
The stop() tells qunit to wait and the start() to go!
There is also a asyncTest() detailed in the api here
Finally, it seems like you are trying to debug your code with these tests. It would be much easier to use chrome developer tools or firebug in firefox to set breakpoints on your code, and use console.log() and console.dir() to output information.
That being said I have no idea how it works for you at all, so I could be missing something :) If you're still stuck, see if you can add some more of the surrounding code and what your trying to achieve. Hope this helps.
PS: there is also a }; at the end which is invalid in the code you have given us, probably relevant in the actual application though ;)
I'm trying to validate image URLs with Qunit by setting the URL as the src attribute of a test image and checking with the error event handler whether that went well. So far what I have is:
test('image',function() {
var test_image = $('#test-image');
test_image.error(function(e) { // properly triggered
console.log(e);
is_valid = false;
// ok(false,'Issue loading image'); breaks qunit
});
var is_valid = true;
test_image.attr('src','doesntexist');
console.log('checking is_valid'); // occurs before error event handler
if (is_valid) { // therefore always evaluates to the same
ok(true,'Image properly loaded');
} else {
ok(false,'Issue loading image');
}
});
My problem is that although the error event is properly triggered, it seems to occur in an asynchronous fashion and after the evaluation of is_valid (therefore whatever check I make, the result will always be the same). I have tried adding the ok() assertion inside the error event handler, but I'm getting the following error:
Error: ok() assertion outside test context
How can I run an assertion based on the processing performed inside the error event handler?
PS: if I insert a alert('test'); before checking is_valid it works fine (which confirms problem with error handler being asynchronous) but as you can imagine is not acceptable. I tried using setTimeout to delay execution of if statement but it brings the same assertion context error.
By quickly looking through QUnit API, I see that you should use asyncTest function for this. Before setting the src-attribute for your test_image, hook a function to load event. Here's an untested code:
asyncTest('image',function() {
var test_image = $('#test-image');
test_image.error(function(e) {
console.log(e);
ok(false,'Issue loading image');
start();
});
test_image.load(function() {
ok(true,'Image properly loaded');
start();
});
test_image.attr('src','doesntexist');
});
I'm using server.listen(...) from PhantomJS. I realize that it is largely experimental and that it shouldn't be used in production. I'm using it for a simple screenshot-server that accepts generates screenshots for a URL; it's a toy project that I'm using to play around with PhantomJS. I've noticed an issue with long-running requests in particular, where the response object is unavailable. Here are the relevant snippets from my code:
var service = server.listen(8080, function (request, response) {
response.statusCode = 200;
if (loglevel === level.VERBOSE) {
log(request);
} else {
console.log("Incoming request with querystring:", request.url);
}
var params = parseQueryString(request.url);
if (params[screenshotOptions.ACTION] === action.SCREENSHOT) {
getScreenshot(params, function (screenshot) {
response.headers["success"] = screenshot.success; //<-- here is where I get the error that response.headers is unavailable. Execution pretty much stops at that point for that particular request.
response.headers["message"] = screenshot.message;
if (screenshot.success) {
response.write(screenshot.base64);
} else {
response.write("<html><body>There were errors!<br /><br />");
response.write(screenshot.message.replace(/\n/g, "<br />"));
response.write("</body></html>");
}
response.close();
});
} else {
response.write("<html><body><h1>Welcome to the screenshot server!</h1></body></html>")
response.close();
}
});
getScreenshot is an asynchronous method that uses the WebPage.open(...) function to open a webpage; this function is also asynchronous. So what seems to be happening is that when the callback that is passed in as an argument to getScreenshot is finally called, it appears that the response object has already been deleted. I basically end up with the following error from PhantomJS:
Error: cannot access member `headers' of deleted QObject
I believe this is because the request times out and so the connection is closed. The documentation mentions calling response.write("") at least once to ensure that the connection stays open. I tried calling response.write("") at the beginning of server.listen(...) and I even tried a pretty hacky solution where I used setInterval(...) to perform a response.write("") every 500 milliseconds (I even lowered it down to as little as 50). I also made sure to clear the interval once I was done. However, I still seem to get this issue.
Is this something that I'm just going to have to deal with until they make the webserver module more robust? Or is there a way around it?
I was able to figure this out. It appears that while loading certain pages with WebPage.open (for example http://fark.com and http://cnn.com) multiple onLoadFinished events are fired. This results in the callback in WebPage.open being called multiple times. So what happens is that when control comes back to the calling function, I've already closed the response and so the response object is no-longer valid. I fixed this by using creating a flag before the WebPage.open function is called. Inside the callback, I check the status of the flag to see if I've already encountered a previous onLoadFinished event. Once I am with whatever I have to do inside the WebPage.open callback, I update the flag to show that I've finished processing. This way spurious (at least in the context of my code) onLoadFinished events are no-longer serviced.
(Note that the following refers to PhantomJS 1.9.7 while the OP was likely referring to 1.6.1 or older.)
In the event that multiple onLoadFinished events are being fired, you can use page.open() instead of listening for onLoadFinished yourself. Using page.open() will wrap your handler in a private handler to ensure that your callback is only called once.
From the source:
definePageSignalHandler(page, handlers, "_onPageOpenFinished", "loadFinished");
page.open = function (url, arg1, arg2, arg3, arg4) {
var thisPage = this;
if (arguments.length === 1) {
this.openUrl(url, 'get', this.settings);
return;
}
else if (arguments.length === 2 && typeof arg1 === 'function') {
this._onPageOpenFinished = function() {
thisPage._onPageOpenFinished = null;
arg1.apply(thisPage, arguments);
}
this.openUrl(url, 'get', this.settings);
return;
}
// ... Truncated for brevity
This functionality is exactly the same as the other answer, exposed as part of the official API.
I googled on how to unit test but examples are so simple. the examples always show functions that return something or do ajax that returns something - but never have i seen examples that do callbacks, nested callbacks and functions that are "one-way", that they just store something and never return anything.
say i have a code like this, how should i go about testing it?
(function(){
var cache = {};
function dependencyLoader(dependencies,callback2){
//loads a script to the page, and notes it in the cache
if(allLoaded){
callback2()
}
}
function moduleLoader(dependencies, callback1){
dependencyLoader(dependencies,function(){
//do some setup
callback1()
});
}
window.framework = {
moduleLoader : moduleLoader
}
}());
framework.moduleLoader(['foo','bar','baz'],function(){
//call when all is loaded
})
This illustrates a problem with keeping things private in an anonymous function in javascript. It's a bit difficult to validate that things are working internally.
If this was done test first then the cache, dependencyLoader and moduleLoader should be publicly available on the framework object. Or else it would be difficult to validate that the cache was handled properly.
To get things going I'd recommend you take a gander on BDD, that conveniently gives you an approach to help you start by letting you spell out the behaviour with a given-when-then convention. I like to use Jasmine, which is a javascript BDD framework (that integrates with jstestdriver), for this kind of thing and the unit tests I'd make for the sample you have above would be:
describe('given the moduleloader is clear', function() {
beforeEach(function() {
// clear cache
// remove script tag
});
describe('when one dependency is loaded', function() {
beforeEach(function() {
// load a dependency
});
it('then should be in cache', function() {
// check the cache
});
it('then should be in a script tag', function() {
// check the script tag
});
describe('when the same dependency is loaded', function() {
beforeEach(function () {
// attempt to load the same dependency again
});
it('then should only occur once in cache', function() {
// validate it only occurs once in the cache
});
it('then should only occur once in script tag', function() {
// validate it only occurs once in the script tag
});
});
});
// I let the exercise of writing tests for loading multiple modules to the OP
});
Hope these tests are self explanatory. I tend to rewrite the tests so that they nest nicely, and usually the actual calls are done in the beforeEach functions while the validation are done in the it functions.