angular-cn/packages/core/test/fake_async_spec.ts

436 lines
12 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 {discardPeriodicTasks, fakeAsync, flush, flushMicrotasks, tick} from '@angular/core/testing';
import {beforeEach, describe, inject, it, Log} from '@angular/core/testing/src/testing_internal';
import {EventManager} from '@angular/platform-browser';
import {expect} from '@angular/platform-browser/testing/src/matchers';
const resolvedPromise = Promise.resolve(null);
const ProxyZoneSpec: {assertPresent: () => void} = (Zone as any)['ProxyZoneSpec'];
{
describe('fake async', () => {
it('should run synchronous code', () => {
let ran = false;
fakeAsync(() => {
ran = true;
})();
expect(ran).toEqual(true);
});
it('should pass arguments to the wrapped function', () => {
fakeAsync((foo: any /** TODO #9100 */, bar: any /** TODO #9100 */) => {
expect(foo).toEqual('foo');
expect(bar).toEqual('bar');
})('foo', 'bar');
});
it('should work with inject()',
fakeAsync(inject([EventManager], (eventManager: EventManager) => {
expect(eventManager).toBeAnInstanceOf(EventManager);
})));
it('should throw on nested calls', () => {
expect(() => {
fakeAsync(() => {
fakeAsync((): any /** TODO #9100 */ => null)();
})();
}).toThrowError('fakeAsync() calls can not be nested');
});
it('should flush microtasks before returning', () => {
let thenRan = false;
fakeAsync(() => {
resolvedPromise.then(_ => {
thenRan = true;
});
})();
expect(thenRan).toEqual(true);
});
it('should propagate the return value', () => {
expect(fakeAsync(() => 'foo')()).toEqual('foo');
});
describe('Promise', () => {
it('should run asynchronous code', fakeAsync(() => {
let thenRan = false;
resolvedPromise.then((_) => {
thenRan = true;
});
expect(thenRan).toEqual(false);
flushMicrotasks();
expect(thenRan).toEqual(true);
}));
it('should run chained thens', fakeAsync(() => {
const log = new Log();
resolvedPromise.then((_) => log.add(1)).then((_) => log.add(2));
expect(log.result()).toEqual('');
flushMicrotasks();
expect(log.result()).toEqual('1; 2');
}));
it('should run Promise created in Promise', fakeAsync(() => {
const log = new Log();
resolvedPromise.then((_) => {
log.add(1);
resolvedPromise.then((_) => log.add(2));
});
expect(log.result()).toEqual('');
flushMicrotasks();
expect(log.result()).toEqual('1; 2');
}));
it('should complain if the test throws an exception during async calls', () => {
expect(() => {
fakeAsync(() => {
resolvedPromise.then((_) => {
throw new Error('async');
});
flushMicrotasks();
})();
}).toThrow();
});
it('should complain if a test throws an exception', () => {
expect(() => {
fakeAsync(() => {
throw new Error('sync');
})();
}).toThrowError('sync');
});
});
describe('timers', () => {
it('should run queued zero duration timer on zero tick', fakeAsync(() => {
let ran = false;
setTimeout(() => {
ran = true;
}, 0);
expect(ran).toEqual(false);
tick();
expect(ran).toEqual(true);
}));
it('should run queued timer after sufficient clock ticks', fakeAsync(() => {
let ran = false;
setTimeout(() => {
ran = true;
}, 10);
tick(6);
expect(ran).toEqual(false);
tick(6);
expect(ran).toEqual(true);
}));
it('should run new macro tasks created by timer callback', fakeAsync(() => {
function nestedTimer(callback: () => any): void {
setTimeout(() => setTimeout(() => callback()));
}
const callback = jasmine.createSpy('callback');
nestedTimer(callback);
expect(callback).not.toHaveBeenCalled();
tick(0);
expect(callback).toHaveBeenCalled();
}));
it('should not queue nested timer on tick with processNewMacroTasksSynchronously=false',
fakeAsync(() => {
function nestedTimer(callback: () => any): void {
setTimeout(() => setTimeout(() => callback()));
}
const callback = jasmine.createSpy('callback');
nestedTimer(callback);
expect(callback).not.toHaveBeenCalled();
tick(0, {processNewMacroTasksSynchronously: false});
expect(callback).not.toHaveBeenCalled();
flush();
expect(callback).toHaveBeenCalled();
}));
it('should run queued timer only once', fakeAsync(() => {
let cycles = 0;
setTimeout(() => {
cycles++;
}, 10);
tick(10);
expect(cycles).toEqual(1);
tick(10);
expect(cycles).toEqual(1);
tick(10);
expect(cycles).toEqual(1);
}));
it('should not run cancelled timer', fakeAsync(() => {
let ran = false;
const id = setTimeout(() => {
ran = true;
}, 10);
clearTimeout(id);
tick(10);
expect(ran).toEqual(false);
}));
it('should throw an error on dangling timers', () => {
expect(() => {
fakeAsync(() => {
setTimeout(() => {}, 10);
})();
}).toThrowError('1 timer(s) still in the queue.');
});
it('should throw an error on dangling periodic timers', () => {
expect(() => {
fakeAsync(() => {
setInterval(() => {}, 10);
})();
}).toThrowError('1 periodic timer(s) still in the queue.');
});
it('should run periodic timers', fakeAsync(() => {
let cycles = 0;
const id = setInterval(() => {
cycles++;
}, 10);
tick(10);
expect(cycles).toEqual(1);
tick(10);
expect(cycles).toEqual(2);
tick(10);
expect(cycles).toEqual(3);
clearInterval(id);
}));
it('should not run cancelled periodic timer', fakeAsync(() => {
let ran = false;
const id = setInterval(() => {
ran = true;
}, 10);
clearInterval(id);
tick(10);
expect(ran).toEqual(false);
}));
it('should be able to cancel periodic timers from a callback', fakeAsync(() => {
let cycles = 0;
let id: any /** TODO #9100 */;
id = setInterval(() => {
cycles++;
clearInterval(id);
}, 10);
tick(10);
expect(cycles).toEqual(1);
tick(10);
expect(cycles).toEqual(1);
}));
it('should clear periodic timers', fakeAsync(() => {
let cycles = 0;
const id = setInterval(() => {
cycles++;
}, 10);
tick(10);
expect(cycles).toEqual(1);
discardPeriodicTasks();
// Tick once to clear out the timer which already started.
tick(10);
expect(cycles).toEqual(2);
tick(10);
// Nothing should change
expect(cycles).toEqual(2);
}));
it('should process microtasks before timers', fakeAsync(() => {
const log = new Log();
resolvedPromise.then((_) => log.add('microtask'));
setTimeout(() => log.add('timer'), 9);
const id = setInterval(() => log.add('periodic timer'), 10);
expect(log.result()).toEqual('');
tick(10);
expect(log.result()).toEqual('microtask; timer; periodic timer');
clearInterval(id);
}));
it('should process micro-tasks created in timers before next timers', fakeAsync(() => {
const log = new Log();
resolvedPromise.then((_) => log.add('microtask'));
setTimeout(() => {
log.add('timer');
resolvedPromise.then((_) => log.add('t microtask'));
}, 9);
const id = setInterval(() => {
log.add('periodic timer');
resolvedPromise.then((_) => log.add('pt microtask'));
}, 10);
tick(10);
expect(log.result())
.toEqual('microtask; timer; t microtask; periodic timer; pt microtask');
tick(10);
expect(log.result())
.toEqual(
'microtask; timer; t microtask; periodic timer; pt microtask; periodic timer; pt microtask');
clearInterval(id);
}));
it('should flush tasks', fakeAsync(() => {
let ran = false;
setTimeout(() => {
ran = true;
}, 10);
flush();
expect(ran).toEqual(true);
}));
it('should flush multiple tasks', fakeAsync(() => {
let ran = false;
let ran2 = false;
setTimeout(() => {
ran = true;
}, 10);
setTimeout(() => {
ran2 = true;
}, 30);
let elapsed = flush();
expect(ran).toEqual(true);
expect(ran2).toEqual(true);
expect(elapsed).toEqual(30);
}));
it('should move periodic tasks', fakeAsync(() => {
let ran = false;
let count = 0;
setInterval(() => {
count++;
}, 10);
setTimeout(() => {
ran = true;
}, 35);
let elapsed = flush();
expect(count).toEqual(3);
expect(ran).toEqual(true);
expect(elapsed).toEqual(35);
discardPeriodicTasks();
}));
});
describe('outside of the fakeAsync zone', () => {
it('calling flushMicrotasks should throw', () => {
expect(() => {
flushMicrotasks();
}).toThrowError('The code should be running in the fakeAsync zone to call this function');
});
it('calling tick should throw', () => {
expect(() => {
tick();
}).toThrowError('The code should be running in the fakeAsync zone to call this function');
});
it('calling flush should throw', () => {
expect(() => {
flush();
}).toThrowError('The code should be running in the fakeAsync zone to call this function');
});
it('calling discardPeriodicTasks should throw', () => {
expect(() => {
discardPeriodicTasks();
}).toThrowError('The code should be running in the fakeAsync zone to call this function');
});
});
describe('only one `fakeAsync` zone per test', () => {
let zoneInBeforeEach: Zone;
let zoneInTest1: Zone;
beforeEach(fakeAsync(() => {
zoneInBeforeEach = Zone.current;
}));
it('should use the same zone as in beforeEach', fakeAsync(() => {
zoneInTest1 = Zone.current;
expect(zoneInTest1).toBe(zoneInBeforeEach);
}));
});
});
describe('ProxyZone', () => {
beforeEach(() => {
ProxyZoneSpec.assertPresent();
});
afterEach(() => {
ProxyZoneSpec.assertPresent();
});
it('should allow fakeAsync zone to retroactively set a zoneSpec outside of fakeAsync', () => {
ProxyZoneSpec.assertPresent();
let state: string = 'not run';
const testZone = Zone.current.fork({name: 'test-zone'});
(fakeAsync(() => {
testZone.run(() => {
Promise.resolve('works').then((v) => state = v);
expect(state).toEqual('not run');
flushMicrotasks();
expect(state).toEqual('works');
});
}))();
expect(state).toEqual('works');
});
});
}