englercj / resource-loader

A middleware-style generic resource loader built with web games in mind.
http://englercj.github.io/resource-loader/
MIT License
424 stars 77 forks source link

Unit testing resource-loader on node.js #108

Closed soanvig closed 6 years ago

soanvig commented 6 years ago

Setup

I use pixi.js, which uses resource-loader. I have my own things in my app, and one of them is loader, which uses pixi.js loader. I use Jest for unit testing.

Issue

Because Jest works in node.js enviroment, I cannot get resource-loader to work. No errors, it just doesn't finish loading (and callback is not called). My suspect is XHR requests are not working.

Moreover: mocking GET request with xhr-mock doesn't work - request is not interrupted. I have even tested pure resource-loader with the following code:

      const l = new Loader();
      l.add('foo', 'data:image/gif;base64,R0lGODlhAQABAPAAAP8REf///yH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==');
      l.onProgress.add(() => { console.log('progress'); });
      l.onError.add(() => { console.log('error'); });
      l.onLoad.add(() => { console.log('load'); });
      l.onComplete.add(() => { console.log('complete'); });
      l.load(() => {
        done();
      });

where that base64 is image taken from resource-loader tests. And no done() is called, neither console logs are executed. Just nothing works. resource-loader tests are written using Karma, and seem to use Chrome as browser, to that may be, why node.js test don't work.

And it's not uncommon case to use some resource-loader on node.js. I imagine some node.js games.

Any ideas what can I do? It makes any unit testing pointless, if I cannot load resources.

englercj commented 6 years ago

First thing I would do is debug why the callbacks aren't getting called. Should be able to see in Resource.js where xhr is created, dispatched, and used when it calls back.

IIRC node doesn't have XHR, Image, Video, or any of the other loading mechanisms that resource-loader uses so not sure how you are poly-filling those but you could check if those work as well.

soanvig commented 6 years ago

I'm not polyfilling them. So it seems that node enviroment lacks too much browser features, and that's why it doesn't work. I should switch to another testing enviroment :/

EDIT: But Jest uses jsdom, so most of things should work. I gonna test things.

soanvig commented 6 years ago
loader.add('foo', 'data:[...]');
loader.loading -> false;
loader._queue.length() -> 1;
loader._queue.idle() -> false;
loader.load();
loader.onStart -> works;

This is what I've debugged. But I logged out loader.resources.foo, and I got:

Resource {
      _flags: 1,
      name: 'foo',
      url: 'data:image/gif;base64,R0lGODlhAQABAPAAAP8REf///yH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==',
      extension: 'gif',
      data: null,
      crossOrigin: undefined,
      loadType: 2,
      xhrType: undefined,
      metadata: {},
      error: null,
      xhr: null,
      children: [],
      type: 0,
      progressChunk: 0,
      _dequeue: [Function: _noop],
      _onLoadBinding: null,
      _boundComplete: [Function: bound complete],
      _boundOnError: [Function: bound _onError],
      _boundOnProgress: [Function: bound _onProgress],
      _boundXhrOnError: [Function: bound _xhrOnError],
      _boundXhrOnAbort: [Function: bound _xhrOnAbort],
      _boundXhrOnLoad: [Function: bound _xhrOnLoad],
      _boundXdrOnTimeout: [Function: bound _xdrOnTimeout],
      onStart: MiniSignal { _tail: undefined, _head: undefined },
      onProgress: MiniSignal { _tail: undefined, _head: undefined },
      onComplete: MiniSignal { _tail: undefined, _head: undefined },
      onAfterMiddleware: MiniSignal { _tail: undefined, _head: undefined } }

That null xhr makes me wondering if everything is OK. This is Resource before starting loading.

soanvig commented 6 years ago

loader.resources.foo.onStart works. loader.resources.foo.onProgress no.

After starting loading Resource is:

Resource {                                                              
  _flags: 5,                                                            
  name: 'foo',                                                          
  url: 'data:image/gif;base64,R0lGODlhAQABAPAAAP8REf///yH5BAAAAAAALAAAAA
  extension: 'gif',                                                     
  data: HTMLImageElement {},                                            
  crossOrigin: '',                                                      
  loadType: 2,                                                          
  xhrType: undefined,                                                   
  metadata: {},                                                         
  error: null,                                                          
  xhr: null,                                                            
  children: [],                                                         
  type: 3,                                                              
  progressChunk: 100,                                                   
  _dequeue: [Function: onceWrapper],                                    
  _onLoadBinding:                                                       
   MiniSignalBinding {                                                  
     _fn: [Function: _onLoad],                                          
     _once: true,                                                       
     _thisArg:                                                          
      Loader {                                                          
        baseUrl: '',                                                    
        progress: 0,                                                    
        loading: true,                                                  
        defaultQueryString: '',                                         
        _beforeMiddleware: [],                                          
        _afterMiddleware: [],                                           
        _resourcesParsing: [],                                          
        _boundLoadResource: [Function],                                 
        _queue: [Object],                                               
        resources: [Object],                                            
        onProgress: [Object],                                           
        onError: [Object],                                              
        onLoad: [Object],                                               
        onStart: [Object],                                              
        onComplete: [Object] },                                         
     _owner: MiniSignal { _tail: [Circular], _head: [Circular] },       
     _prev: null,                                                       
     _next: null },                                                     
  _boundComplete: [Function: bound complete],                           
  _boundOnError: [Function: bound _onError],                            
  _boundOnProgress: [Function: bound _onProgress],                      
  _boundXhrOnError: [Function: bound _xhrOnError],                      
  _boundXhrOnAbort: [Function: bound _xhrOnAbort],                      
  _boundXhrOnLoad: [Function: bound _xhrOnLoad],                        
  _boundXdrOnTimeout: [Function: bound _xdrOnTimeout],                  
  onStart:                                                              
   MiniSignal {                                                         
     _tail:                                                             
      MiniSignalBinding {                                               
        _fn: [Function],                                                
        _once: false,                                                   
        _thisArg: null,                                                 
        _owner: [Circular],                                             
        _prev: null,                                                    
        _next: null },                                                  
     _head:                                                             
      MiniSignalBinding {                                               
        _fn: [Function],                                                
        _once: false,                                                   
        _thisArg: null,                                                 
        _owner: [Circular],                                             
        _prev: null,                                                    
        _next: null } },                                                
  onProgress:                                                           
   MiniSignal {                                                         
     _tail:                                                             
      MiniSignalBinding {                                               
        _fn: [Function],                                                
        _once: false,                                                   
        _thisArg: null,                                                 
        _owner: [Circular],                                             
        _prev: null,                                                    
        _next: null },                                                  
     _head:                                                             
      MiniSignalBinding {                                               
        _fn: [Function],                                                
        _once: false,                                                   
        _thisArg: null,                                                 
        _owner: [Circular],                                             
        _prev: null,                                                    
        _next: null } },                                                
  onComplete:                                                           
   MiniSignal {                                                         
     _tail:                                                             
      MiniSignalBinding {                                               
        _fn: [Function: _onLoad],                                       
        _once: true,                                                    
        _thisArg: [Object],                                             
        _owner: [Circular],                                             
        _prev: null,                                                    
        _next: null },                                                  
     _head:                                                             
      MiniSignalBinding {                                               
        _fn: [Function: _onLoad],                                       
        _once: true,                                                    
        _thisArg: [Object],                                             
        _owner: [Circular],                                             
        _prev: null,                                                    
        _next: null } },                                                
  onAfterMiddleware: MiniSignal { _tail: undefined, _head: undefined } }

So it seems, that data has been set to Image, as it should. So polyfill provided by jsdom work.

soanvig commented 6 years ago

I have clearly no idea what's going on. The lib's code is a bit twisted too...

soanvig commented 6 years ago

I have created GitHub repo with sample configuration: https://github.com/soanvig/resource-loader-node

Without jsdom of course I get Image() or window is undefined, but after adding it - it works. What does not work, and yields no error is the loading itself.

I fiddled around with overwriting boundxhr variables (progress, error), but literally nothing happened.

englercj commented 6 years ago

I ran your code example and attached a node debugger. I stepped through resource-loader and everything is working correctly. Image just never calls the load or error callbacks.

A quick search shows that jsdom doesn't load anything unless you configure it to:

https://github.com/jsdom/jsdom/issues/1816#issuecomment-310106280

Closing this since it is a jsdom/Jest configuration issue.

soanvig commented 6 years ago

You were extremelly generous to me. Thank You, sir!