Re: Using Angular $http service as documentLoader

Hi Dave

Thanks very much for such a detailed walkthrough. And sorry about a
sloppy fiddle. I guess I was a little bit in a hurry.

Anyways, mixing your suggestions into my actual code I managed to wrap
jsonld.js in a promise by using the a deferred object from $q.defer
and rejecting/resolving in jsonld's callbacks:

I've quickly bumped into a problem with the $httpBackend mock. When
jsonld tries to get a remote document, which references another remote
context, the $httpBackend.flush() fails with message
'[$rootScope:inprog] $digest already in progress'. That has something
to do with

On a second thought it occured to me that there may not be any a good
reason to use the $http service fo documentLoader. Do you think?

However I still wanted to be able to mock remote calls and fiddling
some more I simply do the callback with manually preset responses.
Also I found what it takes to wrap jsonld in "angular-friendly"
promises... If you're interested do have a look at my updated code [1]
(still didn't use jasmine-as-promised, because I haven't found a
CORS-happy version). The important bits is the use of $q.defer() and
$rootScope.$apply().

Maybe this helps someone :)

Regards,
Tom

[1] http://jsbin.com/besav/5/edit?js,console,output

On Thu, Jun 19, 2014 at 6:20 PM, Dave Longley
<dlongley@digitalbazaar.com> wrote:
> Hi Tom,
>
> There were several problems with the fiddle. I've updated it to
> something that works [1] with some caveats.
>
> So, first, in the original fiddle, there were some trivial problems:
>
> 1. Setting jsonld.documentLoader was commented out.
> 2. The loader did not return anything (it needed to return a promise).
>
> Then, there were more complicated problems:
>
> The JSON-LD API is asynchronous and, for whatever reason,
> Jasmine/AngularJS-mocks is not playing nicely with it. This results in a
> variety of issues:
>
> 1. In order for the promise returned by $http.get() to get resolved, you
> must call $httpBackend.flush() -- but you must only do so *after*
> $http.get() has been called. Since you don't know which turn of the
> event loop the document loader will be called on, you can't do this
> easily. That means either breaking some abstractions and passing
> $httpBackend to an initialization method on the controller or setting
> some vars and calling wait functions to watch for them to change. It's a
> mess. To be clear: when you call promises.expand(), it, in effect,
> "schedules" the expansion algorithm to be run later and returns
> immediately. Similarly scheduling occurs with the document loader at
> some later point. That means that when the TestController constructor
> returns (and therefore, when beforeEach returns), none of the URL
> loading/expansion/etc. has been called yet.
>
> 2. Promises that are external to Jasmine and AngularJS-mocks don't seem
> to get resolved. This may be due to whatever Jasmine/AngularJS-mocks
> tries to do to manage scheduling when various bits of JS are run. Since
> it doesn't have control over external APIs (eg: JSON-LD), however, this
> is causing some kind of interference with its normal operation. If you
> replace the JSON-LD promises API with a callback-based one, as I have in
> the fiddle link [1], then everything works just fine. I don't know
> enough about/haven't explored the internals of Jasmine or
> AngularJS-mocks to figure out exactly what's going on here.
>
> It may be that the real solution lies in using some different tools --
> either something that is intended to make Jasmine work with promises
> (see jasmine-as-promised [2]) or with some entirely different test suite
> that plays more nicely (maybe mocha).
>
> Anyway, with some really ugly hacks, that fiddle works now.
>
> -Dave
>
> [1] http://jsfiddle.net/R5acr/13/
> [2] https://www.npmjs.org/package/jasmine-as-promised
>
> On 06/19/2014 07:23 AM, Tomasz Pluskiewicz wrote:
>> On Thu, Jun 19, 2014 at 1:10 AM, Dave Longley
>> <dlongley@digitalbazaar.com> wrote:
>>> On 06/18/2014 05:20 PM, Tomasz Pluskiewicz wrote:
>>>> Hi
>>>>
>>>> I'm trying to use the jsonld with Angular and in tests I want to
>>>> replace the default documentLoader with $http service so that I can
>>>> mock the responses.
>>>>
>>>> I assumed that because $http already returns a promise it would be
>>>> enough to simply use a function like
>>>>
>>>> jsonld.documentLoader = function (url) {
>>>>    return $http.get(url);
>>>> }
>>>>
>>>> as replacement for whatever is currently set.
>>>>
>>>> Unfortunately it doesn't work. What's would be correct way to do that?
>>>
>>> That's because the value that promise resolves to isn't what is required
>>> by the JSON-LD API spec. It requires that the promise resolve to a
>>> "RemoteDocument":
>>>
>>> http://www.w3.org/TR/json-ld-api/#idl-def-RemoteDocument
>>>
>>> I didn't test this, but it should be pretty close to what you want:
>>>
>>> jsonld.documentLoader = function(url) {
>>>   return $http.get(url).then(function(response) {
>>>     return {
>>>       contextUrl: null,
>>>       document: response.data,
>>>       documentUrl: url
>>>     }
>>>   });
>>> };
>>>
>>> Hopefully that works for you.
>>>
>>
>> Unfortunately it's not that easy. I'm not very experienced with
>> JavaScript or Angular. I created a fiddle [1] similar to my failing
>> code.
>>
>> As is, XHR fails, because it's a CORS request. If you uncomment the
>> two lines two weird things happen (or don't happen). First, $http mock
>> fails saying there aren't any pending requests and none of the
>> console.log called in callbacks are invoked. Which is weird, because
>> it means that even the $http promise is never resolved. I tried to
>> debug it but I get lost somewhere inside jsonld/angular. Also there
>> isn't any other exception being caught bu Chrome dev tools.
>>
>> Any ideas? Hasn't someone already done that?
>>
>> Thanks,
>> Tom
>>
>> [1] http://jsfiddle.net/tpluscode/R5acr/12/
>>
>>>
>>> -Dave
>>>
>>> --
>>> Dave Longley
>>> CTO
>>> Digital Bazaar, Inc.
>>>
>>
>>
>
>
> --
> Dave Longley
> CTO
> Digital Bazaar, Inc.
>

Received on Tuesday, 24 June 2014 12:48:46 UTC