
committed by
Kara Erickson

parent
7b3bcc23af
commit
5eb7426216
381
packages/zone.js/test/browser/XMLHttpRequest.spec.ts
Normal file
381
packages/zone.js/test/browser/XMLHttpRequest.spec.ts
Normal file
@ -0,0 +1,381 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ifEnvSupports, ifEnvSupportsWithDone, supportPatchXHROnProperty, zoneSymbol} from '../test-util';
|
||||
declare const global: any;
|
||||
const wtfMock = global.wtfMock;
|
||||
|
||||
describe('XMLHttpRequest', function() {
|
||||
let testZone: Zone;
|
||||
|
||||
beforeEach(() => { testZone = Zone.current.fork({name: 'test'}); });
|
||||
|
||||
it('should intercept XHRs and treat them as MacroTasks', function(done) {
|
||||
let req: XMLHttpRequest;
|
||||
let onStable: any;
|
||||
const testZoneWithWtf = Zone.current.fork((Zone as any)['wtfZoneSpec']).fork({
|
||||
name: 'TestZone',
|
||||
onHasTask: (delegate: ZoneDelegate, curr: Zone, target: Zone, hasTask: HasTaskState) => {
|
||||
if (!hasTask.macroTask) {
|
||||
onStable && onStable();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
testZoneWithWtf.run(() => {
|
||||
req = new XMLHttpRequest();
|
||||
const logs: string[] = [];
|
||||
req.onload = () => { logs.push('onload'); };
|
||||
onStable = function() {
|
||||
expect(wtfMock.log[wtfMock.log.length - 2])
|
||||
.toEqual('> Zone:invokeTask:XMLHttpRequest.send("<root>::ProxyZone::WTF::TestZone")');
|
||||
expect(wtfMock.log[wtfMock.log.length - 1])
|
||||
.toEqual('< Zone:invokeTask:XMLHttpRequest.send');
|
||||
if (supportPatchXHROnProperty()) {
|
||||
expect(wtfMock.log[wtfMock.log.length - 3])
|
||||
.toMatch(/\< Zone\:invokeTask.*addEventListener\:load/);
|
||||
expect(wtfMock.log[wtfMock.log.length - 4])
|
||||
.toMatch(/\> Zone\:invokeTask.*addEventListener\:load/);
|
||||
}
|
||||
// if browser can patch onload
|
||||
if ((req as any)[zoneSymbol('loadfalse')]) {
|
||||
expect(logs).toEqual(['onload']);
|
||||
}
|
||||
done();
|
||||
};
|
||||
|
||||
req.open('get', '/', true);
|
||||
req.send();
|
||||
const lastScheduled = wtfMock.log[wtfMock.log.length - 1];
|
||||
expect(lastScheduled).toMatch('# Zone:schedule:macroTask:XMLHttpRequest.send');
|
||||
}, null, undefined, 'unit-test');
|
||||
});
|
||||
|
||||
it('should not trigger Zone callback of internal onreadystatechange', function(done) {
|
||||
const scheduleSpy = jasmine.createSpy('schedule');
|
||||
const xhrZone = Zone.current.fork({
|
||||
name: 'xhr',
|
||||
onScheduleTask: (delegate: ZoneDelegate, currentZone: Zone, targetZone, task: Task) => {
|
||||
if (task.type === 'eventTask') {
|
||||
scheduleSpy(task.source);
|
||||
}
|
||||
return delegate.scheduleTask(targetZone, task);
|
||||
}
|
||||
});
|
||||
|
||||
xhrZone.run(() => {
|
||||
const req = new XMLHttpRequest();
|
||||
req.onload = function() {
|
||||
expect(Zone.current.name).toEqual('xhr');
|
||||
if (supportPatchXHROnProperty()) {
|
||||
expect(scheduleSpy).toHaveBeenCalled();
|
||||
}
|
||||
done();
|
||||
};
|
||||
req.open('get', '/', true);
|
||||
req.send();
|
||||
});
|
||||
});
|
||||
|
||||
it('should work with onreadystatechange', function(done) {
|
||||
let req: XMLHttpRequest;
|
||||
|
||||
testZone.run(function() {
|
||||
req = new XMLHttpRequest();
|
||||
req.onreadystatechange = function() {
|
||||
// Make sure that the wrapCallback will only be called once
|
||||
req.onreadystatechange = null as any;
|
||||
expect(Zone.current).toBe(testZone);
|
||||
done();
|
||||
};
|
||||
req.open('get', '/', true);
|
||||
});
|
||||
|
||||
req !.send();
|
||||
});
|
||||
|
||||
it('should return null when access ontimeout first time without error', function() {
|
||||
let req: XMLHttpRequest = new XMLHttpRequest();
|
||||
expect(req.ontimeout).toBe(null);
|
||||
});
|
||||
|
||||
const supportsOnProgress = function() { return 'onprogress' in (new XMLHttpRequest()); };
|
||||
|
||||
(<any>supportsOnProgress).message = 'XMLHttpRequest.onprogress';
|
||||
|
||||
describe('onprogress', ifEnvSupports(supportsOnProgress, function() {
|
||||
it('should work with onprogress', function(done) {
|
||||
let req: XMLHttpRequest;
|
||||
testZone.run(function() {
|
||||
req = new XMLHttpRequest();
|
||||
req.onprogress = function() {
|
||||
// Make sure that the wrapCallback will only be called once
|
||||
req.onprogress = null as any;
|
||||
expect(Zone.current).toBe(testZone);
|
||||
done();
|
||||
};
|
||||
req.open('get', '/', true);
|
||||
});
|
||||
|
||||
req !.send();
|
||||
});
|
||||
|
||||
it('should allow canceling of an XMLHttpRequest', function(done) {
|
||||
const spy = jasmine.createSpy('spy');
|
||||
let req: XMLHttpRequest;
|
||||
let pending = false;
|
||||
|
||||
const trackingTestZone = Zone.current.fork({
|
||||
name: 'tracking test zone',
|
||||
onHasTask: (delegate: ZoneDelegate, current: Zone, target: Zone,
|
||||
hasTaskState: HasTaskState) => {
|
||||
if (hasTaskState.change == 'macroTask') {
|
||||
pending = hasTaskState.macroTask;
|
||||
}
|
||||
delegate.hasTask(target, hasTaskState);
|
||||
}
|
||||
});
|
||||
|
||||
trackingTestZone.run(function() {
|
||||
req = new XMLHttpRequest();
|
||||
req.onreadystatechange = function() {
|
||||
if (req.readyState === XMLHttpRequest.DONE) {
|
||||
if (req.status !== 0) {
|
||||
spy();
|
||||
}
|
||||
}
|
||||
};
|
||||
req.open('get', '/', true);
|
||||
|
||||
req.send();
|
||||
req.abort();
|
||||
});
|
||||
|
||||
setTimeout(function() {
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
expect(pending).toEqual(false);
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
it('should allow aborting an XMLHttpRequest after its completed', function(done) {
|
||||
let req: XMLHttpRequest;
|
||||
|
||||
testZone.run(function() {
|
||||
req = new XMLHttpRequest();
|
||||
req.onreadystatechange = function() {
|
||||
if (req.readyState === XMLHttpRequest.DONE) {
|
||||
if (req.status !== 0) {
|
||||
setTimeout(function() {
|
||||
req.abort();
|
||||
done();
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
};
|
||||
req.open('get', '/', true);
|
||||
|
||||
req.send();
|
||||
});
|
||||
});
|
||||
}));
|
||||
|
||||
it('should preserve other setters', function() {
|
||||
const req = new XMLHttpRequest();
|
||||
req.open('get', '/', true);
|
||||
req.send();
|
||||
try {
|
||||
req.responseType = 'document';
|
||||
expect(req.responseType).toBe('document');
|
||||
} catch (e) {
|
||||
// Android browser: using this setter throws, this should be preserved
|
||||
expect(e.message).toBe('INVALID_STATE_ERR: DOM Exception 11');
|
||||
}
|
||||
});
|
||||
|
||||
it('should work with synchronous XMLHttpRequest', function() {
|
||||
const log: HasTaskState[] = [];
|
||||
Zone.current
|
||||
.fork({
|
||||
name: 'sync-xhr-test',
|
||||
onHasTask: function(
|
||||
delegate: ZoneDelegate, current: Zone, target: Zone, hasTaskState: HasTaskState) {
|
||||
log.push(hasTaskState);
|
||||
delegate.hasTask(target, hasTaskState);
|
||||
}
|
||||
})
|
||||
.run(() => {
|
||||
const req = new XMLHttpRequest();
|
||||
req.open('get', '/', false);
|
||||
req.send();
|
||||
});
|
||||
expect(log).toEqual([]);
|
||||
});
|
||||
|
||||
it('should preserve static constants', function() {
|
||||
expect(XMLHttpRequest.UNSENT).toEqual(0);
|
||||
expect(XMLHttpRequest.OPENED).toEqual(1);
|
||||
expect(XMLHttpRequest.HEADERS_RECEIVED).toEqual(2);
|
||||
expect(XMLHttpRequest.LOADING).toEqual(3);
|
||||
expect(XMLHttpRequest.DONE).toEqual(4);
|
||||
});
|
||||
|
||||
it('should work properly when send request multiple times on single xmlRequest instance',
|
||||
function(done) {
|
||||
testZone.run(function() {
|
||||
const req = new XMLHttpRequest();
|
||||
req.open('get', '/', true);
|
||||
req.send();
|
||||
req.onload = function() {
|
||||
req.onload = null as any;
|
||||
req.open('get', '/', true);
|
||||
req.onload = function() { done(); };
|
||||
expect(() => { req.send(); }).not.toThrow();
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
it('should keep taskcount correctly when abort was called multiple times before request is done',
|
||||
function(done) {
|
||||
testZone.run(function() {
|
||||
const req = new XMLHttpRequest();
|
||||
req.open('get', '/', true);
|
||||
req.send();
|
||||
req.addEventListener('readystatechange', function(ev) {
|
||||
if (req.readyState >= 2) {
|
||||
expect(() => { req.abort(); }).not.toThrow();
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should trigger readystatechange if xhr request trigger cors error', (done) => {
|
||||
const req = new XMLHttpRequest();
|
||||
let err: any = null;
|
||||
try {
|
||||
req.open('get', 'file:///test', true);
|
||||
} catch (err) {
|
||||
// in IE, open will throw Access is denied error
|
||||
done();
|
||||
return;
|
||||
}
|
||||
req.addEventListener('readystatechange', function(ev) {
|
||||
if (req.readyState === 4) {
|
||||
const xhrScheduled = (req as any)[zoneSymbol('xhrScheduled')];
|
||||
const task = (req as any)[zoneSymbol('xhrTask')];
|
||||
if (xhrScheduled === false) {
|
||||
expect(task.state).toEqual('scheduling');
|
||||
setTimeout(() => {
|
||||
if (err) {
|
||||
expect(task.state).toEqual('unknown');
|
||||
} else {
|
||||
expect(task.state).toEqual('notScheduled');
|
||||
}
|
||||
done();
|
||||
});
|
||||
} else {
|
||||
expect(task.state).toEqual('scheduled');
|
||||
done();
|
||||
}
|
||||
}
|
||||
});
|
||||
try {
|
||||
req.send();
|
||||
} catch (error) {
|
||||
err = error;
|
||||
}
|
||||
});
|
||||
|
||||
it('should invoke task if xhr request trigger cors error', (done) => {
|
||||
const logs: string[] = [];
|
||||
const zone = Zone.current.fork({
|
||||
name: 'xhr',
|
||||
onHasTask: (delegate: ZoneDelegate, curr: Zone, target: Zone, hasTask: HasTaskState) => {
|
||||
logs.push(JSON.stringify(hasTask));
|
||||
}
|
||||
});
|
||||
const req = new XMLHttpRequest();
|
||||
try {
|
||||
req.open('get', 'file:///test', true);
|
||||
} catch (err) {
|
||||
// in IE, open will throw Access is denied error
|
||||
done();
|
||||
return;
|
||||
}
|
||||
zone.run(() => {
|
||||
let isError = false;
|
||||
let timerId = null;
|
||||
try {
|
||||
timerId = (window as any)[zoneSymbol('setTimeout')](() => {
|
||||
expect(logs).toEqual([
|
||||
`{"microTask":false,"macroTask":true,"eventTask":false,"change":"macroTask"}`,
|
||||
`{"microTask":false,"macroTask":false,"eventTask":false,"change":"macroTask"}`
|
||||
]);
|
||||
done();
|
||||
}, 500);
|
||||
req.send();
|
||||
} catch (error) {
|
||||
isError = true;
|
||||
(window as any)[zoneSymbol('clearTimeout')](timerId);
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should not throw error when get XMLHttpRequest.prototype.onreadystatechange the first time',
|
||||
function() {
|
||||
const func = function() {
|
||||
testZone.run(function() {
|
||||
const req = new XMLHttpRequest();
|
||||
req.onreadystatechange;
|
||||
});
|
||||
};
|
||||
expect(func).not.toThrow();
|
||||
});
|
||||
|
||||
it('should be in the zone when use XMLHttpRequest.addEventListener', function(done) {
|
||||
testZone.run(function() {
|
||||
// sometimes this case will cause timeout
|
||||
// so we set it longer
|
||||
const interval = (<any>jasmine).DEFAULT_TIMEOUT_INTERVAL;
|
||||
(<any>jasmine).DEFAULT_TIMEOUT_INTERVAL = 5000;
|
||||
const req = new XMLHttpRequest();
|
||||
req.open('get', '/', true);
|
||||
req.addEventListener('readystatechange', function() {
|
||||
if (req.readyState === 4) {
|
||||
// expect(Zone.current.name).toEqual('test');
|
||||
(<any>jasmine).DEFAULT_TIMEOUT_INTERVAL = interval;
|
||||
done();
|
||||
}
|
||||
});
|
||||
req.send();
|
||||
});
|
||||
});
|
||||
|
||||
it('should return origin listener when call xhr.onreadystatechange',
|
||||
ifEnvSupportsWithDone(supportPatchXHROnProperty, function(done: Function) {
|
||||
testZone.run(function() {
|
||||
// sometimes this case will cause timeout
|
||||
// so we set it longer
|
||||
const req = new XMLHttpRequest();
|
||||
req.open('get', '/', true);
|
||||
const interval = (<any>jasmine).DEFAULT_TIMEOUT_INTERVAL;
|
||||
(<any>jasmine).DEFAULT_TIMEOUT_INTERVAL = 5000;
|
||||
const listener = req.onreadystatechange = function() {
|
||||
if (req.readyState === 4) {
|
||||
(<any>jasmine).DEFAULT_TIMEOUT_INTERVAL = interval;
|
||||
done();
|
||||
}
|
||||
};
|
||||
expect(req.onreadystatechange).toBe(listener);
|
||||
req.onreadystatechange = function() { return listener.call(this); };
|
||||
req.send();
|
||||
});
|
||||
}));
|
||||
});
|
Reference in New Issue
Block a user