I need to test every listener in a controller with Jasmine 2.0, really this question is just to reinforce my logic, and perhaps there is a more elegant way to approach testing listeners, or maybe I am being too thorough!
This might be a better question for codereview but I'll leave it here. How to properly test multiple keypress/event listeners in a controller effectively?
it("should trigger the correct actions from key events", function () {
var listenerSpy = jasmine.createSpy('listenerSpy');
angular.forEach(scope.$$listeners, function (fn, eventName) {
listenerSpy(eventName, fn);
expect(listenerSpy).toHaveBeenCalledWith(eventName, fn);
});
});
What you have above is not really testing anything other than JavaScript itself. You are calling a function and then expecting that you just called that function.
A code coverage report would show that the listener function has not executed at all.
Without seeing the code you are testing, I am unable to properly advise on how to structure your test.
There are two possible intentions:
1) Do you want to test that the scope is listening to a set of known elements?
2) Do you want to test the outcome of listener executions?
Usually, it would be best to take path number two, because with it, you also get number one.
Are all of your listeners performing the same action?
If they are, it may make sense to loop through a list of known elements and change them to verify the proper listener execution output.
If the listeners perform differently, each execution's output should be evaluated.
Related
I want to return a $q instance so that if clients don't call 'then' with a reject handler, then a default one runs.
E.g. assume the default is to alert(1)
Then mypromise.then(function(result){...}) will alert 1 but mypromise.then(null, function(reason){alert(2)}) will alert 2
Let's assume there was a way to do that.
So we have a magical machine that can always find out if .then is called with a function second argument for a specific promise or not.
So what?
So you can detect if someone did:
myPromise.then(..., function(){ F(); });
At any point from anywhere at any time. And have G() as a default action.
And...?
You could take a whole program containing lots of code P (1) and convert that code to:
var myPromise = $q.reject();
P; // inline the program's code
myPromise.then(null, function(){}); // attach a handler
Great, so I can do that, so?
Well, now our magical machine can take an arbitrary program P and detect if myPromise had a rejection handler added to it. Now this happens if and only if P does not contain an infinite loop (i.e. it halts). Thus, our method of detecting if a catch handler is ever added is reduced to the halting problem. Which is impossible. (2)
So generally - it is impossible to detect if a .catch handler is ever attached to a promise.
Stop with the mumbu-jumbo, I want a solution!
Good response! Like many problems this one is theoretically impossible but in practice easy enough to solve for practical cases. The key here is a heuristic:
If an error handler is not attached within a microtask (digest in Angular) - no error handlers are ever attached and we can fire the default handler instead.
That is roughly: You never .then(null, function(){}) asynchronously. Promises are resolved asynchronously but the handlers are usually attached synchronously so this works nicely.
// keeping as library agnostic as possible.
var p = myPromiseSource(); // get a promise from source
var then = p.then; // in 1.3+ you can get the constructor and use prototype instead
var t = setTimeout(function(){ // in angular use $timeout, not a microtask but ok
defaultActionCall(p);// perform the default action!
});
// .catch delegates to `.then` in virtually every library I read so just `then`
p.then = function then(onFulfilled, onRejected){
// delegate, I omitted progression since no one should use it ever anyway.
if(typeof onRejected === "function"){ // remove default action
clearTimeout(t); // `timeout.cancel(t)` in Angular
}
return then.call(this, onFulfilled, onRejected);
};
Is that all?
Well, I just want to add that cases where this extreme approach is needed are rare. When discussing adding rejection tracking to io - several people suggested that if a promise is rejected without a catch then the whole app should likely terminate. So take extra care :)
(1) assume P does not contain a variable myPromise, if it does rename myPromise to something P does not contain.
(2) Of course - one can say that it is enough to read the code of P and not run it in order to detect myPromise gets a rejection handler. Formally we say that we change every return in P and other forms of termination to a return myPromise.then(null, function(){}) instead of simply putting it in the end. this way the "conditionality" is captured.
I'm trying to figure out how to test actions in flux. Stores are simple enough with the provided example, but there seems to be nothing out there for the actions/data/api layer.
In my particular app, I need to pre-process something before posting it to my server. Based on the advice in this post, I decided to implement the async stuff in my actions. What I can't figure out is how to test this preprocessing.
For example in MissionActions.js:
addMissionFromBank: function(bankMission) {
var mission = new Mission({game: GameStore.getGame().resource_uri, order: Constants.MISSION_ORDER_BASE_INT}).convertBankMission(bankMission);
var order = MissionSort.calcOrderBySortMethod(mission, MissionStore.getMissions(), GameStore.getGame().sort_method);
mission['order'] = order;
AppDataController.addMissionFromBank(mission);
},
In this function, I'm converting a stock mission (bankMission) into a regular mission and saving it to a game with the correct order key. Then I'm posting this new regular mission to my server, the callback of which is handled in my MissionStore.
Since this conversion code is important, I want to write a test for it but can't figure out how to do it since there seems to only be examples for testing stores and React components. Any suggestions?
Are you using the flux dispatcher or requiring AppDataController?
Jest will automatically mock modules that you bring in via browserify's require. If you are importing AppDataController via require, then your test might look like this:
jest.dontMock('MissionAction.js') // or path/to/MissionAction.js
describe('#addMissionFromBank', function(){
beforeEach(function(){
MissionAction.addMissionFromBank(exampleMission);
});
it('calls AppDataController.addMissionFromBank', function(){
expect(AppDataController.addMissionFromBank).toBeCalled());
});
});
you want to call the non-mocked method that youre testing and check that the mock is called. to check if its been called at all use
#toBeCalled()
or if you want to check that its called with a specific value (for instance, check that its called with whatever mission evaluates to) use
#toBeCalledWith(value)
You could mock/spyOn AppDataController, and check that it receives a correct mission object. Something like this in jasmine, I'm not sure if it is the same in jest:
spyOn(AppDataController, 'addMissionFromBank');
MissionActions.addMissionFromBank(someBankMission);
expect(AppDataController.addMissionFromBank).toHaveBeenCalledWith(expectedMission);
I'm trying to write what I think is a fairly simple test in protractor, but it would seem that the minute you try to do anything synchronously, Protractor makes life hard for you! Normally, dealing with locator functions (that return a promise) are not an issue, since any expect statement will automatically resolve any promise statement passed to it before testing the assertion. However, what I'm trying to do involves resolving these locator promises before the expect statement so that I can conditionally execute some test logic. Consider (pseudocode):
// Imagine I have a number of possible elements on the page
// and I wish to know which are on the page before continuing with a test.
forEach(elementImLookingFor){
if (elementImLookingFor.isPresent) {
// record the fact that the element is (or isnt) present
}
}
// Now do something for the elements that were not found
However, in my above example, the 'isPresent' call returns a promise, so can't actually be called in that way. Calling it as a promise (i.e. with a then) means that my forEach block exits before I've recorded if the element is present on the page or not.
I'm drawing a blank about how to go about this, has anyone encountered something similar?
I've used bluebird to do the following;
it('element should be present', function(done)
Promise.cast(elementImLookingFor.isPresent)
.then(function(present){
expect(present).toBeTruthy();
})
.nodeify(done);
});
If you have a few elements that you want to check the isPresent on you should be able to do the following;
it('check all elements are present', function(done){
var promises = [element1, element2].map(function(elm){
return elm.isPresent();
});
// wait until all promises resolve
Promise.all(promises)
.then(function(presentValues){
// check that all resolved values is true
expect(presentValues.every(function(present){
return present;
})).toBeTruthy();
})
.nodeify(done);
});
Hope this helps
So elementImLookingFor is a promise returned by element.all, I presume? Or, as stated in the Protractor documentation, an ElementArrayFinder. You can call the method .each() on it and pass it a function that expects things.
I just want to understand why in the following jsFiddle 'here is a lo' is printed three times.
http://jsfiddle.net/wg385a1h/5/
$scope.getLog = function () {
console.log('here is a log');
}
Can someone explain me why ? What should I change to have only one log "here is a log" (that's what I would like this fiddle do). Thanks a lot.
Angular uses digest cycles/iterations to determine when state has changed and needs to update the UI. If it finds any change on one of it's cycles, it keeps rerunning cycles until the data stabilizes itself. If it's done 10 cycles and the data is still changing, you'll see a rather know message: "angularjs 10 iterations reached. aborting".
Therefor, The fact that you are seeing the message displayed 3 times is because you have a simple interface. In fact, you can get up to many more such messages in the log, due to the fact that your directive uses {{getLog()}}. Angular keeps evaluating the expression to see if it changed.
To avoid such problems, under normal circumstances, you should store the value returned by the function you want called only once in the $scope object inside the controller and use that variable (not the function call) in the UI.
So in the controller you'd have $scope.log = getLog() [assuming it returns something, and not just writing to the console] and in the directive use the template {{log}}. This way, you'll get the value only once, per controller instance.
Hope I was clear enough.
I'm trying here to get enough info to go fix this problem, just wanting some help understanding what is going on inside angular.
ng-grid has issues, lots of them, but I've found a "fix" to this one that I don't understand.
I have a grid with enough rows that it fills the visible area. If I click on the different rows, the afterSelectionChange method is called. If after clicking in the grid I move the focus with the arrow keys, it only calls that callback if the grid scrolls.
So I put in a $timeout to print out the selected row every half second to see if it was changing the selected row and just not calling the callback, and THAT fixed the problem. Now every time I move the cursor with the keyboard, the callback fires, even though the only thing happening in the callback is $log.debug().
Is this because $timeout is causing something to happen within the framework like a $apply or a $digest?
If that's the case, why isn't the keyboard causing that to happen?
Edit: Options for #tasseKATT
$scope.callGridOptions = {
data: 'callRecords',
multiSelect: false,
sortInfo: {fields:['startOn'], directions:['asc']},
columnDefs: [ ...
],
afterSelectionChange: $scope.onCallChange,
selectedItems: $scope.selectedCalls
};
In the end, I could reduce the timeout code to this:
function ngGridFixer() {
// Presence of this timer causes the ngGrid to correctly react to up/down arrow and call the
// afterSelectionChange callback like it is supposed to.
$timeout(ngGridFixer, 500);
}
ngGridFixer();
I put this in the rootscope because the problem happens on all the pages of the app.
$log is part of the Angular framework, anything processed by it is might execute watches laid down earlier. In other words by calling $log.debug() to print out the structure, you might be basically running scope.$digest every half second, which cause the callback(s) to fire. If you take out everything inside the $timeout function, or use console.log instead, the callback(s) probably won't fire
A way to do this semi-properly would be to use something like ngKeydown.
EDIT:
$timeout execute the function in scope.$apply by default. https://docs.angularjs.org/api/ng/service/$timeout (invokeApply). I was not aware of this. So essentially your code is calling scope.$apply every half second.