test(zone.js): should invoke XHR task even onload handler throw error. (#41562)

Close #41520.

This case related to the issue #41522.

```
Zone.root
  .fork({
    name: 'xhr',
    onHasTask(delegate, currentZone, zone, taskState) {
      console.log('hasMacrotask', taskState.macroTask);
      return delegate.hasTask(zone, taskState);
    },
  })
  .run(() => {
    const xhr = new XMLHttpRequest();
    xhr.open('GET', 'https://cdnjs.cloudflare.com/ajax/libs/zone.js/0.11.4/zone.min.js');
    xhr.addEventListener('load', () => {
      throw new Error();
    });
    xhr.send();
  });
```

zone.js invoke all `onload` event handlers before change the XHR task's state from
`scheduled` to `notscheduled`, so if any `onload` listener throw error, the XHR task
wlll be hang to `scheduled`, and leave the macroTask status in the zone wrongly.

This has been fixed in the previous commit, this commit add test to verify the case.

PR Close #41562
This commit is contained in:
JiaLiPassion 2021-04-11 14:58:00 +08:00 committed by Jessica Janiuk
parent 008eaf3b7d
commit c3614662cb
2 changed files with 167 additions and 72 deletions

View File

@ -103,6 +103,84 @@ describe('XMLHttpRequest', function() {
req!.send(); req!.send();
}); });
it('should run onload listeners before internal readystatechange', function(done) {
const logs: string[] = [];
const xhrZone = Zone.current.fork({
name: 'xhr',
onInvokeTask: (delegate, curr, target, task, applyThis, applyArgs) => {
logs.push('invokeTask ' + task.source);
return delegate.invokeTask(target, task, applyThis, applyArgs);
}
});
xhrZone.run(function() {
const req = new XMLHttpRequest();
req.onload = function() {
logs.push('onload');
(window as any)[Zone.__symbol__('setTimeout')](() => {
expect(logs).toEqual([
'invokeTask XMLHttpRequest.addEventListener:load', 'onload',
'invokeTask XMLHttpRequest.send'
])
done();
});
};
req.open('get', '/', true);
req.send();
});
});
it('should invoke xhr task even onload listener throw error', function(done) {
const oriWindowError = window.onerror;
window.onerror = function() {};
try {
const logs: string[] = [];
const xhrZone = Zone.current.fork({
name: 'xhr',
onInvokeTask: (delegate, curr, target, task, applyThis, applyArgs) => {
logs.push('invokeTask ' + task.source);
return delegate.invokeTask(target, task, applyThis, applyArgs);
},
onHasTask: (delegate, curr, target, hasTaskState) => {
if (hasTaskState.change === 'macroTask') {
logs.push('hasTask ' + hasTaskState.macroTask);
}
return delegate.hasTask(target, hasTaskState);
}
});
xhrZone.run(function() {
const req = new XMLHttpRequest();
req.onload = function() {
logs.push('onload');
throw new Error('test');
};
const unhandledRejection = (e: PromiseRejectionEvent) => {
logs.push(e.reason.message);
};
window.addEventListener('unhandledrejection', unhandledRejection);
req.addEventListener('load', () => {
logs.push('onload1');
(window as any)[Zone.__symbol__('setTimeout')](() => {
expect(logs).toEqual([
'hasTask true', 'invokeTask XMLHttpRequest.addEventListener:load', 'onload',
'invokeTask XMLHttpRequest.addEventListener:load', 'onload1',
'invokeTask XMLHttpRequest.send', 'hasTask false',
'invokeTask Window.addEventListener:unhandledrejection', 'test'
]);
window.removeEventListener('unhandledrejection', unhandledRejection);
window.onerror = oriWindowError;
done();
});
});
req.open('get', '/', true);
req.send();
});
} catch (e: any) {
window.onerror = oriWindowError;
}
});
it('should return null when access ontimeout first time without error', function() { it('should return null when access ontimeout first time without error', function() {
let req: XMLHttpRequest = new XMLHttpRequest(); let req: XMLHttpRequest = new XMLHttpRequest();
expect(req.ontimeout).toBe(null); expect(req.ontimeout).toBe(null);

View File

@ -2493,6 +2493,10 @@ describe('Zone', function() {
it('should be able to continue to invoke remaining listeners even some listener throw error', it('should be able to continue to invoke remaining listeners even some listener throw error',
function(done: DoneFn) { function(done: DoneFn) {
// override global.onerror to prevent jasmine report error
let oriWindowOnError = window.onerror;
window.onerror = function() {};
try {
let logs: string[] = []; let logs: string[] = [];
const listener1 = function() { const listener1 = function() {
logs.push('listener1'); logs.push('listener1');
@ -2528,12 +2532,20 @@ describe('Zone', function() {
setTimeout(() => { setTimeout(() => {
expect(logs).toEqual(['listener1', 'listener2', 'test1', 'test2']); expect(logs).toEqual(['listener1', 'listener2', 'test1', 'test2']);
window.removeEventListener('unhandledrejection', unhandledRejection); window.removeEventListener('unhandledrejection', unhandledRejection);
window.onerror = oriWindowOnError;
done() done()
}); });
} catch (e: any) {
window.onerror = oriWindowOnError;
}
}); });
it('should be able to continue to invoke remaining listeners even some listener throw error in the different zones', it('should be able to continue to invoke remaining listeners even some listener throw error in the different zones',
function(done: DoneFn) { function(done: DoneFn) {
// override global.onerror to prevent jasmine report error
let oriWindowOnError = window.onerror;
window.onerror = function() {};
try {
let logs: string[] = []; let logs: string[] = [];
const zone1 = Zone.current.fork({ const zone1 = Zone.current.fork({
name: 'zone1', name: 'zone1',
@ -2577,8 +2589,13 @@ describe('Zone', function() {
setTimeout(() => { setTimeout(() => {
expect(logs).toEqual(['listener1', 'test1', 'listener2', 'test2']); expect(logs).toEqual(['listener1', 'test1', 'listener2', 'test2']);
window.removeEventListener('unhandledrejection', unhandledRejection);
window.onerror = oriWindowOnError;
done() done()
}); });
} catch (e: any) {
window.onerror = oriWindowOnError;
}
}); });
}); });