How to unit test onBeforeRendering of SAPUI5 control? - javascript

Let's assume that I have the following code:
var title = new sap.m.Title();
title.addEventDelegate({
onBeforeRendering: jQuery.proxy(function () {
title.setText(this._createTitleText());
}, this)
});
which sets the text to the title only on before rendering.
The question is how to unit test this?
As I understand SAPUI5 framework will fire this event and then I should be able to attach to it and verify that the text of the title is correct, but how to do it and is it a right way?
This is what I was trying, but at least I don't get any text from my control plus for me it is not clear which data I should pass to this method.
title.attachBeforeRendering("data", function (oEvent) {
var text = title.getText();
});

I found the following way:
title.addEventDelegate({
onBeforeRendering: jQuery.proxy(function () {
assert.equal(title.getText(), expectedTitle, "Title has correct text");
}, this)
});
However, this is not enough because onBeforeRendering will happen after the test which means that it depends on the time thus it is not sure if it is going to happen. But there is a way to make it for sure:
In the end of the test we should call:
return new Promise(function (resolve, reject) {
setTimeout(function () {
QUnit.start();
}, 100);
});
which will cover our case plus continue executing to another unit test.

You can try to call onAfterRendering manually or just invalidate or rerender the control.
Try to do something like
// act
oTitle.onAfterRendering();
// assert
assert.strictEqual(oTitle.getTitle(), "__expected_title__", "Title is correct!");
In case you are just testing if the title is changed. I would prefer to stub the setTitle method and check if it is called with a correct parameter.
You can see that usually events are tested in this way e.g.
https://github.com/SAP/openui5/blob/master/src/sap.m/test/sap/m/qunit/MultiComboBox.qunit.html#L4479
In your case you don't need a fake event, but it is quite similar.

Related

Starting Alexa Skill in a specific state

Earlier I ran into the issue of Alexa not changing the state back to the blank state, and found out that there is a bug in doing that. To avoid this issue altogether, I decided that I wanted to force my skill to always begin with START_MODE.
I used this as my reference, where they set the state of the skill by doing alexa.state = constants.states.START before alexa.execute() at Line 55. However, when I do the same in my code, it does not work.
Below is what my skill currently looks like:
exports.newSessionHandler = {
LaunchRequest () {
this.hander.state = states.START;
// Do something
}
};
exports.stateHandler = Alexa.CreateStateHandler(states.START, {
LaunchRequest () {
this.emit("LaunchRequest");
},
IntentA () {
// Do something
},
Unhandled () {
// Do something
}
});
I'm using Bespoken-tools to test this skill with Mocha, and when I directly feed IntentA like so:
alexa.intended("IntentA", {}, function (err, p) { /*...*/ })
The test complains, Error: No 'Unhandled' function defined for event: Unhandled. From what I gather, this can only mean that the skill, at launch, is in the blank state (because I have not defined any Unhandled for that state), which must mean that alexa.state isn't really a thing. But then that makes me wonder how they made it work in the example code above.
I guess a workaround to this would be to create an alias for every intent that I expect to have in the START_MODE, by doing:
IntentA () {
this.handler.state = states.START;
this.emitWithState("IntentA");
}
But I want to know if there is a way to force my skill to start in a specific state because that looks like a much, much better solution in my eyes.
The problem is that when you get a LaunchRequest, there is no state, as you discovered. If you look at the official Alexa examples, you will see that they solve this by doing what you said, making an 'alias' intent for all of their intents and just using them to change the state and then call themselves using 'emitWithState'.
This is likely the best way to handle it, as it gives you the most control over what state and intent is called.
Another option, assuming you want EVERY new session to start with the same state, is to leverage the 'NewSession' event. this event is triggered before a launch request, and all new sessions are funneled through it. your code will look somewhat like this:
NewSession () {
if(this.event.request.type === Events.LAUNCH_REQUEST) {
this.emit('LaunchRequest');
} else if (this.event.request.type === "IntentRequest") {
this.handler.state = states.START;
this.emitWithState(this.event.request.intent.name);
}
};
A full example of this can be seen here (check out the Handlers.js file): https://github.com/alexa/skill-sample-node-device-address-api/tree/master/src
I would also recommend reading through this section on the Alexa GitHub: https://github.com/alexa/alexa-skills-kit-sdk-for-nodejs#making-skill-state-management-simpler
EDIT:
I took a second look at the reference you provided, and it looks like they are setting the state outside of an alexa handler. So, assuming you wanted to mimic what they are doing, you would not set the state in your Intent handler, but rather the Lambda handler itself (where you create the alexa object).
exports.handler = function (event, context, callback) {
var alexa = Alexa.handler(event, context);
alexa.appId = appId;
alexa.registerHandlers(
handlers,
stateHandlers,
);
alexa.state = START_MODE;
alexa.execute();
};

Testing event handlers

I have an event handler that modifies some global variable based on the click action. How can I test it? For example:
function initEvent() {
function enable() {
var arr = Context.get('arr');
arr.push('aaa');
};
function disable() {
var arr = Context.get('arr');
arr.push('bbb');
};
$('#content').on('click', '#element', function () {
if (this.checked) {
enable();
} else {
disable();
}
});
};
This is the function I am calling after the HTML has been rendered. It calls enable() and disable() functions based on the user action. I want to test these functions and check if they behave correctly. How can I do that?
You want to test your code. You should never test code with console.log or alert. These are great to debug something on the fly, but they are not test tools. These promote manual testing, where you need to manually run the code and see that pass, that's horrible time waste.
You should use Jasmine in this case (you can use other testing frameworks, though Jasmine is super easy) to test your code. You can setup browser tests or headless tests, which is out of the scope of this question, there are tons of tutorials on the subject.
Now, in order to test this code, I assume that the Context has a static method get which returns an array which is on the Context IIFE scope. If the case is different feel free to fiddle around with the specs and make it serve your needs, or alternatively if you get stuck, update this question or ask another one on Stackoverflow.
I have setup Jasmine, with jasmine-fixture to test your code, the jQuery click event behavior. In this plunk you will find everything you need.
I am using the browser to test the code, so I need jasmine's html reporter.
The real tests are in script-spec.js, where I am using Jasmine's API, by describing a spec suite (with describe) and defining each spec with it method.
In beforeEach I prepare the code to run before each spec executes. Essentially here, I create a simple div with #content id and a child input element of type checkbox with #element id. I do this by using the
setFixtures('<div id="content"><input type="checkbox" id="element" /></div>');
Which is a method jasmine-fixture library provides.
Now I can test the code, wiring up the specs:
it("Should return an array with 'aaa' element when #element is checked", function() {
// Arrange
initEvent();
var checkbox = $("#content").find("#element");
// Act
checkbox.click();
// Assert
expect(Context.get('arr').length).toBe(1);
expect(Context.get('arr')).toEqual(['aaa']);
});
I run the initEvent method and get a reference of the checkbox element. In Act section I click the element manually, marking it as checked, which is the normal behavior. In Assert, I test the Context.get('arr') return value.
Again, link to plunk is here.
Hope this helps.
One simple test you can do to test enable, disable and the click handler is to create a function that checks the contents of arr in Context, and call it after each of the functions within the click handler that add something to arr.
The general way to test conditions in your code is with assertions which will throw an error if the condition you pass into them is false. You can use console.assert just for that:
$('#content').on('click', '#element', function() {
if (this.checked) {
enable();
// assert last element in `arr` is the enabled string 'aaa'
console.assert(
Context.get('arr')[Context.get('arr').length - 1] === 'aaa',
'enable() works'
);
} else {
disable();
// assert last element in `arr` is the disabled string 'bbb'
console.assert(
Context.get('arr')[Context.get('arr').length - 1] === 'bbb',
'disable() works'
);
}
});
If any of the tests run after you click your element, you know initEvent assigned the click handler and it works. Then, you just toggle the checked flag to test enable()/disable() as well.
If there are no errors in your browser console, the tests have passed. Otherwise, there will be an error in your console containing the message passed as the second argument to console.assert.
You could even make a helper function to simplify the testing a bit:
function assertLastElementInContextArr(elem, msg) {
var arr = Context.get('arr');
// assert last item in `arr` is equal to `elem`
console.assert(arr[arr.length - 1] === elem, msg);
}
$('#content').on('click', '#element', function() {
if (this.checked) {
enable();
// assert last element in `arr` is the enabled string 'aaa'
assertLastElementInContextArr('aaa', 'enable() works');
} else {
disable();
// assert last element in `arr` is the disabled string 'bbb'
assertLastElementInContextArr('bbb', 'disable() works');
}
});
EDIT based on your comment
But how do I mock the click event? I mean, I want to automatically test all those events, no I have to somehow trigger the click automatically. How do I do that?
If you want to programmatically invoke click events, you can use JS to trigger them in code. Since you're using jQuery, it already comes with a method trigger to do just that.
All you need to do is:
$('#content').trigger('click')
And it will activate your click handler and run the assertions tests from above.
In fact, jQuery even comes with aliased handlers for specific events so you can just do:
$('#content').click();
To automate the testing, you can create a function that will trigger the clicks and set the checked state as well, to test both cases.
function test(checked) {
var elem = $('#content');
elem.prop('checked', checked);
elem.click();
}
Important thing to be careful about is that these events will happen asynchronously so you must do something to manage a proper testing order if you're going to trigger multiple clicks. Otherwise you will set checked to true, trigger the click and then run the second test that will set checked to false before the click events even happen.
For demonstration purposes, here's one way to safely test multiple successive clicks by adding an event handler just for testing and removing it once you're done. One requirement for this to work is to attach the handler after all your other handlers have been attached, to make sure the test handler runs last. Additionally, you can run your assertions here as well to not pollute your code and keep the testing fully separated:
function test(checked, value, msg, done) {
var elem = $('#content');
elem.prop('checked', checked);
// attach a test event handler and trigger the click
elem.on('click', testClick);
elem.click();
// once the click is executed,
// remove the test handler,
// run the assertions and then
// call the callback to signal the test is done
function testClick() {
elem.off('click', runTest);
assertLastElementInContextArr(value, msg);
done();
}
}
// run your code before the tests
initEvent();
// test enable(), then once that's done, test disable()
test(true, 'aaa', 'enable() works', function() {
test(false, 'bbb', 'disable() works', function() {
console.log('All tests finished');
});
});
If you're going to be testing your entire app like this, you'd probably want to use a test framework like QUnit, Mocha, Jasmine which will handle all these async issues for you and give you a nice API to work with.
Just add console.log(<some variable>) or alert(<some variable>) at function calls. e.g.:
function initEvent() {
function enable() {
alert("enable called!");
var arr = Context.get('arr');
arr.push('aaa');
};
function disable() {
alert("disable called!");
var arr = Context.get('arr');
arr.push('bbb');
};
$('#content').on('click', '#element', function () {
alert("click occured!");
if (this.checked) {
enable();
} else {
disable();
}
});
};
Or use your browsers developer tools setting breakpoints at these spots.

Qunit error: assertion outside test context

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.

Qunit test alternates between pass and fail on page refresh

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 ;)

How to 'queue' piece of code in javascript

I've made a class that, when initialized, starts to download a JSON file.
However, the downloading runs asynchronously.
So after I declare it I start working with it, but it fails because it hasn't loaded the file yet.
I could turn async off, but is there another way without freezing the browser?
I'm currently working around it with a setTimeout option, but that seems like an ugly hack.
var d;
$(document).ready(function() {
d = new Duk('element');
d.getBlueprint('hud.json');
setTimeout(start, '2000');
});
function start(){
test = new d.Dialog(d.blueprint.screens.test);
test.draw();
}
You have to attach event handler to the object load completion event. The library you are working with must supply this event. I don't know what a Duk is, or what getBlueprint() does, but you should check the documentation for whatever that class and method is, and see if there is an oncomplete callback. If there is, you'd do something like this:
$(document).ready(function() {
d = new Duk('element');
d.getBlueprint('hud.json', {
onComplete: function() {
test = new d.Dialog(d.blueprint.screens.test);
test.draw();
}
});
});
Obviously, I just made that up. I don't know if your library has an onComplete method defined like this, but I hope you see what I mean. Without knowing more about the library you are using, I can't give a better example.
Timeouts are not the way to solve this problem.
You want to set up a callback function alongside the request so that the object handling the request knows what to do when the response comes in.
It is unclear from your code what framework you're using (I can't make any sense of getBlueprint, which seems to be the call that initializes the remote request), so if you could provide more information on this, we can provide more customized help.
You want your getBlueprint() method to be able to accept a callback which is run when the file is finished downloading.
function getBlueprint(file, callback)
{
// do whatever to get the file
// ...
// trigger the callback
callback();
}
$(document).ready(function() {
var d;
function start(){
test = new d.Dialog(d.blueprint.screens.test);
test.draw();
}
d = new Duk('element');
d.getBlueprint('hud.json', start);
setTimeout(start, '2000');
});
I agree that a callback function is the proper way. If this is code you cannot modify, you can do something like this, but seriously, use a callback!
function wait_for_load(expr, func)
{
var interval = window.setInterval(function()
{
if(eval(expr))
{
window.clearInterval(interval);
func();
}
}, 20);
}
var d;
$(document).ready(function() {
d = new Duk('element');
d.getBlueprint('hud.json');
wait_for_load('d.blueprint', start);
});

Categories

Resources