How to capture $compile or $digest error? (AngularJS directive with templateUrl) - angularjs-directive

I'm writing a unit test of an AngularJS 1.x directive.
If I use "template" it works.
If I use "templateUrl" it does not work (the directive element remains the same original HTML instead of being "compiled").
This is how I create the directive element to test in Jasmine:
function createDirectiveElement() {
scope = $rootScope.$new();
var elementHtml = '<my-directive>my directive</my-directive>';
var element = $compile(elementHtml)(scope);
scope.$digest();
if (element[0].tagName == "my-directive".toUpperCase()) throw Error("Directive is not compiled");
return element;
};
(this does not actually work, see Update for real code)
I'm using this workaround to use the $httpBackend from ngMockE2E (instead of the one in ngMock). In the browser developer "network" tab I don't see any request to the template file. It seems to work because I solved the error "Object # has no method 'passThrough'".
I know that the call to the template is done asynchronously using the $httpBackend (this means $compile exit before the template is really applied).
My question is:
obviously $compile is not doing what I expect. How can I trap this error?
If I use a wrong address in the templateUrl I don't receive any error.
How can I found the problem happened when I called $compile(directive) or scope.$digest() ?
Thanks,
Alex
[Solution]
As suggested by #Corvusoft I inject $exceptionHandler and I check for errors after every test.
In the end this is the only code I have added:
afterEach(inject(function ($exceptionHandler) {
if ($exceptionHandler.errors.length > 0)
throw $exceptionHandler.errors;
}));
Now I can clearly see the errors occurred in the Jasmine test result (instead of search for them in the console), example:
Error: Unexpected request: GET /api/category/list
No more request expected,Error: Unexpected request: GET /api/category/list
No more request expected thrown
And, most important, my tests does not pass in case there are some errors.
[Update to show real example case]
Actually the real code to make templateUrl work use asynchronous beforeEach ("done") and a timeout to wait the end of compile/digest.
My directive use some prividers/services and the template contains other directives which in turn use their templateUrl and make calls to some APIs in the link function().
This is the current (working) test code:
// workaround to have .passThrough() in $httpBackend
beforeEach(angular.mock.http.init); // set $httpBackend to use the ngMockE2E to have the .passThrough()
afterEach(angular.mock.http.reset); // restore the $httpBackend to use ngMock
beforeEach(inject(function (_$compile_, _$rootScope_, _$http_, $httpBackend, $templateCache, $injector) {
$compile = _$compile_;
$rootScope = _$rootScope_;
$http = _$http_;
$httpBackend.whenGET(/\/Scripts of my app\/Angular\/*/).passThrough();
$httpBackend.whenGET(/\/api\/*/).passThrough(); // comment out this to see the errors in Jasmine
}));
afterEach(inject(function ($exceptionHandler) {
if ($exceptionHandler.errors.length > 0)
throw $exceptionHandler.errors;
}));
beforeEach(function(done) {
createDirectiveElementAsync(function (_element_) {
element = _element_;
scope = element.isolateScope();
done();
});
});
function createDirectiveElementAsync(callback) {
var scope = $rootScope.$new();
var elementHtml = '<my-directive>directive</my-directive>';
var element = $compile(elementHtml)(scope);
scope.$digest();
// I haven't found an "event" to know when the compile/digest end
setTimeout(function () {
if (element.tagName == "my-directive".toUpperCase()) throw Error("Directive is not compiled");
callback(element);
}, 0.05*1000); // HACK: change it accordingly to your system/code
};
it("is compiled", function () {
expect(element).toBeDefined();
expect(element.tagName).not.toEqual("my-directive".toUpperCase());
});
I hope this example helps someone else.

$exceptionHandler
Any uncaught exception in AngularJS expressions is delegated to this
service. The default implementation simply delegates to $log.error
which logs it into the browser console.
In unit tests, if angular-mocks.js is loaded, this service is overridden by mock $exceptionHandler which aids in testing.
angular.
module('exceptionOverwrite', []).
factory('$exceptionHandler', ['$log', 'logErrorsToBackend', function($log, logErrorsToBackend) {
return function myExceptionHandler(exception, cause) {
logErrorsToBackend(exception, cause);
$log.warn(exception, cause);
};
}]);

Related

Attempting to work with d3.js and load a .json map file from a web server in Chrome, no errors but data are not loaded [duplicate]

I am trying to load a GeoJSON file and to draw some graphics using it as a basis with D3 v5.
The problem is that the browser is skipping over everything included inside the d3.json() call. I tried inserting breakpoints to test but the browser skips over them and I cannot figure out why.
Code snippet below.
d3.json("/trip_animate/tripData.geojson", function(data) {
console.log("It just works"); // This never logs to console.
//...all the rest
}
The code continues on from the initial console.log(), but I omitted all of it since I suspect the issue is with the d3.json call itself.
The signature of d3.json has changed from D3 v4 to v5. It has been moved from the now deprecated module d3-request to the new d3-fetch module. As of v5 D3 uses the Fetch API in favor of the older XMLHttpRequest and has in turn adopted the use of Promises to handle those asynchronous requests.
The second argument to d3.json() no longer is the callback handling the request but an optional RequestInit object. d3.json() will now return a Promise you can handle in its .then() method.
Your code thus becomes:
d3.json("/trip_animate/tripData.geojson")
.then(function(data){
// Code from your callback goes here...
});
Error handling of the call has also changed with the introduction of the Fetch API. Versions prior to v5 used the first parameter of the callback passed to d3.json() to handle errors:
d3.json(url, function(error, data) {
if (error) throw error;
// Normal handling beyond this point.
});
Since D3 v5 the promise returned by d3.json() will be rejected if an error is encountered. Hence, vanilla JS methods of handling those rejections can be applied:
Pass a rejection handler as the second argument to .then(onFulfilled, onRejected).
Use .catch(onRejected) to add a rejection handler to the promise.
Applying the second solution your code thus becomes
d3.json("/trip_animate/tripData.geojson")
.then(function(data) {
// Code from your callback goes here...
})
.catch(function(error) {
// Do some error handling.
});
Since none of the answers helped, I had to find the solution on my own that works. I am using v4 and have to stick with it. The problem was (in my case) that d3.json worked the first time, but did not work the second or third time (with a HTML dropdown).
The idea is to use the initial function, and then simply to use a second function with
let data = await d3.json("URL");
instead of
d3.json("URL", function(data) {
Therefore, the general pattern becomes:
async function drawWordcloudGraph() {
let data = await d3.json("URL");
...
}
function initialFunction() {
d3.json("URL", function (data) {
...
});
}
initialFunction();
I have tried several approaches, and only this worked. Not sure if it can be simplified, please test on your own.

How do I test a Polymer element which should fail when it attaches

I am creating an element (a router - but that is not important), that is scanning the DOM soon after it has attached for particular other custom elements. I certain cases it needs to throw an error and I want to test for these.
The test I constructed is not failing - but as far as I can make out the test has already failed before my element gets attached. I suspect it is the asynchronous nature of things.
Here is the snippet of the test in question. The test fixture in question contains elements that will cause one of the elements to fail after a 'dom-change' event happens (which it has a listener for) when it then scans the dom for other things.
it('should fail if two route elements both designate thenselves as home', function(done) {
var t= document.getElementById('multiple_home');
function multiple () {
t.create();
}
expect(multiple).to.throw(Error);
t.restore();
done();
});
I think the problem is related to the fact that the fixture is created in multiple, but hasn't yet failed by the time multiple exits. I am wondering if I can pass a Promise to expect - except I am not sure how to turn mulitple into a Promise to try it out.
I eventually found a way, but it requires instrumenting the element a bit to support this.
In the elements "created" callback I create a Promise and store the two functions to resolve and reject it in "this" variables - thus:-
this.statusPromise = new Promise(function(resolve,reject){
this.statusResolver = resolve;
this.statusRejector = reject;
}.bind(this));
In the DOM parsing section I use a try catch block like this
try {
//parse the dom throwing errors if anything bad happens
this.statusResolver('Any useful value I like');
} catch (error) {
this.statusRejector(error);
}
I then made a function that returns the promise
domOK: function() {
return this.statusPromise;
}
Finally in my test I was now able to test something like this (I load the fixture in each test, rather than a beforeEach, because I am using a different fixture for each test. I do clear it down again in an afterEach). Note the use of the .then and .catch functions from the Promise.
it('should fail if two route elements declare the same path name',function(done){
t = document.getElementById('multiple_path');
t.create();
r = document.getElementById('router')
r.domOK().then(function(status){
//We should not get here throw an error
assert.fail('Did not error - status is: ' + status);
done();
}).catch(function(error){
expect(error.message).to.equal('There are two nodes with the same name: /user');
done();
});

Returning promise from reflux store

I'm working on my first react/reflux app so I may be approaching this problem in completely the wrong way. I'm trying to return a promise from a reflux store's action handler. This is the minimum code that represents how I'm trying to do this. If I display this in the browser, I get an error saying that the promise is never caught, because the result of the onLogin function is not passed back when the action is initiated. What is the best way to do this?
var Reflux = require('reflux');
var React = require('react/addons')
const Action = Reflux.createAction();
const Store = Reflux.createStore({
init: function() {
this.listenTo(Action, this.onAction);
},
onAction: function(username, password) {
var p = new Promise((resolve, reject) => {
reject('Bad password');
});
return p;
}
});
var LoginForm = React.createClass({
mixins: [Reflux.connect(Store, 'store')],
login: function() {
Action('nate', 'password1').catch(function(e) {
console.log(e); // This line is never executed
});
},
render: function() {
return (
<a onClick={this.login} href="#">login</a>
)
}
});
React.render(<LoginForm />, document.body);
Several things seem a bit confused here.
Reflux.connect(Store, 'store') is a shorthand for listening to the provided store, and automatically set the "store" property of your component state to whatever is passed in your store's this.trigger() call. However, your store never calls this.trigger so "store" in your component's state will never be updated. Returning a value from your store's action handlers doesn't trigger an update.
Stores should listen to actions to update their internal state, and typically then broadcast this state update by calling this.trigger. No component is going to get your returned promise from the store's onAction unless it explicitly calls Store.onAction (and then it doesn't matter if the actual action was invoked or not).
Async work should typically happen in the action's preEmit hook, not in the store. You should then also declare the action as async in createAction by setting the asyncResult option to true to automatically create "completed" and "failed" child actions. Check out the Reflux documentation here to learn about async events. Async actions automatically return promises, whose resolve and reject are called when the "completed" and "failed" sub-actions are called respectively. This is a bit opinionated, but that is definitely what I perceive is the intended Reflux way.

unit testing : http call in controller inside directive

describe("File Upload directive", function() {
var elm, scope,httpBackend , controller , isolateScope;
beforeEach(angular.mock.module("fileApp"));
beforeEach(angular.mock.inject(function($rootScope, $compile , $httpBackend) {
elm = angular.element("<div file-upload base-upload-url='files/' ></div>");
httpBackend = $httpBackend;
scope = $rootScope;
httpBackend.whenGET("fileUploadTemplate.html").respond(true);
$compile(elm)(scope);
controller = elm.controller;
scope.$digest();
}));
it("should upload selected file",function(){
httpBackend.flush();
isolateScope = elm.isolateScope();
expect(isolateScope).toBeDefined();
isolateScope.selectedFiles=[{"webkitRelativePath":"","lastModifiedDate":"2014-05-26T11:15:55.000Z","name":"a.csv","type":"text/csv","size":131}];
var index = 0;
httpBackend.expectPOST("files/").respond({"fileId":67603});
expect(isolateScope.uploadResult.length).toBe(1);
//httpBackend.flush();
});
});
Now, here I have situations:
If I comment out httpBackend.flush() at the start of it block , isolateScope is undefined.
If I comment out httpBackend.flush() at the end of it block , the isolateScope.uploadResult.length = 0 which suggests httpPOST didn't execute correctly as expected (httpBackend.flush is never called after the http call).
3.If I keep both httpBackend.flush() (which is not supposed to be done) , I get error as expected : Error: No pending request to flush !.
Can anyone please guide me ? I am a newbie to karma.
Thanks in advance.
Try adding these lines too in describe block.
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
or you might have not called the function in directive that instantiated the post call, otherwise it should work.

Dojo - ReferenceError exception in promise being swallowed

In jQuery, if you make a mistake in your ajax callback method, you will get a proper console error message and stacktrace.
$.get("https://api.github.com/users/octocat/orgs", function() {
var a = FAIL;
});
However, in dojo using dojo/request/xhr it seems these dumb mistakes are being swallowed completely. The only thing in my console when I run this is "then" and "always".
require(["dojo/request/xhr" ], function(xhr) {
var promise = xhr.get("https://api.github.com/users/octocat/orgs");
promise.then(function(data) {
console.log('then');
var a = FAIL;
console.log('goodbye');
}, function() {
console.log('error');
});
promise.otherwise(function() {
console.log('otherwise');
});
promise.always(function() {
console.log('always');
});
});
Using the deprecated dojo.xhrGet, the problem is very slightly improved. I get a console error message and my error handler is called but it only says "ReferenceError {}" and provides me with a stack trace that never points to a function I own:
dojo.xhrGet({
url: "https://api.github.com/users/octocat/orgs",
load: function() {
console.log('dojo.xhrGet.load');
var a = FAIL;
console.log('goodbye dojo.xhrGet.load');
},
error: function() {
console.log('dojo.xhrGet.error');
},
handle: function() {
console.log('dojo.xhrGet.handle');
}
});
When writing a program we make mistakes, it's nice that we have tools like chrome developer tools to point us to those mistakes. The time it takes to find an error when you can see a stacktrace and error message is obviously much quicker than if you get no feedback. I get no feedback in dojo, I can't believe that such a popular library could operate in this way. What am I doing wrong?
The understanding of promises which you inherited from jQuery is fundamentally different to the one everyone else (check Promises/a+ implementations) has. For the rest of this answer I will talk about promises/a+ compliant promises. Dojo's Deferred actually isn't a+ compliant, but it's close enough that everything I discuss here applies equally well.
Promises are immutable, you cannot change a promises state by calling then. A promise represents an eventual value, it would be nonsensical to be able to change the promise by saying "once the value is ready, do this".
So then, hopefully that explains why your error handler is not invoked, but the basic idea, of catching errors, is still totally possible. You just need to use return values. When you call then on a promise, it returns a new and (almost always) different promise. This new promise is very special, if the original is resolved, and the success handler you passed is invoked, and that returns something, that something will be the resolution value of the second promise.
Equally, if the error handler (on the first promise) is triggered, and that function returns something, that something will be the resolution value of the second promise. The same is true for thrown errors, they are passed to the error handler (of the second promise!).
So here's your first code sample written in a more promises/a+ way :
require(["dojo/request/xhr" ], function(xhr) {
var promise = xhr.get("https://api.github.com/users/octocat/orgs");
promise.then(function(data) {
console.log('then');
var a = FAIL;
console.log('goodbye');
}, function() {
console.log('error');
}).then(null, function() {
console.log('otherwise');
});
promise.always(function() {
console.log('always');
});
});
I don't really understand what you want to do with the always function, so I wasn't sure where to place that one. On the subject of call stacks, I would recommend checking out the Q promise library which has incredibly advanced asynchronous call stack support.
In dojoConfig set useDeferredInstrumentation: true. Here's an example.
<script>
var dojoConfig = {
useDeferredInstrumentation: true
};
</script>
<script src="js/lib/dojo/dojo.js.uncompressed.js"></script>
This gives a fairly functional error message and stacktrace output on console.error:
ReferenceError {} "ReferenceError: FAIL is not defined
at http://fiddle.jshell.net/gNdCb/2/show/:25:17
at signalListener (http://ajax.googleapis.com/ajax/libs/dojo/1.9.0/dojo/dojo.js.uncompressed.js:14205:21)
at signalWaiting (http://ajax.googleapis.com/ajax/libs/dojo/1.9.0/dojo/dojo.js.uncompressed.js:14196:4)
at resolve (http://ajax.googleapis.com/ajax/libs/dojo/1.9.0/dojo/dojo.js.uncompressed.js:14360:5)
at signalDeferred (http://ajax.googleapis.com/ajax/libs/dojo/1.9.0/dojo/dojo.js.uncompressed.js:14249:15)
at signalListener (http://ajax.googleapis.com/ajax/libs/dojo/1.9.0/dojo/dojo.js.uncompressed.js:14220:6)
at signalWaiting (http://ajax.googleapis.com/ajax/libs/dojo/1.9.0/dojo/dojo.js.uncompressed.js:14196:4)
at resolve (http://ajax.googleapis.com/ajax/libs/dojo/1.9.0/dojo/dojo.js.uncompressed.js:14360:5)
at signalDeferred (http://ajax.googleapis.com/ajax/libs/dojo/1.9.0/dojo/dojo.js.uncompressed.js:14249:15)
at signalListener (http://ajax.googleapis.com/ajax/libs/dojo/1.9.0/dojo/dojo.js.uncompressed.js:14226:4)
----------------------------------------
rejected at signalDeferred (http://ajax.googleapis.com/ajax/libs/dojo/1.9.0/dojo/dojo.js.uncompressed.js:14252:15)
at signalListener (http://ajax.googleapis.com/ajax/libs/dojo/1.9.0/dojo/dojo.js.uncompressed.js:14223:5)
at signalWaiting (http://ajax.googleapis.com/ajax/libs/dojo/1.9.0/dojo/dojo.js.uncompressed.js:14196:4)
at resolve (http://ajax.googleapis.com/ajax/libs/dojo/1.9.0/dojo/dojo.js.uncompressed.js:14360:5)
at signalDeferred (http://ajax.googleapis.com/ajax/libs/dojo/1.9.0/dojo/dojo.js.uncompressed.js:14249:15)
at signalListener (http://ajax.googleapis.com/ajax/libs/dojo/1.9.0/dojo/dojo.js.uncompressed.js:14220:6)
at signalWaiting (http://ajax.googleapis.com/ajax/libs/dojo/1.9.0/dojo/dojo.js.uncompressed.js:14196:4)
at resolve (http://ajax.googleapis.com/ajax/libs/dojo/1.9.0/dojo/dojo.js.uncompressed.js:14360:5)
at signalDeferred (http://ajax.googleapis.com/ajax/libs/dojo/1.9.0/dojo/dojo.js.uncompressed.js:14249:15)
at signalListener (http://ajax.googleapis.com/ajax/libs/dojo/1.9.0/dojo/dojo.js.uncompressed.js:14226:4)
----------------------------------------
Error
at Promise.then.promise.then (http://ajax.googleapis.com/ajax/libs/dojo/1.9.0/dojo/dojo.js.uncompressed.js:14420:24)
at http://fiddle.jshell.net/gNdCb/2/show/:23:13
at runFactory (http://ajax.googleapis.com/ajax/libs/dojo/1.9.0/dojo/dojo.js.uncompressed.js:1117:43)
at execModule (http://ajax.googleapis.com/ajax/libs/dojo/1.9.0/dojo/dojo.js.uncompressed.js:1245:5)
at http://ajax.googleapis.com/ajax/libs/dojo/1.9.0/dojo/dojo.js.uncompressed.js:812:7
at guardCheckComplete (http://ajax.googleapis.com/ajax/libs/dojo/1.9.0/dojo/dojo.js.uncompressed.js:1260:5)
at contextRequire (http://ajax.googleapis.com/ajax/libs/dojo/1.9.0/dojo/dojo.js.uncompressed.js:811:6)
at req (http://ajax.googleapis.com/ajax/libs/dojo/1.9.0/dojo/dojo.js.uncompressed.js:137:11)
at http://fiddle.jshell.net/gNdCb/2/show/:21:1"
I had very specific needs in that I needed the exception to hit the native catch clause that the browser implements. Nevermind why I needed this, But I used something like this:
function scream(func) {
return function() {
var args = arguments;
setTimeout(function(){
func.apply(null, args);
}, 0);
};
}
Then, to use it
var promise = xhr.get("https://api.github.com/users/octocat/orgs");
promise.then(scream(function(data) {
//do stuff
}));
By using setTimeout, you execute the function on the browsers event queue, making it impossible for dojo to swallow your exception. But, in general this is a bad solution because:
it changes part of the stack trace
it changes a part of your code which previous executed synchronously to asynchronously, which can change program behavior
you can't chain multiple .then() promise objects to the return value, which is one of the really nice things about promises.
Anyway, I'm just presenting it as option.