Re: Using Angular $http service as documentLoader

On 06/24/2014 08:47 AM, Tomasz Pluskiewicz wrote:
> 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.

Sure, and no problem.

> 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

That happens when $rootScope.$apply() isn't high enough in the stack. If 
it's too low, you can end up double-scheduling a $digest op. It may be 
best to just let users of your wrapper call it as needed. I don't know 
if this causes some issue with the required manual-triggering of 
promise/deferred resolution during testing though. You'd have to 
investigate further.

> 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?

Well, it's certainly not necessary; we use JSON-LD and AngularJS and 
don't bother with it. It could be useful for testing and mocks as you're 
trying to do here.

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

I hope so!

>
> 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.
>>
>


-- 
Dave Longley
CTO
Digital Bazaar, Inc.
http://digitalbazaar.com

Received on Tuesday, 24 June 2014 20:08:43 UTC