hyprstack
hyprstack

Reputation: 4228

Backbone - Test method in view that uses ReadFile

I have written a backbone view which takes a file object or blob as an option in instantiation and then checks that file for EXIF data, corrects orientation and resizes the image if necessary depending on the options passed in.

Within the view there is a function mainFn which takes the file object and calls all other subsequent functions.

My issue is how to I test mainFn that uses ReadFile and an image constructor?

For my test set-up I am using mocah, chai, sinon and phantomjs.

In my sample code I have removed all other functions as to not add unnecessary clutter. If you wish to see the whole view visit its github repository.

var imageUpLoad = Backbone.View.extend({

    template: _.template(document.getElementById("file-uploader-template").innerHTML),

    // global variables passed in through options - required
    _file: null, // our target file
    cb: null,
    maxFileSize: null, // megabytes
    maxHeight: null, // pixels - resize target
    maxWidth: null, // pixels - resize target
    minWidth: null, // pixels
    maxAllowedHeight: null, //pixels
    maxAllowedWidth: null, // pixels

    // globals determined through function
    sourceWidth: null,
    sourceHeight: null,

    initialize: function (options) {

        this._file = options.file;
        this.cb = options.cb;
        this.maxHeight = options.maxHeight;
        this.maxWidth = options.maxWidth;
        this.maxFileSize = options.maxFileSize;
        this.minWidth = options.minWidth;
        this.maxAllowedHeight = options.maxAllowedHeight;
        this.maxAllowedWidth = options.maxAllowedWidth;

    },

    render: function () {

        this.setElement(this.template());

        this.mainFn(this._file);

        return this;
    },

    // returns the width and height of the source file and calls the transform function
    mainFn: function (file) {

        var fr = new FileReader();

        var that = this;

        fr.onloadend = function () {

            var _img = new Image();

            // image width and height can only be determined once the image has loaded
            _img.onload = function () {
                that.sourceWidth = _img.width;
                that.sourceHeight = _img.height;
                that.transformImg(file);
            };

            _img.src = fr.result;
        };

        fr.readAsDataURL(file);
    }
});

My test set-up

describe("image-upload view", function () {

    before(function () {

        // create test fixture
        this.$fixture = $('<div id="image-view-fixture"></div><div>');

    });

    beforeEach(function () {

        // fake image
        this.b64DataJPG = '/9j/4AAQSkZJRgABAQEAYABgAAD/4QAiRXhpZgAASUkqAAgAAA' +
            'ABABIBAwABAAAABgASAAAAAAD/2wBDAAEBAQEBAQEBAQEBAQEB' +
            'AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQ' +
            'EBAQEBAQEBAQEBAQH/2wBDAQEBAQEBAQEBAQEBAQEBAQEBAQEB' +
            'AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQ' +
            'EBAQEBAQH/wAARCAABAAIDASIAAhEBAxEB/8QAHwAAAQUBAQEB' +
            'AQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBA' +
            'QAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAk' +
            'M2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1' +
            'hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKj' +
            'pKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+' +
            'Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAA' +
            'AAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAx' +
            'EEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl' +
            '8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2' +
            'hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmq' +
            'srO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8v' +
            'P09fb3+Pn6/9oADAMBAAIRAxEAPwD+/iiiigD/2Q==';


        var b64toBlob = function (b64Data, contentType, sliceSize) {
            contentType = contentType || '';
            sliceSize = sliceSize || 512;

            var input = b64Data.replace(/\s/g, '');

            var byteCharacters = atob(b64Data);
            var byteArrays = [];

            for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {
                var slice = byteCharacters.slice(offset, offset + sliceSize);

                var byteNumbers = new Array(slice.length);
                for (var i = 0; i < slice.length; i++) {
                    byteNumbers[i] = slice.charCodeAt(i);
                }

                var byteArray = new Uint8Array(byteNumbers);

                byteArrays.push(byteArray);
            }

            try{
                var blob = new Blob( byteArrays, {type : contentType});
            }
            catch(e){
                // TypeError old chrome and FF
                window.BlobBuilder = window.BlobBuilder ||
                    window.WebKitBlobBuilder ||
                    window.MozBlobBuilder ||
                    window.MSBlobBuilder;
                if(e.name == 'TypeError' && window.BlobBuilder){
                    var bb = new BlobBuilder();
                    bb.append(byteArrays);
                    blob = bb.getBlob(contentType);
                }
                else if(e.name == "InvalidStateError"){
                    // InvalidStateError (tested on FF13 WinXP)
                    blob = new Blob(byteArrays, {type : contentType});
                }
                else{
                    // We're screwed, blob constructor unsupported entirely
                }
            }
            return blob;
        };

        this.blobJPG = b64toBlob(this.b64DataJPG, "image/jpg");
        /* **************** */

        this.$fixture.empty().appendTo($("#fixtures"));

        this.view = new imageUpLoad({
            file: this.blobJPG,
            cb: function (url) {console.log(url);},
            maxFileSize: 500000,
            minWidth: 200,
            maxHeight: 900,
            maxWidth: 1000,
            maxAllowedHeight: 4300,
            maxAllowedWidth: 1000
        });

        this.renderSpy = sinon.spy(this.view, "render");
        this.readFileDataStub = sinon.stub(this.view, 'readFileData');
        this.resizeImageStub = sinon.stub(this.view, 'resizeImage');
        this.returnDataUrlStub = sinon.stub(this.view, 'returnDataUrl');
        this.mainFnSpy = sinon.spy(this.view, 'mainFn');
        this.transformImgStub = sinon.stub(this.view, 'transformImg');
        this.sizeConfigStub = sinon.stub(this.view, 'sizeConfig');
        this.resizeConfStub = sinon.stub(this.view, 'resizeConf');
        this.callbackSpy = sinon.spy();


    });

    afterEach(function () {
        this.renderSpy.restore();
        this.readFileDataStub.restore();
        this.resizeImageStub.restore();
        this.returnDataUrlStub.restore();
        this.mainFnSpy.restore();
        this.sizeConfigStub.restore();
        this.resizeConfStub.restore();
        this.transformImgStub.restore();
    });

    after(function () {
        $("#fixtures").empty();
    });

    it("can render", function () {

        var _view = this.view.render();

        expect(this.renderSpy).to.have.been.called;
        expect(this.view).to.equal(_view);
    });
});

Upvotes: 0

Views: 444

Answers (1)

Richard Scarrott
Richard Scarrott

Reputation: 7053

You could either mock the FileReader / Image on the window, e.g.

// beforeEach
var _FileReader = window.FileReader;
window.FileReader = sinon.stub().return('whatever');

// afterEach
window.FileReader = _FileReader;

Or reference the constructor on the instance, e.g.

// view.js
var View = Backbone.View.extend({
    FileReader: window.FileReader,
    mainFn: function() {
        var fileReader = new this.FileReader();
    }
});

// view.spec.js
sinon.stub(this.view, 'FileReader').return('whatever');

Personally I'd prefer the latter as there's no risk of breaking the global reference if, for example, you forget to reassign the original value.

Upvotes: 1

Related Questions