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

414 lines
13 KiB
TypeScript

/**
* @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} 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((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']);
}));
});
});
});