Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Testing Image onload using jsdom, sinon, mocha and chai

Can anyone help me with testing of following function

function onload(cb){
  const image = 'http://placehold.it/350x150'
  const img = new Image()
  img.src = image
  img.onload = () => {
    cb()
  }
}

In my test file Image is available via jsdom. I want that I can test that cb is calledOnce

like image 619
ritz078 Avatar asked Oct 09 '16 09:10

ritz078


1 Answers

Option 1: active external resource loading in JSDOM

Initializing JSDOM with resources: 'usable' should normally be enough with the latest API (v10+):

const jsdom = new JSDOM('<!doctype html><html><body></body></html>',{
    resources: 'usable'
});
const { window } = jsdom;

It requires the canvas (canvas or canvas-prebuilt) module as stated in JSDOM doc.


Option 2: mock window.Image to gain control over it

Using the latest canvas module, you can mock the window.Image to trigger load events.

So first create a mock for the window.Image class:

import CanvasImage from 'canvas/lib/image';
import fs from 'fs';
import path from 'path';
import { URL } from 'url';
import request from 'request';
// Where to fetch assets on the file system when path are provided.
const ASSETS_DIRECTORY = path.join(__dirname,'.');

const WindowImage = function() {    
    // Reimplemented the following class:
    // https://github.com/tmpvar/jsdom/blob/master/lib/jsdom/living/nodes/HTMLImageElement-impl.js
    // https://github.com/Automattic/node-canvas#imagesrcbuffer
    let source;
    let image;
    let onload;
    let onerror;
    return {
        set src (value) {
            // TODO Throw errors to the Image.onerror handler.
            const onDataLoaded = function (data) {
                image = new CanvasImage();
                image.onload = () => {
                    if (onload) {
                        onload(image);
                    }
                };
                image.onerror = err => {
                    if (onerror) {
                        onerror(err);
                    }
                };
                image.src = data;
                source = value;
            };
            // URL or path?
            let url;
            try {
                url = new URL(value);
            } catch (e) {}
            // Fetch the data.
            if (url) {
                request(url.href, (error, response, body) => {
                    if (response && response.statusCode !== undefined && response.statusCode !== 200) {
                        throw new Error("Status code: " + response.statusCode);
                    }
                    onDataLoaded(body);
                });
            } else {
                // Assume it is a file path: try a local file read.
                fs.readFile(path.join(ASSETS_DIRECTORY,value),function (err,data) {
                    if (err) {
                        throw err;
                    }
                    onDataLoaded(data);
                });
            }
        },
        set onload (handler) {
            onload = handler;
        },
        set onerror (handler) {
            onerror = handler;
        },
        get src () {
            return source;
        },
        // TODO Should allows to modify height and width
        // + add natural height and width
        // Cf. https://github.com/tmpvar/jsdom/blob/master/lib/jsdom/living/nodes/HTMLImageElement-impl.js#L51
        get width () {
            return image && image.width;
        },
        get height () {
            return image && image.height;
        }
    };
};

Then you can mock window.Image in your tests, by redefining the one used by JSDOM:

window.Image = WindowImage;

This is not really elegant, but it gives you control over how image data is loaded, as canvas.Image.src can normally take any Buffer.

like image 60
Yoan Tournade Avatar answered Nov 14 '22 23:11

Yoan Tournade