Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add XMLHttpRequestUpload support #26

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,14 @@ Get the request headers.

Get the request body.

#### .progress(loaded : number, total : number, lengthComputable : bool)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for the fix here. do you think this should have been implemented on the response interface?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what do you think about

req.progress(...) //dispatches xhr.upload.onprogress
res.progress(...) //dispatches xhr.onprogress

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good suggestion. I like the symmetry of having the upload related events on the request and the download related events on the response.


Trigger progress event. Pass in loaded size, total size and if event is lengthComputable.

#### .uploadProgress(loaded : number, total : number, lengthComputable : bool)

Trigger progress event on the upload object. Pass in loaded size, total size and if event is lengthComputable.

### MockResponse

#### .status() : number
Expand Down Expand Up @@ -184,10 +192,6 @@ Get whether the response will trigger a time out.

Set whether the response will trigger a time out. `timeout` defaults to the value set on the XHR object.

#### .progress(loaded : number, total : number, lengthComputable : bool)

Trigger progress event. Pass in loaded size, total size and if event is lengthComputable.

## Change log

### 1.9.0
Expand Down
51 changes: 51 additions & 0 deletions lib/MockEventTarget.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* An EventTarget object
* @constructor
*/
function MockEventTarget() {
this._eventListeners = [];
}


/**
* Trigger an event
* @param {String} event
* @param {Object} eventDetails
* @returns {MockEventTarget}
*/
MockEventTarget.prototype.trigger = function(event, eventDetails) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be dispatchEvent?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

trigger seems clear enough

for (var x = 0; x < this._eventListeners.length; x++) {
var eventListener = this._eventListeners[x];

if (eventListener.event === event) {
var eventListenerDetails = eventDetails || {};
eventListenerDetails.currentTarget = this;
eventListenerDetails.type = event;
eventListener.listener.call(this, eventListenerDetails);
}
}

return this;
};

MockEventTarget.prototype.addEventListener = function(event, listener) {
this._eventListeners.push({
event: event,
listener: listener
});
};

MockEventTarget.prototype.removeEventListener = function(event, listener) {
var currentIndex = 0;

while (currentIndex < this._eventListeners.length) {
var eventListener = this._eventListeners[currentIndex];
if (eventListener.event === event && eventListener.listener === listener) {
this._eventListeners.splice(currentIndex, 1);
} else {
currentIndex++;
}
}
};

module.exports = MockEventTarget;
16 changes: 16 additions & 0 deletions lib/MockRequest.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,20 @@ MockRequest.prototype.progress = function(loaded, total, lengthComputable) {
})
};


/**
* Trigger upload progress event
* @param {number} [loaded]
* @param {number} [total]
* @param {boolean} [lengthComputable]
* @returns {}
*/
MockRequest.prototype.uploadProgress = function(loaded, total, lengthComputable) {
this._xhr.upload.trigger('progress', {
lengthComputable: lengthComputable || true,
loaded: loaded,
total: total
})
};

module.exports = MockRequest;
43 changes: 10 additions & 33 deletions lib/MockXMLHttpRequest.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
var MockRequest = require('./MockRequest');
var MockResponse = require('./MockResponse');
var MockEventTarget = require('./MockEventTarget');

var notImplementedError = new Error('This feature hasn\'t been implmented yet. Please submit an Issue or Pull Request on Github.');

Expand Down Expand Up @@ -72,14 +73,18 @@ MockXMLHttpRequest.handle = function(request) {
* @constructor
*/
function MockXMLHttpRequest() {
MockEventTarget.call(this);
this.reset();
this._eventListeners = [];
this.upload = new MockEventTarget();
this.timeout = 0;
// some libraries (like Mixpanel) use the presence of this field to check if XHR is properly supported
// https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/withCredentials
this.withCredentials = false;
}

MockXMLHttpRequest.prototype = Object.create(MockEventTarget.prototype);
MockXMLHttpRequest.prototype.constructor = MockXMLHttpRequest;

/**
* Reset the response values
* @private
Expand Down Expand Up @@ -115,18 +120,7 @@ MockXMLHttpRequest.prototype.trigger = function(event, eventDetails) {
this['on'+event]();
}

for (var x = 0; x < this._eventListeners.length; x++) {
var eventListener = this._eventListeners[x];

if (eventListener.event === event) {
var eventListenerDetails = eventDetails || {};
eventListenerDetails.currentTarget = this;
eventListenerDetails.type = event;
eventListener.listener.call(this, eventListenerDetails);
}
}

return this;
return MockEventTarget.prototype.trigger.apply(this, arguments);
};

MockXMLHttpRequest.prototype.open = function(method, url, async, user, password) {
Expand Down Expand Up @@ -171,6 +165,8 @@ MockXMLHttpRequest.prototype.send = function(data) {
}, typeof(timeout) === 'number' ? timeout : self.timeout+1);

} else {
//trigger a load event to indicate the data has been sent
self.upload.trigger('load');
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

onloadstart too? https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/upload

we should probably dispatch the other events e.g. onloadend at some stage too?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

possibly so. I didn't need any of the other events to get my tests working, but having a full lifecycle would probably be useful.


//map the response to the XHR object
self.status = response.status();
Expand Down Expand Up @@ -203,6 +199,7 @@ MockXMLHttpRequest.prototype.abort = function() {

if (this.readyState > MockXMLHttpRequest.STATE_UNSENT && this.readyState < MockXMLHttpRequest.STATE_DONE) {
this.readyState = MockXMLHttpRequest.STATE_UNSENT;
this.upload.trigger('abort');
this.trigger('abort');
}

Expand Down Expand Up @@ -233,24 +230,4 @@ MockXMLHttpRequest.prototype.getResponseHeader = function(name) {
return this._responseHeaders[name.toLowerCase()] || null;
};

MockXMLHttpRequest.prototype.addEventListener = function(event, listener) {
this._eventListeners.push({
event: event,
listener: listener
});
};

MockXMLHttpRequest.prototype.removeEventListener = function(event, listener) {
var currentIndex = 0;

while (currentIndex < this._eventListeners.length) {
var eventListener = this._eventListeners[currentIndex];
if (eventListener.event === event && eventListener.listener === listener) {
this._eventListeners.splice(currentIndex, 1);
} else {
currentIndex++;
}
}
};

module.exports = MockXMLHttpRequest;
78 changes: 78 additions & 0 deletions test/MockXMLHttpRequest.js
Original file line number Diff line number Diff line change
Expand Up @@ -262,4 +262,82 @@ describe('MockXMLHttpRequest', function() {

});

describe('upload.addEventListener()', function() {

it('should allow registering load event listener', function(done) {

MockXMLHttpRequest.addHandler(function(req, res) {
return res
});

var xhr = new MockXMLHttpRequest();
xhr.upload.addEventListener('load', function(event) {
assert.equal(event.currentTarget, xhr.upload);
assert.equal(event.type, 'load');
assert.equal(this, xhr.upload);
done();
});
xhr.open('/');
xhr.send();

});

it('should allow registering abort event listener', function(done) {

MockXMLHttpRequest.addHandler(function(req, res) {
return res
});

var xhr = new MockXMLHttpRequest();
xhr.upload.addEventListener('abort', function(event) {
assert.equal(event.currentTarget, xhr.upload);
assert.equal(this, xhr.upload);
done();
});
xhr.open('/');
xhr.send();
xhr.abort();

});

it('should allow registering progress event listener', function(done) {

MockXMLHttpRequest.addHandler(function(req, res) {
req.uploadProgress(50, 100);

return res
});

var xhr = new MockXMLHttpRequest();
xhr.upload.addEventListener('progress', function(event) {
assert.equal(event.lengthComputable, true);
assert.equal(event.loaded, 50);
assert.equal(event.total, 100);
done();
});
xhr.open('/');
xhr.send();

});

it('should allow unregistering event listener', function(done) {

MockXMLHttpRequest.addHandler(function(req, res) {
return res
});

var xhr = new MockXMLHttpRequest();
var removeLoadFunction = function () { done() }
xhr.upload.addEventListener('load', function(event) {
done();
});
xhr.upload.addEventListener('load', removeLoadFunction);
xhr.upload.removeEventListener('load', removeLoadFunction);
xhr.open('/');
xhr.send();

});

})

});