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();
});
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() {
let req: XMLHttpRequest = new XMLHttpRequest();
expect(req.ontimeout).toBe(null);

View File

@ -2493,92 +2493,109 @@ describe('Zone', function() {
it('should be able to continue to invoke remaining listeners even some listener throw error',
function(done: DoneFn) {
let logs: string[] = [];
const listener1 = function() {
logs.push('listener1');
};
const listener2 = function() {
throw new Error('test1');
};
const listener3 = function() {
throw new Error('test2');
};
const listener4 = {
handleEvent: function() {
logs.push('listener2');
}
};
// override global.onerror to prevent jasmine report error
let oriWindowOnError = window.onerror;
window.onerror = function() {};
try {
let logs: string[] = [];
const listener1 = function() {
logs.push('listener1');
};
const listener2 = function() {
throw new Error('test1');
};
const listener3 = function() {
throw new Error('test2');
};
const listener4 = {
handleEvent: function() {
logs.push('listener2');
}
};
button.addEventListener('click', listener1);
button.addEventListener('click', listener2);
button.addEventListener('click', listener3);
button.addEventListener('click', listener4);
button.addEventListener('click', listener1);
button.addEventListener('click', listener2);
button.addEventListener('click', listener3);
button.addEventListener('click', listener4);
const mouseEvent = document.createEvent('MouseEvent');
mouseEvent.initEvent('click', true, true);
const mouseEvent = document.createEvent('MouseEvent');
mouseEvent.initEvent('click', true, true);
const unhandledRejection = (e: PromiseRejectionEvent) => {
logs.push(e.reason.message);
};
window.addEventListener('unhandledrejection', unhandledRejection);
const unhandledRejection = (e: PromiseRejectionEvent) => {
logs.push(e.reason.message);
};
window.addEventListener('unhandledrejection', unhandledRejection);
button.dispatchEvent(mouseEvent);
expect(logs).toEqual(['listener1', 'listener2']);
button.dispatchEvent(mouseEvent);
expect(logs).toEqual(['listener1', 'listener2']);
setTimeout(() => {
expect(logs).toEqual(['listener1', 'listener2', 'test1', 'test2']);
window.removeEventListener('unhandledrejection', unhandledRejection);
done()
});
setTimeout(() => {
expect(logs).toEqual(['listener1', 'listener2', 'test1', 'test2']);
window.removeEventListener('unhandledrejection', unhandledRejection);
window.onerror = oriWindowOnError;
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',
function(done: DoneFn) {
let logs: string[] = [];
const zone1 = Zone.current.fork({
name: 'zone1',
onHandleError: (delegate, curr, target, error) => {
logs.push(error.message);
return false;
}
});
const listener1 = function() {
logs.push('listener1');
};
const listener2 = function() {
throw new Error('test1');
};
const listener3 = function() {
throw new Error('test2');
};
const listener4 = {
handleEvent: function() {
logs.push('listener2');
}
};
// override global.onerror to prevent jasmine report error
let oriWindowOnError = window.onerror;
window.onerror = function() {};
try {
let logs: string[] = [];
const zone1 = Zone.current.fork({
name: 'zone1',
onHandleError: (delegate, curr, target, error) => {
logs.push(error.message);
return false;
}
});
const listener1 = function() {
logs.push('listener1');
};
const listener2 = function() {
throw new Error('test1');
};
const listener3 = function() {
throw new Error('test2');
};
const listener4 = {
handleEvent: function() {
logs.push('listener2');
}
};
button.addEventListener('click', listener1);
zone1.run(() => {
button.addEventListener('click', listener2);
});
button.addEventListener('click', listener3);
button.addEventListener('click', listener4);
button.addEventListener('click', listener1);
zone1.run(() => {
button.addEventListener('click', listener2);
});
button.addEventListener('click', listener3);
button.addEventListener('click', listener4);
const mouseEvent = document.createEvent('MouseEvent');
mouseEvent.initEvent('click', true, true);
const mouseEvent = document.createEvent('MouseEvent');
mouseEvent.initEvent('click', true, true);
const unhandledRejection = (e: PromiseRejectionEvent) => {
logs.push(e.reason.message);
};
window.addEventListener('unhandledrejection', unhandledRejection);
const unhandledRejection = (e: PromiseRejectionEvent) => {
logs.push(e.reason.message);
};
window.addEventListener('unhandledrejection', unhandledRejection);
button.dispatchEvent(mouseEvent);
expect(logs).toEqual(['listener1', 'test1', 'listener2']);
button.dispatchEvent(mouseEvent);
expect(logs).toEqual(['listener1', 'test1', 'listener2']);
setTimeout(() => {
expect(logs).toEqual(['listener1', 'test1', 'listener2', 'test2']);
done()
});
setTimeout(() => {
expect(logs).toEqual(['listener1', 'test1', 'listener2', 'test2']);
window.removeEventListener('unhandledrejection', unhandledRejection);
window.onerror = oriWindowOnError;
done()
});
} catch (e: any) {
window.onerror = oriWindowOnError;
}
});
});