angular-docs-cn/packages/zone.js/test/zone-spec/async-test.spec.ts

532 lines
15 KiB
TypeScript

/**
* @license
* Copyright Google LLC 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} from '../test-util';
describe('AsyncTestZoneSpec', function() {
let log: string[];
const AsyncTestZoneSpec = (Zone as any)['AsyncTestZoneSpec'];
function finishCallback() {
log.push('finish');
}
function failCallback() {
log.push('fail');
}
beforeEach(() => {
log = [];
});
it('should call finish after zone is run in sync call', (done) => {
let finished = false;
const testZoneSpec = new AsyncTestZoneSpec(() => {
expect(finished).toBe(true);
done();
}, failCallback, 'name');
const atz = Zone.current.fork(testZoneSpec);
atz.run(function() {
finished = true;
});
});
it('should call finish after a setTimeout is done', (done) => {
let finished = false;
const testZoneSpec = new AsyncTestZoneSpec(
() => {
expect(finished).toBe(true);
done();
},
() => {
done.fail('async zone called failCallback unexpectedly');
},
'name');
const atz = Zone.current.fork(testZoneSpec);
atz.run(function() {
setTimeout(() => {
finished = true;
}, 10);
});
});
it('should call finish after microtasks are done', (done) => {
let finished = false;
const testZoneSpec = new AsyncTestZoneSpec(
() => {
expect(finished).toBe(true);
done();
},
() => {
done.fail('async zone called failCallback unexpectedly');
},
'name');
const atz = Zone.current.fork(testZoneSpec);
atz.run(function() {
Promise.resolve().then(() => {
finished = true;
});
});
});
it('should call finish after both micro and macrotasks are done', (done) => {
let finished = false;
const testZoneSpec = new AsyncTestZoneSpec(
() => {
expect(finished).toBe(true);
done();
},
() => {
done.fail('async zone called failCallback unexpectedly');
},
'name');
const atz = Zone.current.fork(testZoneSpec);
atz.run(function() {
new Promise<void>((resolve) => {
setTimeout(() => {
resolve();
}, 10);
}).then(() => {
finished = true;
});
});
});
it('should call finish after both macro and microtasks are done', (done) => {
let finished = false;
const testZoneSpec = new AsyncTestZoneSpec(
() => {
expect(finished).toBe(true);
done();
},
() => {
done.fail('async zone called failCallback unexpectedly');
},
'name');
const atz = Zone.current.fork(testZoneSpec);
atz.run(function() {
Promise.resolve().then(() => {
setTimeout(() => {
finished = true;
}, 10);
});
});
});
describe('event tasks', ifEnvSupports('document', () => {
let button: HTMLButtonElement;
beforeEach(function() {
button = document.createElement('button');
document.body.appendChild(button);
});
afterEach(function() {
document.body.removeChild(button);
});
it('should call finish because an event task is considered as sync', (done) => {
let finished = false;
const testZoneSpec = new AsyncTestZoneSpec(
() => {
expect(finished).toBe(true);
done();
},
() => {
done.fail('async zone called failCallback unexpectedly');
},
'name');
const atz = Zone.current.fork(testZoneSpec);
atz.run(function() {
const listener = () => {
finished = true;
};
button.addEventListener('click', listener);
const clickEvent = document.createEvent('Event');
clickEvent.initEvent('click', true, true);
button.dispatchEvent(clickEvent);
});
});
it('should call finish after an event task is done asynchronously', (done) => {
let finished = false;
const testZoneSpec = new AsyncTestZoneSpec(
() => {
expect(finished).toBe(true);
done();
},
() => {
done.fail('async zone called failCallback unexpectedly');
},
'name');
const atz = Zone.current.fork(testZoneSpec);
atz.run(function() {
button.addEventListener('click', () => {
setTimeout(() => {
finished = true;
}, 10);
});
const clickEvent = document.createEvent('Event');
clickEvent.initEvent('click', true, true);
button.dispatchEvent(clickEvent);
});
});
}));
describe('XHRs', ifEnvSupports('XMLHttpRequest', () => {
it('should wait for XHRs to complete', function(done) {
let req: XMLHttpRequest;
let finished = false;
const testZoneSpec = new AsyncTestZoneSpec(
() => {
expect(finished).toBe(true);
done();
},
(err: Error) => {
done.fail('async zone called failCallback unexpectedly');
},
'name');
const atz = Zone.current.fork(testZoneSpec);
atz.run(function() {
req = new XMLHttpRequest();
req.onreadystatechange = () => {
if (req.readyState === XMLHttpRequest.DONE) {
finished = true;
}
};
req.open('get', '/', true);
req.send();
});
});
it('should fail if an xhr fails', function(done) {
let req: XMLHttpRequest;
const testZoneSpec = new AsyncTestZoneSpec(
() => {
done.fail('expected failCallback to be called');
},
(err: Error) => {
expect(err.message).toEqual('bad url failure');
done();
},
'name');
const atz = Zone.current.fork(testZoneSpec);
atz.run(function() {
req = new XMLHttpRequest();
req.onload = () => {
if (req.status != 200) {
throw new Error('bad url failure');
}
};
req.open('get', '/bad-url', true);
req.send();
});
});
}));
it('should not fail if setInterval is used and canceled', (done) => {
const testZoneSpec = new AsyncTestZoneSpec(
() => {
done();
},
(err: Error) => {
done.fail('async zone called failCallback unexpectedly');
},
'name');
const atz = Zone.current.fork(testZoneSpec);
atz.run(function() {
let id = setInterval(() => {
clearInterval(id);
}, 100);
});
});
it('should fail if an error is thrown asynchronously', (done) => {
const testZoneSpec = new AsyncTestZoneSpec(
() => {
done.fail('expected failCallback to be called');
},
(err: Error) => {
expect(err.message).toEqual('my error');
done();
},
'name');
const atz = Zone.current.fork(testZoneSpec);
atz.run(function() {
setTimeout(() => {
throw new Error('my error');
}, 10);
});
});
it('should fail if a promise rejection is unhandled', (done) => {
const testZoneSpec = new AsyncTestZoneSpec(
() => {
done.fail('expected failCallback to be called');
},
(err: Error) => {
expect(err.message).toEqual('Uncaught (in promise): my reason');
done();
},
'name');
const atz = Zone.current.fork(testZoneSpec);
atz.run(function() {
Promise.reject('my reason');
});
});
const asyncTest: any = (Zone as any)[Zone.__symbol__('asyncTest')];
function wrapAsyncTest(fn: Function, doneFn?: Function) {
return function(this: unknown, done: Function) {
const asyncWrapper = asyncTest(fn);
return asyncWrapper.apply(this, [function(this: unknown) {
if (doneFn) {
doneFn();
}
return done.apply(this, arguments);
}]);
};
}
describe('async', () => {
describe('non zone aware async task in promise should be detected', () => {
let finished = false;
const _global: any =
typeof window !== 'undefined' && window || typeof self !== 'undefined' && self || global;
beforeEach(() => {
_global[Zone.__symbol__('supportWaitUnResolvedChainedPromise')] = true;
});
afterEach(() => {
_global[Zone.__symbol__('supportWaitUnResolvedChainedPromise')] = false;
});
it('should be able to detect non zone aware async task in promise',
wrapAsyncTest(
() => {
new Promise((res, rej) => {
const g: any = typeof window === 'undefined' ? global : window;
g[Zone.__symbol__('setTimeout')](res, 100);
}).then(() => {
finished = true;
});
},
() => {
expect(finished).toBe(true);
}));
});
describe('test without beforeEach', () => {
const logs: string[] = [];
it('should automatically done after async tasks finished',
wrapAsyncTest(
() => {
setTimeout(() => {
logs.push('timeout');
}, 100);
},
() => {
expect(logs).toEqual(['timeout']);
logs.splice(0);
}));
it('should automatically done after all nested async tasks finished',
wrapAsyncTest(
() => {
setTimeout(() => {
logs.push('timeout');
setTimeout(() => {
logs.push('nested timeout');
}, 100);
}, 100);
},
() => {
expect(logs).toEqual(['timeout', 'nested timeout']);
logs.splice(0);
}));
it('should automatically done after multiple async tasks finished',
wrapAsyncTest(
() => {
setTimeout(() => {
logs.push('1st timeout');
}, 100);
setTimeout(() => {
logs.push('2nd timeout');
}, 100);
},
() => {
expect(logs).toEqual(['1st timeout', '2nd timeout']);
logs.splice(0);
}));
});
describe('test with sync beforeEach', () => {
const logs: string[] = [];
beforeEach(() => {
logs.splice(0);
logs.push('beforeEach');
});
it('should automatically done after async tasks finished',
wrapAsyncTest(
() => {
setTimeout(() => {
logs.push('timeout');
}, 100);
},
() => {
expect(logs).toEqual(['beforeEach', 'timeout']);
}));
});
describe('test with async beforeEach', () => {
const logs: string[] = [];
beforeEach(wrapAsyncTest(() => {
setTimeout(() => {
logs.splice(0);
logs.push('beforeEach');
}, 100);
}));
it('should automatically done after async tasks finished',
wrapAsyncTest(
() => {
setTimeout(() => {
logs.push('timeout');
}, 100);
},
() => {
expect(logs).toEqual(['beforeEach', 'timeout']);
}));
it('should automatically done after all nested async tasks finished',
wrapAsyncTest(
() => {
setTimeout(() => {
logs.push('timeout');
setTimeout(() => {
logs.push('nested timeout');
}, 100);
}, 100);
},
() => {
expect(logs).toEqual(['beforeEach', 'timeout', 'nested timeout']);
}));
it('should automatically done after multiple async tasks finished',
wrapAsyncTest(
() => {
setTimeout(() => {
logs.push('1st timeout');
}, 100);
setTimeout(() => {
logs.push('2nd timeout');
}, 100);
},
() => {
expect(logs).toEqual(['beforeEach', '1st timeout', '2nd timeout']);
}));
});
describe('test with async beforeEach and sync afterEach', () => {
const logs: string[] = [];
beforeEach(wrapAsyncTest(() => {
setTimeout(() => {
expect(logs).toEqual([]);
logs.push('beforeEach');
}, 100);
}));
afterEach(() => {
logs.splice(0);
});
it('should automatically done after async tasks finished',
wrapAsyncTest(
() => {
setTimeout(() => {
logs.push('timeout');
}, 100);
},
() => {
expect(logs).toEqual(['beforeEach', 'timeout']);
}));
});
describe('test with async beforeEach and async afterEach', () => {
const logs: string[] = [];
beforeEach(wrapAsyncTest(() => {
setTimeout(() => {
expect(logs).toEqual([]);
logs.push('beforeEach');
}, 100);
}));
afterEach(wrapAsyncTest(() => {
setTimeout(() => {
logs.splice(0);
}, 100);
}));
it('should automatically done after async tasks finished',
wrapAsyncTest(
() => {
setTimeout(() => {
logs.push('timeout');
}, 100);
},
() => {
expect(logs).toEqual(['beforeEach', 'timeout']);
}));
});
});
});