When testing stylus and the assertion throws, it calls the callback a second time with the Assertion error:
var expect = require('chai').expect,
stylus = require('stylus'),
i = 0
describe('test stylus', function(){
it('calls back', function(done){
stylus('p\n\tcolor white').render(function(err,css){
i++;
console.log('callback', i) //logs twice
expect(css).equal('p\n\t{ color: bad;\n}')
done()
})
})
})
I'm using this to work-around:
describe('test stylus', function(){
it('calls back', function(done){
stylus('p\n\tcolor white').render(function(err,css){
try {
expect(css).equal('p\n\t{ color: bad;\n}')
} catch(e) {
done(e)
}
})
})
})
I'm thinking it's a stylus bug to re-call the callback. Or Am I missing something here?
Your hypothesis is correct. Here is the code in stylus:
Renderer.prototype.render = function(fn){
// ...
try {
// ...
var listeners = this.listeners('end');
if (fn) listeners.push(fn);
for (var i = 0, len = listeners.length; i < len; i++) {
var ret = listeners[i](null, css); // Called here once.
if (ret) css = ret;
}
if (!fn) return css;
} catch (err) {
var options = {};
options.input = err.input || this.str;
options.filename = err.filename || this.options.filename;
options.lineno = err.lineno || parser.lexer.lineno;
if (!fn) throw utils.formatException(err, options);
// Called here a second time if there is an exception.
fn(utils.formatException(err, options));
}
};
fn is the callback. It is added to listeners and will be called once as part of the loop that calls all listeners. If the callback raises an exception there, then it is called again as part of the exception handling.
Related
I have successfully used await browser.tabs.sendMessage in chrome to get response from the listener, but the same code in firefox does not work. await browser.tabs.sendMessage return immediately and sets response to undefined. In content script inject.js, sendResponse should be called after 1000ms timeout.
I attached a minimalistic example. Any idea why await browser.tabs.sendMessage
returns what sendResponse set only in chrome, but not in firefox?
//inject.js
(async () => {
if (typeof browser === "undefined") {
var browser = chrome;
}
browser.runtime.onMessage.addListener((msg, sender, sendResponse) => {
console.log(msg);
setTimeout(function(){
let pageObject = {a:1};
sendResponse(pageObject);
},1000)
return true;
});
})();
//background.js
(async () => {
if (typeof browser === "undefined") {
var browser = chrome;
}
//**code for injecting content scripts on extension reload**
browser.runtime.onInstalled.addListener(async () => {
let manifest = browser.runtime.getManifest();
for (const cs of manifest.content_scripts) {
for (const tab of await browser.tabs.query({ url: cs.matches })) {
browser.scripting.executeScript({
target: { tabId: tab.id },
files: cs.js,
});
}
}
});
async function SendMessageToFront(message) {
let resolve;
const promise = new Promise(r => resolve = r);
browser.tabs.query({}, async function (tabs) {
for (let index = 0; index < tabs.length; index++) {
const tab = tabs[index];
if (tab.url) {
let url = new URL(tab.url)
if (url.hostname.includes("tragetdomain.com")) {
var startTime = performance.now()
let response = await browser.tabs.sendMessage(tab.id, { message: message });
var endTime = performance.now()
console.log(`Call to doSomething took ${endTime - startTime} milliseconds`) // this takes 0ms
console.log("got response");
console.log(response); // this is undefined
console.log(browser.runtime.lastError); // this is empty
resolve(response);
break;
}
}
}
});
return promise;
}
await SendMessageToFront();
})();
I guess for the tests in firefox you do the reload of the background script (F5 or the specific button in devtools)
Just as you have coded the background you have little hope of getting an answer because every time you reload the background you break the wire with all content scripts injected into the page(s).
Move the browser check inside the "SendMessageToFront" function. Move the "SendMessageToFront" function (async is not needed) to the main thread and run that function in the main thread.
/*async*/ function SendMessageToFront(message) {
if (typeof browser === "undefined")
var browser = chrome;
let resolve;
const promise = new Promise(r => resolve = r);
browser.tabs.query({}, async function(tabs) {
for (let index = 0; index < tabs.length; index++) {
const tab = tabs[index];
if (tab.url) {
let url = new URL(tab.url);
if (url.hostname.includes("tragetdomain.com")) {
var startTime = performance.now()
let response = await browser.tabs.sendMessage(tab.id, {'message': message});
var endTime = performance.now()
console.log(`Call to doSomething took ${endTime - startTime} milliseconds`) // this takes 0ms
console.log("got response");
console.log(response); // this is undefined
console.log(browser.runtime.lastError); // this is empty
resolve(response);
break
}
}
}
});
return promise
}
(async _ => {
await SendMessageToFront()
})();
in this way you will get an error message as soon as the background is ready which tells you that the content script on the other side does not exists or it's not ready yet, but now, when the content script will be ready, you should just re-launch the function from the background script devtools
(async _ => {
await SendMessageToFront()
})();
this time you will get the correct answer {a: 1}
I tried to maka a QUnit async test for checking ajax update.
I read of QUnit.asyncTest here
https://www.sitepoint.com/test-asynchronous-code-qunit/
but if i try this i get a
TypeError: QUnit.asyncTest is not a function
thats the complete source: https://gist.github.com/232457b002e5363439aece7535600356
of course i new by using QUnit and used JavaScript not for long time.
that a snippet of the part where the error happens:
function max() {
var max = -Infinity;
for (var i = 0; i < arguments.length; i++) {
if (arguments[i] > max) {
max = arguments[i];
}
}
return max;
}
// https://www.sitepoint.com/test-asynchronous-code-qunit/
//TypeError: QUnit.asyncTest is not a function
QUnit.asyncTest('max', function (assert) {
expect(1);
window.setTimeout(function() {
assert.strictEqual(max(3, 1, 2), 3, 'All positive numbers');
QUnit.start();
}, 0);
});
this test gives no syntax error but gives old date:
QUnit.test('usersInnerHTMLlength_Is24', function(assert) {
// problem: this not reads the updates done by ajax. means that are old data:
let innerHTMLlength = $("#users").html().toString().length;
assert.equal(innerHTMLlength, 24);
});
May its not possible to check ajax with QUnit?
I thougt this when i have read here:
QUnit testing AJAX calls
I use it inside a Wordpress Plugin
That sitepoint article is very old (by web standards). You'll need to use the newer syntax found on the documentation website:
function someAsyncThing(value) {
return new Promise(function (resolve, reject) {
setTimeout(function() {
if (value > 5) {
resolve(value);
} else {
reject(new Error("bad value"));
}
}, 500);
});
}
QUnit.test( "some async thing success test", function( assert ) {
// This is what tells QUnit the test is asynchronous
// "done" here will be a callback function used later
var done = assert.async();
// Now call your Async code, passing in a callback...
someAsyncThing(10)
.then(function(result) {
// do your assertions once the async function ends...
assert.equal(result, 10)
// Now tell QUnit you're all done with the test
done();
})
// we can pass the "done" callback from above into catch() to force a test failure
.catch(done);
});
QUnit.test( "some async thing FAILURE test", function( assert ) {
var done = assert.async();
someAsyncThing(4)
.then(function() {
done(new Error("we should NOT succeed with a value < 5"));
})
.catch(function(err) {
assert.equal(err.message, "bad value")
});
});
I'm trying to test that, when the Submit button is clicked on an empty form, all the "please fill in this field" labels are displayed.
I'm doing so with this:
page.click('#btn_submit');
page.expect.element('#validation_label_required').to.be.visible;
where #validation_label_required is represented by the CSS selector:
input[required] ~ p.error-message-required
However, this test passes if ANY of the validation labels are visible. The test should only pass if they ALL are.
How can I achieve this?
You will need to create a custom assertion for that where you locate all elements by selenium commands and then loop to verify condition. It should look something like this
var util = require('util');
exports.assertion = function (elementSelector, expectedValue, msg) {
this.message = msg || util.format('Testing if elements located by "%s" are visible', elementSelector);
this.expected = expectedValue;
this.pass = function (value) {
return value === this.expected;
};
this.value = function (result) {
return result;
};
this.command = function (callback) {
var that = this.api;
this.api.elements('css selector',elementSelector, function (elements) {
elements.value.forEach(function(element){
that.elementIdDisplayed(element.ELEMENT,function(result){
if(!result.value){
callback(false);
}
});
});
callback(true);
});
return this;
};
};
I've just ended up with another custom assertion that check how many elements are visible by given css selector.
/**
* Check how many elements are visible by given css selector.
*
*/
var util = require('util');
exports.assertion = function(elementSelector, expectedCount, msg) {
this.message = msg || util.format('Asserting %s elements located by css selector "%s" are visible', expectedCount, elementSelector);
this.expected = expectedCount;
this.count = 0;
this.pass = function(value) {
return value === this.expected;
};
this.value = function(result) {
return this.count;
};
this.command = function(callback) {
var me = this, elcount = 0;
this.count = 0;
this.api.elements('css selector', elementSelector, function(elements) {
if(elements.value && elements.value.length > 0){
elcount = elements.value.length;
}else{
return callback(false);
}
elements.value.forEach(function(element) {
me.api.elementIdDisplayed(element.ELEMENT, function(result) {
if (result.value) {
me.count++;
}
elcount--;
if (elcount === 0) {
callback(me.count);
}
});
});
});
};
};
I'm trying to emulate a synchronous ajax call by blocking for a short period of time and then flipping a flag in the AJAX return. But the flags don't get updated, even though the time limit is reached. Instead the browser just hangs. I've tested in Firefox and Safari with the same results.
Broken design:
function syncAjax(args, timeLimit) {
var obj = {},
outOfTime = false,
timeout, k,
response = {returned: false, failed: false, responseText: null};
timeout = window.setTimeout(function() {
outOfTime = true;
}, timeLimit);
for(k in args) {
if(args.hasOwnProperty(k)) {
obj[k] = args[k];
}
}
obj.success = function(responseText) {
window.clearTimeout(timeout);
response.returned = true;
response.responseText = responseText;
};
obj.failure = function() {
window.clearTimeout(timeout);
response.returned = true;
response.failed = true;
};
// obj["async"] = true; // (automatically async)
$.ajax(obj);
// insert spinner timeout
while(true) {
if(response.returned || outOfTime) break;
}
return response;
}
Sample usage:
function doIt() {
var response = syncAjax({
url: "webpage.php",
data: {x: 5},
}, 500);
if(!response.returned) {
// do time out stuff
} else if(response.failed) {
// do failed stuff
} else {
console.log(response.responseText);
// do success stuff with response.responseText
}
}
// !! executing this function will lock up your browser !!
// doIt();
javascript cannot call your timeout until you return from your function. setTimeout is not a threaded call.
You could do this for your loop:
var start = Date().getTime();
while( start+timeLimit > Date().getTime() ) ;
I've written a function to call the default jQuery.fn.on-handler, after a given number of fired events. Now I stuck, because the original event will not passed to the function, any ideas how to improve this?
;(function ($) {
var oldOn = $.fn.on,
i = 0;
$.fn.on = function () {
var args = arguments,
j = args.length;
for (var last in args);
while (j--) {
if ($.isFunction(args[j]) && !isNaN(args[last])) {
var oldFn = args[j],
after = args[last];
args[j] = function () {
i++;
if (i === after) {
oldFn.call();
i = 0;
}
};
}
}
if (!isNaN(args[last])) delete args[last];
return oldOn.apply(this, arguments);
};
})(jQuery);
// call the plugin and fire the `fn` after each 20 mousemoves
$(document).on('mousemove', function (e) {
console.log(e); // undefined
}, 20);
As you can see, will the following work without problems:
var oldOn = $.fn.on;
$.fn.on = function () {
return oldOn.apply(this, arguments);
};
$(document).on('click', function(e){
console.log(e) // jQuery.Event
});
Where's the mistake, how can i get this to work?
Update
I got it much simpler now: https://github.com/yckart/jquery.unevent.js
You're not passing the arguments from your callback wrapper function to the original callback function.
args[j] = function (*HERE*) {
i++;
if (i === after) {
oldFn.call(*TO HERE*);
i = 0;
}
};
Try replacing oldFn.call(); with oldFn.apply(this, [].slice.call(arguments)); to carry them over (and keeping up jQuery's this).
Edit: http://jsfiddle.net/QFyhX/
I think
for (var last in args);
should be
var last = args[args.length-1];