diff --git a/.gitignore b/.gitignore index 6a642d111..1c0011dee 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ parsed-idl/ .vscode npm-debug.log build-esm/ +yarn-error.log diff --git a/.travis.yml b/.travis.yml index 3ec8c6596..28ef8cf91 100644 --- a/.travis.yml +++ b/.travis.yml @@ -35,18 +35,19 @@ script: - node_modules/.bin/gulp promisetest - yarn promisefinallytest - yarn test:phantomjs-single - - node_modules/.bin/karma start karma-dist-sauce-jasmine.conf.js --single-run - - node_modules/.bin/karma start karma-build-sauce-mocha.conf.js --single-run - - node_modules/.bin/karma start karma-dist-sauce-selenium3-jasmine.conf.js --single-run - - node_modules/.bin/karma start karma-build-sauce-selenium3-mocha.conf.js --single-run + - node_modules/.bin/karma start ./test/karma/ci/dist/karma-dist-sauce-jasmine.conf.js --single-run + - node_modules/.bin/karma start ./test/karma/ci/build/karma-build-sauce-mocha.conf.js --single-run + - node_modules/.bin/karma start ./test/karma/ci/dist/karma-dist-sauce-selenium3-jasmine.conf.js --single-run + - node_modules/.bin/karma start ./test/karma/ci/build/karma-build-sauce-selenium3-mocha.conf.js --single-run - node_modules/.bin/gulp test/node - node_modules/.bin/gulp test/node -no-patch-clock - node_modules/.bin/gulp test/bluebird - - node simple-server.js 2>&1> server.log& - - node ./test/webdriver/test.sauce.js + - node ./test/spec/webdriver/simple-server.js 2>&1> server.log& + - node ./test/spec/webdriver/test.sauce.js - yarn add jasmine@3.0.0 jasmine-core@3.0.0 mocha@5.0.1 - yarn test:phantomjs-single - - node_modules/.bin/karma start karma-dist-sauce-jasmine3.conf.js --single-run - - node_modules/.bin/karma start karma-build-sauce-selenium3-mocha.conf.js --single-run + - yarn test-mocha-jasmine-bridge-node + - node_modules/.bin/karma start ./test/karma/ci/dist/karma-dist-sauce-jasmine3.conf.js --single-run + - node_modules/.bin/karma start ./test/karma/ci/build/karma-build-sauce-selenium3-mocha.conf.js --single-run - node_modules/.bin/gulp test/node - node_modules/.bin/gulp test/node -no-patch-clock diff --git a/README.md b/README.md index a9f854ce8..943b8a7fd 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Implements _Zones_ for JavaScript, inspired by [Dart](https://www.dartlang.org/articles/zones/). > If you're using zone.js via unpkg (i.e. using `https://unpkg.com/zone.js`) -> and you're using any of the following libraries, make sure you import them first +> and you're using any of the following libraries, make sure you import them first > * 'newrelic' as it patches global.Promise before zone.js does > * 'async-listener' as it patches global.setTimeout, global.setInterval before zone.js does @@ -36,19 +36,19 @@ See this video from ng-conf 2014 for a detailed explanation: ## Standard API support zone.js patched most standard web APIs (such as DOM events, `XMLHttpRequest`, ...) and nodejs APIs -(`EventEmitter`, `fs`, ...), for more details, please see [STANDARD-APIS.md](STANDARD-APIS.md). +(`EventEmitter`, `fs`, ...), for more details, please see [STANDARD-APIS.md](./doc/design/STANDARD-APIS.md). ## Nonstandard API support We are adding support to some nonstandard APIs, such as MediaQuery and -Notification. Please see [NON-STANDARD-APIS.md](NON-STANDARD-APIS.md) for more details. +Notification. Please see [NON-STANDARD-APIS.md](./doc/design/NON-STANDARD-APIS.md) for more details. ## Modules zone.js patches the async APIs described above, but those patches will have some overhead. Starting from zone.js v0.8.9, you can choose which web API module you want to patch. For more details, please -see [MODULE.md](MODULE.md). +see [MODULE.md](./doc/design/MODULE.md). ## Promise A+ test passed [![Promises/A+ 1.1 compliant](https://promisesaplus.com/assets/logo-small.png)](https://promisesaplus.com/) diff --git a/MODULE.md b/doc/design/MODULE.md similarity index 100% rename from MODULE.md rename to doc/design/MODULE.md diff --git a/NON-STANDARD-APIS.md b/doc/design/NON-STANDARD-APIS.md similarity index 100% rename from NON-STANDARD-APIS.md rename to doc/design/NON-STANDARD-APIS.md diff --git a/STANDARD-APIS.md b/doc/design/STANDARD-APIS.md similarity index 100% rename from STANDARD-APIS.md rename to doc/design/STANDARD-APIS.md diff --git a/doc/design/TEST.md b/doc/design/TEST.md new file mode 100644 index 000000000..e1c3b3cd0 --- /dev/null +++ b/doc/design/TEST.md @@ -0,0 +1,261 @@ +# Test Runner + +`zone-testing` monkey-patch `jasmine` and `mocha` runner to provide the following functionalities. + +1. All `describe/xdescribe/fdescribe` are guaranteed to run in `SyncTestZone`, so no `async` code are allowed under `describe` directly. + +```javascript +describe('test', () => { + // setTimeout(() => {}, 100); // not allowed +}); +``` + +2. Support `fakeAsync` in `test`. + +```javascript +import 'zone.js`; +import 'zone.js/dist/zone-testing`; + +// support simulate timer function. +describe('fakeAsync', () => { + it('setTimeout', fakeAsync(() => { + let called = false; + setTimeout(() => {called = true}, 100); + expect(called).toBe(false); + tick(100); + expect(called).toBe(true); + })); + + it ('Promise', fakeAsync(() => { + let thenRan = false; + Promise.resolve(null).then((_) => { + thenRan = true; + }); + + expect(thenRan).toEqual(false); + flushMicrotasks(); + expect(thenRan).toEqual(true); + })); +}); +``` + +Will add more examples later. + +3. support `asyncTest`. + +```javascript +import 'zone.js`; +import 'zone.js/dist/zone-testing`; +// TODO: this is too complex to load asyncTest, should expose as global function. +const asyncTest = (Zone as any)[Zone.__symbol__('asyncTest')]; + +describe('async', () => { + it('setTimeout and Promise', asyncTest(() => { // don't need to provide doneFn here. + let timeoutCalled = false; + setTimeout(() => { + timeoutCalled = true; + }, 100); + let thenRan = false; + Promise.resolve(null).then((_) => { + thenRan = true; + }); + setTimeout(() => { + expect(timeoutCalled).toBe(true); + expect(thenRan).toBe(true); + }, 200); + })); +}); +``` + +Will add more examples later. + +And `asyncTest/fakeAsyncTest/flush/tick/...` those APIs have been exposed as `global APIs`, you can check this type definition file for the full list. [zone-testing.typing.ts](../../lib/testing/zone-testing.typing.ts). + +4. Date.now/jasmine.clock()/rxjs.Scheduler support in fakeAsync. + +```javascript +describe('fakeAsync', () => { + it('setTimeout', fakeAsync(() => { + const start = Date.now(); + testZoneSpec.tick(100); + const end = Date.now(); + expect(end - start).toBe(100); + })); +}); + +// NOTE: automatically fall into fakeAsync need to set this flag to true before loading zone-testing +// (window as any).__zone_symbol__fakeAsyncPatchLock = true; +describe('jasmine.clock', () => { + beforeEach(() => { + jasmine.clock().install(); + }); + + afterEach(() => { + jasmine.clock().uninstall(); + }); + + it('should get date diff correctly', () => { // we don't need fakeAsync here. + // automatically run into fake async zone, because jasmine.clock() is installed. + const start = Date.now(); + jasmine.clock().tick(100); + const end = Date.now(); + expect(end - start).toBe(100); + }); +}); + +// import the following files to patch Date.now of rxjs.Scheduler/rxjs.asap/rxjs.async +import 'zone.js/dist/zone-patch-rxjs-fakeAsync'; + +describe('fakeAsync', () => { + it('should get date diff correctly', fakeAsync(() => { + let result = null; + const observable = new Observable((subscribe: any) => { + subscribe.next('hello'); + }); + observable.delay(1000).subscribe(v => { + result = v; + }); + expect(result).toBeNull(); + testZoneSpec.tick(1000); + expect(result).toBe('hello'); + }); +}); +``` + +5. Unify `jasmine/mocha/jest` test cases in `Mocha` runner. + +You can write `jasmine`, `mocha`, `jest` style test cases when you use `Mocha` runner. +You can use `jasmine spy` inside `mocha` cases, you can use `jest style expect and mock`, and you can use `jasmine clock` and `jest TimerMock`. + +for the full list of supported APIs, please check this type definition file. [zone-testing.typing.ts](../../lib/testing/zone-testing.typing.ts). + +```javascript +describe('mixed', () => { + // jasmine style beforeAll + beforeAll(() => {}); + + // mocha style setup + setup(() => {}); + + it('jasmine style', () => { + expect(true).toBe(true); + }); + + // mocha specify + specify('mocha style', () => { + foo = { + setBar: function(value: any) { + bar = value; + } + }; + + spyOn(foo, 'setBar').and.callThrough(); + foo.setBar(123); + expect(bar).toEqual(123); + }); + + test('jest style', () => { + // TODO: will add type definition later. + (expect([1, 2, 3]) as any).toHaveLength(3); + // can handle promise with jest expect.resolves + return (expect(Promise.resolve('lemon')) as any).resolves.toBe('lemon'); + }); + + test('jest mock', () => { + // can support jest.mock + const myMockFn = jest.fn(() => 'default') + .mockImplementationOnce(() => 'first call') + .mockImplementationOnce(() => 'second call'); + + // 'first call', 'second call', 'default', 'default' + logs.push(myMockFn(), myMockFn(), myMockFn(), myMockFn()); + expect(logs).toEqual(['first call', 'second call', 'default', 'default']); + }); +}); +``` + +For full examples, you can find in [jasmine](../../test/spec/mocha/jasmine-bridge.spec.ts) and [jest](../../test/spec/jest-bridge.spec.ts) + +And here is the mapping about which `jasmine/jest` functionalities are supported inside `Mocha` runner. + +1. BDD/TDD interface. + +| jasmine | mocha | jest | +| --- | --- | --- | +| beforeAll | before | beforeAll | +| afterAll | after | beforeAll | +| xdescribe | describe.skip | describe.skip | +| fdescribe | describe.only | describe.only | +| xit | it.skip | it.skip | +| fit | it.only | it.only | + +And of course you can use `setup/suiteSetup/tearDown/suiteTearDown` in Mocha. + +2. jasmine.clock +You can use `jasmine.clock` inside `Mocha` runner. And you can also use the `auto fakeAsync` feature. + +3. jest TimerMock +You can use `jest.useFakeTimers()` to setup jest Timer Mock, and you can use `jest.runAllTimers()/jest.runOnlyPendingTimers()/jest.advanceTimersByTime()/...` to do the time control. + +3. jasmine.spy +In Mocha, no built-in spy lib is provided, you can still use 3rd party spy library, with `zone-testing`, you can use `jasmine spy`, you can use `jasmine.createSpy, jasmine.createSpyObj, spyOn, spyOnProperty` to create `jasmine style spy`. And you can also use all `spy strategy` such as `callThough/callFake/...` + +4. jest mock +Not only `jasmine spy`, you can also use `jest mock`, You can use `jest.fn` to create `jest style mock`. And you can also use the `jest mockFn method` such as `mochFn.mockReturnValue`. + +5. jasmine expect +In Mocha, there is no built in `expect` lib, you can still use 3rd party `assert/expect` lib, with `zone-testing`, you can use `jasmine expect mathers`. + + - nothing + - toBe + - toBeCloseTo + - toEqual + - toBeGreaterThan + - toBeGreaterThanOrEqual + - toBeLessThan + - toBeLessThanOrEqual + - toBeDefined + - toBeNaN + - toBeNegativeInfinity + - toBeNull + - toBePositiveInfinity + - toBeUndefined + - toThrow + - toThrowError + - toBeTruthy + - toBeFalsy + - toContain + - toHaveBeenCalled + - toHaveBeenCalledWith + - toMatch + - not + +You can also add customMatchers and customEqualityTesters. + +6. Jest expect +And you can also get `jest expect matchers`. + + - toBeCalled + - toBeCalledWith + - toHaveBeenCalledTimes + - lastCalledWith + - toHaveBeenLastCalledWith + - toBeInstanceOf + - toContainEqual + - toHaveLength + - toHaveProperty + - toMatchObject + +And `expect util method`. + + - expect.anything + - expect.any + - expect.arrayContaining + - expect.objectContaining + - expect.stringContaining + - expect.stringMatching + - expect.extend + - expect.assertions + - expect.hasAssertions + - expect.resolves (Promise) + - expect.rejects (Promise) diff --git a/doc/error.png b/doc/lifecycle/png/error.png similarity index 100% rename from doc/error.png rename to doc/lifecycle/png/error.png diff --git a/doc/eventtask.png b/doc/lifecycle/png/eventtask.png similarity index 100% rename from doc/eventtask.png rename to doc/lifecycle/png/eventtask.png diff --git a/doc/microtask.png b/doc/lifecycle/png/microtask.png similarity index 100% rename from doc/microtask.png rename to doc/lifecycle/png/microtask.png diff --git a/doc/non-periodical-macrotask.png b/doc/lifecycle/png/non-periodical-macrotask.png similarity index 100% rename from doc/non-periodical-macrotask.png rename to doc/lifecycle/png/non-periodical-macrotask.png diff --git a/doc/override-task.png b/doc/lifecycle/png/override-task.png similarity index 100% rename from doc/override-task.png rename to doc/lifecycle/png/override-task.png diff --git a/doc/periodical-macrotask.png b/doc/lifecycle/png/periodical-macrotask.png similarity index 100% rename from doc/periodical-macrotask.png rename to doc/lifecycle/png/periodical-macrotask.png diff --git a/doc/reschedule-task.png b/doc/lifecycle/png/reschedule-task.png similarity index 100% rename from doc/reschedule-task.png rename to doc/lifecycle/png/reschedule-task.png diff --git a/doc/error.puml b/doc/lifecycle/puml/error.puml similarity index 100% rename from doc/error.puml rename to doc/lifecycle/puml/error.puml diff --git a/doc/eventtask.puml b/doc/lifecycle/puml/eventtask.puml similarity index 100% rename from doc/eventtask.puml rename to doc/lifecycle/puml/eventtask.puml diff --git a/doc/microtask.puml b/doc/lifecycle/puml/microtask.puml similarity index 100% rename from doc/microtask.puml rename to doc/lifecycle/puml/microtask.puml diff --git a/doc/non-periodical-macrotask.puml b/doc/lifecycle/puml/non-periodical-macrotask.puml similarity index 100% rename from doc/non-periodical-macrotask.puml rename to doc/lifecycle/puml/non-periodical-macrotask.puml diff --git a/doc/override-task.puml b/doc/lifecycle/puml/override-task.puml similarity index 100% rename from doc/override-task.puml rename to doc/lifecycle/puml/override-task.puml diff --git a/doc/periodical-macrotask.puml b/doc/lifecycle/puml/periodical-macrotask.puml similarity index 100% rename from doc/periodical-macrotask.puml rename to doc/lifecycle/puml/periodical-macrotask.puml diff --git a/doc/reschedule-task.puml b/doc/lifecycle/puml/reschedule-task.puml similarity index 100% rename from doc/reschedule-task.puml rename to doc/lifecycle/puml/reschedule-task.puml diff --git a/doc/task.md b/doc/lifecycle/task.md similarity index 100% rename from doc/task.md rename to doc/lifecycle/task.md diff --git a/gulpfile.js b/gulpfile.js index dc0490804..ccd9459a3 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -106,6 +106,12 @@ gulp.task('build/zone.js.d.ts', ['compile-esm'], function() { .pipe(gulp.dest('./dist')); }); +gulp.task('build/zone-testing.d.ts', ['compile-esm'], function() { + return gulp.src('./build-esm/lib/testing/zone-testing.typing.d.ts') + .pipe(rename('zone-testing.d.ts')) + .pipe(gulp.dest('./dist')); +}); + // Zone for Node.js environment. gulp.task('build/zone-node.js', ['compile-esm-node'], function(cb) { return generateScript('./lib/node/rollup-main.ts', 'zone-node.js', false, cb); @@ -404,9 +410,8 @@ function nodeTest(specFiles, cb) { require('./build/lib/node/rollup-main'); var args = process.argv; if (args.length > 3) { - require('./build/test/test-env-setup-jasmine' + args[3]); + require('./build/test/env/config/test-env-setup-jasmine' + args[3]); } - var JasmineRunner = require('jasmine'); var JasmineRunner = require('jasmine'); var jrunner = new JasmineRunner(); @@ -433,12 +438,12 @@ function nodeTest(specFiles, cb) { } gulp.task('test/node', ['compile-node'], function(cb) { - var specFiles = ['build/test/node_entry_point.js']; + var specFiles = ['build/test/env/node/node_entry_point.js']; nodeTest(specFiles, cb); }); gulp.task('test/bluebird', ['compile-node'], function(cb) { - var specFiles = ['build/test/node_bluebird_entry_point.js']; + var specFiles = ['build/test/env/node/node_bluebird_entry_point.js']; nodeTest(specFiles, cb); }); @@ -498,7 +503,7 @@ gulp.task('changelog', () => { // run promise aplus test gulp.task('promisetest', ['build/zone-node.js'], (cb) => { const promisesAplusTests = require('promises-aplus-tests'); - const adapter = require('./promise-adapter'); + const adapter = require('./test/spec/promise/promise-adapter'); promisesAplusTests(adapter, {reporter: 'dot'}, function(err) { if (err) { cb(err); @@ -510,8 +515,8 @@ gulp.task('promisetest', ['build/zone-node.js'], (cb) => { // check dist file size limitation gulp.task('filesize', ['build'], (cb) => { - const checker = require('./check-file-size'); - const result = checker(require('./file-size-limit.json')); + const checker = require('./scripts/size/check-file-size'); + const result = checker(require('./scripts/size/file-size-limit.json')); if (result) { cb(); } else { diff --git a/karma-dist-sauce-jasmine3.conf.js b/karma-dist-sauce-jasmine3.conf.js deleted file mode 100644 index 550d2ad94..000000000 --- a/karma-dist-sauce-jasmine3.conf.js +++ /dev/null @@ -1,12 +0,0 @@ -/** - * @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 - */ - -module.exports = function (config) { - require('./karma-dist-jasmine.conf.js')(config); - require('./sauce.conf')(config, ['SL_IOS9', 'SL_CHROME', 'SL_FIREFOX_54', 'SL_SAFARI8', 'SL_SAFARI9', 'SL_SAFARI10', 'SL_IOS8', 'SL_IOS9', 'SL_IOS10', 'SL_IE9', 'SL_IE10', 'SL_IE11', 'SL_MSEDGE15', 'SL_ANDROID4.4', 'SL_ANDROID5.1']) -}; diff --git a/karma-dist.conf.js b/karma-dist.conf.js deleted file mode 100644 index 204225c4d..000000000 --- a/karma-dist.conf.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * @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 - */ - -module.exports = function(config) { - require('./karma-base.conf.js')(config); - config.files.push('build/test/wtf_mock.js'); - config.files.push('build/test/test_fake_polyfill.js'); - config.files.push('build/test/custom_error.js'); - config.files.push('dist/zone.js'); - config.files.push('dist/zone-patch-user-media.js'); - config.files.push('dist/zone-patch-resize-observer.js'); - config.files.push('dist/long-stack-trace-zone.js'); - config.files.push('dist/proxy.js'); - config.files.push('dist/sync-test.js'); - config.files.push('dist/async-test.js'); - config.files.push('dist/fake-async-test.js'); - config.files.push('dist/task-tracking.js'); - config.files.push('dist/zone-patch-promise-test.js'); - config.files.push('dist/wtf.js'); - config.files.push('build/test/main.js'); -}; diff --git a/lib/jasmine/jasmine-patch.ts b/lib/jasmine/jasmine-patch.ts new file mode 100644 index 000000000..54908cb8a --- /dev/null +++ b/lib/jasmine/jasmine-patch.ts @@ -0,0 +1,245 @@ +/** + * @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 + */ + +'use strict'; +import {patchJasmineClock} from './jasmine.clock'; +import { mappingMochaMethods } from './mocha-bridge/mocha.bdd'; +Zone.__load_patch('jasmine', (global: any) => { + const __extends = function(d: any, b: any) { + for (const p in b) + if (b.hasOwnProperty(p)) d[p] = b[p]; + function __() { + this.constructor = d; + } + d.prototype = b === null ? Object.create(b) : ((__.prototype = b.prototype), new (__ as any)()); + }; + // Patch jasmine's describe/it/beforeEach/afterEach functions so test code always runs + // in a testZone (ProxyZone). (See: angular/zone.js#91 & angular/angular#10503) + if (!Zone) throw new Error('Missing: zone.js'); + if (typeof jasmine == 'undefined') { + // not using jasmine, just return; + return; + } + if ((jasmine as any)['__zone_symbol__isBridge']) { + // jasmine is a mock bridge + return; + } + if ((jasmine as any)['__zone_patch__']) + throw new Error(`'jasmine' has already been patched with 'Zone'.`); + (jasmine as any)['__zone_patch__'] = true; + + const SyncTestZoneSpec: {new (name: string): ZoneSpec} = (Zone as any)['SyncTestZoneSpec']; + const ProxyZoneSpec: {new (): ZoneSpec} = (Zone as any)['ProxyZoneSpec']; + if (!SyncTestZoneSpec) throw new Error('Missing: SyncTestZoneSpec'); + if (!ProxyZoneSpec) throw new Error('Missing: ProxyZoneSpec'); + + const ambientZone = Zone.current; + // Create a synchronous-only zone in which to run `describe` blocks in order to raise an + // error if any asynchronous operations are attempted inside of a `describe` but outside of + // a `beforeEach` or `it`. + const syncZone = ambientZone.fork(new SyncTestZoneSpec('jasmine.describe')); + + const symbol = Zone.__symbol__; + + // Monkey patch all of the jasmine DSL so that each function runs in appropriate zone. + const jasmineEnv: any = jasmine.getEnv(); + ['describe', 'xdescribe', 'fdescribe'].forEach(methodName => { + let originalJasmineFn: Function = jasmineEnv[methodName]; + jasmineEnv[methodName] = function(description: string, specDefinitions: Function) { + return originalJasmineFn.call(this, description, wrapDescribeInZone(specDefinitions)); + }; + }); + ['it', 'xit', 'fit'].forEach(methodName => { + let originalJasmineFn: Function = jasmineEnv[methodName]; + jasmineEnv[symbol(methodName)] = originalJasmineFn; + jasmineEnv[methodName] = function( + description: string, specDefinitions: Function, timeout: number) { + const wrappedSpecDef = wrapTestInZone(specDefinitions); + return originalJasmineFn.apply( + this, + typeof timeout === 'number' ? [description, wrappedSpecDef, timeout] : + [description, wrappedSpecDef]); + }; + }); + ['beforeAll', 'afterAll', 'beforeEach', 'afterEach'].forEach(methodName => { + let originalJasmineFn: Function = jasmineEnv[methodName]; + jasmineEnv[symbol(methodName)] = originalJasmineFn; + jasmineEnv[methodName] = function(specDefinitions: Function, timeout: number) { + const wrappedSpecDef = wrapTestInZone(specDefinitions); + return originalJasmineFn.apply( + this, typeof timeout === 'number' ? [wrappedSpecDef, timeout] : [wrappedSpecDef]); + }; + }); + + patchJasmineClock(jasmine, global); + /** + * Gets a function wrapping the body of a Jasmine `describe` block to execute in a + * synchronous-only zone. + */ + function wrapDescribeInZone(describeBody: Function): Function { + return function() { + mappingMochaMethods(jasmine, global, this); + return syncZone.run(describeBody, this, (arguments as any) as any[]); + }; + } + + function runInTestZone(testBody: Function, applyThis: any, queueRunner: any, done?: Function) { + const isClockInstalled = (jasmine as any)[symbol('clockInstalled')] === true; + const testProxyZoneSpec = queueRunner.testProxyZoneSpec; + const testProxyZone = queueRunner.testProxyZone; + if (isClockInstalled) { + // auto run a fakeAsync + const fakeAsyncModule = (Zone as any)[Zone.__symbol__('fakeAsyncTest')]; + if (fakeAsyncModule && typeof fakeAsyncModule.fakeAsync === 'function') { + testBody = fakeAsyncModule.fakeAsync(testBody); + } + } + mappingMochaMethods(jasmine, global, applyThis); + if (done) { + return testProxyZone.run(testBody, applyThis, [done]); + } else { + return testProxyZone.run(testBody, applyThis); + } + } + + /** + * Gets a function wrapping the body of a Jasmine `it/beforeEach/afterEach` block to + * execute in a ProxyZone zone. + * This will run in `testProxyZone`. The `testProxyZone` will be reset by the `ZoneQueueRunner` + */ + function wrapTestInZone(testBody: Function): Function { + // The `done` callback is only passed through if the function expects at least one argument. + // Note we have to make a function with correct number of arguments, otherwise jasmine will + // think that all functions are sync or async. + return (testBody && (testBody.length ? function(done: Function) { + return runInTestZone(testBody, this, this.queueRunner, done); + } : function() { + return runInTestZone(testBody, this, this.queueRunner); + })); + } + interface QueueRunner { + execute(): void; + } + interface QueueRunnerAttrs { + queueableFns: {fn: Function}[]; + onComplete: () => void; + clearStack: (fn: any) => void; + onException: (error: any) => void; + catchException: () => boolean; + userContext: any; + timeout: {setTimeout: Function; clearTimeout: Function}; + fail: () => void; + } + + const QueueRunner = (jasmine as any).QueueRunner as { + new (attrs: QueueRunnerAttrs): QueueRunner; + }; + (jasmine as any).QueueRunner = (function(_super) { + __extends(ZoneQueueRunner, _super); + function ZoneQueueRunner(attrs: { + onComplete: Function; + userContext?: any; + timeout?: {setTimeout: Function; clearTimeout: Function}; + onException?: (error: any) => void; + }) { + attrs.onComplete = (fn => () => { + // All functions are done, clear the test zone. + this.testProxyZone = null; + this.testProxyZoneSpec = null; + const originalTimeout = (jasmine as any)['__zone_symbol__DEFAULT_TIMEOUT_INTERVAL']; + if (typeof originalTimeout === 'number') { + jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout; + } + ambientZone.scheduleMicroTask('jasmine.onComplete', fn); + })(attrs.onComplete); + + const nativeSetTimeout = global['__zone_symbol__setTimeout']; + const nativeClearTimeout = global['__zone_symbol__clearTimeout']; + if (nativeSetTimeout) { + // should run setTimeout inside jasmine outside of zone + attrs.timeout = { + setTimeout: nativeSetTimeout ? nativeSetTimeout : global.setTimeout, + clearTimeout: nativeClearTimeout ? nativeClearTimeout : global.clearTimeout + }; + } + + // create a userContext to hold the queueRunner itself + // so we can access the testProxy in it/xit/beforeEach ... + if ((jasmine as any).UserContext) { + if (!attrs.userContext) { + attrs.userContext = new (jasmine as any).UserContext(); + } + attrs.userContext.queueRunner = this; + } else { + if (!attrs.userContext) { + attrs.userContext = {}; + } + attrs.userContext.queueRunner = this; + } + + // patch attrs.onException + const onException = attrs.onException; + attrs.onException = function(error: any) { + if (error && + error.message === + 'Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.') { + // jasmine timeout, we can make the error message more + // reasonable to tell what tasks are pending + const proxyZoneSpec: any = this && this.testProxyZoneSpec; + if (proxyZoneSpec) { + const pendingTasksInfo = proxyZoneSpec.getAndClearPendingTasksInfo(); + error.message += pendingTasksInfo; + } + } + if (onException) { + onException.call(this, error); + } + }; + + _super.call(this, attrs); + } + ZoneQueueRunner.prototype.execute = function() { + let zone: Zone = Zone.current; + let isChildOfAmbientZone = false; + while (zone) { + if (zone === ambientZone) { + isChildOfAmbientZone = true; + break; + } + zone = zone.parent; + } + + if (!isChildOfAmbientZone) throw new Error('Unexpected Zone: ' + Zone.current.name); + + // This is the zone which will be used for running individual tests. + // It will be a proxy zone, so that the tests function can retroactively install + // different zones. + // Example: + // - In beforeEach() do childZone = Zone.current.fork(...); + // - In it() try to do fakeAsync(). The issue is that because the beforeEach forked the + // zone outside of fakeAsync it will be able to escape the fakeAsync rules. + // - Because ProxyZone is parent fo `childZone` fakeAsync can retroactively add + // fakeAsync behavior to the childZone. + + this.testProxyZoneSpec = new ProxyZoneSpec(); + this.testProxyZone = ambientZone.fork(this.testProxyZoneSpec); + if (!Zone.currentTask) { + // if we are not running in a task then if someone would register a + // element.addEventListener and then calling element.click() the + // addEventListener callback would think that it is the top most task and would + // drain the microtask queue on element.click() which would be incorrect. + // For this reason we always force a task when running jasmine tests. + Zone.current.scheduleMicroTask( + 'jasmine.execute().forceTask', () => QueueRunner.prototype.execute.call(this)); + } else { + _super.prototype.execute.call(this); + } + }; + return ZoneQueueRunner; + })(QueueRunner); +}); diff --git a/lib/jasmine/jasmine.clock.ts b/lib/jasmine/jasmine.clock.ts new file mode 100644 index 000000000..879cb65c6 --- /dev/null +++ b/lib/jasmine/jasmine.clock.ts @@ -0,0 +1,53 @@ +/** + * @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 + */ +// need to patch jasmine.clock().mockDate and jasmine.clock().tick() so +// they can work properly in FakeAsyncTest +export function patchJasmineClock(jasmine: any, global: any) { + const symbol = Zone.__symbol__; + const originalClockFn: Function = ((jasmine as any)[symbol('clock')] = jasmine['clock']); + (jasmine as any)['clock'] = function() { + const clock = originalClockFn.apply(this, arguments); + if (!clock[symbol('patched')]) { + clock[symbol('patched')] = symbol('patched'); + const originalTick = (clock[symbol('tick')] = clock.tick); + clock.tick = function() { + const fakeAsyncZoneSpec = Zone.current.get('FakeAsyncTestZoneSpec'); + if (fakeAsyncZoneSpec) { + return fakeAsyncZoneSpec.tick.apply(fakeAsyncZoneSpec, arguments); + } + return originalTick.apply(this, arguments); + }; + const originalMockDate = (clock[symbol('mockDate')] = clock.mockDate); + clock.mockDate = function() { + const fakeAsyncZoneSpec = Zone.current.get('FakeAsyncTestZoneSpec'); + if (fakeAsyncZoneSpec) { + const dateTime = arguments.length > 0 ? arguments[0] : new Date(); + return fakeAsyncZoneSpec.setCurrentRealTime.apply( + fakeAsyncZoneSpec, + dateTime && typeof dateTime.getTime === 'function' ? [dateTime.getTime()] : + arguments); + } + return originalMockDate.apply(this, arguments); + }; + // for auto go into fakeAsync feature, we need the flag to enable it + ['install', 'uninstall'].forEach(methodName => { + const originalClockFn: Function = (clock[symbol(methodName)] = clock[methodName]); + clock[methodName] = function () { + const enableClockPatch = global[symbol('fakeAsyncPatchLock')] === true; + const FakeAsyncTestZoneSpec = (Zone as any)['FakeAsyncTestZoneSpec']; + if (enableClockPatch && FakeAsyncTestZoneSpec) { + (jasmine as any)[symbol('clockInstalled')] = 'install' === methodName; + return; + } + return originalClockFn.apply(this, arguments); + }; + }); + } + return clock; + }; +} diff --git a/lib/jasmine/jasmine.ts b/lib/jasmine/jasmine.ts index 5d279ab36..197905f52 100644 --- a/lib/jasmine/jasmine.ts +++ b/lib/jasmine/jasmine.ts @@ -5,271 +5,6 @@ * 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 */ - -'use strict'; -(() => { - const __extends = function(d: any, b: any) { - for (const p in b) - if (b.hasOwnProperty(p)) d[p] = b[p]; - function __() { - this.constructor = d; - } - d.prototype = b === null ? Object.create(b) : ((__.prototype = b.prototype), new (__ as any)()); - }; - const _global: any = - typeof window !== 'undefined' && window || typeof self !== 'undefined' && self || global; - // Patch jasmine's describe/it/beforeEach/afterEach functions so test code always runs - // in a testZone (ProxyZone). (See: angular/zone.js#91 & angular/angular#10503) - if (!Zone) throw new Error('Missing: zone.js'); - if (typeof jasmine == 'undefined') throw new Error('Missing: jasmine.js'); - if ((jasmine as any)['__zone_patch__']) - throw new Error(`'jasmine' has already been patched with 'Zone'.`); - (jasmine as any)['__zone_patch__'] = true; - - const SyncTestZoneSpec: {new (name: string): ZoneSpec} = (Zone as any)['SyncTestZoneSpec']; - const ProxyZoneSpec: {new (): ZoneSpec} = (Zone as any)['ProxyZoneSpec']; - if (!SyncTestZoneSpec) throw new Error('Missing: SyncTestZoneSpec'); - if (!ProxyZoneSpec) throw new Error('Missing: ProxyZoneSpec'); - - const ambientZone = Zone.current; - // Create a synchronous-only zone in which to run `describe` blocks in order to raise an - // error if any asynchronous operations are attempted inside of a `describe` but outside of - // a `beforeEach` or `it`. - const syncZone = ambientZone.fork(new SyncTestZoneSpec('jasmine.describe')); - - const symbol = Zone.__symbol__; - - // whether patch jasmine clock when in fakeAsync - const enableClockPatch = _global[symbol('fakeAsyncPatchLock')] === true; - - // Monkey patch all of the jasmine DSL so that each function runs in appropriate zone. - const jasmineEnv: any = jasmine.getEnv(); - ['describe', 'xdescribe', 'fdescribe'].forEach(methodName => { - let originalJasmineFn: Function = jasmineEnv[methodName]; - jasmineEnv[methodName] = function(description: string, specDefinitions: Function) { - return originalJasmineFn.call(this, description, wrapDescribeInZone(specDefinitions)); - }; - }); - ['it', 'xit', 'fit'].forEach(methodName => { - let originalJasmineFn: Function = jasmineEnv[methodName]; - jasmineEnv[symbol(methodName)] = originalJasmineFn; - jasmineEnv[methodName] = function( - description: string, specDefinitions: Function, timeout: number) { - arguments[1] = wrapTestInZone(specDefinitions); - return originalJasmineFn.apply(this, arguments); - }; - }); - ['beforeEach', 'afterEach'].forEach(methodName => { - let originalJasmineFn: Function = jasmineEnv[methodName]; - jasmineEnv[symbol(methodName)] = originalJasmineFn; - jasmineEnv[methodName] = function(specDefinitions: Function, timeout: number) { - arguments[0] = wrapTestInZone(specDefinitions); - return originalJasmineFn.apply(this, arguments); - }; - }); - - // need to patch jasmine.clock().mockDate and jasmine.clock().tick() so - // they can work properly in FakeAsyncTest - const originalClockFn: Function = ((jasmine as any)[symbol('clock')] = jasmine['clock']); - (jasmine as any)['clock'] = function() { - const clock = originalClockFn.apply(this, arguments); - if (!clock[symbol('patched')]) { - clock[symbol('patched')] = symbol('patched'); - const originalTick = (clock[symbol('tick')] = clock.tick); - clock.tick = function() { - const fakeAsyncZoneSpec = Zone.current.get('FakeAsyncTestZoneSpec'); - if (fakeAsyncZoneSpec) { - return fakeAsyncZoneSpec.tick.apply(fakeAsyncZoneSpec, arguments); - } - return originalTick.apply(this, arguments); - }; - const originalMockDate = (clock[symbol('mockDate')] = clock.mockDate); - clock.mockDate = function() { - const fakeAsyncZoneSpec = Zone.current.get('FakeAsyncTestZoneSpec'); - if (fakeAsyncZoneSpec) { - const dateTime = arguments.length > 0 ? arguments[0] : new Date(); - return fakeAsyncZoneSpec.setCurrentRealTime.apply( - fakeAsyncZoneSpec, - dateTime && typeof dateTime.getTime === 'function' ? [dateTime.getTime()] : - arguments); - } - return originalMockDate.apply(this, arguments); - }; - // for auto go into fakeAsync feature, we need the flag to enable it - if (enableClockPatch) { - ['install', 'uninstall'].forEach(methodName => { - const originalClockFn: Function = (clock[symbol(methodName)] = clock[methodName]); - clock[methodName] = function() { - const FakeAsyncTestZoneSpec = (Zone as any)['FakeAsyncTestZoneSpec']; - if (FakeAsyncTestZoneSpec) { - (jasmine as any)[symbol('clockInstalled')] = 'install' === methodName; - return; - } - return originalClockFn.apply(this, arguments); - }; - }); - } - } - return clock; - }; - - /** - * Gets a function wrapping the body of a Jasmine `describe` block to execute in a - * synchronous-only zone. - */ - function wrapDescribeInZone(describeBody: Function): Function { - return function() { - return syncZone.run(describeBody, this, (arguments as any) as any[]); - }; - } - - function runInTestZone(testBody: Function, applyThis: any, queueRunner: any, done?: Function) { - const isClockInstalled = !!(jasmine as any)[symbol('clockInstalled')]; - const testProxyZoneSpec = queueRunner.testProxyZoneSpec; - const testProxyZone = queueRunner.testProxyZone; - let lastDelegate; - if (isClockInstalled && enableClockPatch) { - // auto run a fakeAsync - const fakeAsyncModule = (Zone as any)[Zone.__symbol__('fakeAsyncTest')]; - if (fakeAsyncModule && typeof fakeAsyncModule.fakeAsync === 'function') { - testBody = fakeAsyncModule.fakeAsync(testBody); - } - } - if (done) { - return testProxyZone.run(testBody, applyThis, [done]); - } else { - return testProxyZone.run(testBody, applyThis); - } - } - - /** - * Gets a function wrapping the body of a Jasmine `it/beforeEach/afterEach` block to - * execute in a ProxyZone zone. - * This will run in `testProxyZone`. The `testProxyZone` will be reset by the `ZoneQueueRunner` - */ - function wrapTestInZone(testBody: Function): Function { - // The `done` callback is only passed through if the function expects at least one argument. - // Note we have to make a function with correct number of arguments, otherwise jasmine will - // think that all functions are sync or async. - return (testBody && (testBody.length ? function(done: Function) { - return runInTestZone(testBody, this, this.queueRunner, done); - } : function() { - return runInTestZone(testBody, this, this.queueRunner); - })); - } - interface QueueRunner { - execute(): void; - } - interface QueueRunnerAttrs { - queueableFns: {fn: Function}[]; - onComplete: () => void; - clearStack: (fn: any) => void; - onException: (error: any) => void; - catchException: () => boolean; - userContext: any; - timeout: {setTimeout: Function; clearTimeout: Function}; - fail: () => void; - } - - const QueueRunner = (jasmine as any).QueueRunner as { - new (attrs: QueueRunnerAttrs): QueueRunner; - }; - (jasmine as any).QueueRunner = (function(_super) { - __extends(ZoneQueueRunner, _super); - function ZoneQueueRunner(attrs: { - onComplete: Function; userContext?: any; - timeout?: {setTimeout: Function; clearTimeout: Function}; - onException?: (error: any) => void; - }) { - attrs.onComplete = (fn => () => { - // All functions are done, clear the test zone. - this.testProxyZone = null; - this.testProxyZoneSpec = null; - ambientZone.scheduleMicroTask('jasmine.onComplete', fn); - })(attrs.onComplete); - - const nativeSetTimeout = _global['__zone_symbol__setTimeout']; - const nativeClearTimeout = _global['__zone_symbol__clearTimeout']; - if (nativeSetTimeout) { - // should run setTimeout inside jasmine outside of zone - attrs.timeout = { - setTimeout: nativeSetTimeout ? nativeSetTimeout : _global.setTimeout, - clearTimeout: nativeClearTimeout ? nativeClearTimeout : _global.clearTimeout - }; - } - - // create a userContext to hold the queueRunner itself - // so we can access the testProxy in it/xit/beforeEach ... - if ((jasmine as any).UserContext) { - if (!attrs.userContext) { - attrs.userContext = new (jasmine as any).UserContext(); - } - attrs.userContext.queueRunner = this; - } else { - if (!attrs.userContext) { - attrs.userContext = {}; - } - attrs.userContext.queueRunner = this; - } - - // patch attrs.onException - const onException = attrs.onException; - attrs.onException = function(error: any) { - if (error && - error.message === - 'Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.') { - // jasmine timeout, we can make the error message more - // reasonable to tell what tasks are pending - const proxyZoneSpec: any = this && this.testProxyZoneSpec; - if (proxyZoneSpec) { - const pendingTasksInfo = proxyZoneSpec.getAndClearPendingTasksInfo(); - error.message += pendingTasksInfo; - } - } - if (onException) { - onException.call(this, error); - } - }; - - _super.call(this, attrs); - } - ZoneQueueRunner.prototype.execute = function() { - let zone: Zone = Zone.current; - let isChildOfAmbientZone = false; - while (zone) { - if (zone === ambientZone) { - isChildOfAmbientZone = true; - break; - } - zone = zone.parent; - } - - if (!isChildOfAmbientZone) throw new Error('Unexpected Zone: ' + Zone.current.name); - - // This is the zone which will be used for running individual tests. - // It will be a proxy zone, so that the tests function can retroactively install - // different zones. - // Example: - // - In beforeEach() do childZone = Zone.current.fork(...); - // - In it() try to do fakeAsync(). The issue is that because the beforeEach forked the - // zone outside of fakeAsync it will be able to escape the fakeAsync rules. - // - Because ProxyZone is parent fo `childZone` fakeAsync can retroactively add - // fakeAsync behavior to the childZone. - - this.testProxyZoneSpec = new ProxyZoneSpec(); - this.testProxyZone = ambientZone.fork(this.testProxyZoneSpec); - if (!Zone.currentTask) { - // if we are not running in a task then if someone would register a - // element.addEventListener and then calling element.click() the - // addEventListener callback would think that it is the top most task and would - // drain the microtask queue on element.click() which would be incorrect. - // For this reason we always force a task when running jasmine tests. - Zone.current.scheduleMicroTask( - 'jasmine.execute().forceTask', () => QueueRunner.prototype.execute.call(this)); - } else { - _super.prototype.execute.call(this); - } - }; - return ZoneQueueRunner; - })(QueueRunner); -})(); +import './jasmine-patch'; +// TODO: @JiaLiPassion, add mocha/jest bridge for jasmine later +// import './mocha-bridge/mocha-bridge'; \ No newline at end of file diff --git a/karma-build-sauce-mocha.conf.js b/lib/jasmine/mocha-bridge/mocha-bridge.ts similarity index 62% rename from karma-build-sauce-mocha.conf.js rename to lib/jasmine/mocha-bridge/mocha-bridge.ts index c35699113..faed6d5bb 100644 --- a/karma-build-sauce-mocha.conf.js +++ b/lib/jasmine/mocha-bridge/mocha-bridge.ts @@ -6,7 +6,4 @@ * found in the LICENSE file at https://angular.io/license */ -module.exports = function (config) { - require('./karma-dist-mocha.conf.js')(config); - require('./sauce.conf')(config); -}; +import './mocha'; diff --git a/lib/jasmine/mocha-bridge/mocha.bdd.ts b/lib/jasmine/mocha-bridge/mocha.bdd.ts new file mode 100644 index 000000000..287932ce1 --- /dev/null +++ b/lib/jasmine/mocha-bridge/mocha.bdd.ts @@ -0,0 +1,45 @@ +/** + * @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 + */ + +export function mappingBDD(Mocha: any, jasmine: any, global: any) { + const mappings: {Mocha: string, jasmine: string, target?: any}[] = [ + {jasmine: 'beforeAll', Mocha: 'before'}, {jasmine: 'afterAll', Mocha: 'after'}, + {jasmine: 'beforeEach', Mocha: 'setup'}, {jasmine: 'afterEach', Mocha: 'tearDown'}, + {jasmine: 'it', Mocha: 'it'}, {jasmine: 'it', Mocha: 'specify'}, {jasmine: 'it', Mocha: 'test'}, + {jasmine: 'describe', Mocha: 'suite'}, {jasmine: 'describe', Mocha: 'context'}, + {jasmine: 'beforeAll', Mocha: 'suiteSetup'}, {jasmine: 'afterAll', Mocha: 'suiteTeardown'}, + {jasmine: 'xdescribe', Mocha: 'skip', target: global['describe']}, + {jasmine: 'fdescribe', Mocha: 'only', target: global['describe']}, + {jasmine: 'xit', Mocha: 'skip', target: global['it']}, {jasmine: 'fit', Mocha: 'only'} + ]; + mappings.forEach(map => { + const mocha: any = map.Mocha; + const jasmine = map.jasmine; + const target = map.target || global; + if (!target[mocha]) { + target[mocha] = global[jasmine]; + } + }); +} + +const symbol = Zone.__symbol__; +export function mappingMochaMethods(jasmine: any, global: any, context: any) { + if (context && !context.timeout) { + context.timeout = function (timeout: number) { + (jasmine as any)['__zone_symbol__DEFAULT_TIMEOUT_INTERVAL'] = jasmine.DEFAULT_TIMEOUT_INTERVAL; + jasmine.DEFAULT_TIMEOUT_INTERVAL = timeout; + }; + } + if (context && !context.skip) { + context.skip = function () { + if (typeof global['pending'] === 'function') { + global['pending'](); + } + }; + } +} \ No newline at end of file diff --git a/lib/jasmine/mocha-bridge/mocha.ts b/lib/jasmine/mocha-bridge/mocha.ts new file mode 100644 index 000000000..92106048a --- /dev/null +++ b/lib/jasmine/mocha-bridge/mocha.ts @@ -0,0 +1,17 @@ +/** + * @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 {mappingBDD} from './mocha.bdd'; +Zone.__load_patch('mocha-bridge', (global: any) => { + if (global.Mocha) { + return; + } + global.Mocha = {}; + // set a flag to tell global.Mocha is a mock. + global.Mocha['__zone_symbol__isBridge'] = true; + mappingBDD(global.Mocha, global.jasmine, global); +}); \ No newline at end of file diff --git a/karma-dist-sauce-selenium3-jasmine.conf.js b/lib/mocha/jasmine-bridge/jasmine-bridge.ts similarity index 60% rename from karma-dist-sauce-selenium3-jasmine.conf.js rename to lib/mocha/jasmine-bridge/jasmine-bridge.ts index d5c65da90..1fd4c16c5 100644 --- a/karma-dist-sauce-selenium3-jasmine.conf.js +++ b/lib/mocha/jasmine-bridge/jasmine-bridge.ts @@ -6,7 +6,4 @@ * found in the LICENSE file at https://angular.io/license */ -module.exports = function (config) { - require('./karma-dist-jasmine.conf.js')(config); - require('./sauce-selenium3.conf')(config); -}; +import './jasmine'; diff --git a/lib/mocha/jasmine-bridge/jasmine.bdd.ts b/lib/mocha/jasmine-bridge/jasmine.bdd.ts new file mode 100644 index 000000000..a8b9edd24 --- /dev/null +++ b/lib/mocha/jasmine-bridge/jasmine.bdd.ts @@ -0,0 +1,52 @@ +/** + * @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 + */ + +export function mappingBDD(jasmine: any, Mocha: any, global: any) { + const mappings: {jasmine: string, Mocha: string}[] = [ + {jasmine: 'beforeAll', Mocha: 'before'}, {jasmine: 'afterAll', Mocha: 'after'}, + {jasmine: 'xdescribe', Mocha: 'describe.skip'}, {jasmine: 'fdescribe', Mocha: 'describe.only'}, + {jasmine: 'xit', Mocha: 'it.skip'}, {jasmine: 'fit', Mocha: 'it.only'} + ]; + mappings.forEach(map => { + if (!global[map.jasmine]) { + const mocha = map.Mocha; + const chains = mocha.split('.'); + let mochaMethod: any = null; + for (let i = 0; i < chains.length; i++) { + mochaMethod = mochaMethod ? mochaMethod[chains[i]] : global[chains[i]]; + } + global[map.jasmine] = jasmine[map.jasmine] = function() { + const args = Array.prototype.slice.call(arguments); + if (args.length > 0 && typeof args[args.length - 1] === 'number') { + // get a timeout + const timeout = args[args.length - 1]; + if (this && typeof this.timeout === 'function') { + this.timeout(timeout); + } + } + return mochaMethod.apply(this, args); + }; + } + }); + + if (!global['pending']) { + global['pending'] = function() { + const ctx = Mocha.__zone_symbol__current_ctx; + if (ctx && typeof ctx.skip === 'function') { + ctx.skip(); + } + }; + } + + if (!global['fail']) { + global['fail'] = function(error?: any) { + const err = error ? error : new Error(); + throw err; + }; + } +} \ No newline at end of file diff --git a/lib/mocha/jasmine-bridge/jasmine.clock.ts b/lib/mocha/jasmine-bridge/jasmine.clock.ts new file mode 100644 index 000000000..4339adb5e --- /dev/null +++ b/lib/mocha/jasmine-bridge/jasmine.clock.ts @@ -0,0 +1,19 @@ +/** + * @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 {patchJasmineClock} from '../../jasmine/jasmine.clock'; +export function addJasmineClock(jasmine: any, global: any) { + jasmine.clock = function() { + return { + tick: function() {}, + install: function() {}, + uninstall: function() {}, + mockDate: function() {} + }; + }; + patchJasmineClock(jasmine, global); +} diff --git a/lib/mocha/jasmine-bridge/jasmine.expect.ts b/lib/mocha/jasmine-bridge/jasmine.expect.ts new file mode 100644 index 000000000..ee26f6959 --- /dev/null +++ b/lib/mocha/jasmine-bridge/jasmine.expect.ts @@ -0,0 +1,258 @@ +/** + * @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 {addCustomEqualityTester, Any, buildFailureMessage, customEqualityTesters, eq, ObjectContaining, toMatch} from './jasmine.util'; + +export function addJasmineExpect(jasmine: any, global: any) { + jasmine['__zone_symbol__customMatchers'] = []; + addExpect(global, jasmine); + addAny(jasmine); + addObjectContaining(jasmine); + addCustomEqualityTester(jasmine); + addCustomMatchers(jasmine, global); +} + +function addAny(jasmine: any) { + jasmine.any = function(expected: any) { + return new Any(expected); + }; +} + +function addObjectContaining(jasmine: any) { + jasmine.objectContaining = function(expected: any) { + return new ObjectContaining(expected); + }; +} + +function addCustomMatchers(jasmine: any, global: any) { + jasmine.addMatchers = function(customMatcher: any) { + let customMatchers = getCustomMatchers(jasmine); + customMatchers.push(customMatcher); + }; +} + +function getCustomMatchers(jasmine: any) { + return jasmine['__zone_symbol__customMatchers']; +} + +function buildCustomMatchers(jasmine: any, actual: any) { + const matchers: any = {not: {}}; + const util = {equals: eq, toMatch: toMatch, buildFailureMessage: buildFailureMessage}; + let customMatchers: any = getCustomMatchers(jasmine); + customMatchers.forEach((matcher: any) => { + Object.keys(matcher).forEach(key => { + if (matcher.hasOwnProperty(key)) { + const customMatcher = matcher[key](util, customEqualityTesters); + matchers[key] = function(...expects: any[]) { + const args = expects ? expects.slice() : []; + args.unshift(actual); + const result = customMatcher.compare.apply(null, args); + if (!result.pass) { + const message = result.messge || util.buildFailureMessage(key, false, actual, expects); + throw new Error(message); + } + }; + matchers['not'][key] = function(...expects: any[]) { + const args = expects ? [...expects] : []; + args.unshift(actual); + const result = customMatcher.compare.apply(null, args); + if (result.pass) { + const message = result.messge || util.buildFailureMessage(key, true, actual, expects); + throw new Error(message); + } + }; + } + }); + }); + return matchers; +} + +function getMatchers() { + return { + nothing: function() { + return {compare: (actual: any) => ({pass: true})}; + }, + toBe: function() { + return {compare: (actual: any, expected: any) => ({pass: actual === expected})}; + }, + toBeCloseTo: function() { + return { + compare: function(actual: any, expected: any, precision: any) { + if (precision !== 0) { + precision = precision || 2; + } + const pow = Math.pow(10, precision + 1); + const delta = Math.abs(expected - actual); + const maxDelta = Math.pow(10, -precision) / 2; + return {pass: Math.round(delta * pow) / pow <= maxDelta}; + } + }; + }, + toEqual: function() { + return {compare: (actual: any, expected: any) => ({pass: eq(actual, expected)})}; + }, + toBeGreaterThan: function() { + return {compare: (actual: any, expected: any) => ({pass: actual > expected})}; + }, + toBeGreaterThanOrEqual: function() { + return {compare: (actual: any, expected: any) => ({pass: actual >= expected})}; + }, + toBeLessThan: function() { + return {compare: (actual: any, expected: any) => ({pass: actual < expected})}; + }, + toBeLessThanOrEqual: function() { + return {compare: (actual: any, expected: any) => ({pass: actual <= expected})}; + }, + toBeDefined: function() { + return {compare: (actual: any) => ({pass: actual !== undefined})}; + }, + toBeNaN: function() { + return {compare: (actual: any) => ({pass: actual !== actual})}; + }, + toBeNegativeInfinity: function() { + return {compare: (actual: any) => ({pass: actual === Number.NEGATIVE_INFINITY})}; + }, + toBeNull: function() { + return {compare: (actual: any) => ({pass: actual === null})}; + }, + toBePositiveInfinity: function() { + return {compare: (actual: any) => ({pass: actual === Number.POSITIVE_INFINITY})}; + }, + toBeUndefined: function() { + return {compare: (actual: any) => ({pass: actual === undefined})}; + }, + toThrow: function() { + return { + compare: (actual: any, expected: any) => { + let pass = false; + try { + if (typeof actual === 'function') { + actual(); + } else { + pass = (!expected && actual instanceof Error) || eq(actual, expected); + } + } catch (error) { + pass = !expected || eq(error, expected); + } + return {pass}; + } + }; + }, + toThrowError: function() { + return { + compare: (actual: any) => { + let pass = false; + try { + if (typeof actual === 'function') { + actual(); + } else { + pass = actual instanceof Error; + } + } catch (error) { + pass = true; + } + return {pass}; + } + }; + }, + toBeTruthy: function() { + return {compare: (actual: any) => ({pass: !!actual})}; + }, + toBeFalsy: function() { + return {compare: (actual: any) => ({pass: !actual})}; + }, + toContain: function() { + return {compare: (actual: any, expected: any) => ({pass: actual.indexOf(expected) !== -1})}; + }, + toBeCalled: function() { + return {compare: (actual: any) => ({pass: actual.calls.count() > 0})}; + }, + toHaveBeenCalled: function() { + return {compare: (actual: any) => ({pass: actual.calls.count() > 0})}; + }, + toBeCalledWith: function() { + return { + compare: (actual: any, ...expected: any[]) => + ({pass: actual.calls.allArgs().filter((args: any) => eq(args, expected)).length > 0}) + }; + }, + toHaveBeenCalledWith: function() { + return { + compare: (actual: any, ...expected: any[]) => + ({pass: actual.calls.allArgs().filter((args: any) => eq(args, expected)).length > 0}) + }; + }, + toMatch: function() { + return {compare: (actual: any, expected: any) => ({pass: toMatch(actual, expected)})}; + } + }; +} + +function buildResolveRejects(key: string, matchers: any, actual: any, isNot = false) { + const resolveFnFactory = function(isNot = false) { + return function() { + const self = this; + const args = Array.prototype.slice.call(arguments); + return actual.then( + (value: any) => { + const newMatchers: any = buildCustomMatchers(jasmine, value); + return isNot ? newMatchers.not[key].apply(self, args) : + newMatchers[key].apply(self, args); + }, + (error: any) => { + throw error; + }); + }; + }; + if (isNot) { + matchers.resolves.not[key] = resolveFnFactory(true); + } else { + matchers.resolves[key] = resolveFnFactory(); + } + const rejectFnFactory = function(isNot = false) { + return function() { + const self = this; + const args = Array.prototype.slice.call(arguments); + return actual.then((value: any) => {}, (error: any) => { + const newMatchers: any = buildCustomMatchers(jasmine, error); + return isNot ? newMatchers.not[key].apply(self, args) : newMatchers[key].apply(self, args); + }); + }; + }; + if (isNot) { + matchers.rejects.not[key] = rejectFnFactory(true); + } else { + matchers.rejects[key] = rejectFnFactory(); + } +} + +function addExpect(global: any, jasmine: any) { + jasmine.__zone_symbol__expect_assertions = 0; + const builtinMatchers: any = getMatchers(); + const customMatchers = getCustomMatchers(jasmine); + customMatchers.unshift(builtinMatchers); + global['expect'] = jasmine['__zone_symbol__expect'] = function(actual: any) { + jasmine.__zone_symbol__expect_assertions++; + const matchers = buildCustomMatchers(jasmine, actual); + if (actual && typeof actual.then === 'function') { + // expected maybe a promise + matchers.resolves = {not: {}}; + matchers.rejects = {not: {}}; + Object.keys(matchers).forEach(key => { + if (matchers.hasOwnProperty(key)) { + buildResolveRejects(key, matchers, actual); + } + }); + Object.keys(matchers.not).forEach(key => { + if (matchers.not.hasOwnProperty(key)) { + buildResolveRejects(key, matchers, actual, true); + } + }); + } + return matchers; + }; +} \ No newline at end of file diff --git a/lib/mocha/jasmine-bridge/jasmine.spy.ts b/lib/mocha/jasmine-bridge/jasmine.spy.ts new file mode 100644 index 000000000..c87ef722c --- /dev/null +++ b/lib/mocha/jasmine-bridge/jasmine.spy.ts @@ -0,0 +1,363 @@ +/** + * @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 + */ +// a simple version of jasmine spy +import {cloneArgs, eq} from './jasmine.util'; +export function addJasmineSpy(jasmine: any, Mocha: any, global: any) { + let order = 0; + function nextOrder() { + return order++; + } + + interface CallContext { + object: any; + invocationOrder: number; + args: any[]; + returnValue?: any; + } + interface SpyStrategyOptions { + identity?: string; + originalFn?: Function; + getSpy?: Function; + plan?: Function; + customStrategies?: {[k: string]: Function}; + } + class CallTracker { + private calls: CallContext[] = []; + private opts: {cloneArgs?: boolean} = {}; + + track(context: CallContext) { + if (this.opts.cloneArgs) { + context.args = cloneArgs(context.args); + } + this.calls.push(context); + } + + any() { + return !!this.calls.length; + } + + count() { + return this.calls.length; + } + + argsFor(index: number) { + const call = this.calls[index]; + return call ? call.args : []; + }; + + all() { + return this.calls; + } + + allArgs() { + return this.calls.map(call => call.args); + } + + first() { + return this.calls[0]; + } + + mostRecent() { + return this.calls[this.calls.length - 1]; + } + + reset() { + this.calls = []; + } + + saveArgumentsByValue() { + this.opts.cloneArgs = true; + } + } + class SpyStrategy { + private plan: any; + private _defaultPlan: any; + public identity: string; + public originalFn: Function; + constructor(private options: SpyStrategyOptions = {}) { + this.identity = this.options.identity || 'unknown'; + this.originalFn = this.options.originalFn || function() {}; + this.options.getSpy = this.options.getSpy || function() {}; + this.plan = this._defaultPlan = this.options.plan || function() {}; + + if (this.options.customStrategies) { + for (let k in this.options.customStrategies) { + if ((this as any)[k]) { + continue; + } + const factory = this.options.customStrategies[k]; + (this as any)[k] = function() { + const plan = factory.apply(null, arguments); + this.plan = plan; + return this.getSpy(); + }; + } + } + } + + exec(context: any, args: any) { + return this.plan.apply(context, args); + } + + getSpy() { + return this.options.getSpy!(); + } + + callThrough() { + this.plan = this.originalFn; + return this.getSpy(); + } + + returnValue(value: any) { + this.plan = function() { + return value; + }; + return this.getSpy(); + }; + + returnValues() { + const values = Array.prototype.slice.call(arguments); + this.plan = function() { + return values.shift(); + }; + return this.getSpy(); + }; + + throwError(something: any) { + const error = (something instanceof Error) ? something : new Error(something); + this.plan = function() { + throw error; + }; + return this.getSpy(); + }; + + callFake(fn: Function) { + this.plan = fn; + return this.getSpy(); + }; + + stub(fn: Function) { + this.plan = function() {}; + return this.getSpy(); + }; + + isConfigured() { + return this.plan !== this._defaultPlan; + }; + } + + class SpyStrategyDispatcher { + private baseStrategy: SpyStrategy; + public and: SpyStrategy; + public withArgs: () => { + and: SpyStrategy + }; + private strategyDict: SpyStrategyDict; + constructor(private args: SpyStrategyOptions) { + this.baseStrategy = new SpyStrategy(args); + this.and = this.baseStrategy; + this.strategyDict = new SpyStrategyDict(function() { + return new SpyStrategy(args); + }); + const self = this; + this.withArgs = function() { + return {and: self.strategyDict.getOrCreate(Array.prototype.slice.call(arguments))}; + }; + } + + updateArgs(newArgs: SpyStrategyOptions) { + if (newArgs.identity) { + this.args.identity = newArgs.identity; + this.baseStrategy.identity = newArgs.identity; + this.and.identity = newArgs.identity; + } + if (newArgs.originalFn) { + this.args.originalFn = newArgs.originalFn; + this.baseStrategy.originalFn = newArgs.originalFn; + this.and.originalFn = newArgs.originalFn; + } + } + + exec(spy: any, args: any) { + let strategy = this.strategyDict.get(args); + if (!strategy) { + strategy = this.baseStrategy; + } + return strategy.exec(spy, args); + } + } + + class SpyStrategyDict { + private strategies: {args: any[], strategy: SpyStrategy}[] = []; + constructor(private strategyFactory: () => SpyStrategy) {} + + any() { + return this.strategies.length > 0; + } + + get(args: any[]) { + for (let i = 0; i < this.strategies.length; i++) { + const dictArgs = this.strategies[i].args; + if (eq(dictArgs, args)) { + return this.strategies[i].strategy; + } + } + } + + getOrCreate(args: any[]) { + let strategy = this.get(args); + if (!strategy) { + strategy = this.strategyFactory(); + this.strategies.push({args, strategy}); + } + return strategy; + } + } + + function Spy(name: string, originalFn?: Function, customStrategies?: {[key: string]: Function}) { + const calls = new CallTracker(); + const spyStrategyDispatcher = new SpyStrategyDispatcher( + {identity: name, originalFn, getSpy: () => wrapper, customStrategies}); + const spy = function() { + const callContext: CallContext = { + object: this, + invocationOrder: nextOrder(), + args: Array.prototype.slice.call(arguments) + }; + calls.track(callContext); + const returnValue = spyStrategyDispatcher.exec(this, arguments); + callContext.returnValue = returnValue; + return returnValue; + }; + const wrapper: any = function() { + return spy.apply(this, arguments); + }; + if (originalFn) { + for (let prop in originalFn) { + wrapper[prop] = (originalFn as any)[prop]; + } + } + wrapper.calls = calls; + wrapper.and = spyStrategyDispatcher.and; + wrapper.withArgs = function() { + return spyStrategyDispatcher.withArgs.apply(spyStrategyDispatcher, arguments); + }; + wrapper.updateArgs = function(newArgs: SpyStrategyOptions) { + spyStrategyDispatcher.updateArgs(newArgs); + }; + return wrapper; + } + + class SpyRegistry { + registeredSpies: {suite: any, test: any, spy: any, unRegister: Function}[] = []; + register(spy: any, unRegister: Function) { + const currentTestInfo = Mocha.getCurrentTestInfo(); + this.registeredSpies.push({ + suite: currentTestInfo.suite, + test: currentTestInfo.test, + spy: spy, + unRegister: unRegister + }); + } + + clearAllSpies() { + if (this.registeredSpies.length === 0) { + return; + } + this.registeredSpies.forEach(spy => spy.unRegister()); + this.registeredSpies.length = 0; + } + + clearSpies(testInfo: any) { + if (this.registeredSpies.length === 0) { + return; + } + let isSuite = false; + if (testInfo instanceof Mocha.Suite) { + isSuite = true; + } + for (let i = this.registeredSpies.length - 1; i--; i >= 0) { + const registerSpy = this.registeredSpies[i]; + if ((isSuite && registerSpy.suite === testInfo) || + (!isSuite && registerSpy.test === testInfo)) { + registerSpy.unRegister(); + this.registeredSpies.splice(i, 1); + } + } + } + } + + const spyRegistry = new SpyRegistry(); + Mocha.clearSpies = function(testInfo: any) { + spyRegistry.clearSpies(testInfo); + }; + + Mocha.clearAllSpies = function() { + spyRegistry.clearAllSpies(); + }; + + jasmine.createSpy = function(spyName: string|Function, originalFn?: Function) { + if (typeof spyName === 'function') { + originalFn = spyName; + spyName = (spyName as any).name; + } + return Spy(spyName as string, originalFn); + }; + + jasmine.createSpyObj = function( + baseName: string|string[]|{[key: string]: any}, methodNames: string[]|{[key: string]: any}) { + if (typeof baseName !== 'string' && !methodNames) { + methodNames = baseName; + baseName = 'unknown'; + } + const obj: any = {}; + if (Array.isArray(methodNames)) { + methodNames.forEach(methodName => { + obj[methodName] = jasmine.createSpy(baseName + '.' + methodName); + }); + } else { + for (let key in methodNames) { + if (methodNames.hasOwnProperty(key)) { + obj[key] = jasmine.createSpy(baseName + '.' + key); + obj[key].and.returnValue(methodNames[key]); + } + } + } + return obj; + }; + + global['spyOn'] = jasmine.spyOn = function(obj: any, methodName: string) { + if (!obj || !methodName || !obj[methodName]) { + throw new Error( + `Can not find a valid object ${obj} or a method name ${methodName} to spy on.`); + } + const originalMethod: Function = obj[methodName]; + const spiedMethod = jasmine.createSpy(methodName, originalMethod); + spyRegistry.register(spiedMethod, () => { + obj[methodName] = originalMethod; + }); + obj[methodName] = spiedMethod; + return spiedMethod; + }; + + global['spyOnProperty'] = + jasmine.spyOnProperty = function(obj: any, propertyName: string, accessType: string) { + if (!obj || !propertyName || !obj[propertyName]) { + throw new Error( + `Can not find a valid object ${obj} or a property name ${propertyName} to spy on.`); + } + const originalDesc = Object.getOwnPropertyDescriptor(obj, propertyName); + const originalAccess = (originalDesc as any)[accessType]; + const spiedAccess = jasmine.createSpy(propertyName, (originalDesc as any)[accessType]); + spyRegistry.register(spiedAccess, () => { + (orientation as any)[accessType] = originalAccess; + }); + (originalDesc as any)[accessType] = spiedAccess; + return spiedAccess; + }; +} diff --git a/lib/mocha/jasmine-bridge/jasmine.ts b/lib/mocha/jasmine-bridge/jasmine.ts new file mode 100644 index 000000000..3237679dd --- /dev/null +++ b/lib/mocha/jasmine-bridge/jasmine.ts @@ -0,0 +1,56 @@ +/** + * @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 {mappingBDD} from './jasmine.bdd'; +import {addJasmineClock} from './jasmine.clock'; +import {addJasmineExpect} from './jasmine.expect'; +import {addJasmineSpy} from './jasmine.spy'; +import { formatObject } from './jasmine.util'; + +Zone.__load_patch('jasmine2mocha', (global: any) => { + if (typeof global.Mocha === 'undefined') { + // not using mocha, just return + return; + } + let jasmine = global['jasmine']; + if (typeof jasmine !== 'undefined') { + // jasmine already loaded, just return + return; + } + // create a jasmine global object + jasmine = global['jasmine'] = {}; + jasmine['__zone_symbol__isBridge'] = true; + // BDD mapping + mappingBDD(jasmine, global.Mocha, global); + + // Mocha don't have a built in assert implementation + // add expect functionality + addJasmineExpect(jasmine, global); + + // Mocha don't have a built in spy implementation + // add spy functionality + addJasmineSpy(jasmine, global.Mocha, global); + + // Add jasmine clock functionality + addJasmineClock(jasmine, global); + + Object.defineProperty(jasmine, 'DEFAULT_TIMEOUT_INTERVAL', { + configurable: true, + enumerable: true, + get: function() { + return jasmine.__zone_symbol__TIMEOUT || 2000; + }, + set: function(newValue: number) { + jasmine.__zone_symbol__TIMEOUT = newValue; + global.Mocha.__zone_symbol__TIMEOUT = newValue; + } + }); + + jasmine.pp = function (obj: any): string { + return formatObject(obj); + }; +}); \ No newline at end of file diff --git a/lib/mocha/jasmine-bridge/jasmine.util.ts b/lib/mocha/jasmine-bridge/jasmine.util.ts new file mode 100644 index 000000000..7b113d465 --- /dev/null +++ b/lib/mocha/jasmine-bridge/jasmine.util.ts @@ -0,0 +1,203 @@ +/** + * @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 + */ +export function argsToArray(args: any) { + const arrayOfArgs = []; + for (let i = 0; i < args.length; i++) { + arrayOfArgs.push(args[i]); + } + return arrayOfArgs; +}; + +export function clone(obj: any) { + if (Object.prototype.toString.apply(obj) === '[object Array]') { + return obj.slice(); + } + + const cloned: any = {}; + for (let prop in obj) { + if (obj.hasOwnProperty(prop)) { + cloned[prop] = obj[prop]; + } + } + + return cloned; +}; + +const primitives = /^\[object (Boolean|String|RegExp|Number)/; +export function cloneArgs(args: any) { + const clonedArgs = []; + const argsAsArray = argsToArray(args); + for (let i = 0; i < argsAsArray.length; i++) { + const str = Object.prototype.toString.apply(argsAsArray[i]); + + // All falsey values are either primitives, `null`, or `undefined. + if (!argsAsArray[i] || str.match(primitives)) { + clonedArgs.push(argsAsArray[i]); + } else { + clonedArgs.push(clone(argsAsArray[i])); + } + } + return clonedArgs; +}; + +export class Any { + constructor(public expectedObject: any) {} + + eq(other: any) { + if (this.expectedObject == String) { + return typeof other == 'string' || other instanceof String; + } + + if (this.expectedObject == Number) { + return typeof other == 'number' || other instanceof Number; + } + + if (this.expectedObject == Function) { + return typeof other == 'function' || other instanceof Function; + } + + if (this.expectedObject == Object) { + return other !== null && typeof other == 'object'; + } + + if (this.expectedObject == Boolean) { + return typeof other == 'boolean'; + } + + return other instanceof this.expectedObject; + } +} + +export class ObjectContaining { + constructor(public expectedObject: any) {} + + match(other: any) { + for (let prop in this.expectedObject) { + if (this.expectedObject.hasOwnProperty(prop)) { + if (!eq(this.expectedObject[prop], other[prop])) { + return false; + } + } + } + return true; + } +} + +export const customEqualityTesters: ((a: any, b: any) => boolean | undefined)[] = []; + +export function addCustomEqualityTester(jasmine: any) { + jasmine.addCustomEqualityTester = function( + customEqualityTester: (a: any, b: any) => boolean | undefined) { + customEqualityTesters.push(customEqualityTester); + }; +} + +function getErrorMessage(error: any) { + return error.message || error.description; +} + +export function eq(a: any, b: any): boolean { + for (let i = 0; i < customEqualityTesters.length; i++) { + const result = customEqualityTesters[i](a, b); + if (result === true || result === false) { + return result; + } + } + if (a === b) { + return true; + } else if (Array.isArray(a) && Array.isArray(b)) { + if (a.length !== b.length) { + return false; + } + + let isEqual = true; + + for (let prop in a) { + if (a.hasOwnProperty(prop)) { + if (!eq(a[prop], b[prop])) { + isEqual = false; + break; + } + } + } + + return isEqual; + } else if (typeof a === 'object' && typeof b === 'object') { + if (a instanceof Any) { + return a.eq(b); + } else if (b instanceof Any) { + return b.eq(a); + } + if (b instanceof ObjectContaining) { + return b.match(a); + } + if (a instanceof Error && b instanceof Error) { + return getErrorMessage(a) === getErrorMessage(b) || + toMatch(getErrorMessage(a), getErrorMessage(b)); + } + if (Object.keys(a).length !== Object.keys(b).length) { + return false; + } + let isEqual = true; + + for (let prop in a) { + if (a.hasOwnProperty(prop)) { + if (!eq(a[prop], b[prop])) { + isEqual = false; + break; + } + } + } + return isEqual; + } + if (a instanceof Any) { + return a.eq(b); + } else if (b instanceof Any) { + return b.eq(a); + } + + if (a instanceof Error) { + return eq(getErrorMessage(a), b) || toMatch(getErrorMessage(a), b); + } + if (b instanceof Error) { + return eq(a, getErrorMessage(b)) || toMatch(a, getErrorMessage(b)); + } + + return false; +} + +export function toMatch(actual: any, expected: any) { + const regExp = expected instanceof RegExp ? expected : new RegExp(expected); + return regExp.test(actual); +} + +const Mocha: any = typeof window === 'undefined' ? (global as any).Mocha : (window as any).Mocha; + +export function formatObject(obj: any) { + return Object.prototype.toString.call(obj); +} + +export function buildFailureMessage( + matcherName: string, isNot: boolean, actual: any, expected: any[]) { + const englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { + return ' ' + s.toLowerCase(); + }); + + let message = 'Expected ' + formatObject(actual) + (isNot ? ' not ' : ' ') + englishyPredicate; + + if (expected && expected.length > 0) { + for (let i = 0; i < expected.length; i++) { + if (i > 0) { + message += ','; + } + message += ' ' + formatObject(expected[i]); + } + } + + return message + '.'; +} diff --git a/karma-build-sauce-selenium3-mocha.conf.js b/lib/mocha/jest-bridge/jest-bridge.ts similarity index 58% rename from karma-build-sauce-selenium3-mocha.conf.js rename to lib/mocha/jest-bridge/jest-bridge.ts index d23462147..6c5b0251f 100644 --- a/karma-build-sauce-selenium3-mocha.conf.js +++ b/lib/mocha/jest-bridge/jest-bridge.ts @@ -6,7 +6,4 @@ * found in the LICENSE file at https://angular.io/license */ -module.exports = function (config) { - require('./karma-dist-mocha.conf.js')(config); - require('./sauce-selenium3.conf')(config, ['SL_IE9']); -}; +import './jest'; diff --git a/lib/mocha/jest-bridge/jest.bdd.ts b/lib/mocha/jest-bridge/jest.bdd.ts new file mode 100644 index 000000000..63dc017fe --- /dev/null +++ b/lib/mocha/jest-bridge/jest.bdd.ts @@ -0,0 +1,25 @@ +/** + * @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 + */ + +export function mappingBDD(jest: any, Mocha: any, global: any) { + const mappings: {jest: string, Mocha: string}[] = [ + // other Jest APIs has already mapping in jasmine2mocha patch + {jest: 'test', Mocha: 'it'} + ]; + mappings.forEach(map => { + if (!global[map.jest]) { + const mocha = map.Mocha; + const chains = mocha.split('.'); + let mochaMethod: any = null; + for (let i = 0; i < chains.length; i++) { + mochaMethod = mochaMethod ? mochaMethod[chains[i]] : global[chains[i]]; + } + global[map.jest] = jest[map.jest] = mochaMethod; + } + }); +} \ No newline at end of file diff --git a/lib/mocha/jest-bridge/jest.clock.ts b/lib/mocha/jest-bridge/jest.clock.ts new file mode 100644 index 000000000..29322ea07 --- /dev/null +++ b/lib/mocha/jest-bridge/jest.clock.ts @@ -0,0 +1,76 @@ +/** + * @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 + */ +export function addJestTimer(jest: any, global: any) { + const {resetFakeAsyncZone, flushMicrotasks, discardPeriodicTasks, tick, flush, fakeAsync} = + (Zone as any)[Zone.__symbol__('fakeAsyncTest')]; + const FakeAsyncTestZoneSpec = (Zone as any)['FakeAsyncTestZoneSpec']; + const ProxyZoneSpec = (Zone as any)['ProxyZoneSpec']; + + function getFakeAsyncTestZoneSpec() { + return Zone.current.get('FakeAsyncTestZoneSpec'); + } + + jest.clearAllTimers = function() { + clearAllMacrotasks(); + }; + + jest.runAllTimers = function() { + const zs = getFakeAsyncTestZoneSpec(); + if (!zs) { + return; + } + if (zs.pendingPeriodicTimers.length > 0) { + throw new Error('Can not runAllTimers when having interval timers.'); + } + // flush non-perodic-tasks with 10000 maxTurns + flush(10000); + }; + + jest.runOnlyPendingTimers = function() { + const zs = getFakeAsyncTestZoneSpec(); + if (!zs) { + return; + } + // flush both periodic tasks and non-perodic-tasks + flush(10000, true); + }; + + jest.advanceTimersByTime = function(msToRun: number, doTick?: (elapsed: number) => void) { + const zs = getFakeAsyncTestZoneSpec(); + if (!zs) { + return; + } + tick(msToRun, doTick); + }; + + jest.runAllTicks = function() { + const zs = getFakeAsyncTestZoneSpec(); + if (!zs) { + return; + } + flushMicrotasks(); + }; + + jest.useFakeTimers = function() { + const zs = getFakeAsyncTestZoneSpec(); + if (zs) { + return; + } + /** + * a wrapper of jasmine.clock().install() + */ + global['__zone_symbol__originFakeAsyncPatchLock'] = global['__zone_symbol__fakeAsyncPatchLock']; + global['__zone_symbol__fakeAsyncPatchLock'] = true; + global.jasmine && global.jasmine.clock().install(); + }; + + jest.useRealTimers = function () { + global['__zone_symbol__fakeAsyncPatchLock'] = global['__zone_symbol__originFakeAsyncPatchLock']; + global.jasmine && global.jasmine.clock().uninstall(); + }; +} \ No newline at end of file diff --git a/lib/mocha/jest-bridge/jest.expect.ts b/lib/mocha/jest-bridge/jest.expect.ts new file mode 100644 index 000000000..e34c13198 --- /dev/null +++ b/lib/mocha/jest-bridge/jest.expect.ts @@ -0,0 +1,257 @@ +/** + * @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 {Any, eq, toMatch} from '../jasmine-bridge/jasmine.util'; +export function expandExpect(global: any) { + const jasmine = global.jasmine; + const expect: any = global.expect; + + class Anything {} + + expect.anything = function() { + return new Anything(); + }; + + expect.any = function(obj: any) { + return new Any(obj); + }; + + class ArrayContaining { + constructor(public expectedArray: any[]) {} + } + + expect.arrayContaining = function(expectedArray: string[]) { + return new ArrayContaining(expectedArray); + }; + + class ObjectContaining { + constructor(public expectedObject: any) {} + } + + expect.objectContaining = function(expectedObject: any) { + return new ObjectContaining(expectedObject); + }; + + class StringContaining { + constructor(public expectedString: string) {} + } + + expect.stringContaining = function(expectedString: string) { + return new StringContaining(expectedString); + }; + + class StringMatching { + constructor(public expectedMatcher: RegExp|string) {} + } + + expect.stringMatching = function(expectedMatcher: RegExp|string) { + return new StringMatching(expectedMatcher); + }; + + const assertions: {test: any, numbers: number}[] = (expect as any).__zone_symbol__assertionsMap = + []; + + jasmine.addCustomEqualityTester((a: any, b: any) => { + if (b instanceof Anything) { + if (a === null || a === undefined) { + return false; + } + return true; + } + if (b instanceof Any) { + return b.eq(a); + } + if (b instanceof ArrayContaining && Array.isArray(a)) { + for (let i = 0; i < b.expectedArray.length; i++) { + let found = false; + const bitem = b.expectedArray[i]; + for (let j = 0; j < a.length; j++) { + if (eq(a[j], bitem)) { + found = true; + break; + } + } + if (!found) { + return false; + } + } + return true; + } + if (b instanceof ObjectContaining) { + Object.keys(b.expectedObject).forEach(key => { + if (b.expectedObject.hasOwnProperty(key)) { + if (!eq(a[key], b.expectedObject[key]) || !toMatch(a[key], b.expectedObject[key])) { + return false; + } + } + }); + return true; + } + if (b instanceof StringContaining) { + let astr = a; + if (typeof a !== 'string') { + astr = Object.prototype.toString.call(a); + } + if (!astr) { + return false; + } + return astr.indexOf(b.expectedString) !== -1; + } + if (b instanceof StringMatching) { + let astr = a; + if (typeof a !== 'string') { + astr = Object.prototype.toString.call(a); + } + return toMatch(astr, b.expectedMatcher); + } + }); + + expect.extend = function(extendedMatchers: any) { + const jasmineMatchers: any = {}; + Object.keys(extendedMatchers).forEach(key => { + if (extendedMatchers.hasOwnProperty(key)) { + const matcher = extendedMatchers[key]; + jasmineMatchers[key] = function(util: any, customEqualityTester: any) { + return { + compare: function(actual: any, expected: any) { + return matcher(actual, expected); + } + }; + }; + } + }); + jasmine.addMatchers(jasmineMatchers); + }; + + jasmine.addMatchers({ + toHaveBeenCalledTimes: function(util: any, customEqualityTester: any) { + return { + compare: function(actual: any, expected: any) { + return {pass: actual.calls.count() === expected}; + } + }; + }, + lastCalledWith: function(util: any, customEqualityTester: any) { + return { + compare: function(actual: any, expected: any) { + return {pass: util.equals(actual.calls.last().args, expected)}; + } + }; + }, + toHaveBeenLastCalledWith: function(util: any, customEqualityTester: any) { + return { + compare: function(actual: any, expected: any) { + return {pass: util.equals(actual.calls.last().args, expected)}; + } + }; + }, + toBeInstanceOf: function(util: any, customEqualityTester: any) { + return { + compare: function(actual: any, expected: any) { + return {pass: actual instanceof expected}; + } + }; + }, + toContainEqual: function(util: any, customEqualityTester: any) { + return { + compare: function(actual: any, expected: any) { + if (!Array.isArray(actual)) { + return {pass: false}; + } + return {pass: actual.filter(a => util.equals(a, expected)).length > 0}; + } + }; + }, + toHaveLength: function(util: any, customEqualityTester: any) { + return { + compare: function(actual: any, expected: any) { + return {pass: actual.length === expected}; + } + }; + }, + toHaveProperty: function(util: any, customEqualityTester: any) { + return { + compare: function(actual: any, expected: any, expectedValue: any) { + const split: string[] = Array.isArray(expected) ? expected : expected.split('.'); + let value = null; + let hasKey = false; + for (let i = 0; i < split.length; i++) { + const prop = split[i]; + const isIndex = typeof prop === 'number'; + if (value) { + hasKey = isIndex ? Array.isArray(value) && (value as any).length > prop : + Object.keys(value).filter(a => util.equals(a, prop)).length > 0; + value = value[prop]; + } else { + hasKey = isIndex ? Array.isArray(actual) && (actual as any).length > prop : + Object.keys(actual).filter(a => util.equals(a, prop)).length > 0; + value = actual[prop]; + } + if (!hasKey) { + return {pass: false}; + } + } + + if (expectedValue !== undefined) { + return {pass: util.equals(expectedValue, value)}; + } else { + return {pass: true}; + } + } + }; + }, + toMatchObject: function(util: any, customEqualityTester: any) { + return { + compare: function(actual: any, expected: any) { + Object.keys(expected).forEach(key => { + if (expected.hasOwnProperty(key)) { + if (!util.equals(actual[key], expected[key]) && + !util.toMatch(actual[key], expected[key])) { + return {pass: false}; + } + } + }); + return {pass: true}; + } + }; + }, + }); + + expect.assertions = function(numbers: number) { + if (typeof numbers !== 'number') { + return; + } + const currentTest = global.Mocha.__zone_symbol__test; + assertions.push({test: currentTest, numbers}); + }; + + expect.hasAssertions = function() { + const currentTest = global.Mocha.__zone_symbol__test; + assertions.push({test: currentTest, numbers: 1}); + }; + + if (!global.Mocha.__zone_symbol__afterEach) { + global.Mocha.__zone_symbol__afterEach = []; + } + + global.Mocha.__zone_symbol__afterEach.push((test: any) => { + // check assertions + for (let i = 0; i < assertions.length; i++) { + const ass = assertions[i]; + if (ass.test === test) { + assertions.splice(i, 1); + const actual = jasmine.__zone_symbol__expect_assertions; + jasmine.__zone_symbol__expect_assertions = 0; + if (ass.numbers != actual) { + throw new Error(`Assertions failed, expect should be called ${ + ass.numbers} times, it was actual called ${actual} times.`); + } + return; + } + } + }); +} diff --git a/lib/mocha/jest-bridge/jest.spy.ts b/lib/mocha/jest-bridge/jest.spy.ts new file mode 100644 index 000000000..879a05cec --- /dev/null +++ b/lib/mocha/jest-bridge/jest.spy.ts @@ -0,0 +1,140 @@ +/** + * @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 + */ +export function mappingSpy(jest: any, jasmine: any, global: any) { + jest.__zone_symbol__mocks = []; + function createSpy(spyFactory: (implFn?: Function) => any, implFn?: Function) { + const spy = spyFactory(implFn); + spy.defaultFn = implFn; + const instances: any[] = []; + const mockFn: any = function MockFn() { + if (this instanceof MockFn) { + instances.push(this); + } else { + let fn = spy.defaultFn; + if (spy.onceFns && spy.onceFns.length > 0) { + fn = spy.onceFns.shift(); + } + const args = Array.prototype.slice.call(arguments); + if (fn) { + return spy.and.callFake(fn).apply(this, args); + } else { + return spy.and.callThrough().apply(this, args); + } + } + }; + mockFn.getMockName = function() { + return spy.mockName || 'jestSpy'; + }; + mockFn.mockName = function(name: string) { + spy.updateArgs({identity: name}); + spy.mockName = name; + return this; + }; + mockFn.mock = {instances}; + Object.defineProperty(mockFn.mock, 'calls', { + configurable: true, + enumerable: true, + get: function() { + return spy.calls.allArgs(); + } + }); + Object.defineProperty(mockFn, 'calls', { + configurable: true, + enumerable: true, + get: function() { + return spy.calls; + } + }); + mockFn.mockClear = function() { + spy.calls.length = 0; + instances.length = 0; + return this; + }; + mockFn.mockReset = function() { + spy.calls.length = 0; + instances.length = 0; + return this; + }; + mockFn.mockImplementation = function(fn: Function) { + spy.defaultFn = fn; + return this; + }; + mockFn.mockImplementationOnce = function(fn: Function) { + if (!spy.onceFns) { + spy.onceFns = []; + } + spy.onceFns.push(fn); + return this; + }; + mockFn.mockReturnThis = function() { + return mockFn.mockImplementation(function() { + return this; + }); + }; + mockFn.mockReturnValue = function(value: any) { + return mockFn.mockImplementation(function() { + return value; + }); + }; + mockFn.mockReturnValueOnce = function(value: any) { + return mockFn.mockImplementationOnce(function() { + return value; + }); + }; + mockFn.mockResolvedValue = function(value: any) { + return mockFn.mockReturnValue(Promise.resolve(value)); + }; + mockFn.mockResolvedValueOnce = function(value: any) { + return mockFn.mockReturnValueOnce(Promise.resolve(value)); + }; + mockFn.mockRejectedValue = function(value: any) { + return mockFn.mockReturnValue(Promise.reject(value)); + }; + mockFn.mockRejectedValueOnce = function(value: any) { + return mockFn.mockReturnValueOnce(Promise.reject(value)); + }; + mockFn.mockRestore = function() { + global.Mocha.clearSpies(global.Mocha.__zone_symbol__current_ctx); + }; + + jest.__zone_symbol__mocks.push(mockFn); + return mockFn; + } + + jest.fn = function(implFn?: Function) { + return createSpy((implFn?: Function) => jasmine.createSpy('jestSpy', implFn), implFn); + }; + + jest.spyOn = function(obj: any, methodName: string, accessType?: string) { + return accessType ? createSpy(() => global['spyOnProperty'](obj, methodName, accessType)) : + createSpy(() => global['spyOn'](obj, methodName)); + }; + + jest.clearAllMocks = function() { + jest.__zone_symbol__mocks.forEach((mock: any) => { + mock.mockClear(); + }); + return jest; + }; + + jest.resetAllMocks = function() { + jest.__zone_symbol__mocks.forEach((mock: any) => { + mock.mockReset(); + }); + return jest; + }; + + jest.restoreAllMocks = function() { + global.Mocha.clearAllSpies(); + return jest; + }; + + jest.isMockFunction = function(fn: Function) { + return jest.__zone_symbol__mocks.filter((m: any) => m === fn).length > 0; + }; +} diff --git a/lib/mocha/jest-bridge/jest.ts b/lib/mocha/jest-bridge/jest.ts new file mode 100644 index 000000000..8cae92778 --- /dev/null +++ b/lib/mocha/jest-bridge/jest.ts @@ -0,0 +1,43 @@ +/** + * @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 {mappingBDD} from './jest.bdd'; +import {addJestTimer} from './jest.clock'; +import {expandExpect} from './jest.expect'; +import {mappingSpy} from './jest.spy'; + +Zone.__load_patch('jest2mocha', (global: any) => { + let jest = global['jest']; + if (typeof jest !== 'undefined') { + // jasmine already loaded, just return + return; + } + // TODO: @JiaLiPassion, now we only support jest in Mocha runner + // support jasmine later. + if (!global.Mocha || global.Mocha['__zone_symbol__isBridge']) { + return; + } + if (global.jasmine && !global.jasmine['__zone_symbol__isBridge']) { + // real jasmine is loaded + return; + } + // create a jasmine global object + jest = global['jest'] = {}; + jest['__zone_symbol__isBridge'] = true; + // BDD mapping + mappingBDD(jest, global.Mocha, global); + expandExpect(global); + mappingSpy(jest, jasmine, global); + addJestTimer(jest, global); + + jest.setTimeout = function(timeout: number) { + const ctx = global.Mocha.__zone_symbol__current_ctx; + if (ctx && typeof ctx.timeout === 'function') { + ctx.timeout(timeout); + } + }; +}); \ No newline at end of file diff --git a/lib/mocha/mocha-node-checker.ts b/lib/mocha/mocha-node-checker.ts new file mode 100644 index 000000000..c042b8c7a --- /dev/null +++ b/lib/mocha/mocha-node-checker.ts @@ -0,0 +1,21 @@ +/** + * @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 + */ +(function(global: any) { + const isNode: boolean = + (typeof global.process !== 'undefined' && + {}.toString.call(global.process) === '[object process]'); + if (isNode && global && !global.Mocha) { + try { + const module = 'mocha'; + // to prevent systemjs to preload the require module + // which will cause error. + global.Mocha = require('' + module); + } catch (err) { + } + } +}(typeof window === 'undefined' ? global : window)); \ No newline at end of file diff --git a/lib/mocha/mocha-node.ts b/lib/mocha/mocha-node.ts new file mode 100644 index 000000000..812df69e1 --- /dev/null +++ b/lib/mocha/mocha-node.ts @@ -0,0 +1,9 @@ +/** + * @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 './mocha-node-checker'; +import './mocha'; \ No newline at end of file diff --git a/lib/mocha/mocha-patch.ts b/lib/mocha/mocha-patch.ts new file mode 100644 index 000000000..adc43a13b --- /dev/null +++ b/lib/mocha/mocha-patch.ts @@ -0,0 +1,285 @@ +/** + * @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 + */ + +'use strict'; + +Zone.__load_patch('Mocha', (global: any, Zone: ZoneType, api: _ZonePrivate) => { + const Mocha = global.Mocha; + const jasmine = global.jasmine; + + if (typeof Mocha === 'undefined') { + return; + } + + if (Mocha['__zone_symbol__isBridge']) { + return; + } + + if (jasmine && !jasmine['__zone_symbol__isBridge']) { + return; + } + + if (typeof Zone === 'undefined') { + throw new Error('Missing Zone.js'); + } + + const ProxyZoneSpec = (Zone as any)['ProxyZoneSpec']; + const SyncTestZoneSpec = (Zone as any)['SyncTestZoneSpec']; + const FakeAsyncTestZoneSpec = (Zone as any)['FakeAsyncTestZoneSpec']; + + if (!ProxyZoneSpec) { + throw new Error('Missing ProxyZoneSpec'); + } + + if (Mocha['__zone_patch__']) { + throw new Error('"Mocha" has already been patched with "Zone".'); + } + + Mocha['__zone_patch__'] = true; + + const rootZone = Zone.current; + const syncZone = rootZone.fork(new SyncTestZoneSpec('Mocha.describe')); + let testZone: Zone = null; + let testZoneSpec: ZoneSpec = null; + let suiteZoneSpec: ZoneSpec = new ProxyZoneSpec(); + const suiteZone = rootZone.fork(suiteZoneSpec); + + function modifyArguments(args: IArguments, syncTest: Function, asyncTest?: Function): any[] { + for (let i = 0; i < args.length; i++) { + let arg = args[i]; + if (typeof arg === 'function') { + // The `done` callback is only passed through if the function expects at + // least one argument. + // Note we have to make a function with correct number of arguments, + // otherwise mocha will + // think that all functions are sync or async. + args[i] = (arg.length === 0) ? syncTest(arg) : asyncTest(arg); + // Mocha uses toString to view the test body in the result list, make sure we return the + // correct function body + args[i].toString = function() { + return arg.toString(); + }; + } + } + + return args as any; + } + + function wrapDescribeInZone(args: IArguments): any[] { + const syncTest: any = function(fn: Function) { + return function() { + if (this && this instanceof Mocha.Suite) { + // add an afterAll hook to clear spies. + this.afterAll('afterAll clear spies', () => { + Mocha.clearSpies(this); + }); + this.afterEach('afterEach clear spies', function() { + if (this.test && this.test.ctx && this.test.currentTest) { + Mocha.clearSpies(this.test.ctx.currentTest); + if (Mocha.__zone_symbol__afterEach) { + Mocha.__zone_symbol__afterEach.forEach((afterEachCallback: any) => { + afterEachCallback(this.test.ctx.currentTest); + }); + } + } + }); + Mocha.__zone_symbol__suite = this; + } + return syncZone.run(fn, this, arguments as any as any[]); + }; + }; + + return modifyArguments(args, syncTest); + } + + function beforeTest(ctx: any, testBody: any) { + registerCurrentTestBeforeTest(ctx); + checkTimeout(ctx && ctx.test); + return checkIsFakeAsync(testBody); + } + + function checkTimeout(test: any) { + if (test && typeof test.timeout === 'function' && + typeof Mocha.__zone_symbol__TIMEOUT === 'number') { + test.timeout(Mocha.__zone_symbol__TIMEOUT); + // clear timeout, until user set jasmine.DEFAULT_TIMEOUT_INTERVAL again + Mocha.__zone_symbol__TIMEOUT = null; + } + } + + function registerCurrentTestBeforeTest(ctx: any) { + Mocha.__zone_symbol__current_ctx = ctx; + if (ctx && ctx.test && ctx.test.ctx && ctx.test.ctx.currentTest) { + Mocha.__zone_symbol__test = ctx.test.ctx.currentTest; + } + } + + function checkIsFakeAsync(testBody: any) { + const jasmine = global.jasmine; + const isClockInstalled = jasmine && !!jasmine[api.symbol('clockInstalled')]; + if (isClockInstalled) { + // auto run a fakeAsync + const fakeAsyncModule = (Zone as any)[api.symbol('fakeAsyncTest')]; + if (fakeAsyncModule && typeof fakeAsyncModule.fakeAsync === 'function') { + testBody = fakeAsyncModule.fakeAsync( + testBody, {checkNested: false, checkRemainingMacrotasks: false}); + } + } + return testBody; + } + + function wrapTestInZone(args: IArguments): any[] { + const timeoutArgs = args.length > 0 ? args[args.length - 1] : null; + const asyncTest = function(fn: Function) { + return function(done: Function) { + if (this && typeof this.timeout === 'function' && typeof timeoutArgs === 'number') { + this.timeout(timeoutArgs); + } + fn = beforeTest(this, fn); + return testZone.run(fn, this, [done]); + }; + }; + + const syncTest: any = function(fn: Function) { + return function() { + fn = beforeTest(this, fn); + return testZone.run(fn, this); + }; + }; + + return modifyArguments(args, syncTest, asyncTest); + } + + function wrapSuiteInZone(args: IArguments): any[] { + const asyncTest = function(fn: Function) { + return function(done: Function) { + fn = beforeTest(this, fn); + return suiteZone.run(fn, this, [done]); + }; + }; + + const syncTest: any = function(fn: Function) { + return function() { + fn = beforeTest(this, fn); + return suiteZone.run(fn, this); + }; + }; + + return modifyArguments(args, syncTest, asyncTest); + } + + Mocha.getCurrentTestInfo = function() { + return {suite: Mocha.__zone_symbol__suite, test: Mocha.__zone_symbol__test}; + }; + + Mocha.clearSpies = function() {}; + + function patchGlobal() { + const mochaOriginal = { + after: Mocha[api.symbol('after')] || Mocha.after, + afterEach: Mocha[api.symbol('afterEach')] || Mocha.afterEach, + before: Mocha[api.symbol('before')] || Mocha.before, + beforeEach: Mocha[api.symbol('beforeEach')] || Mocha.beforeEach, + describe: Mocha[api.symbol('describe')] || Mocha.describe, + it: Mocha[api.symbol('it')] || Mocha.it + }; + /*if (!Mocha[api.symbol('describe')]) { + Mocha[api.symbol('describe')] = Mocha['describe']; + } + if (!Mocha[api.symbol('after')]) { + Mocha[api.symbol('after')] = Mocha['after']; + } + if (!Mocha[api.symbol('afterEach')]) { + Mocha[api.symbol('afterEach')] = Mocha['afterEach']; + } + if (!Mocha[api.symbol('before')]) { + Mocha[api.symbol('before')] = Mocha['before']; + } + if (!Mocha[api.symbol('beforeEach')]) { + Mocha[api.symbol('beforeEach')] = Mocha['beforeEach']; + } + if (!Mocha[api.symbol('it')]) { + Mocha[api.symbol('it')] = Mocha['it']; + }*/ + + global.describe = global.suite = Mocha.describe = function () { + return mochaOriginal.describe.apply(this, wrapDescribeInZone(arguments)); + }; + + global.xdescribe = global.suite.skip = Mocha.describe.skip = function () { + return mochaOriginal.describe.skip.apply(this, wrapDescribeInZone(arguments)); + }; + + global.describe.only = global.suite.only = Mocha.describe.only = function () { + return mochaOriginal.describe.only.apply(this, wrapDescribeInZone(arguments)); + }; + + global.it = global.specify = global.test = Mocha.it = function () { + return mochaOriginal.it.apply(this, wrapTestInZone(arguments)); + }; + + global.xit = global.xspecify = Mocha.it.skip = function () { + return mochaOriginal.it.skip.apply(this, wrapTestInZone(arguments)); + }; + + global.it.only = global.test.only = Mocha.it.only = function () { + return mochaOriginal.it.only.apply(this, wrapTestInZone(arguments)); + }; + + global.after = global.suiteTeardown = Mocha.after = function () { + return mochaOriginal.after.apply(this, wrapSuiteInZone(arguments)); + }; + + global.afterEach = global.teardown = Mocha.afterEach = function () { + return mochaOriginal.afterEach.apply(this, wrapTestInZone(arguments)); + }; + + global.before = global.suiteSetup = Mocha.before = function () { + return mochaOriginal.before.apply(this, wrapSuiteInZone(arguments)); + }; + + global.beforeEach = global.setup = Mocha.beforeEach = function () { + return mochaOriginal.beforeEach.apply(this, wrapTestInZone(arguments)); + }; + } + + if (typeof Mocha.describe === 'function') { + patchGlobal(); + } + ((originalRunTest, originalRun, originalMochaRun) => { + Mocha.prototype.run = function () { + if (this.suite) { + this.suite.on('pre-require', () => { + patchGlobal(); + }); + } + return originalMochaRun.apply(this, arguments); + } + Mocha.Runner.prototype.runTest = function(fn: Function) { + Zone.current.scheduleMicroTask('mocha.forceTask', () => { + originalRunTest.call(this, fn); + }); + }; + + Mocha.Runner.prototype.run = function(fn: Function) { + this.on('test', (e: any) => { + testZoneSpec = new ProxyZoneSpec(); + testZone = rootZone.fork(testZoneSpec); + }); + + this.on('fail', (test: any, err: any) => { + const proxyZoneSpec = testZone && testZone.get('ProxyZoneSpec'); + if (proxyZoneSpec && err) { + err.message += proxyZoneSpec.getAndClearPendingTasksInfo(); + } + }); + + return originalRun.call(this, fn); + }; + })(Mocha.Runner.prototype.runTest, Mocha.Runner.prototype.run, Mocha.prototype.run); +}); diff --git a/lib/mocha/mocha.ts b/lib/mocha/mocha.ts index f36f76a4a..fbd4f168c 100644 --- a/lib/mocha/mocha.ts +++ b/lib/mocha/mocha.ts @@ -6,172 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -'use strict'; - -((context: any) => { - const Mocha = context.Mocha; - - if (typeof Mocha === 'undefined') { - throw new Error('Missing Mocha.js'); - } - - if (typeof Zone === 'undefined') { - throw new Error('Missing Zone.js'); - } - - const ProxyZoneSpec = (Zone as any)['ProxyZoneSpec']; - const SyncTestZoneSpec = (Zone as any)['SyncTestZoneSpec']; - - if (!ProxyZoneSpec) { - throw new Error('Missing ProxyZoneSpec'); - } - - if (Mocha['__zone_patch__']) { - throw new Error('"Mocha" has already been patched with "Zone".'); - } - - Mocha['__zone_patch__'] = true; - - const rootZone = Zone.current; - const syncZone = rootZone.fork(new SyncTestZoneSpec('Mocha.describe')); - let testZone: Zone = null; - const suiteZone = rootZone.fork(new ProxyZoneSpec()); - - const mochaOriginal = { - after: Mocha.after, - afterEach: Mocha.afterEach, - before: Mocha.before, - beforeEach: Mocha.beforeEach, - describe: Mocha.describe, - it: Mocha.it - }; - - function modifyArguments(args: IArguments, syncTest: Function, asyncTest?: Function): any[] { - for (let i = 0; i < args.length; i++) { - let arg = args[i]; - if (typeof arg === 'function') { - // The `done` callback is only passed through if the function expects at - // least one argument. - // Note we have to make a function with correct number of arguments, - // otherwise mocha will - // think that all functions are sync or async. - args[i] = (arg.length === 0) ? syncTest(arg) : asyncTest(arg); - // Mocha uses toString to view the test body in the result list, make sure we return the - // correct function body - args[i].toString = function() { - return arg.toString(); - }; - } - } - - return args as any; - } - - function wrapDescribeInZone(args: IArguments): any[] { - const syncTest: any = function(fn: Function) { - return function() { - return syncZone.run(fn, this, arguments as any as any[]); - }; - }; - - return modifyArguments(args, syncTest); - } - - function wrapTestInZone(args: IArguments): any[] { - const asyncTest = function(fn: Function) { - return function(done: Function) { - return testZone.run(fn, this, [done]); - }; - }; - - const syncTest: any = function(fn: Function) { - return function() { - return testZone.run(fn, this); - }; - }; - - return modifyArguments(args, syncTest, asyncTest); - } - - function wrapSuiteInZone(args: IArguments): any[] { - const asyncTest = function(fn: Function) { - return function(done: Function) { - return suiteZone.run(fn, this, [done]); - }; - }; - - const syncTest: any = function(fn: Function) { - return function() { - return suiteZone.run(fn, this); - }; - }; - - return modifyArguments(args, syncTest, asyncTest); - } - - context.describe = context.suite = Mocha.describe = function() { - return mochaOriginal.describe.apply(this, wrapDescribeInZone(arguments)); - }; - - context.xdescribe = context.suite.skip = Mocha.describe.skip = function() { - return mochaOriginal.describe.skip.apply(this, wrapDescribeInZone(arguments)); - }; - - context.describe.only = context.suite.only = Mocha.describe.only = function() { - return mochaOriginal.describe.only.apply(this, wrapDescribeInZone(arguments)); - }; - - context.it = context.specify = context.test = Mocha.it = function() { - return mochaOriginal.it.apply(this, wrapTestInZone(arguments)); - }; - - context.xit = context.xspecify = Mocha.it.skip = function() { - return mochaOriginal.it.skip.apply(this, wrapTestInZone(arguments)); - }; - - context.it.only = context.test.only = Mocha.it.only = function() { - return mochaOriginal.it.only.apply(this, wrapTestInZone(arguments)); - }; - - context.after = context.suiteTeardown = Mocha.after = function() { - return mochaOriginal.after.apply(this, wrapSuiteInZone(arguments)); - }; - - context.afterEach = context.teardown = Mocha.afterEach = function() { - return mochaOriginal.afterEach.apply(this, wrapTestInZone(arguments)); - }; - - context.before = context.suiteSetup = Mocha.before = function() { - return mochaOriginal.before.apply(this, wrapSuiteInZone(arguments)); - }; - - context.beforeEach = context.setup = Mocha.beforeEach = function() { - return mochaOriginal.beforeEach.apply(this, wrapTestInZone(arguments)); - }; - - ((originalRunTest, originalRun) => { - Mocha.Runner.prototype.runTest = function(fn: Function) { - Zone.current.scheduleMicroTask('mocha.forceTask', () => { - originalRunTest.call(this, fn); - }); - }; - - Mocha.Runner.prototype.run = function(fn: Function) { - this.on('test', (e: any) => { - testZone = rootZone.fork(new ProxyZoneSpec()); - }); - - this.on('fail', (test:any, err: any) => { - const proxyZoneSpec = testZone && testZone.get('ProxyZoneSpec'); - if (proxyZoneSpec && err) { - err.message += proxyZoneSpec.getAndClearPendingTasksInfo(); - } - }); - - return originalRun.call(this, fn); - }; - - - })(Mocha.Runner.prototype.runTest, Mocha.Runner.prototype.run); - -})(typeof window !== 'undefined' && window || typeof self !== 'undefined' && self || global); +import './mocha-node-checker'; +import './mocha-patch'; +import './jasmine-bridge/jasmine-bridge'; +import './jest-bridge/jest-bridge'; \ No newline at end of file diff --git a/lib/testing/async-testing.ts b/lib/testing/async-testing.ts index 1e3c3ab4a..55f058f22 100644 --- a/lib/testing/async-testing.ts +++ b/lib/testing/async-testing.ts @@ -12,7 +12,7 @@ Zone.__load_patch('asynctest', (global: any, Zone: ZoneType, api: _ZonePrivate) * Wraps a test function in an asynchronous test zone. The test will automatically * complete when all asynchronous calls within this zone are done. */ - (Zone as any)[api.symbol('asyncTest')] = function asyncTest(fn: Function): (done: any) => any { + global['asyncTest'] = (Zone as any)[api.symbol('asyncTest')] = function asyncTest(fn: Function): (done: any) => any { // If we're running using the Jasmine test framework, adapt to call the 'done' // function when asynchronous activity is finished. if (global.jasmine) { diff --git a/lib/testing/fake-async.ts b/lib/testing/fake-async.ts index 72e934f02..c7b34e3b2 100644 --- a/lib/testing/fake-async.ts +++ b/lib/testing/fake-async.ts @@ -33,35 +33,49 @@ Zone.__load_patch('fakeasync', (global: any, Zone: ZoneType, api: _ZonePrivate) } /** - * Wraps a function to be executed in the fakeAsync zone: - * - microtasks are manually executed by calling `flushMicrotasks()`, - * - timers are synchronous, `tick()` simulates the asynchronous passage of time. - * - * If there are any pending timers at the end of the function, an exception will be thrown. - * - * Can be used to wrap inject() calls. - * - * ## Example - * - * {@example core/testing/ts/fake_async.ts region='basic'} - * - * @param fn - * @returns The function wrapped to be executed in the fakeAsync zone - * - * @experimental - */ - function fakeAsync(fn: Function): (...args: any[]) => any { + * Wraps a function to be executed in the fakeAsync zone: + * - microtasks are manually executed by calling `flushMicrotasks()`, + * - timers are synchronous, `tick()` simulates the asynchronous passage of time. + * + * If there are any pending timers at the end of the function, an exception will be thrown. + * + * Can be used to wrap inject() calls. + * + * ## Example + * + * {@example core/testing/ts/fake_async.ts region='basic'} + * + * @param fn + * @returns The function wrapped to be executed in the fakeAsync zone + * + * @experimental + */ + function fakeAsync(fn: Function, options: { + checkNested?: boolean, + checkRemainingMacrotasks?: boolean + } = {checkNested: true, checkRemainingMacrotasks: true}): (...args: any[]) => any { // Not using an arrow function to preserve context passed from call site + if (global['__zone_symbol__fakeAsyncCheckRemaining'] === false) { + options.checkRemainingMacrotasks = false; + } return function(...args: any[]) { const proxyZoneSpec = ProxyZoneSpec.assertPresent(); if (Zone.current.get('FakeAsyncTestZoneSpec')) { - throw new Error('fakeAsync() calls can not be nested'); + if (options.checkNested) { + throw new Error('fakeAsync() calls can not be nested'); + } + // already in fakeAsyncZone + return fn.apply(this, args); } try { // in case jasmine.clock init a fakeAsyncTestZoneSpec if (!_fakeAsyncTestZoneSpec) { if (proxyZoneSpec.getDelegate() instanceof FakeAsyncTestZoneSpec) { - throw new Error('fakeAsync() calls can not be nested'); + if (options.checkNested) { + throw new Error('fakeAsync() calls can not be nested'); + } + // already in fakeAsyncZone + return fn.apply(this, args); } _fakeAsyncTestZoneSpec = new FakeAsyncTestZoneSpec(); @@ -78,15 +92,19 @@ Zone.__load_patch('fakeasync', (global: any, Zone: ZoneType, api: _ZonePrivate) proxyZoneSpec.setDelegate(lastProxyZoneSpec); } - if (_fakeAsyncTestZoneSpec.pendingPeriodicTimers.length > 0) { - throw new Error( - `${_fakeAsyncTestZoneSpec.pendingPeriodicTimers.length} ` + - `periodic timer(s) still in the queue.`); - } + // TODO: @JiaLiPassion, we don't need to report error here. + // need to confirm. + if (options.checkRemainingMacrotasks) { + if (_fakeAsyncTestZoneSpec.pendingPeriodicTimers.length > 0) { + throw new Error( + `${_fakeAsyncTestZoneSpec.pendingPeriodicTimers.length} ` + + `periodic timer(s) still in the queue.`); + } - if (_fakeAsyncTestZoneSpec.pendingTimers.length > 0) { - throw new Error( - `${_fakeAsyncTestZoneSpec.pendingTimers.length} timer(s) still in the queue.`); + if (_fakeAsyncTestZoneSpec.pendingTimers.length > 0) { + throw new Error( + `${_fakeAsyncTestZoneSpec.pendingTimers.length} timer(s) still in the queue.`); + } } return res; } finally { @@ -117,8 +135,8 @@ Zone.__load_patch('fakeasync', (global: any, Zone: ZoneType, api: _ZonePrivate) * * @experimental */ - function tick(millis: number = 0): void { - _getFakeAsyncZoneSpec().tick(millis); + function tick(millis: number = 0, doTick?: (elapsed: number) => void): void { + _getFakeAsyncZoneSpec().tick(millis, doTick); } /** @@ -131,8 +149,8 @@ Zone.__load_patch('fakeasync', (global: any, Zone: ZoneType, api: _ZonePrivate) * * @experimental */ - function flush(maxTurns?: number): number { - return _getFakeAsyncZoneSpec().flush(maxTurns); + function flush(maxTurns?: number, isPeriodic: boolean = false): number { + return _getFakeAsyncZoneSpec().flush(maxTurns, isPeriodic); } /** @@ -154,6 +172,40 @@ Zone.__load_patch('fakeasync', (global: any, Zone: ZoneType, api: _ZonePrivate) function flushMicrotasks(): void { _getFakeAsyncZoneSpec().flushMicrotasks(); } + + /** + * Clear all microtasks + * + * @experimental + */ + function clearAllMacrotasks(): void { + _getFakeAsyncZoneSpec().clearAllMacrotasks(); + } + + /** + * flush all macroTasks and discard periodic tasks + * + * @experimental + */ + function flushAndDiscardPeriodicTasks(): void { + const fakeAsyncSpec = _getFakeAsyncZoneSpec(); + fakeAsyncSpec.flush(100, true); + discardPeriodicTasks(); + } + + (Zone as any)[api.symbol('fakeAsyncTest')] = - {resetFakeAsyncZone, flushMicrotasks, discardPeriodicTasks, tick, flush, fakeAsync}; + {resetFakeAsyncZone, flushMicrotasks, discardPeriodicTasks, tick, flush, fakeAsync, clearAllMacrotasks}; + + /** + * expose those function to global + */ + global['resetFakeAsyncZone'] = resetFakeAsyncZone; + global['flushMicrotasks'] = flushMicrotasks; + global['discardPeriodicTasks'] = discardPeriodicTasks; + global['tick'] = tick; + global['flush'] = flush; + global['fakeAsyncTest'] = fakeAsync; + global['clearAllMacrotasks'] = clearAllMacrotasks; + global['flushAndDiscardPeriodicTasks'] = flushAndDiscardPeriodicTasks; }); \ No newline at end of file diff --git a/lib/testing/zone-testing.ts b/lib/testing/zone-testing.ts index c5ebd1ad3..f03fa7155 100644 --- a/lib/testing/zone-testing.ts +++ b/lib/testing/zone-testing.ts @@ -10,7 +10,8 @@ import '../zone-spec/long-stack-trace'; import '../zone-spec/proxy'; import '../zone-spec/sync-test'; -import '../jasmine/jasmine'; import './async-testing'; import './fake-async'; -import './promise-testing'; \ No newline at end of file +import './promise-testing'; +import '../jasmine/jasmine'; +import '../mocha/mocha'; diff --git a/lib/testing/zone-testing.typing.ts b/lib/testing/zone-testing.typing.ts new file mode 100644 index 000000000..55d6f4bb0 --- /dev/null +++ b/lib/testing/zone-testing.typing.ts @@ -0,0 +1,289 @@ +/** + * @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 + */ +/** + * Zone testing type definition, mix jasmine/mocha/jest + */ +/** + * Jasmine/Jest/Mocha BDD/TDD interface + */ +declare const describe: ZoneTest.Describe; +declare const xdescribe: ZoneTest.Describe; +declare const fdescribe: ZoneTest.Describe; +// alias for `describe` +declare const context: ZoneTest.Describe; +// alias for `describe` +declare const suite: ZoneTest.Describe; +declare const it: ZoneTest.Test; +declare const xit: ZoneTest.Test; +// alias for `it` +declare const test: ZoneTest.Test; +declare const specify: ZoneTest.Test; + +declare const before: ZoneTest.BeforeAndAfter; +declare const beforeAll: ZoneTest.BeforeAndAfter; +declare const setup: ZoneTest.BeforeAndAfter; + +declare const after: ZoneTest.BeforeAndAfter; +declare const afterAll: ZoneTest.BeforeAndAfter; +declare const teardown: ZoneTest.BeforeAndAfter; + +declare const beforeEach: ZoneTest.BeforeAndAfter; +declare const suiteSetup: ZoneTest.BeforeAndAfter; + +declare const afterEach: ZoneTest.BeforeAndAfter; +declare const suiteTeardown: ZoneTest.BeforeAndAfter; + +declare const expect: ZoneTest.Expect; +declare function fail(error?: any): void; +declare function pending(reason?: string): void; + +/** + * Zone testing global interface for asyncTest/syncTest/fakeAsyncTest + */ + +declare function asyncTest(fn: ZoneTest.BeforeAndAfter | ZoneTest.Test): (done: ZoneTest.DoneCallback) => PromiseLike | void; +declare function fakeAsyncTest(fn: ZoneTest.BeforeAndAfter | ZoneTest.Test, fakeAsyncTestOptions?: { + checkNested?: boolean, checkRemainingMacrotasks?: boolean +}): (done: ZoneTest.DoneCallback) => PromiseLike | void; +declare function resetFakeAsyncZone(): void; +declare function flushMicrotasks(): void; +declare function flushAndDiscardPeriodicTasks(): void; +declare function discardPeriodicTasks(): void; +declare function tick(millis?: number, doTick?: (elapsed: number) => void): void; +declare function flush(maxTurns?: number): void; +declare function clearAllMacrotasks(): void; + +declare namespace ZoneTest { + interface DoneCallback extends Function { + (...args: any[]): void; + fail(error?: string | { message: string }): any; + } + + interface TestSpecContext { + skip(): this; + timeout(ms: number | string): this; + [index: string]: any; + } + + interface Describe { + (description: string, spec: (this: DescribeSpecContext) => void): void; + only(description: string, spec: (this: DescribeSpecContext) => void): void; + skip(description: string, spec: (this: DescribeSpecContext) => void): void; + } + + interface Test { + (description: string, callback?: (this: TestSpecContext, done?: DoneCallback) => PromiseLike | void, timeout?: number): void; + only(description: string, callback?: (this: TestSpecContext, done?: DoneCallback) => PromiseLike | void): void; + skip(description: string, callback?: (this: TestSpecContext, done?: DoneCallback) => PromiseLike | void): void; + } + + interface DescribeSpecContext { + timeout(ms: number | string): this; + } + + interface BeforeAndAfter { + (callback?: (this: TestSpecContext, done?: DoneCallback) => PromiseLike | void, timeout?: number): void; + (description: string, callback?: (this: TestSpecContext, done?: DoneCallback) => PromiseLike | void, timeout?: number): void; + } + + type CustomEqualityTester = (first: any, second: any) => boolean | void; + + interface CustomMatcher { + compare(actual: T, expected: T, ...args: any[]): CustomMatcherResult; + compare(actual: any, ...expected: any[]): CustomMatcherResult; + } + + interface MatchersUtil { + equals(a: any, b: any, customTesters?: CustomEqualityTester[]): boolean; + contains(haystack: ArrayLike | string, needle: any, customTesters?: CustomEqualityTester[]): boolean; + buildFailureMessage(matcherName: string, isNot: boolean, actual: any, ...expected: any[]): string; + } + + type CustomMatcherFactory = (util: MatchersUtil, customEqualityTesters: CustomEqualityTester[]) => CustomMatcher; + + interface CustomMatcherFactories { + [index: string]: CustomMatcherFactory; + } + + interface CustomMatcherResult { + pass: boolean; + message?: string; + } + + interface Expect { + (actual: any): Matchers; + anything(): any; + any(classType: any): any; + arrayContaining(arr: any[]): any; + assertions(num: number): void; + hasAssertions(): void; + extend(obj: any): void; + objectContaining(obj: any): any; + stringMatching(str: string | RegExp): any; + stringContaining(str: string): any; + } + + interface Matchers { + not: Matchers; + resolves: Matchers>; + rejects: Matchers>; + lastCalledWith(...args: any[]): R; + toBe(expected: any): R; + toBeCalled(): R; + toBeCalledWith(...args: any[]): R; + toBeCloseTo(expected: number, numDigits?: number): R; + toBeDefined(): R; + toBeFalsy(): R; + toBeGreaterThan(expected: number): R; + toBeGreaterThanOrEqual(expected: number): R; + toBeInstanceOf(expected: any): R; + toBeLessThan(expected: number): R; + toBeLessThanOrEqual(expected: number): R; + toBeNull(): R; + toBeTruthy(): R; + toBeUndefined(): R; + toBeNaN(): R; + toContain(expected: any): R; + toContainEqual(expected: any): R; + toEqual(expected: any): R; + toHaveBeenCalled(): R; + toHaveBeenCalledTimes(expected: number): R; + toHaveBeenCalledWith(...params: any[]): R; + toHaveBeenLastCalledWith(...params: any[]): R; + toHaveLength(expected: number): R; + toHaveProperty(propertyPath: string | any[], value?: any): R; + toMatch(expected: string | RegExp): R; + toMatchObject(expected: {} | any[]): R; + toThrow(error?: string | RegExp | Error): R; + toThrowError(message?: string | RegExp): boolean; + toThrowError(expected?: new (...args: any[]) => Error, message?: string | RegExp): boolean; + } +} + +interface DoneFn extends Function { + (): void; + fail: (message?: Error | string) => void; +} +declare namespace jasmine { + type SpyObjMethodNames = string[] | {[methodName: string]: any}; + let clock: () => Clock; + function any(aclass: any): any; + function anything(): any; + function arrayContaining(sample: ArrayLike): any; + function arrayWithExactContents(sample: ArrayLike): any; + function objectContaining(sample: Partial): any; + function createSpy(name?: string, originalFn?: Function): Spy; + function createSpyObj(baseName: string, methodNames: SpyObjMethodNames): any; + function createSpyObj(baseName: string, methodNames: SpyObjMethodNames): SpyObj; + function createSpyObj(methodNames: SpyObjMethodNames): any; + function createSpyObj(methodNames: SpyObjMethodNames): SpyObj; + function addCustomEqualityTester(equalityTester: ZoneTest.CustomEqualityTester): void; + function addMatchers(matchers: ZoneTest.CustomMatcherFactories): void; + function pp(value: any): string; + + function getEnv(): any; + let DEFAULT_TIMEOUT_INTERVAL: number; + + interface Clock { + install(): void; + uninstall(): void; + tick(ms: number): void; + mockDate(date?: Date): void; + } + + interface Spy { + (...params: any[]): any; + + identity: string; + and: SpyAnd; + calls: Calls; + mostRecentCall: { args: any[]; }; + argsForCall: any[]; + } + + type SpyObj = T & { + [k in keyof T]: Spy; + }; + + interface SpyAnd { + callThrough(): Spy; + returnValue(val: any): Spy; + returnValues(...values: any[]): Spy; + callFake(fn: Function): Spy; + throwError(msg: string): Spy; + stub(): Spy; + } + + interface Calls { + any(): boolean; + count(): number; + argsFor(index: number): any[]; + allArgs(): any[]; + all(): CallInfo[]; + mostRecent(): CallInfo; + first(): CallInfo; + reset(): void; + } + + interface CallInfo { + object: any; + args: any[]; + returnValue: any; + } +} + +declare function spyOn(object: T, method: keyof T): jasmine.Spy; +declare function spyOnProperty(object: T, property: keyof T, accessType?: 'get' | 'set'): jasmine.Spy; + +declare namespace jest { + function addMatchers(matchers: ZoneTest.CustomMatcherFactories): typeof jest; + function clearAllMocks(): typeof jest; + function resetAllMocks(): typeof jest; + function restoreAllMocks(): typeof jest; + function clearAllTimers(): typeof jest; + function fn(implementation: (...args: any[]) => T): Mock; + function fn(implementation?: (...args: any[]) => any): Mock; + function isMockFunction(fn: any): fn is Mock; + function runAllTicks(): typeof jest; + function runAllTimers(): typeof jest; + function runOnlyPendingTimers(): typeof jest; + function runTimersToTime(msToRun: number): typeof jest; + function advanceTimersByTime(msToRun: number): typeof jest; + function setTimeout(timeout: number): typeof jest; + function spyOn(object: T, method: M, accessType?: 'get' | 'set'): jasmine.Spy; + function useFakeTimers(): typeof jest; + function useRealTimers(): typeof jest; + + interface MockInstance { + getMockName(): string; + mock: MockContext; + mockClear(): void; + mockReset(): void; + mockImplementation(fn: (...args: any[]) => any): Mock; + mockImplementationOnce(fn: (...args: any[]) => any): Mock; + mockName(name: string): Mock; + mockReturnThis(): Mock; + mockReturnValue(value: any): Mock; + mockReturnValueOnce(value: any): Mock; + mockResolvedValue(value: any): Mock; + mockResolvedValueOnce(value: any): Mock; + mockRejectedValue(value: any): Mock; + mockRejectedValueOnce(value: any): Mock; + } + + interface MockContext { + calls: any[][]; + instances: T[]; + } + + interface Mock extends Function, MockInstance { + new (...args: any[]): T; + (...args: any[]): any; + } +} + diff --git a/lib/zone-spec/fake-async-test.ts b/lib/zone-spec/fake-async-test.ts index 278cc1f52..2f81775a8 100644 --- a/lib/zone-spec/fake-async-test.ts +++ b/lib/zone-spec/fake-async-test.ts @@ -416,6 +416,18 @@ return elapsed; } + clearAllMacrotasks() { + while (this.pendingTimers.length > 0) { + const timerId = this.pendingTimers.shift(); + this._clearTimeout(timerId); + } + + while (this.pendingPeriodicTimers.length > 0) { + const intervalId = this.pendingPeriodicTimers.shift(); + this._clearInterval(intervalId); + } + } + // ZoneSpec implementation below. name: string; diff --git a/package.json b/package.json index b93ffc01b..0493dd433 100644 --- a/package.json +++ b/package.json @@ -19,21 +19,21 @@ "ci": "npm run lint && npm run format && npm run promisetest && npm run test:single && npm run test-node", "closure:test": "scripts/closure/closure_compiler.sh", "format": "gulp format:enforce", - "karma-jasmine": "karma start karma-build-jasmine.conf.js", - "karma-jasmine:phantomjs": "karma start karma-build-jasmine-phantomjs.conf.js --single-run", - "karma-jasmine:single": "karma start karma-build-jasmine.conf.js --single-run", + "karma-jasmine": "karma start ./test/karma/common/build/karma-build-jasmine.conf.js", + "karma-jasmine:phantomjs": "karma start ./test/karma/common/build/karma-build-jasmine-phantomjs.conf.js --single-run", + "karma-jasmine:single": "karma start ./test/karma/common/build/karma-build-jasmine.conf.js --single-run", "karma-jasmine:autoclose": "npm run karma-jasmine:single && npm run ws-client", "karma-jasmine-phantomjs:autoclose": "npm run karma-jasmine:phantomjs && npm run ws-client", "lint": "gulp lint", "prepublish": "tsc && gulp build", "promisetest": "gulp promisetest", - "promisefinallytest": "mocha promise.finally.spec.js", + "promisefinallytest": "mocha ./test/spec/promise/promise.finally.spec.js", "webdriver-start": "webdriver-manager update && webdriver-manager start", - "webdriver-http": "node simple-server.js", - "webdriver-test": "node test/webdriver/test.js", - "webdriver-sauce-test": "node test/webdriver/test.sauce.js", - "ws-client": "node ./test/ws-client.js", - "ws-server": "node ./test/ws-server.js", + "webdriver-http": "node test/spec/webdriver/simple-server.js", + "webdriver-test": "node test/spec/webdriver/test.js", + "webdriver-sauce-test": "node test/spec/webdriver/test.sauce.js", + "ws-client": "node ./test/env/websocket/ws-client.js", + "ws-server": "node ./test/env/websocket/ws-server.js", "tsc": "tsc -p .", "tsc:w": "tsc -w -p .", "tslint": "tslint -c tslint.json 'lib/**/*.ts'", @@ -41,10 +41,12 @@ "test:phantomjs": "npm run tsc && concurrently \"npm run tsc:w\" \"npm run ws-server\" \"npm run karma-jasmine:phantomjs\"", "test:phantomjs-single": "npm run tsc && concurrently \"npm run ws-server\" \"npm run karma-jasmine-phantomjs:autoclose\"", "test:single": "npm run tsc && concurrently \"npm run ws-server\" \"npm run karma-jasmine:autoclose\"", - "test-dist": "concurrently \"npm run tsc:w\" \"npm run ws-server\" \"karma start karma-dist-jasmine.conf.js\"", + "test-dist": "concurrently \"npm run tsc:w\" \"npm run ws-server\" \"karma start ./test/karma/common/dist/karma-dist-jasmine.conf.js\"", "test-node": "gulp test/node", "test-bluebird": "gulp test/bluebird", - "test-mocha": "npm run tsc && concurrently \"npm run tsc:w\" \"npm run ws-server\" \"karma start karma-build-mocha.conf.js\"", + "test-mocha": "npm run tsc && concurrently \"npm run tsc:w\" \"npm run ws-server\" \"karma start ./test/karma/common/build/karma-build-mocha.conf.js\"", + "test-mocha-jasmine-bridge-browser": "npm run tsc && concurrently \"npm run ws-server\" \"karma start ./test/karma/common/build/karma-build-mocha-jasmine-bridge.conf.js\"", + "test-mocha-jasmine-bridge-node": "npm run tsc && mocha ./build/test/spec/mocha/mocha-node-test-entry-point.js", "serve": "python -m SimpleHTTPServer 8000" }, "repository": { @@ -58,7 +60,6 @@ }, "dependencies": {}, "devDependencies": { - "@types/jasmine": "2.2.33", "@types/node": "^6.0.96", "@types/systemjs": "^0.19.30", "assert": "^1.4.1", @@ -105,4 +106,4 @@ "webdriverio": "^4.8.0", "whatwg-fetch": "^2.0.1" } -} +} \ No newline at end of file diff --git a/promise.finally.spec.js b/promise.finally.spec.js deleted file mode 100644 index 35a5c4e57..000000000 --- a/promise.finally.spec.js +++ /dev/null @@ -1,345 +0,0 @@ -'use strict'; - -var assert = require('assert'); -var adapter = require('./promise-adapter'); -var P = global['__zone_symbol__Promise']; - -var someRejectionReason = {message: 'some rejection reason'}; -var anotherReason = {message: 'another rejection reason'}; -process.on('unhandledRejection', function(reason, promise) { - console.log('unhandledRejection', reason); -}); - -describe('mocha promise sanity check', () => { - it('passes with a resolved promise', () => { - return P.resolve(3); - }); - - it('passes with a rejected then resolved promise', () => { - return P.reject(someRejectionReason).catch(x => 'this should be resolved'); - }); - - var ifPromiseIt = P === Promise ? it : it.skip; - ifPromiseIt('is the native Promise', () => { - assert.equal(P, Promise); - }); -}); - -describe('onFinally', () => { - describe('no callback', () => { - specify('from resolved', (done) => { - adapter.resolved(3) - .then((x) => { - assert.strictEqual(x, 3); - return x; - }) - .finally() - .then(function onFulfilled(x) { - assert.strictEqual(x, 3); - done(); - }, function onRejected() { - done(new Error('should not be called')); - }); - }); - - specify('from rejected', (done) => { - adapter.rejected(someRejectionReason) - .catch((e) => { - assert.strictEqual(e, someRejectionReason); - throw e; - }) - .finally() - .then(function onFulfilled() { - done(new Error('should not be called')); - }, function onRejected(reason) { - assert.strictEqual(reason, someRejectionReason); - done(); - }); - }); - }); - - describe('throws an exception', () => { - specify('from resolved', (done) => { - adapter.resolved(3) - .then((x) => { - assert.strictEqual(x, 3); - return x; - }) - .finally(function onFinally() { - assert(arguments.length === 0); - throw someRejectionReason; - }).then(function onFulfilled() { - done(new Error('should not be called')); - }, function onRejected(reason) { - assert.strictEqual(reason, someRejectionReason); - done(); - }); - }); - - specify('from rejected', (done) => { - adapter.rejected(anotherReason).finally(function onFinally() { - assert(arguments.length === 0); - throw someRejectionReason; - }).then(function onFulfilled() { - done(new Error('should not be called')); - }, function onRejected(reason) { - assert.strictEqual(reason, someRejectionReason); - done(); - }); - }); - }); - - describe('returns a non-promise', () => { - specify('from resolved', (done) => { - adapter.resolved(3) - .then((x) => { - assert.strictEqual(x, 3); - return x; - }) - .finally(function onFinally() { - assert(arguments.length === 0); - return 4; - }).then(function onFulfilled(x) { - assert.strictEqual(x, 3); - done(); - }, function onRejected() { - done(new Error('should not be called')); - }); - }); - - specify('from rejected', (done) => { - adapter.rejected(anotherReason) - .catch((e) => { - assert.strictEqual(e, anotherReason); - throw e; - }) - .finally(function onFinally() { - assert(arguments.length === 0); - throw someRejectionReason; - }).then(function onFulfilled() { - done(new Error('should not be called')); - }, function onRejected(e) { - assert.strictEqual(e, someRejectionReason); - done(); - }); - }); - }); - - describe('returns a pending-forever promise', () => { - specify('from resolved', (done) => { - var timeout; - adapter.resolved(3) - .then((x) => { - assert.strictEqual(x, 3); - return x; - }) - .finally(function onFinally() { - assert(arguments.length === 0); - timeout = setTimeout(done, 0.1e3); - return new P(() => {}); // forever pending - }).then(function onFulfilled(x) { - clearTimeout(timeout); - done(new Error('should not be called')); - }, function onRejected() { - clearTimeout(timeout); - done(new Error('should not be called')); - }); - }); - - specify('from rejected', (done) => { - var timeout; - adapter.rejected(someRejectionReason) - .catch((e) => { - assert.strictEqual(e, someRejectionReason); - throw e; - }) - .finally(function onFinally() { - assert(arguments.length === 0); - timeout = setTimeout(done, 0.1e3); - return new P(() => {}); // forever pending - }).then(function onFulfilled(x) { - clearTimeout(timeout); - done(new Error('should not be called')); - }, function onRejected() { - clearTimeout(timeout); - done(new Error('should not be called')); - }); - }); - }); - - describe('returns an immediately-fulfilled promise', () => { - specify('from resolved', (done) => { - adapter.resolved(3) - .then((x) => { - assert.strictEqual(x, 3); - return x; - }) - .finally(function onFinally() { - assert(arguments.length === 0); - return adapter.resolved(4); - }).then(function onFulfilled(x) { - assert.strictEqual(x, 3); - done(); - }, function onRejected() { - done(new Error('should not be called')); - }); - }); - - specify('from rejected', (done) => { - adapter.rejected(someRejectionReason) - .catch((e) => { - assert.strictEqual(e, someRejectionReason); - throw e; - }) - .finally(function onFinally() { - assert(arguments.length === 0); - return adapter.resolved(4); - }).then(function onFulfilled() { - done(new Error('should not be called')); - }, function onRejected(e) { - assert.strictEqual(e, someRejectionReason); - done(); - }); - }); - }); - - describe('returns an immediately-rejected promise', () => { - specify('from resolved ', (done) => { - adapter.resolved(3) - .then((x) => { - assert.strictEqual(x, 3); - return x; - }) - .finally(function onFinally() { - assert(arguments.length === 0); - return adapter.rejected(4); - }).then(function onFulfilled(x) { - done(new Error('should not be called')); - }, function onRejected(e) { - assert.strictEqual(e, 4); - done(); - }); - }); - - specify('from rejected', (done) => { - const newReason = {}; - adapter.rejected(someRejectionReason) - .catch((e) => { - assert.strictEqual(e, someRejectionReason); - throw e; - }) - .finally(function onFinally() { - assert(arguments.length === 0); - return adapter.rejected(newReason); - }).then(function onFulfilled(x) { - done(new Error('should not be called')); - }, function onRejected(e) { - assert.strictEqual(e, newReason); - done(); - }); - }); - }); - - describe('returns a fulfilled-after-a-second promise', () => { - specify('from resolved', (done) => { - var timeout; - adapter.resolved(3) - .then((x) => { - assert.strictEqual(x, 3); - return x; - }) - .finally(function onFinally() { - assert(arguments.length === 0); - timeout = setTimeout(done, 1.5e3); - return new P((resolve) => { - setTimeout(() => resolve(4), 1e3); - }); - }).then(function onFulfilled(x) { - clearTimeout(timeout); - assert.strictEqual(x, 3); - done(); - }, function onRejected() { - clearTimeout(timeout); - done(new Error('should not be called')); - }); - }); - - specify('from rejected', (done) => { - var timeout; - adapter.rejected(3) - .catch((e) => { - assert.strictEqual(e, 3); - throw e; - }) - .finally(function onFinally() { - assert(arguments.length === 0); - timeout = setTimeout(done, 1.5e3); - return new P((resolve) => { - setTimeout(() => resolve(4), 1e3); - }); - }).then(function onFulfilled() { - clearTimeout(timeout); - done(new Error('should not be called')); - }, function onRejected(e) { - clearTimeout(timeout); - assert.strictEqual(e, 3); - done(); - }); - }); - }); - - describe('returns a rejected-after-a-second promise', () => { - specify('from resolved', (done) => { - var timeout; - adapter.resolved(3) - .then((x) => { - assert.strictEqual(x, 3); - return x; - }) - .finally(function onFinally() { - assert(arguments.length === 0); - timeout = setTimeout(done, 1.5e3); - return new P((resolve, reject) => { - setTimeout(() => reject(4), 1e3); - }); - }).then(function onFulfilled() { - clearTimeout(timeout); - done(new Error('should not be called')); - }, function onRejected(e) { - clearTimeout(timeout); - assert.strictEqual(e, 4); - done(); - }); - }); - - specify('from rejected', (done) => { - var timeout; - adapter.rejected(someRejectionReason) - .catch((e) => { - assert.strictEqual(e, someRejectionReason); - throw e; - }) - .finally(function onFinally() { - assert(arguments.length === 0); - timeout = setTimeout(done, 1.5e3); - return new P((resolve, reject) => { - setTimeout(() => reject(anotherReason), 1e3); - }); - }).then(function onFulfilled() { - clearTimeout(timeout); - done(new Error('should not be called')); - }, function onRejected(e) { - clearTimeout(timeout); - assert.strictEqual(e, anotherReason); - done(); - }); - }); - }); - - specify('has the correct property descriptor', () => { - var descriptor = Object.getOwnPropertyDescriptor(Promise.prototype, 'finally'); - - assert.strictEqual(descriptor.writable, true); - assert.strictEqual(descriptor.configurable, true); - }); -}); \ No newline at end of file diff --git a/scripts/closure/closure_flagfile b/scripts/closure/closure_flagfile index 524aa0ef6..e83484162 100644 --- a/scripts/closure/closure_flagfile +++ b/scripts/closure/closure_flagfile @@ -1,5 +1,5 @@ --compilation_level ADVANCED_OPTIMIZATIONS --js_output_file "build/closure/closure-bundle.js" --rewrite_polyfills false ---js "build/test/closure/zone.closure.js" +--js "build/test/spec/closure/zone.closure.js" --formatting PRETTY_PRINT \ No newline at end of file diff --git a/check-file-size.js b/scripts/size/check-file-size.js similarity index 100% rename from check-file-size.js rename to scripts/size/check-file-size.js diff --git a/file-size-limit.json b/scripts/size/file-size-limit.json similarity index 100% rename from file-size-limit.json rename to scripts/size/file-size-limit.json diff --git a/test/browser-zone-setup.ts b/test/browser-zone-setup.ts deleted file mode 100644 index 8b812dc1c..000000000 --- a/test/browser-zone-setup.ts +++ /dev/null @@ -1,22 +0,0 @@ -/** - * @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 - */ -if (typeof window !== 'undefined') { - (window as any)['__Zone_enable_cross_context_check'] = true; - (window as any)['__zone_symbol__fakeAsyncPatchLock'] = true; -} -import '../lib/common/to-string'; -import '../lib/browser/browser'; -import '../lib/browser/webapis-user-media'; -import '../lib/testing/zone-testing'; -import '../lib/zone-spec/task-tracking'; -import '../lib/zone-spec/wtf'; -import '../lib/extra/cordova'; -import '../lib/testing/promise-testing'; -import '../lib/testing/async-testing'; -import '../lib/testing/fake-async'; -import '../lib/browser/webapis-resize-observer'; diff --git a/test/browser_entry_point.ts b/test/browser_entry_point.ts deleted file mode 100644 index 4ec6e5776..000000000 --- a/test/browser_entry_point.ts +++ /dev/null @@ -1,27 +0,0 @@ -/** - * @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 - */ - -// List all tests here: -import './common_tests'; -import './browser/browser.spec'; -import './browser/define-property.spec'; -import './browser/element.spec'; -import './browser/FileReader.spec'; -// import './browser/geolocation.spec.manual'; -import './browser/HTMLImports.spec'; -import './browser/MutationObserver.spec'; -import './browser/registerElement.spec'; -import './browser/requestAnimationFrame.spec'; -import './browser/WebSocket.spec'; -import './browser/XMLHttpRequest.spec'; -import './browser/MediaQuery.spec'; -import './browser/Notification.spec'; -import './browser/Worker.spec'; -import './mocha-patch.spec'; -import './jasmine-patch.spec'; -import './extra/cordova.spec'; diff --git a/test/common_tests.ts b/test/common_tests.ts deleted file mode 100644 index 2fbb7ecfd..000000000 --- a/test/common_tests.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * @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 './common/microtasks.spec'; -import './common/zone.spec'; -import './common/task.spec'; -import './common/util.spec'; -import './common/Promise.spec'; -import './common/Error.spec'; -import './common/setInterval.spec'; -import './common/setTimeout.spec'; -import './common/toString.spec'; -import './zone-spec/long-stack-trace-zone.spec'; -import './zone-spec/async-test.spec'; -import './zone-spec/sync-test.spec'; -import './zone-spec/fake-async-test.spec'; -import './zone-spec/proxy.spec'; -import './zone-spec/task-tracking.spec'; -import './rxjs/rxjs.spec'; - -Error.stackTraceLimit = Number.POSITIVE_INFINITY; diff --git a/test/env/browser/browser-zone-setup.ts b/test/env/browser/browser-zone-setup.ts new file mode 100644 index 000000000..30c3e725e --- /dev/null +++ b/test/env/browser/browser-zone-setup.ts @@ -0,0 +1,22 @@ +/** + * @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 + */ +if (typeof window !== 'undefined') { + (window as any)['__Zone_enable_cross_context_check'] = true; + (window as any)['__zone_symbol__fakeAsyncPatchLock'] = true; +} +import '../../../lib/common/to-string'; +import '../../../lib/browser/browser'; +import '../../../lib/browser/webapis-user-media'; +import '../../../lib/testing/zone-testing'; +import '../../../lib/zone-spec/task-tracking'; +import '../../../lib/zone-spec/wtf'; +import '../../../lib/extra/cordova'; +import '../../../lib/testing/promise-testing'; +import '../../../lib/testing/async-testing'; +import '../../../lib/testing/fake-async'; +import '../../../lib/browser/webapis-resize-observer'; diff --git a/test/env/browser/browser_entry_point.ts b/test/env/browser/browser_entry_point.ts new file mode 100644 index 000000000..675eeeda7 --- /dev/null +++ b/test/env/browser/browser_entry_point.ts @@ -0,0 +1,25 @@ +/** + * @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 + */ + +// List all tests here: +import '../common/common_tests'; +import '../../spec/browser/browser.spec'; +import '../../spec/browser/define-property.spec'; +import '../../spec/browser/element.spec'; +import '../../spec/browser/FileReader.spec'; +// import '../../spec/browser/geolocation.spec.manual'; +import '../../spec/browser/HTMLImports.spec'; +import '../../spec/browser/MutationObserver.spec'; +import '../../spec/browser/registerElement.spec'; +import '../../spec/browser/requestAnimationFrame.spec'; +import '../../spec/browser/WebSocket.spec'; +import '../../spec/browser/XMLHttpRequest.spec'; +import '../../spec/browser/MediaQuery.spec'; +import '../../spec/browser/Notification.spec'; +import '../../spec/browser/Worker.spec'; +import '../../spec/extra/cordova.spec'; diff --git a/test/main.ts b/test/env/browser/main.ts similarity index 71% rename from test/main.ts rename to test/env/browser/main.ts index f8c33f3ea..7b4ca188c 100644 --- a/test/main.ts +++ b/test/env/browser/main.ts @@ -28,16 +28,18 @@ if ((window as any)[(Zone as any).__symbol__('setTimeout')]) { } else { // this means that Zone has not patched the browser yet, which means we must be running in // build mode and need to load the browser patch. - browserPatchedPromise = System.import('/base/build/test/browser-zone-setup'); + browserPatchedPromise = System.import('/base/build/test/env/browser/browser-zone-setup'); } browserPatchedPromise.then(() => { - let testFrameworkPatch = typeof(window as any).Mocha !== 'undefined' ? - '/base/build/test/test-env-setup-mocha' : - '/base/build/test/test-env-setup-jasmine'; + let testFrameworkPatch = typeof (window as any).Mocha !== 'undefined' ? + '/base/build/test/env/config/test-env-setup-mocha' : + '/base/build/test/env/config/test-env-setup-jasmine'; // Setup test environment + const entryPoint = (window as any)['__zone_symbol__test_entry_point'] || + '/base/build/test/env/browser/browser_entry_point'; System.import(testFrameworkPatch).then(() => { - System.import('/base/build/test/browser_entry_point') + System.import(entryPoint) .then( () => { __karma__.start(); diff --git a/test/env/common/common_tests.ts b/test/env/common/common_tests.ts new file mode 100644 index 000000000..966ab17c6 --- /dev/null +++ b/test/env/common/common_tests.ts @@ -0,0 +1,27 @@ +/** + * @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 '../../spec/common/microtasks.spec'; +import '../../spec/common/zone.spec'; +import '../../spec/common/task.spec'; +import '../../spec/common/util.spec'; +import '../../spec/common/Promise.spec'; +import '../../spec/common/Error.spec'; +import '../../spec/common/setInterval.spec'; +import '../../spec/common/setTimeout.spec'; +import '../../spec/common/toString.spec'; +import '../../spec/zone-spec/long-stack-trace-zone.spec'; +import '../../spec/zone-spec/async-test.spec'; +import '../../spec/zone-spec/sync-test.spec'; +import '../../spec/zone-spec/fake-async-test.spec'; +import '../../spec/zone-spec/proxy.spec'; +import '../../spec/zone-spec/task-tracking.spec'; +import '../../spec/rxjs/rxjs.spec'; +import '../../spec/jasmine/jasmine-patch.spec'; + +Error.stackTraceLimit = Number.POSITIVE_INFINITY; diff --git a/test/saucelabs.js b/test/env/config/saucelabs.js similarity index 100% rename from test/saucelabs.js rename to test/env/config/saucelabs.js diff --git a/test/test-env-setup-jasmine-no-patch-clock.ts b/test/env/config/test-env-setup-jasmine-no-patch-clock.ts similarity index 100% rename from test/test-env-setup-jasmine-no-patch-clock.ts rename to test/env/config/test-env-setup-jasmine-no-patch-clock.ts diff --git a/test/test-env-setup-jasmine.ts b/test/env/config/test-env-setup-jasmine.ts similarity index 88% rename from test/test-env-setup-jasmine.ts rename to test/env/config/test-env-setup-jasmine.ts index 8cee81df5..85ea4d9d9 100644 --- a/test/test-env-setup-jasmine.ts +++ b/test/env/config/test-env-setup-jasmine.ts @@ -7,4 +7,3 @@ */ (jasmine).DEFAULT_TIMEOUT_INTERVAL = 5000; -import '../lib/jasmine/jasmine'; diff --git a/test/env/config/test-env-setup-mocha.ts b/test/env/config/test-env-setup-mocha.ts new file mode 100644 index 000000000..8184c6e7b --- /dev/null +++ b/test/env/config/test-env-setup-mocha.ts @@ -0,0 +1,11 @@ +/** + * @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 + */ + +if (global && (global as any).Mocha) { + (global as any).Mocha.__zone_symbol__TIMEOUT = 5000; +} \ No newline at end of file diff --git a/test/env/node/node_bluebird_entry_point.ts b/test/env/node/node_bluebird_entry_point.ts new file mode 100644 index 000000000..467bd5f57 --- /dev/null +++ b/test/env/node/node_bluebird_entry_point.ts @@ -0,0 +1,32 @@ +/** + * @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 + */ + +// Must be loaded before zone loads, so that zone can detect WTF. +import '../polyfills/wtf_mock'; +import '../polyfills/test_fake_polyfill'; + +// Setup tests for Zone without microtask support +import '../../../lib/zone'; +import '../../../lib/common/promise'; +import '../../../lib/common/to-string'; +import '../../../lib/node/node'; +import '../../../lib/zone-spec/async-test'; +import '../../../lib/zone-spec/fake-async-test'; +import '../../../lib/zone-spec/long-stack-trace'; +import '../../../lib/zone-spec/proxy'; +import '../../../lib/zone-spec/sync-test'; +import '../../../lib/zone-spec/task-tracking'; +import '../../../lib/zone-spec/wtf'; +import '../../../lib/rxjs/rxjs'; + +import '../../../lib/testing/promise-testing'; +// Setup test environment +import '../config/test-env-setup-jasmine'; + +// List all tests here: +import '../../spec/extra/bluebird.spec'; \ No newline at end of file diff --git a/test/node_entry_point.ts b/test/env/node/node_entry_point.ts similarity index 56% rename from test/node_entry_point.ts rename to test/env/node/node_entry_point.ts index fd521eb92..8ce12650f 100644 --- a/test/node_entry_point.ts +++ b/test/env/node/node_entry_point.ts @@ -11,21 +11,21 @@ if (typeof global !== 'undefined' && (global as any)['__zone_symbol__fakeAsyncPatchLock'] !== false) { (global as any)['__zone_symbol__fakeAsyncPatchLock'] = true; } -import './wtf_mock'; -import './test_fake_polyfill'; +import '../polyfills/wtf_mock'; +import '../polyfills/test_fake_polyfill'; // Setup tests for Zone without microtask support -import '../lib/testing/zone-testing'; -import '../lib/zone-spec/task-tracking'; -import '../lib/zone-spec/wtf'; -import '../lib/rxjs/rxjs'; +import '../../../lib/testing/zone-testing'; +import '../../../lib/zone-spec/task-tracking'; +import '../../../lib/zone-spec/wtf'; +import '../../../lib/rxjs/rxjs'; -import '../lib/testing/promise-testing'; -import '../lib/testing/async-testing'; -import '../lib/testing/fake-async'; +import '../../../lib/testing/promise-testing'; +import '../../../lib/testing/async-testing'; +import '../../../lib/testing/fake-async'; // Setup test environment -import './test-env-setup-jasmine'; +import '../config/test-env-setup-jasmine'; // List all tests here: -import './common_tests'; +import '../common/common_tests'; import './node_tests'; diff --git a/test/env/node/node_tests.ts b/test/env/node/node_tests.ts new file mode 100644 index 000000000..33c282594 --- /dev/null +++ b/test/env/node/node_tests.ts @@ -0,0 +1,15 @@ +/** + * @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 '../../spec/node/events.spec'; +import '../../spec/node/fs.spec'; +import '../../spec/node/process.spec'; +import '../../spec/node/Error.spec'; +import '../../spec/node/crypto.spec'; +import '../../spec/node/http.spec'; +import '../../spec/node/console.spec'; diff --git a/test/test_fake_polyfill.ts b/test/env/polyfills/test_fake_polyfill.ts similarity index 100% rename from test/test_fake_polyfill.ts rename to test/env/polyfills/test_fake_polyfill.ts diff --git a/test/wtf_mock.ts b/test/env/polyfills/wtf_mock.ts similarity index 100% rename from test/wtf_mock.ts rename to test/env/polyfills/wtf_mock.ts diff --git a/test/ws-client.js b/test/env/websocket/ws-client.js similarity index 100% rename from test/ws-client.js rename to test/env/websocket/ws-client.js diff --git a/test/ws-server.js b/test/env/websocket/ws-server.js similarity index 100% rename from test/ws-server.js rename to test/env/websocket/ws-server.js diff --git a/test/ws-webworker-context.ts b/test/env/websocket/ws-webworker-context.ts similarity index 58% rename from test/ws-webworker-context.ts rename to test/env/websocket/ws-webworker-context.ts index b52632ea4..1999d4c14 100644 --- a/test/ws-webworker-context.ts +++ b/test/env/websocket/ws-webworker-context.ts @@ -8,6 +8,6 @@ declare function importScripts(path: string): void; - importScripts('/base/build/lib/zone.js'); - importScripts('/base/node_modules/systemjs/dist/system.src.js'); - importScripts('/base/build/test/zone_worker_entry_point.js'); +importScripts('/base/build/lib/zone.js'); +importScripts('/base/node_modules/systemjs/dist/system.src.js'); +importScripts('/base/build/test/env/websocket/zone_worker_entry_point.js'); diff --git a/test/zone_worker_entry_point.ts b/test/env/websocket/zone_worker_entry_point.ts similarity index 92% rename from test/zone_worker_entry_point.ts rename to test/env/websocket/zone_worker_entry_point.ts index 2c35278c1..d73eade24 100644 --- a/test/zone_worker_entry_point.ts +++ b/test/env/websocket/zone_worker_entry_point.ts @@ -8,7 +8,7 @@ // Setup tests for Zone without microtask support System.config({defaultJSExtensions: true}); -System.import('../lib/browser/browser').then(() => { +System.import('../../../lib/browser/browser').then(() => { Zone.current.fork({name: 'webworker'}).run(() => { const websocket = new WebSocket('ws://localhost:8001'); websocket.addEventListener('open', () => { diff --git a/karma-dist-sauce-jasmine.conf.js b/test/karma/ci/build/karma-build-sauce-mocha.conf.js similarity index 60% rename from karma-dist-sauce-jasmine.conf.js rename to test/karma/ci/build/karma-build-sauce-mocha.conf.js index 44f6be74a..b1573d41c 100644 --- a/karma-dist-sauce-jasmine.conf.js +++ b/test/karma/ci/build/karma-build-sauce-mocha.conf.js @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -module.exports = function (config) { - require('./karma-dist-jasmine.conf.js')(config); - require('./sauce.conf')(config, ['SL_IOS9']); +module.exports = function(config) { + require('../../common/dist/karma-dist-mocha.conf.js')(config); + require('../sauce.conf')(config); }; diff --git a/test/karma/ci/build/karma-build-sauce-selenium3-mocha.conf.js b/test/karma/ci/build/karma-build-sauce-selenium3-mocha.conf.js new file mode 100644 index 000000000..eef7b4ea0 --- /dev/null +++ b/test/karma/ci/build/karma-build-sauce-selenium3-mocha.conf.js @@ -0,0 +1,12 @@ +/** + * @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 + */ + +module.exports = function(config) { + require('../../common/dist/karma-dist-mocha.conf.js')(config); + require('../sauce-selenium3.conf')(config, ['SL_IE9']); +}; diff --git a/test/karma/ci/dist/karma-dist-sauce-jasmine.conf.js b/test/karma/ci/dist/karma-dist-sauce-jasmine.conf.js new file mode 100644 index 000000000..f23f5fc29 --- /dev/null +++ b/test/karma/ci/dist/karma-dist-sauce-jasmine.conf.js @@ -0,0 +1,12 @@ +/** + * @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 + */ + +module.exports = function(config) { + require('../../common/dist/karma-dist-jasmine.conf.js')(config); + require('../sauce.conf')(config, ['SL_IOS9']); +}; diff --git a/test/karma/ci/dist/karma-dist-sauce-jasmine3.conf.js b/test/karma/ci/dist/karma-dist-sauce-jasmine3.conf.js new file mode 100644 index 000000000..643d4bc84 --- /dev/null +++ b/test/karma/ci/dist/karma-dist-sauce-jasmine3.conf.js @@ -0,0 +1,16 @@ +/** + * @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 + */ + +module.exports = function(config) { + require('../../common/dist/karma-dist-jasmine.conf.js')(config); + require('../sauce.conf')(config, [ + 'SL_IOS9', 'SL_CHROME', 'SL_FIREFOX_54', 'SL_SAFARI8', 'SL_SAFARI9', 'SL_SAFARI10', 'SL_IOS8', + 'SL_IOS9', 'SL_IOS10', 'SL_IE9', 'SL_IE10', 'SL_IE11', 'SL_MSEDGE15', 'SL_ANDROID4.4', + 'SL_ANDROID5.1' + ]) +}; diff --git a/test/karma/ci/dist/karma-dist-sauce-selenium3-jasmine.conf.js b/test/karma/ci/dist/karma-dist-sauce-selenium3-jasmine.conf.js new file mode 100644 index 000000000..4b1172e04 --- /dev/null +++ b/test/karma/ci/dist/karma-dist-sauce-selenium3-jasmine.conf.js @@ -0,0 +1,12 @@ +/** + * @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 + */ + +module.exports = function(config) { + require('../../common/dist/karma-dist-jasmine.conf.js')(config); + require('../sauce-selenium3.conf')(config); +}; diff --git a/sauce-selenium3.conf.js b/test/karma/ci/sauce-selenium3.conf.js similarity index 57% rename from sauce-selenium3.conf.js rename to test/karma/ci/sauce-selenium3.conf.js index 4c4b7322e..a7c7c4645 100644 --- a/sauce-selenium3.conf.js +++ b/test/karma/ci/sauce-selenium3.conf.js @@ -1,16 +1,12 @@ // Sauce configuration with Welenium drivers 3+ -module.exports = function (config) { +module.exports = function(config) { // The WS server is not available with Sauce - config.files.unshift('test/saucelabs.js'); + config.files.unshift('test/env/config/saucelabs.js'); var customLaunchers = { - 'SL_CHROME60': { - base: 'SauceLabs', - browserName: 'Chrome', - platform: 'Windows 10', - version: '60.0' - } + 'SL_CHROME60': + {base: 'SauceLabs', browserName: 'Chrome', platform: 'Windows 10', version: '60.0'} }; config.set({ @@ -22,11 +18,11 @@ module.exports = function (config) { startConnect: false, recordVideo: false, recordScreenshots: false, - options: { - 'selenium-version': '3.5.0', - 'command-timeout': 600, - 'idle-timeout': 600, - 'max-duration': 5400 + options: { + 'selenium-version': '3.5.0', + 'command-timeout': 600, + 'idle-timeout': 600, + 'max-duration': 5400 } }, @@ -38,13 +34,12 @@ module.exports = function (config) { singleRun: true, - plugins: [ - 'karma-*' - ] + plugins: ['karma-*'] }); if (process.env.TRAVIS) { - config.sauceLabs.build = 'TRAVIS #' + process.env.TRAVIS_BUILD_NUMBER + ' (' + process.env.TRAVIS_BUILD_ID + ')'; + config.sauceLabs.build = + 'TRAVIS #' + process.env.TRAVIS_BUILD_NUMBER + ' (' + process.env.TRAVIS_BUILD_ID + ')'; config.sauceLabs.tunnelIdentifier = process.env.TRAVIS_JOB_NUMBER; process.env.SAUCE_ACCESS_KEY = process.env.SAUCE_ACCESS_KEY.split('').reverse().join(''); diff --git a/sauce.conf.js b/test/karma/ci/sauce.conf.js similarity index 98% rename from sauce.conf.js rename to test/karma/ci/sauce.conf.js index 1aac560bd..749b0da04 100644 --- a/sauce.conf.js +++ b/test/karma/ci/sauce.conf.js @@ -2,7 +2,7 @@ module.exports = function(config, ignoredLaunchers) { // The WS server is not available with Sauce - config.files.unshift('test/saucelabs.js'); + config.files.unshift('test/env/config/saucelabs.js'); var basicLaunchers = { 'SL_CHROME': {base: 'SauceLabs', browserName: 'chrome', version: '48'}, diff --git a/karma-build-jasmine-phantomjs.conf.js b/test/karma/common/build/karma-build-jasmine-phantomjs.conf.js similarity index 86% rename from karma-build-jasmine-phantomjs.conf.js rename to test/karma/common/build/karma-build-jasmine-phantomjs.conf.js index 91b0d0c31..54989858d 100644 --- a/karma-build-jasmine-phantomjs.conf.js +++ b/test/karma/common/build/karma-build-jasmine-phantomjs.conf.js @@ -1,5 +1,5 @@ -module.exports = function (config) { +module.exports = function(config) { require('./karma-build.conf.js')(config); config.plugins.push(require('karma-jasmine')); diff --git a/karma-build-jasmine.conf.js b/test/karma/common/build/karma-build-jasmine.conf.js similarity index 77% rename from karma-build-jasmine.conf.js rename to test/karma/common/build/karma-build-jasmine.conf.js index 29747ca33..7c038f67c 100644 --- a/karma-build-jasmine.conf.js +++ b/test/karma/common/build/karma-build-jasmine.conf.js @@ -1,5 +1,4 @@ - -module.exports = function (config) { +module.exports = function(config) { require('./karma-build.conf.js')(config); config.plugins.push(require('karma-jasmine')); diff --git a/test/karma/common/build/karma-build-mocha-jasmine-bridge.conf.js b/test/karma/common/build/karma-build-mocha-jasmine-bridge.conf.js new file mode 100644 index 000000000..9fd3a802d --- /dev/null +++ b/test/karma/common/build/karma-build-mocha-jasmine-bridge.conf.js @@ -0,0 +1,18 @@ + +module.exports = function(config) { + require('../karma-base.conf.js')(config); + config.files.push('build/test/spec/mocha/mocha-browser-karma.js'); + config.files.push('build/test/env/polyfills/wtf_mock.js'); + config.files.push('build/test/env/polyfills/test_fake_polyfill.js'); + config.files.push('build/lib/zone.js'); + config.files.push('build/lib/common/promise.js'); + config.files.push('build/lib/common/error-rewrite.js'); + config.files.push('build/test/env/browser/main.js'); + + config.plugins.push(require('karma-mocha')); + config.frameworks.push('mocha'); + config.client.mocha = { + timeout: 5000 // copied timeout for Jasmine in WebSocket.spec (otherwise Mochas default timeout + // at 2 sec is to low for the tests) + }; +}; diff --git a/karma-build-mocha.conf.js b/test/karma/common/build/karma-build-mocha.conf.js similarity index 100% rename from karma-build-mocha.conf.js rename to test/karma/common/build/karma-build-mocha.conf.js diff --git a/karma-build.conf.js b/test/karma/common/build/karma-build.conf.js similarity index 57% rename from karma-build.conf.js rename to test/karma/common/build/karma-build.conf.js index 87c87a5ea..d25337e61 100644 --- a/karma-build.conf.js +++ b/test/karma/common/build/karma-build.conf.js @@ -6,12 +6,12 @@ * found in the LICENSE file at https://angular.io/license */ -module.exports = function (config) { - require('./karma-base.conf.js')(config); - config.files.push('build/test/wtf_mock.js'); - config.files.push('build/test/test_fake_polyfill.js'); +module.exports = function(config) { + require('../karma-base.conf.js')(config); + config.files.push('build/test/env/polyfills/wtf_mock.js'); + config.files.push('build/test/env/polyfills/test_fake_polyfill.js'); config.files.push('build/lib/zone.js'); config.files.push('build/lib/common/promise.js'); config.files.push('build/lib/common/error-rewrite.js'); - config.files.push('build/test/main.js'); + config.files.push('build/test/env/browser/main.js'); }; diff --git a/karma-dist-jasmine.conf.js b/test/karma/common/dist/karma-dist-jasmine.conf.js similarity index 78% rename from karma-dist-jasmine.conf.js rename to test/karma/common/dist/karma-dist-jasmine.conf.js index 9d6cb428d..32627d46e 100644 --- a/karma-dist-jasmine.conf.js +++ b/test/karma/common/dist/karma-dist-jasmine.conf.js @@ -1,5 +1,5 @@ -module.exports = function (config) { +module.exports = function(config) { require('./karma-dist.conf.js')(config); config.plugins.push(require('karma-jasmine')); diff --git a/karma-dist-mocha.conf.js b/test/karma/common/dist/karma-dist-mocha.conf.js similarity index 100% rename from karma-dist-mocha.conf.js rename to test/karma/common/dist/karma-dist-mocha.conf.js diff --git a/test/karma/common/dist/karma-dist.conf.js b/test/karma/common/dist/karma-dist.conf.js new file mode 100644 index 000000000..74454bc1e --- /dev/null +++ b/test/karma/common/dist/karma-dist.conf.js @@ -0,0 +1,20 @@ +/** + * @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 + */ + +module.exports = function(config) { + require('../karma-base.conf.js')(config); + config.files.push('build/test/env/polyfills/wtf_mock.js'); + config.files.push('build/test/env/polyfills/test_fake_polyfill.js'); + config.files.push('dist/zone.js'); + config.files.push('dist/zone-patch-user-media.js'); + config.files.push('dist/zone-patch-resize-observer.js'); + config.files.push('dist/zone-testing.js'); + config.files.push('dist/task-tracking.js'); + config.files.push('dist/wtf.js'); + config.files.push('build/test/env/browser/main.js'); +}; diff --git a/karma-base.conf.js b/test/karma/common/karma-base.conf.js similarity index 68% rename from karma-base.conf.js rename to test/karma/common/karma-base.conf.js index 0778ebe2e..f51a1947e 100644 --- a/karma-base.conf.js +++ b/test/karma/common/karma-base.conf.js @@ -6,45 +6,39 @@ * found in the LICENSE file at https://angular.io/license */ -module.exports = function (config) { +module.exports = function(config) { config.set({ - basePath: '', + basePath: '../../../../', files: [ - 'node_modules/systemjs/dist/system-polyfills.js', - 'node_modules/systemjs/dist/system.src.js', + 'node_modules/systemjs/dist/system-polyfills.js', 'node_modules/systemjs/dist/system.src.js', 'node_modules/whatwg-fetch/fetch.js', - {pattern: 'node_modules/rxjs/**/**/*.js', included: false, watched: false }, - {pattern: 'node_modules/rxjs/**/**/*.js.map', included: false, watched: false }, - {pattern: 'node_modules/rxjs/**/*.js', included: false, watched: false }, - {pattern: 'node_modules/es6-promise/**/*.js', included: false, watched: false }, - {pattern: 'node_modules/rxjs/**/*.js.map', included: false, watched: false }, + {pattern: 'node_modules/rxjs/**/**/*.js', included: false, watched: false}, + {pattern: 'node_modules/rxjs/**/**/*.js.map', included: false, watched: false}, + {pattern: 'node_modules/rxjs/**/*.js', included: false, watched: false}, + {pattern: 'node_modules/es6-promise/**/*.js', included: false, watched: false}, + {pattern: 'node_modules/rxjs/**/*.js.map', included: false, watched: false}, {pattern: 'test/assets/**/*.*', watched: true, served: true, included: false}, {pattern: 'build/**/*.js.map', watched: true, served: true, included: false}, {pattern: 'build/**/*.js', watched: true, served: true, included: false} ], plugins: [ - require('karma-chrome-launcher'), - require('karma-firefox-launcher'), + require('karma-chrome-launcher'), require('karma-firefox-launcher'), require('karma-sourcemap-loader') ], - preprocessors: { - '**/*.js': ['sourcemap'] - }, + preprocessors: {'**/*.js': ['sourcemap']}, - exclude: [ - 'test/microtasks.spec.ts' - ], + exclude: ['test/spec/common/microtasks.spec.ts'], reporters: ['progress'], - //port: 9876, + // port: 9876, colors: true, logLevel: config.LOG_INFO, - browsers: ['Firefox'], + browsers: ['Chrome'], captureTimeout: 60000, diff --git a/test/node_bluebird_entry_point.ts b/test/node_bluebird_entry_point.ts deleted file mode 100644 index 8c5a86f65..000000000 --- a/test/node_bluebird_entry_point.ts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * @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 - */ - -// Must be loaded before zone loads, so that zone can detect WTF. -import './wtf_mock'; -import './test_fake_polyfill'; - -// Setup tests for Zone without microtask support -import '../lib/zone'; -import '../lib/common/promise'; -import '../lib/common/to-string'; -import '../lib/node/node'; -import '../lib/zone-spec/async-test'; -import '../lib/zone-spec/fake-async-test'; -import '../lib/zone-spec/long-stack-trace'; -import '../lib/zone-spec/proxy'; -import '../lib/zone-spec/sync-test'; -import '../lib/zone-spec/task-tracking'; -import '../lib/zone-spec/wtf'; -import '../lib/rxjs/rxjs'; - -import '../lib/testing/promise-testing'; -// Setup test environment -import './test-env-setup-jasmine'; - -// List all tests here: -import './extra/bluebird.spec'; \ No newline at end of file diff --git a/test/node_tests.ts b/test/node_tests.ts deleted file mode 100644 index 0fcc9b6aa..000000000 --- a/test/node_tests.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * @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 './node/events.spec'; -import './node/fs.spec'; -import './node/process.spec'; -import './node/Error.spec'; -import './node/crypto.spec'; -import './node/http.spec'; -import './node/console.spec'; diff --git a/test/rxjs/rxjs.fromEvent.spec.ts b/test/rxjs/rxjs.fromEvent.spec.ts deleted file mode 100644 index 6020ee795..000000000 --- a/test/rxjs/rxjs.fromEvent.spec.ts +++ /dev/null @@ -1,121 +0,0 @@ -/** - * @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 * as Rx from 'rxjs/Rx'; - -import {isBrowser} from '../../lib/common/utils'; -import {ifEnvSupports} from '../test-util'; - -function isEventTarget() { - return isBrowser; -} - -(isEventTarget as any).message = 'EventTargetTest'; - -describe('Observable.fromEvent', () => { - let log: string[]; - const constructorZone1: Zone = Zone.current.fork({ - name: 'Constructor Zone1' - }); - const subscriptionZone: Zone = Zone.current.fork({ - name: 'Subscription Zone' - }); - const triggerZone: Zone = Zone.current.fork({ name: 'Trigger Zone' }); - let observable1: any; - - beforeEach(() => { - log = []; - }); - - it( - 'fromEvent EventTarget func callback should run in the correct zone', - ifEnvSupports(isEventTarget, () => { - observable1 = constructorZone1.run(() => { - return Rx.Observable.fromEvent(document, 'click'); - }); - - const clickEvent = document.createEvent('Event'); - clickEvent.initEvent('click', true, true); - - subscriptionZone.run(() => { - observable1.subscribe( - (result: any) => { - expect(Zone.current.name).toEqual(subscriptionZone.name); - log.push(result); - }, - () => { - fail('should not call error'); - }, - () => { - expect(Zone.current.name).toEqual(subscriptionZone.name); - log.push('completed'); - } - ); - }); - - triggerZone.run(() => { - document.dispatchEvent(clickEvent); - }); - - expect(log).toEqual([clickEvent]); - }) - ); - - it( - 'fromEventPattern EventTarget func callback should run in the correct zone', - ifEnvSupports(isEventTarget, () => { - const button = document.createElement('button'); - document.body.appendChild(button); - observable1 = constructorZone1.run(() => { - return Rx.Observable.fromEventPattern( - (handler: any) => { - expect(Zone.current.name).toEqual(constructorZone1.name); - button.addEventListener('click', handler); - log.push('addListener'); - }, - (handler: any) => { - expect(Zone.current.name).toEqual(constructorZone1.name); - button.removeEventListener('click', handler); - document.body.removeChild(button); - log.push('removeListener'); - } - ); - }); - - const clickEvent = document.createEvent('Event'); - clickEvent.initEvent('click', false, false); - - const subscriper: any = subscriptionZone.run(() => { - return observable1.subscribe( - (result: any) => { - expect(Zone.current.name).toEqual(subscriptionZone.name); - log.push(result); - }, - () => { - fail('should not call error'); - }, - () => { - expect(Zone.current.name).toEqual(subscriptionZone.name); - log.push('completed'); - } - ); - }); - - triggerZone.run(() => { - button.dispatchEvent(clickEvent); - subscriper.complete(); - }); - expect(log).toEqual([ - 'addListener', - clickEvent, - 'completed', - 'removeListener' - ]); - }) - ); -}); diff --git a/test/browser/FileReader.spec.ts b/test/spec/browser/FileReader.spec.ts similarity index 100% rename from test/browser/FileReader.spec.ts rename to test/spec/browser/FileReader.spec.ts diff --git a/test/browser/HTMLImports.spec.ts b/test/spec/browser/HTMLImports.spec.ts similarity index 100% rename from test/browser/HTMLImports.spec.ts rename to test/spec/browser/HTMLImports.spec.ts diff --git a/test/browser/MediaQuery.spec.ts b/test/spec/browser/MediaQuery.spec.ts similarity index 89% rename from test/browser/MediaQuery.spec.ts rename to test/spec/browser/MediaQuery.spec.ts index 465d23102..a92458c24 100644 --- a/test/browser/MediaQuery.spec.ts +++ b/test/spec/browser/MediaQuery.spec.ts @@ -6,9 +6,9 @@ * found in the LICENSE file at https://angular.io/license */ -import '../../lib/browser/webapis-media-query'; +import '../../../lib/browser/webapis-media-query'; -import {zoneSymbol} from '../../lib/common/utils'; +import {zoneSymbol} from '../../../lib/common/utils'; import {ifEnvSupports} from '../test-util'; declare const global: any; diff --git a/test/browser/MutationObserver.spec.ts b/test/spec/browser/MutationObserver.spec.ts similarity index 100% rename from test/browser/MutationObserver.spec.ts rename to test/spec/browser/MutationObserver.spec.ts diff --git a/test/browser/Notification.spec.ts b/test/spec/browser/Notification.spec.ts similarity index 89% rename from test/browser/Notification.spec.ts rename to test/spec/browser/Notification.spec.ts index 5c23fccab..70e2b35d6 100644 --- a/test/browser/Notification.spec.ts +++ b/test/spec/browser/Notification.spec.ts @@ -6,9 +6,9 @@ * found in the LICENSE file at https://angular.io/license */ -import '../../lib/browser/webapis-notification'; +import '../../../lib/browser/webapis-notification'; -import {zoneSymbol} from '../../lib/common/utils'; +import {zoneSymbol} from '../../../lib/common/utils'; import {ifEnvSupports} from '../test-util'; declare const window: any; diff --git a/test/browser/WebSocket.spec.ts b/test/spec/browser/WebSocket.spec.ts similarity index 95% rename from test/browser/WebSocket.spec.ts rename to test/spec/browser/WebSocket.spec.ts index 1ee4b39d8..3f4aa1f19 100644 --- a/test/browser/WebSocket.spec.ts +++ b/test/spec/browser/WebSocket.spec.ts @@ -19,7 +19,6 @@ if (!window['saucelabs']) { const TEST_SERVER_URL = 'ws://localhost:8001'; const testZone = Zone.current.fork({name: 'test'}); - beforeEach(function(done) { socket = new WebSocket(TEST_SERVER_URL); socket.addEventListener('open', function() { @@ -28,7 +27,7 @@ if (!window['saucelabs']) { socket.addEventListener('error', function() { fail( 'Can\'t establish socket to ' + TEST_SERVER_URL + - '! do you have test/ws-server.js running?'); + '! do you have test/env/websocket/ws-server.js running?'); done(); }); }, TIMEOUT); @@ -42,7 +41,7 @@ if (!window['saucelabs']) { it('should be patched in a Web Worker', done => { - const worker = new Worker('/base/build/test/ws-webworker-context.js'); + const worker = new Worker('/base/build/test/env/websocket/ws-webworker-context.js'); worker.onmessage = (e: MessageEvent) => { expect(e.data).toBe('pass'); done(); diff --git a/test/browser/Worker.spec.ts b/test/spec/browser/Worker.spec.ts similarity index 95% rename from test/browser/Worker.spec.ts rename to test/spec/browser/Worker.spec.ts index fcaa386d0..75193d45f 100644 --- a/test/browser/Worker.spec.ts +++ b/test/spec/browser/Worker.spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {zoneSymbol} from '../../lib/common/utils'; +import {zoneSymbol} from '../../../lib/common/utils'; import {asyncTest, ifEnvSupports} from '../test-util'; function workerSupport() { diff --git a/test/browser/XMLHttpRequest.spec.ts b/test/spec/browser/XMLHttpRequest.spec.ts similarity index 100% rename from test/browser/XMLHttpRequest.spec.ts rename to test/spec/browser/XMLHttpRequest.spec.ts diff --git a/test/browser/browser.spec.ts b/test/spec/browser/browser.spec.ts similarity index 99% rename from test/browser/browser.spec.ts rename to test/spec/browser/browser.spec.ts index 9b013d70c..37db17f40 100644 --- a/test/browser/browser.spec.ts +++ b/test/spec/browser/browser.spec.ts @@ -6,9 +6,9 @@ * found in the LICENSE file at https://angular.io/license */ -import {patchFilteredProperties} from '../../lib/browser/property-descriptor'; -import {patchEventTarget} from '../../lib/common/events'; -import {isBrowser, isIEOrEdge, isMix, zoneSymbol} from '../../lib/common/utils'; +import {patchFilteredProperties} from '../../../lib/browser/property-descriptor'; +import {patchEventTarget} from '../../../lib/common/events'; +import {isBrowser, isIEOrEdge, isMix, zoneSymbol} from '../../../lib/common/utils'; import {getIEVersion, ifEnvSupports, ifEnvSupportsWithDone, isEdge} from '../test-util'; import Spy = jasmine.Spy; @@ -122,7 +122,6 @@ describe('Zone', function() { return parentZoneDelegate.scheduleTask(targetZone, task); } }); - beforeEach(function() { mouseEvent.initEvent('mousedown', true, true); hookSpy = jasmine.createSpy('hook'); @@ -135,7 +134,7 @@ describe('Zone', function() { ignoredProperties.filter(ignoreProp => ignoreProp === prop).length > 0) { continue; } - if (prop.substr(0, 2) === 'on' && prop.length > 2) { + if (prop.substr(0, 2) === 'on' && prop.length > 2 && prop !== 'only') { target[prop] = noop; if (!target[Zone.__symbol__('ON_PROPERTY' + prop.substr(2))]) { console.log('onProp is null:', prop); diff --git a/test/browser/define-property.spec.ts b/test/spec/browser/define-property.spec.ts similarity index 100% rename from test/browser/define-property.spec.ts rename to test/spec/browser/define-property.spec.ts diff --git a/test/browser/element.spec.ts b/test/spec/browser/element.spec.ts similarity index 100% rename from test/browser/element.spec.ts rename to test/spec/browser/element.spec.ts diff --git a/test/browser/geolocation.spec.manual.ts b/test/spec/browser/geolocation.spec.manual.ts similarity index 100% rename from test/browser/geolocation.spec.manual.ts rename to test/spec/browser/geolocation.spec.manual.ts diff --git a/test/browser/registerElement.spec.ts b/test/spec/browser/registerElement.spec.ts similarity index 100% rename from test/browser/registerElement.spec.ts rename to test/spec/browser/registerElement.spec.ts diff --git a/test/browser/requestAnimationFrame.spec.ts b/test/spec/browser/requestAnimationFrame.spec.ts similarity index 100% rename from test/browser/requestAnimationFrame.spec.ts rename to test/spec/browser/requestAnimationFrame.spec.ts diff --git a/test/closure/zone.closure.ts b/test/spec/closure/zone.closure.ts similarity index 100% rename from test/closure/zone.closure.ts rename to test/spec/closure/zone.closure.ts diff --git a/test/common/Error.spec.ts b/test/spec/common/Error.spec.ts similarity index 100% rename from test/common/Error.spec.ts rename to test/spec/common/Error.spec.ts diff --git a/test/common/Promise.spec.ts b/test/spec/common/Promise.spec.ts similarity index 90% rename from test/common/Promise.spec.ts rename to test/spec/common/Promise.spec.ts index d6a803b72..c21c12475 100644 --- a/test/common/Promise.spec.ts +++ b/test/spec/common/Promise.spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {isNode, zoneSymbol} from '../../lib/common/utils'; +import {isNode, zoneSymbol} from '../../../lib/common/utils'; import {ifEnvSupports} from '../test-util'; declare const global: any; @@ -51,11 +51,12 @@ describe( pZone = Zone.current.fork({ name: 'promise-zone', - onScheduleTask: (parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, - task: MicroTask): any => { - log.push('scheduleTask'); - parentZoneDelegate.scheduleTask(targetZone, task); - } + onScheduleTask: + (parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, + task: MicroTask): any => { + log.push('scheduleTask'); + parentZoneDelegate.scheduleTask(targetZone, task); + } }); queueZone = Zone.current.fork(new MicroTaskQueueZoneSpec()); @@ -352,11 +353,11 @@ describe( .fork({ name: 'promise-error', onHandleError: (delegate: ZoneDelegate, current: Zone, target: Zone, error: any): - boolean => { - promiseError = error; - delegate.handleError(target, error); - return false; - } + boolean => { + promiseError = error; + delegate.handleError(target, error); + return false; + } }) .run(() => { zone = Zone.current; @@ -391,11 +392,11 @@ describe( .fork({ name: 'promise-error', onHandleError: (delegate: ZoneDelegate, current: Zone, target: Zone, error: any): - boolean => { - promiseError = error; - delegate.handleError(target, error); - return false; - } + boolean => { + promiseError = error; + delegate.handleError(target, error); + return false; + } }) .run(() => { zone = Zone.current; @@ -462,27 +463,27 @@ describe( }); }); - it('should resolve generators', ifEnvSupports( - () => { - return isNode; - }, - () => { - const generators: any = function*() { - yield Promise.resolve(1); - yield Promise.resolve(2); - return; - }; - queueZone.run(() => { - let value = null; - Promise.all(generators()).then(val => { - value = val; - }); - // expect(Zone.current.get('queue').length).toEqual(2); - flushMicrotasks(); - expect(value).toEqual([1, 2]); - }); - - })); + it('should resolve generators', + ifEnvSupports( + () => { + return isNode; + }, + () => { + const generators: any = function*() { + yield Promise.resolve(1); + yield Promise.resolve(2); + return; + }; + queueZone.run(() => { + let value = null; + Promise.all(generators()).then(val => { + value = val; + }); + // expect(Zone.current.get('queue').length).toEqual(2); + flushMicrotasks(); + expect(value).toEqual([1, 2]); + }); + })); }); }); @@ -540,7 +541,7 @@ describe( expect(result).toBe('foo'); done && done(); }); - } + } it('should resolve if the Promise subclass resolves', jasmine ? function(done) { testPromiseSubClass(done); @@ -619,6 +620,5 @@ describe( }); }); }); - })); })); diff --git a/test/common/microtasks.spec.ts b/test/spec/common/microtasks.spec.ts similarity index 100% rename from test/common/microtasks.spec.ts rename to test/spec/common/microtasks.spec.ts diff --git a/test/common/setInterval.spec.ts b/test/spec/common/setInterval.spec.ts similarity index 98% rename from test/common/setInterval.spec.ts rename to test/spec/common/setInterval.spec.ts index 42aaac7b0..e059be8ea 100644 --- a/test/common/setInterval.spec.ts +++ b/test/spec/common/setInterval.spec.ts @@ -7,11 +7,10 @@ */ 'use strict'; -import {isNode, zoneSymbol} from '../../lib/common/utils'; +import {isNode, zoneSymbol} from '../../../lib/common/utils'; declare const global: any; describe('setInterval', function() { - it('should work with setInterval', function(done) { let cancelId: any; const testZone = Zone.current.fork((Zone as any)['wtfZoneSpec']).fork({name: 'TestZone'}); @@ -86,5 +85,4 @@ describe('setInterval', function() { }, 300); }); }); - }); diff --git a/test/common/setTimeout.spec.ts b/test/spec/common/setTimeout.spec.ts similarity index 98% rename from test/common/setTimeout.spec.ts rename to test/spec/common/setTimeout.spec.ts index c71ab4d5f..f849c2a01 100644 --- a/test/common/setTimeout.spec.ts +++ b/test/spec/common/setTimeout.spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {isNode, zoneSymbol} from '../../lib/common/utils'; +import {isNode, zoneSymbol} from '../../../lib/common/utils'; declare const global: any; describe('setTimeout', function() { @@ -119,5 +119,4 @@ describe('setTimeout', function() { clearTimeout(null); clearTimeout({}); }); - }); diff --git a/test/common/task.spec.ts b/test/spec/common/task.spec.ts similarity index 100% rename from test/common/task.spec.ts rename to test/spec/common/task.spec.ts diff --git a/test/common/toString.spec.ts b/test/spec/common/toString.spec.ts similarity index 94% rename from test/common/toString.spec.ts rename to test/spec/common/toString.spec.ts index 1eb33c839..2ec0d56e6 100644 --- a/test/common/toString.spec.ts +++ b/test/spec/common/toString.spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {zoneSymbol} from '../../lib/common/utils'; +import {zoneSymbol} from '../../../lib/common/utils'; import {ifEnvSupports} from '../test-util'; const g: any = @@ -37,7 +37,8 @@ describe('global function patch', () => { }); it('Function toString should look like native', () => { - expect(Function.prototype.toString.call(Function.prototype.toString)).toContain('[native code]'); + expect(Function.prototype.toString.call(Function.prototype.toString)) + .toContain('[native code]'); }); it('EventTarget addEventListener should look like native', ifEnvSupports('HTMLElement', () => { diff --git a/test/common/util.spec.ts b/test/spec/common/util.spec.ts similarity index 99% rename from test/common/util.spec.ts rename to test/spec/common/util.spec.ts index b6b122d10..669bd68ed 100644 --- a/test/common/util.spec.ts +++ b/test/spec/common/util.spec.ts @@ -6,10 +6,9 @@ * found in the LICENSE file at https://angular.io/license */ -import {patchMethod, patchProperty, patchPrototype, zoneSymbol} from '../../lib/common/utils'; +import {patchMethod, patchProperty, patchPrototype, zoneSymbol} from '../../../lib/common/utils'; describe('utils', function() { - describe('patchMethod', () => { it('should patch target where the method is defined', () => { let args; diff --git a/test/common/zone.spec.ts b/test/spec/common/zone.spec.ts similarity index 97% rename from test/common/zone.spec.ts rename to test/spec/common/zone.spec.ts index f969df913..9bd5e9437 100644 --- a/test/common/zone.spec.ts +++ b/test/spec/common/zone.spec.ts @@ -5,7 +5,7 @@ * 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 {zoneSymbol} from '../../lib/common/utils'; +import {zoneSymbol} from '../../../lib/common/utils'; describe('Zone', function() { const rootZone = Zone.current; @@ -15,7 +15,6 @@ describe('Zone', function() { }); describe('hooks', function() { - it('should throw if onError is not defined', function() { expect(function() { Zone.current.run(throwError); @@ -126,10 +125,10 @@ describe('Zone', function() { const zone: Zone = Zone.current.fork({ name: 'parent', onHasTask: (delegate: ZoneDelegate, current: Zone, target: Zone, hasTaskState: HasTaskState): - void => { - (hasTaskState as any)['zone'] = target.name; - log.push(hasTaskState); - }, + void => { + (hasTaskState as any)['zone'] = target.name; + log.push(hasTaskState); + }, onScheduleTask: (delegate: ZoneDelegate, current: Zone, target: Zone, task: Task) => { // Do nothing to prevent tasks from being run on VM turn; // Tests run task explicitly. @@ -394,10 +393,11 @@ describe('Zone', function() { const spy = jasmine.createSpy('error'); const hasTaskZone = Zone.current.fork({ name: 'hasTask', - onHasTask: (delegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, - hasTasState: HasTaskState) => { - throw new Error('onHasTask Error'); - }, + onHasTask: + (delegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, + hasTasState: HasTaskState) => { + throw new Error('onHasTask Error'); + }, onHandleError: (delegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, error: Error) => { spy(error.message); diff --git a/test/extra/bluebird.spec.ts b/test/spec/extra/bluebird.spec.ts similarity index 91% rename from test/extra/bluebird.spec.ts rename to test/spec/extra/bluebird.spec.ts index b86ceac15..fb1080ca6 100644 --- a/test/extra/bluebird.spec.ts +++ b/test/spec/extra/bluebird.spec.ts @@ -15,7 +15,7 @@ describe('bluebird promise', () => { beforeAll(() => { BluebirdPromise = require('bluebird'); // import bluebird patch - require('../../lib/extra/bluebird'); + require('../../../lib/extra/bluebird'); const patchBluebird = (Zone as any)[(Zone as any).__symbol__('bluebird')]; patchBluebird(BluebirdPromise); }); @@ -76,7 +76,8 @@ describe('bluebird promise', () => { .spread((r1: string, r2: string) => { expect(r1).toEqual('test1'); expect(r2).toEqual('test2'); - expect(log.filter(item => item === 'schedule bluebird task Promise.then').length).toBe(1); + expect(log.filter(item => item === 'schedule bluebird task Promise.then').length) + .toBe(1); expect(log.filter(item => item === 'invoke bluebird task Promise.then').length).toBe(1); expect(Zone.current.name).toEqual('bluebird'); done(); @@ -112,7 +113,8 @@ describe('bluebird promise', () => { }) .then(() => { expect(Zone.current.name).toEqual('bluebird'); - expect(log.filter(item => item === 'schedule bluebird task Promise.then').length).toBe(1); + expect(log.filter(item => item === 'schedule bluebird task Promise.then').length) + .toBe(1); expect(log.filter(item => item === 'invoke bluebird task Promise.then').length).toBe(1); done(); }); @@ -126,7 +128,8 @@ describe('bluebird promise', () => { throw new Error('promise error'); }) .catch((err: Error) => { - expect(log.filter(item => item === 'schedule bluebird task Promise.then').length).toBe(1); + expect(log.filter(item => item === 'schedule bluebird task Promise.then').length) + .toBe(1); expect(log.filter(item => item === 'invoke bluebird task Promise.then').length).toBe(1); expect(Zone.current.name).toEqual('bluebird'); expect(err.message).toEqual('promise error'); @@ -142,7 +145,8 @@ describe('bluebird promise', () => { return 'test'; })() .then((result: string) => { - expect(log.filter(item => item === 'schedule bluebird task Promise.then').length).toBe(1); + expect(log.filter(item => item === 'schedule bluebird task Promise.then').length) + .toBe(1); expect(log.filter(item => item === 'invoke bluebird task Promise.then').length).toBe(1); expect(Zone.current.name).toEqual('bluebird'); expect(result).toEqual('test'); @@ -181,7 +185,8 @@ describe('bluebird promise', () => { .then((r: string[]) => { expect(r[0]).toEqual('test1'); expect(r[1]).toEqual('test2'); - expect(log.filter(item => item === 'schedule bluebird task Promise.then').length).toBe(1); + expect(log.filter(item => item === 'schedule bluebird task Promise.then').length) + .toBe(1); expect(log.filter(item => item === 'invoke bluebird task Promise.then').length).toBe(1); expect(Zone.current.name).toEqual('bluebird'); done(); @@ -196,7 +201,8 @@ describe('bluebird promise', () => { .then((r: any) => { expect(r.test1).toEqual('test1'); expect(r.test2).toEqual('test2'); - expect(log.filter(item => item === 'schedule bluebird task Promise.then').length).toBe(1); + expect(log.filter(item => item === 'schedule bluebird task Promise.then').length) + .toBe(1); expect(log.filter(item => item === 'invoke bluebird task Promise.then').length).toBe(1); expect(Zone.current.name).toEqual('bluebird'); done(); @@ -209,7 +215,8 @@ describe('bluebird promise', () => { BluebirdPromise.any([BluebirdPromise.resolve('test1'), BluebirdPromise.resolve('test2')]) .then((r: any) => { expect(r).toEqual('test1'); - expect(log.filter(item => item === 'schedule bluebird task Promise.then').length).toBe(1); + expect(log.filter(item => item === 'schedule bluebird task Promise.then').length) + .toBe(1); expect(log.filter(item => item === 'invoke bluebird task Promise.then').length).toBe(1); expect(Zone.current.name).toEqual('bluebird'); done(); @@ -223,7 +230,8 @@ describe('bluebird promise', () => { .then((r: any) => { expect(r.length).toBe(1); expect(r[0]).toEqual('test1'); - expect(log.filter(item => item === 'schedule bluebird task Promise.then').length).toBe(1); + expect(log.filter(item => item === 'schedule bluebird task Promise.then').length) + .toBe(1); expect(log.filter(item => item === 'invoke bluebird task Promise.then').length).toBe(1); expect(Zone.current.name).toEqual('bluebird'); done(); @@ -243,7 +251,8 @@ describe('bluebird promise', () => { expect(r.length).toBe(2); expect(r[0]).toEqual('test1'); expect(r[1]).toEqual('test2'); - expect(log.filter(item => item === 'schedule bluebird task Promise.then').length).toBe(1); + expect(log.filter(item => item === 'schedule bluebird task Promise.then').length) + .toBe(1); expect(log.filter(item => item === 'invoke bluebird task Promise.then').length).toBe(1); expect(Zone.current.name).toEqual('bluebird'); done(); @@ -282,7 +291,8 @@ describe('bluebird promise', () => { }) .then((r: number[]) => { expect(r[0]).toBe(2); - expect(log.filter(item => item === 'schedule bluebird task Promise.then').length).toBe(1); + expect(log.filter(item => item === 'schedule bluebird task Promise.then').length) + .toBe(1); expect(log.filter(item => item === 'invoke bluebird task Promise.then').length).toBe(1); expect(Zone.current.name).toEqual('bluebird'); done(); @@ -293,12 +303,14 @@ describe('bluebird promise', () => { it('bluebird promise each method should be in zone', (done) => { zone.run(() => { const arr = [1, 2, 3]; - BluebirdPromise.each( - BluebirdPromise.map(arr, (item: number) => BluebirdPromise.resolve(item)), - (r: number[], idx: number) => { - expect(r[idx] === arr[idx]); - expect(Zone.current.name).toEqual('bluebird'); - }).then((r: any) => { + BluebirdPromise + .each( + BluebirdPromise.map(arr, (item: number) => BluebirdPromise.resolve(item)), + (r: number[], idx: number) => { + expect(r[idx] === arr[idx]); + expect(Zone.current.name).toEqual('bluebird'); + }) + .then((r: any) => { expect(log.filter(item => item === 'schedule bluebird task Promise.then').length) .toBeTruthy(); expect(log.filter(item => item === 'invoke bluebird task Promise.then').length) @@ -312,19 +324,22 @@ describe('bluebird promise', () => { it('bluebird promise mapSeries method should be in zone', (done) => { zone.run(() => { const arr = [1, 2, 3]; - BluebirdPromise.mapSeries( - BluebirdPromise.map(arr, (item: number) => BluebirdPromise.resolve(item)), - (r: number[], idx: number) => { - expect(r[idx] === arr[idx]); - expect(Zone.current.name).toEqual('bluebird'); - }).then((r: any) => { + BluebirdPromise + .mapSeries( + BluebirdPromise.map(arr, (item: number) => BluebirdPromise.resolve(item)), + (r: number[], idx: number) => { + expect(r[idx] === arr[idx]); + expect(Zone.current.name).toEqual('bluebird'); + }) + .then((r: any) => { expect(log.filter(item => item === 'schedule bluebird task Promise.then').length) .toBeTruthy(); expect(log.filter(item => item === 'invoke bluebird task Promise.then').length) .toBeTruthy(); expect(Zone.current.name).toEqual('bluebird'); done(); - });; + }); + ; }); }); @@ -333,7 +348,8 @@ describe('bluebird promise', () => { BluebirdPromise.race([BluebirdPromise.resolve('test1'), BluebirdPromise.resolve('test2')]) .then((r: string) => { expect(r).toEqual('test1'); - expect(log.filter(item => item === 'schedule bluebird task Promise.then').length).toBe(1); + expect(log.filter(item => item === 'schedule bluebird task Promise.then').length) + .toBe(1); expect(log.filter(item => item === 'invoke bluebird task Promise.then').length).toBe(1); expect(Zone.current.name).toEqual('bluebird'); done(); @@ -362,8 +378,10 @@ describe('bluebird promise', () => { expect(Zone.current.name).toEqual('bluebird'); expect(p.leakObj).toBe(null); // using will generate several promise inside bluebird - expect(log.filter(item => item === 'schedule bluebird task Promise.then').length).toBeTruthy(); - expect(log.filter(item => item === 'invoke bluebird task Promise.then').length).toBeTruthy(); + expect(log.filter(item => item === 'schedule bluebird task Promise.then').length) + .toBeTruthy(); + expect(log.filter(item => item === 'invoke bluebird task Promise.then').length) + .toBeTruthy(); done(); }); }); @@ -410,7 +428,8 @@ describe('bluebird promise', () => { expect(r[0]).toBe('test1'); expect(r[1]).toBe('test2'); // using will generate several promise inside - expect(log.filter(item => item === 'schedule bluebird task Promise.then').length).toBe(1); + expect(log.filter(item => item === 'schedule bluebird task Promise.then').length) + .toBe(1); expect(log.filter(item => item === 'invoke bluebird task Promise.then').length).toBe(1); done(); }); @@ -468,7 +487,8 @@ describe('bluebird promise', () => { .then((r: string) => { expect(Zone.current.name).toEqual('bluebird'); expect(r).toBe('test'); - expect(log.filter(item => item === 'schedule bluebird task Promise.then').length).toBe(1); + expect(log.filter(item => item === 'schedule bluebird task Promise.then').length) + .toBe(1); expect(log.filter(item => item === 'invoke bluebird task Promise.then').length).toBe(1); done(); }); @@ -483,8 +503,8 @@ describe('bluebird promise', () => { }, 0); }); p.tap(() => { - expect(Zone.current.name).toEqual('bluebird'); - }).then(() => { + expect(Zone.current.name).toEqual('bluebird'); + }).then(() => { expect(log.filter(item => item === 'schedule bluebird task Promise.then').length).toBe(1); expect(log.filter(item => item === 'invoke bluebird task Promise.then').length).toBe(1); expect(Zone.current.name).toEqual('bluebird'); @@ -508,7 +528,8 @@ describe('bluebird promise', () => { }) .then((r: string) => { expect(r).toEqual('test1'); - expect(log.filter(item => item === 'schedule bluebird task Promise.then').length).toBe(1); + expect(log.filter(item => item === 'schedule bluebird task Promise.then').length) + .toBe(1); expect(log.filter(item => item === 'invoke bluebird task Promise.then').length).toBe(1); expect(Zone.current.name).toEqual('bluebird'); done(); @@ -530,7 +551,7 @@ describe('bluebird promise', () => { it('bluebird promise return method should be in zone', (done) => { zone.run(() => { - BluebirdPromise.resolve().return ('test1').then((r: string) => { + BluebirdPromise.resolve().return('test1').then((r: string) => { expect(r).toEqual('test1'); expect(log.filter(item => item === 'schedule bluebird task Promise.then').length).toBe(1); expect(log.filter(item => item === 'invoke bluebird task Promise.then').length).toBe(1); @@ -596,9 +617,7 @@ describe('bluebird promise', () => { }); it('bluebird should be able to run into different zone', (done: Function) => { - Zone.current.fork({ - name: 'zone_A' - }).run(() => { + Zone.current.fork({name: 'zone_A'}).run(() => { new BluebirdPromise((resolve: any, reject: any) => { expect(Zone.current.name).toEqual('zone_A'); resolve(1); @@ -606,10 +625,8 @@ describe('bluebird promise', () => { expect(Zone.current.name).toEqual('zone_A'); }); }); - - Zone.current.fork({ - name: 'zone_B' - }).run(() => { + + Zone.current.fork({name: 'zone_B'}).run(() => { new BluebirdPromise((resolve: any, reject: any) => { expect(Zone.current.name).toEqual('zone_B'); resolve(2); diff --git a/test/extra/cordova.spec.ts b/test/spec/extra/cordova.spec.ts similarity index 100% rename from test/extra/cordova.spec.ts rename to test/spec/extra/cordova.spec.ts diff --git a/test/jasmine-patch.spec.ts b/test/spec/jasmine/jasmine-patch.spec.ts similarity index 78% rename from test/jasmine-patch.spec.ts rename to test/spec/jasmine/jasmine-patch.spec.ts index c7fc9be7d..9f5e83cb0 100644 --- a/test/jasmine-patch.spec.ts +++ b/test/spec/jasmine/jasmine-patch.spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {ifEnvSupports} from './test-util'; +import {ifEnvSupports} from '../test-util'; function supportJasmineSpec() { return jasmine && (jasmine as any)['Spec']; @@ -22,6 +22,8 @@ ifEnvSupports(supportJasmineSpec, () => { describe('jasmine', () => { let throwOnAsync = false; + let beforeAllZone: Zone = null; + let beforeAllCalledCount = 0; let beforeEachZone: Zone = null; let itZone: Zone = null; const syncZone = Zone.current; @@ -31,7 +33,19 @@ ifEnvSupports(supportJasmineSpec, () => { throwOnAsync = true; } - beforeEach(() => beforeEachZone = Zone.current); + beforeAll(() => { + beforeAllZone = Zone.current; + beforeAllCalledCount ++; + }); + + afterAll(() => { + let zone = Zone.current; + expect(zone.name).toEqual('ProxyZone'); + expect(beforeAllZone.name).toEqual(zone.name); + expect(beforeAllCalledCount).toBe(1); + }); + + beforeEach(() => { beforeEachZone = Zone.current; }); it('should throw on async in describe', () => { expect(throwOnAsync).toBe(true); diff --git a/test/spec/mocha/jasmine-bridge.spec.ts b/test/spec/mocha/jasmine-bridge.spec.ts new file mode 100644 index 000000000..d93902f26 --- /dev/null +++ b/test/spec/mocha/jasmine-bridge.spec.ts @@ -0,0 +1,1280 @@ +/** + * @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 + */ +/** + Jasmine is a behavior-driven development framework for testing JavaScript code. It does not depend + on any other JavaScript frameworks. It does not require a DOM. And it has a clean, obvious syntax + so that you can easily write tests. This guide is running against Jasmine version FILLED IN AT RUNTIME. + */ +/** + ## Standalone Distribution + The [releases page](https://github.com/pivotal/jasmine/releases) has links to download the + standalone distribution, which contains everything you need to start running Jasmine. After + downloading a particular version and unzipping, opening `SpecRunner.html` will run the included + specs. You'll note that both the source files and their respective specs are linked in the + `` of the `SpecRunner.html`. To start using Jasmine, replace the source/spec files with your + own. + */ +/** + ## Suites: `describe` Your Tests + A test suite begins with a call to the global Jasmine function `describe` with two parameters: a + string and a function. The string is a name or title for a spec suite - usually what is being + tested. The function is a block of code that implements the suite. + ## Specs + Specs are defined by calling the global Jasmine function `it`, which, like `describe` takes a + string and a function. The string is the title of the spec and the function is the spec, or test. A + spec contains one or more expectations that test the state of the code. An expectation in Jasmine + is an assertion that is either true or false. A spec with all true expectations is a passing spec. + A spec with one or more false expectations is a failing spec. + */ +describe('A suite', function() { + it('contains spec with an expectation', function() { + expect(true).toBe(true); + }); +}); + +/** + ### It's Just Functions + Since `describe` and `it` blocks are functions, they can contain any executable code necessary to + implement the test. JavaScript scoping rules apply, so letiables declared in a `describe` are + available to any `it` block inside the suite. + */ +describe('A suite is just a function', function() { + let a; + + it('and so is a spec', function() { + a = true; + + expect(a).toBe(true); + }); +}); + +/** + ## Expectations + Expectations are built with the function `expect` which takes a value, called the actual. It is + chained with a Matcher function, which takes the expected value. + */ +describe('The \'toBe\' matcher compares with ===', function() { + /** + ### Matchers + Each matcher implements a boolean comparison between the actual value and the expected value. It + is responsible for reporting to Jasmine if the expectation is true or false. Jasmine will then + pass or fail the spec. + */ + + it('and has a positive case', function() { + expect(true).toBe(true); + }); + + /** + Any matcher can evaluate to a negative assertion by chaining the call to `expect` with a `not` + before calling the matcher. + */ + + it('and can have a negative case', function() { + expect(false).not.toBe(true); + }); +}); + +/** + ### Included Matchers + Jasmine has a rich set of matchers included. Each is used here - all expectations and specs pass. + There is also the ability to write [custom matchers](custom_matcher.html) for when a project's + domain calls for specific assertions that are not included below. + */ + +describe('Included matchers:', function() { + it('The \'toBe\' matcher compares with ===', function() { + let a = 12; + let b = a; + + expect(a).toBe(b); + expect(a).not.toBe(null); + }); + + describe('The \'toEqual\' matcher', function() { + it('works for simple literals and letiables', function() { + let a = 12; + expect(a).toEqual(12); + }); + + it('should work for objects', function() { + let foo = {a: 12, b: 34}; + let bar = {a: 12, b: 34}; + expect(foo).toEqual(bar); + }); + }); + + it('The \'toMatch\' matcher is for regular expressions', function() { + let message = 'foo bar baz'; + + expect(message).toMatch(/bar/); + expect(message).toMatch('bar'); + expect(message).not.toMatch(/quux/); + }); + + it('The \'toBeDefined\' matcher compares against `undefined`', function() { + let a: any = {foo: 'foo'}; + + expect(a.foo).toBeDefined(); + expect(a.bar).not.toBeDefined(); + }); + + it('The `toBeUndefined` matcher compares against `undefined`', function() { + let a: any = {foo: 'foo'}; + + expect(a.foo).not.toBeUndefined(); + expect(a.bar).toBeUndefined(); + }); + + it('The \'toBeNull\' matcher compares against null', function() { + let a = null; + let foo = 'foo'; + + expect(null).toBeNull(); + expect(a).toBeNull(); + expect(foo).not.toBeNull(); + }); + + it('The \'toBeTruthy\' matcher is for boolean casting testing', function() { + let a, foo = 'foo'; + + expect(foo).toBeTruthy(); + expect(a).not.toBeTruthy(); + }); + + it('The \'toBeFalsy\' matcher is for boolean casting testing', function() { + let a, foo = 'foo'; + + expect(a).toBeFalsy(); + expect(foo).not.toBeFalsy(); + }); + + describe('The \'toContain\' matcher', function() { + it('works for finding an item in an Array', function() { + let a = ['foo', 'bar', 'baz']; + + expect(a).toContain('bar'); + expect(a).not.toContain('quux'); + }); + + it('also works for finding a substring', function() { + let a = 'foo bar baz'; + + expect(a).toContain('bar'); + expect(a).not.toContain('quux'); + }); + }); + + it('The \'toBeLessThan\' matcher is for mathematical comparisons', function() { + let pi = 3.1415926, e = 2.78; + + expect(e).toBeLessThan(pi); + expect(pi).not.toBeLessThan(e); + }); + + it('The \'toBeGreaterThan\' matcher is for mathematical comparisons', function() { + let pi = 3.1415926, e = 2.78; + + expect(pi).toBeGreaterThan(e); + expect(e).not.toBeGreaterThan(pi); + }); + + it('The \'toBeCloseTo\' matcher is for precision math comparison', function() { + let pi = 3.1415926, e = 2.78; + + expect(pi).not.toBeCloseTo(e, 2); + expect(pi).toBeCloseTo(e, 0); + }); + + it('The \'toThrow\' matcher is for testing if a function throws an exception', function() { + let foo = function() { + return 1 + 2; + }; + let baz = function() { + throw 'what'; + }; + + expect(foo).not.toThrow(); + expect(baz).toThrow('what'); + }); + + it('The \'toThrowError\' matcher is for testing a specific thrown exception', function() { + let foo = function() { + throw new TypeError('foo bar baz'); + }; + + expect(foo).toThrowError('foo bar baz'); + expect(foo).toThrowError(/bar/); + expect(foo).toThrowError(TypeError); + expect(foo).toThrowError(TypeError, 'foo bar baz'); + }); +}); + +/** + ## Grouping Related Specs with `describe` + The `describe` function is for grouping related specs. The string parameter is for naming the + collection of specs, and will be concatenated with specs to make a spec's full name. This aids + in finding specs in a large suite. If you name them well, your specs read as full sentences in + traditional [BDD][bdd] style. [bdd]: http://en.wikipedia.org/wiki/Behavior-driven_development + */ +describe('A spec', function() { + it('is just a function, so it can contain any code', function() { + let foo = 0; + foo += 1; + + expect(foo).toEqual(1); + }); + + it('can have more than one expectation', function() { + let foo = 0; + foo += 1; + + expect(foo).toEqual(1); + expect(true).toEqual(true); + }); +}); + +/** + ### Setup and Teardown + To help a test suite DRY up any duplicated setup and teardown code, Jasmine provides the global + `beforeEach`, `afterEach`, `beforeAll`, and `afterAll` functions. + */ + +/** + As the name implies, the `beforeEach` function is called once before each spec in the `describe` + in which it is called, and the `afterEach` function is called once after each spec. + * + * + Here is the same set of specs written a little differently. The letiable under test is defined + at the top-level scope -- the `describe` block -- and initialization code is moved into a + `beforeEach` function. The `afterEach` function resets the letiable before continuing. + */ +describe('A spec using beforeEach and afterEach', function() { + let foo = 0; + + beforeEach(function() { + foo += 1; + }); + + afterEach(function() { + foo = 0; + }); + + it('is just a function, so it can contain any code', function() { + expect(foo).toEqual(1); + }); + + it('can have more than one expectation', function() { + expect(foo).toEqual(1); + expect(true).toEqual(true); + }); +}); + +/** + * The `beforeAll` function is called only once before all the specs in `describe` are run, and + the + * `afterAll` function is called after all specs finish. These functions can be used to speed up + * test suites with expensive setup and teardown. + * + * + * However, be careful using `beforeAll` and `afterAll`! Since they are not reset between specs, + it + * is easy to accidentally leak state between your specs so that they erroneously pass or fail. + */ +describe('A spec using beforeAll and afterAll', function() { + let foo: any; + + beforeAll(function() { + foo = 1; + }); + + afterAll(function() { + foo = 0; + }); + + it('sets the initial value of foo before specs run', function() { + expect(foo).toEqual(1); + foo += 1; + }); + + it('does not reset foo between specs', function() { + expect(foo).toEqual(2); + }); +}); + + +// /** +// ### The `this` keyword +// Another way to share letiables between a `beforeEach`, `it`, and `afterEach` is through the +// `this` keyword. Each spec's `beforeEach`/`it`/`afterEach` has the `this` as the same empty object +// that is set back to empty for the next spec's `beforeEach`/`it`/`afterEach`. +// */ +describe('A spec', function() { + beforeEach(function() { + this.foo = 0; + }); + + it('can use the `this` to share state', function() { + expect(this.foo).toEqual(0); + this.bar = 'test pollution?'; + }); + + it('prevents test pollution by having an empty `this` created for the next spec', function() { + expect(this.foo).toEqual(0); + // TODO: @JiaLiPassion, this has not been implemented. + // expect(this.bar).toBe(undefined); + }); +}); + +/** + ### Nesting `describe` Blocks + Calls to `describe` can be nested, with specs defined at any level. This allows a suite to be + composed as a tree of functions. Before a spec is executed, Jasmine walks down the tree + executing each `beforeEach` function in order. After the spec is executed, Jasmine walks through + the `afterEach` functions similarly. + */ +describe('A spec', function() { + let foo: any; + + beforeEach(function() { + foo = 0; + foo += 1; + }); + + afterEach(function() { + foo = 0; + }); + + it('is just a function, so it can contain any code', function() { + expect(foo).toEqual(1); + }); + + it('can have more than one expectation', function() { + expect(foo).toEqual(1); + expect(true).toEqual(true); + }); + + describe('nested inside a second describe', function() { + let bar: any; + + beforeEach(function() { + bar = 1; + }); + + it('can reference both scopes as needed', function() { + expect(foo).toEqual(bar); + }); + }); +}); + +/** + ## Disabling Suites + Suites can be disabled with the `xdescribe` function. These suites and any specs inside them are + skipped when run and thus their results will not appear in the results. + */ +xdescribe('A spec', function() { + let foo: any; + + beforeEach(function() { + foo = 0; + foo += 1; + }); + + it('is just a function, so it can contain any code', function() { + expect(foo).toEqual(1); + }); +}); + +/** + ## Pending Specs + Pending specs do not run, but their names will show up in the results as `pending`. + */ + +describe('Pending specs', function() { + /** + * Any spec declared with `xit` is marked as pending. + */ + xit('can be declared \'xit\'', function() { + expect(true).toBe(false); + }); + + /** + * Any spec declared without a function body will also be marked pending in results. + */ + + it('can be declared with \'it\' but without a function'); + + /** + * And if you call the function `pending` anywhere in the spec body, no matter the + expectations, + * the spec will be marked pending. + */ + it('can be declared by calling \'pending\' in the spec body', function() { + // TODO: @JiaLiPassion, not support pending after failed expect + // expect(true).toBe(false); + pending(); + }); +}); + +/** + ## Spies + Jasmine has test double functions called spies. A spy can stub any function and tracks calls to + it and all arguments. A spy only exists in the `describe` or `it` block in which it is defined, + and will be removed after each spec. There are special matchers for interacting with spies. + __This syntax has changed for Jasmine 2.0.__ + The `toHaveBeenCalled` matcher will return true if the spy was called. The + `toHaveBeenCalledWith` matcher will return true if the argument list matches any of the recorded + calls to the spy. + */ + +describe('A spy', function() { + let foo: any, bar: any = null; + + beforeEach(function() { + foo = { + setBar: function(value: any) { + bar = value; + } + }; + + spyOn(foo, 'setBar'); + + foo.setBar(123); + foo.setBar(456, 'another param'); + }); + + it('tracks that the spy was called', function() { + expect(foo.setBar).toHaveBeenCalled(); + }); + + it('tracks all the arguments of its calls', function() { + expect(foo.setBar).toHaveBeenCalledWith(123); + expect(foo.setBar).toHaveBeenCalledWith(456, 'another param'); + }); + + it('stops all execution on a function', function() { + expect(bar).toBeNull(); + }); +}); + +/** + ### Spies: `and.callThrough` + By chaining the spy with `and.callThrough`, the spy will still track all calls to it but in + addition it will delegate to the actual implementation. + */ +describe('A spy, when configured to call through', function() { + let foo: any, bar: any, fetchedBar: any; + + beforeEach(function() { + foo = { + setBar: function(value: any) { + bar = value; + }, + getBar: function() { + return bar; + } + }; + + spyOn(foo, 'getBar').and.callThrough(); + + foo.setBar(123); + fetchedBar = foo.getBar(); + }); + + it('tracks that the spy was called', function() { + expect(foo.getBar).toHaveBeenCalled(); + }); + + it('should not affect other functions', function() { + expect(bar).toEqual(123); + }); + + it('when called returns the requested value', function() { + expect(fetchedBar).toEqual(123); + }); +}); + +/** + ### Spies: `and.returnValue` + By chaining the spy with `and.returnValue`, all calls to the function will return a specific + value. + */ +describe('A spy, when configured to fake a return value', function() { + let foo: any, bar: any, fetchedBar: any; + + beforeEach(function() { + foo = { + setBar: function(value: any) { + bar = value; + }, + getBar: function() { + return bar; + } + }; + + spyOn(foo, 'getBar').and.returnValue(745); + + foo.setBar(123); + fetchedBar = foo.getBar(); + }); + + it('tracks that the spy was called', function() { + expect(foo.getBar).toHaveBeenCalled(); + }); + + it('should not affect other functions', function() { + expect(bar).toEqual(123); + }); + + it('when called returns the requested value', function() { + expect(fetchedBar).toEqual(745); + }); +}); + +/** + ### Spies: `and.callFake` + By chaining the spy with `and.callFake`, all calls to the spy will delegate to the supplied + function. + */ +describe('A spy, when configured with an alternate implementation', function() { + let foo: any, bar: any, fetchedBar: any; + + beforeEach(function() { + foo = { + setBar: function(value: any) { + bar = value; + }, + getBar: function() { + return bar; + } + }; + + /** + * If the function being spied on receives arguments that the fake needs, you can get those + as + * well + */ + spyOn(foo, 'getBar').and.callFake(function(arguments: any, can: any, be: any, received: any) { + return 1001; + }); + + foo.setBar(123); + fetchedBar = foo.getBar(); + }); + + it('tracks that the spy was called', function() { + expect(foo.getBar).toHaveBeenCalled(); + }); + + it('should not affect other functions', function() { + expect(bar).toEqual(123); + }); + + it('when called returns the requested value', function() { + expect(fetchedBar).toEqual(1001); + }); +}); + + +/** + ### Spies: `and.throwError` + By chaining the spy with `and.throwError`, all calls to the spy will `throw` the specified value + as an error. + */ +describe('A spy, when configured to throw an error', function() { + let foo: any, bar: any; + + beforeEach(function() { + foo = { + setBar: function(value: any) { + bar = value; + } + }; + + spyOn(foo, 'setBar').and.throwError('quux'); + }); + + it('throws the value', function() { + expect(function() { + foo.setBar(123); + }).toThrowError('quux'); + }); +}); + +/** + ### Spies: `and.stub` + When a calling strategy is used for a spy, the original stubbing behavior can be returned at any + time with `and.stub`. + */ +describe('A spy', function() { + let foo: any, bar: any = null; + + beforeEach(function() { + foo = { + setBar: function(value: any) { + bar = value; + } + }; + + spyOn(foo, 'setBar').and.callThrough(); + }); + + it('can call through and then stub in the same spec', function() { + foo.setBar(123); + expect(bar).toEqual(123); + + foo.setBar.and.stub(); + bar = null; + + foo.setBar(123); + expect(bar).toBe(null); + }); +}); + +/** + ### Other tracking properties + + Every call to a spy is tracked and exposed on the `calls` property. + */ +describe('A spy', function() { + let foo: any, bar: any = null; + + beforeEach(function() { + foo = { + setBar: function(value: any) { + bar = value; + } + }; + + spyOn(foo, 'setBar'); + }); + + /** + * `.calls.any()`: returns `false` if the spy has not been called at all, and then `true` once + at + * least one call happens. + */ + it('tracks if it was called at all', function() { + expect(foo.setBar.calls.any()).toEqual(false); + + foo.setBar(); + + expect(foo.setBar.calls.any()).toEqual(true); + }); + + /** + * `.calls.count()`: returns the number of times the spy was called + */ + it('tracks the number of times it was called', function() { + expect(foo.setBar.calls.count()).toEqual(0); + + foo.setBar(); + foo.setBar(); + + expect(foo.setBar.calls.count()).toEqual(2); + }); + + /** + * `.calls.argsFor(index)`: returns the arguments passed to call number `index` + */ + it('tracks the arguments of each call', function() { + foo.setBar(123); + foo.setBar(456, 'baz'); + + expect(foo.setBar.calls.argsFor(0)).toEqual([123]); + expect(foo.setBar.calls.argsFor(1)).toEqual([456, 'baz']); + }); + + /** + * `.calls.allArgs()`: returns the arguments to all calls + */ + it('tracks the arguments of all calls', function() { + foo.setBar(123); + foo.setBar(456, 'baz'); + + expect(foo.setBar.calls.allArgs()).toEqual([[123], [456, 'baz']]); + }); + + /** + * `.calls.all()`: returns the context (the `this`) and arguments passed all calls + */ + it('can provide the context and arguments to all calls', function() { + foo.setBar(123); + const {invocationOrder, ...firstWithoutOrder} = foo.setBar.calls.all()[0]; + expect([firstWithoutOrder]).toEqual([{object: foo, args: [123], returnValue: undefined}]); + }); + + /** + * `.calls.mostRecent()`: returns the context (the `this`) and arguments for the most recent + call + */ + it('has a shortcut to the most recent call', function() { + foo.setBar(123); + foo.setBar(456, 'baz'); + + const {invocationOrder, ...recentWithoutOrder} = foo.setBar.calls.mostRecent(); + expect(recentWithoutOrder).toEqual({object: foo, args: [456, 'baz'], returnValue: undefined}); + }); + + /** + * `.calls.first()`: returns the context (the `this`) and arguments for the first call + */ + it('has a shortcut to the first call', function() { + foo.setBar(123); + foo.setBar(456, 'baz'); + + const {invocationOrder, ...firstWithoutOrder} = foo.setBar.calls.first(); + expect(firstWithoutOrder).toEqual({object: foo, args: [123], returnValue: undefined}); + }); + + /** + * When inspecting the return from `all()`, `mostRecent()` and `first()`, the `object` property + is + * set to the value of `this` when the spy was called. + */ + it('tracks the context', function() { + let spy = jasmine.createSpy('spy'); + let baz = {fn: spy}; + let quux = {fn: spy}; + baz.fn(123); + quux.fn(456); + + expect(spy.calls.first().object).toBe(baz); + expect(spy.calls.mostRecent().object).toBe(quux); + }); + + /** + * `.calls.reset()`: clears all tracking for a spy + */ + it('can be reset', function() { + foo.setBar(123); + foo.setBar(456, 'baz'); + + expect(foo.setBar.calls.any()).toBe(true); + + foo.setBar.calls.reset(); + + expect(foo.setBar.calls.any()).toBe(false); + }); +}); + + +/** + ### Spies: `createSpy` + When there is not a function to spy on, `jasmine.createSpy` can create a "bare" spy. This spy + acts as any other spy - tracking calls, arguments, etc. But there is no implementation behind + it. Spies are JavaScript objects and can be used as such. + */ +describe('A spy, when created manually', function() { + let whatAmI: any; + + beforeEach(function() { + whatAmI = jasmine.createSpy('whatAmI'); + + whatAmI('I', 'am', 'a', 'spy'); + }); + + it('is named, which helps in error reporting', function() { + expect(whatAmI.and.identity).toEqual('whatAmI'); + }); + + it('tracks that the spy was called', function() { + expect(whatAmI).toHaveBeenCalled(); + }); + + it('tracks its number of calls', function() { + expect(whatAmI.calls.count()).toEqual(1); + }); + + it('tracks all the arguments of its calls', function() { + expect(whatAmI).toHaveBeenCalledWith('I', 'am', 'a', 'spy'); + }); + + it('allows access to the most recent call', function() { + expect(whatAmI.calls.mostRecent().args[0]).toEqual('I'); + }); +}); + +/** + ### Spies: `createSpyObj` + In order to create a mock with multiple spies, use `jasmine.createSpyObj` and pass an array of + strings. It returns an object that has a property for each string that is a spy. + */ +describe('Multiple spies, when created manually', function() { + let tape: any; + + beforeEach(function() { + tape = jasmine.createSpyObj('tape', ['play', 'pause', 'stop', 'rewind']); + + tape.play(); + tape.pause(); + tape.rewind(0); + }); + + it('creates spies for each requested function', function() { + expect(tape.play).toBeDefined(); + expect(tape.pause).toBeDefined(); + expect(tape.stop).toBeDefined(); + expect(tape.rewind).toBeDefined(); + }); + + it('tracks that the spies were called', function() { + expect(tape.play).toHaveBeenCalled(); + expect(tape.pause).toHaveBeenCalled(); + expect(tape.rewind).toHaveBeenCalled(); + expect(tape.stop).not.toHaveBeenCalled(); + }); + + it('tracks all the arguments of its calls', function() { + expect(tape.rewind).toHaveBeenCalledWith(0); + }); +}); + +/** + ## Matching Anything with `jasmine.any` + `jasmine.any` takes a constructor or "class" name as an expected value. It returns `true` if the + constructor matches the constructor of the actual value. + */ + +describe('jasmine.any', function() { + it('matches any value', function() { + expect({}).toEqual(jasmine.any(Object)); + expect(12).toEqual(jasmine.any(Number)); + }); + + describe('when used with a spy', function() { + it('is useful for comparing arguments', function() { + let foo = jasmine.createSpy('foo'); + foo(12, function() { + return true; + }); + + expect(foo).toHaveBeenCalledWith(jasmine.any(Number), jasmine.any(Function)); + }); + }); +}); + +/** + ## Partial Matching with `jasmine.objectContaining` + `jasmine.objectContaining` is for those times when an expectation only cares about certain + key/value pairs in the actual. + */ + +describe('jasmine.objectContaining', function() { + let foo: any; + + beforeEach(function() { + foo = {a: 1, b: 2, bar: 'baz'}; + }); + + it('matches objects with the expect key/value pairs', function() { + expect(foo).toEqual(jasmine.objectContaining({bar: 'baz'})); + expect(foo).not.toEqual(jasmine.objectContaining({c: 37})); + }); + + describe('when used with a spy', function() { + it('is useful for comparing arguments', function() { + let callback = jasmine.createSpy('callback'); + + callback({bar: 'baz'}); + + expect(callback).toHaveBeenCalledWith(jasmine.objectContaining({bar: 'baz'})); + expect(callback).not.toHaveBeenCalledWith(jasmine.objectContaining({c: 37})); + }); + }); +}); + + +/** + ## Jasmine Clock + __This syntax has changed for Jasmine 2.0.__ + The Jasmine Clock is available for testing time dependent code. + */ +describe('Manually ticking the Jasmine Clock', function() { + let timerCallback: any; + + /** + It is installed with a call to `jasmine.clock().install` in a spec or suite that needs to + manipulate time. + */ + beforeEach(function() { + timerCallback = jasmine.createSpy('timerCallback'); + jasmine.clock().install(); + }); + + /** + Be sure to uninstall the clock after you are done to restore the original functions. + */ + afterEach(function() { + jasmine.clock().uninstall(); + }); + + /** + ### Mocking the JavaScript Timeout Functions + You can make `setTimeout` or `setInterval` synchronous executing the registered functions only + once the clock is ticked forward in time. + + To execute registered functions, move time forward via the `jasmine.clock().tick` function, + which takes a number of milliseconds. + */ + it('causes a timeout to be called synchronously', function() { + setTimeout(function() { + timerCallback(); + }, 100); + + expect(timerCallback).not.toHaveBeenCalled(); + + jasmine.clock().tick(101); + + expect(timerCallback).toHaveBeenCalled(); + }); + + it('causes an interval to be called synchronously', function() { + setInterval(function() { + timerCallback(); + }, 100); + + expect(timerCallback).not.toHaveBeenCalled(); + + jasmine.clock().tick(101); + expect(timerCallback.calls.count()).toEqual(1); + + jasmine.clock().tick(50); + expect(timerCallback.calls.count()).toEqual(1); + + jasmine.clock().tick(50); + expect(timerCallback.calls.count()).toEqual(2); + }); + + /** + ### Mocking the Date + The Jasmine Clock can also be used to mock the current date. + */ + describe('Mocking the Date object', function() { + it('mocks the Date object and sets it to a given time', function() { + let baseTime = new Date(2013, 9, 23); + // If you do not provide a base time to `mockDate` it will use the current date. + jasmine.clock().mockDate(baseTime); + + jasmine.clock().tick(50); + expect(new Date().getTime()).toEqual(baseTime.getTime() + 50); + }); + }); +}); + + +/** + ## Asynchronous Support + __This syntax has changed for Jasmine 2.0.__ + Jasmine also has support for running specs that require testing asynchronous operations. + */ +describe('Asynchronous specs', function() { + let value: any; + /** + Calls to `beforeAll`, `afterAll`, `beforeEach`, `afterEach`, and `it` can take an optional + single argument that should be called when the async work is complete. + */ + beforeEach(function(done) { + setTimeout(function() { + value = 0; + done(); + }, 1); + }); + + /** + This spec will not start until the `done` function is called in the call to `beforeEach` + above. And this spec will not complete until its `done` is called. + */ + + it('should support async execution of test preparation and expectations', function(done) { + value++; + expect(value).toBeGreaterThan(0); + done(); + }); + + /** + By default jasmine will wait for 5 seconds for an asynchronous spec to finish before causing a + timeout failure. If the timeout expires before `done` is called, the current spec will be + marked as failed and suite execution will continue as if `done` was called. + + If specific specs should fail faster or need more time this can be adjusted by setting + `jasmine.DEFAULT_TIMEOUT_INTERVAL` around them. + + If the entire suite should have a different timeout, `jasmine.DEFAULT_TIMEOUT_INTERVAL` can be + set globally, outside of any given `describe`. + */ + describe('long asynchronous specs', function() { + let originalTimeout: any; + beforeEach(function() { + originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL; + jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000; + }); + + it('takes a long time', function(done) { + setTimeout(function() { + done(); + }, 9000); + }); + + afterEach(function() { + jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout; + }); + }); +}); + +/** + Focusing specs will make it so that they are the only specs that run. + */ + +// xdescribe('Focused specs', function() { +// /** +// * Any spec declared with `fit` is focused. +// */ +// fit('is focused and will run', function() { +// expect(true).toBeTruthy(); +// }); + +// it('is not focused and will not run', function() { +// expect(true).toBeFalsy(); +// }); + +// /** +// * You can focus on a `describe` with `fdescribe` +// * +// */ +// fdescribe('focused describe', function() { +// it('will run', function() { +// expect(true).toBeTruthy(); +// }); + +// it('will also run', function() { +// expect(true).toBeTruthy(); +// }); +// }); + +// /** +// * If you nest focused and unfocused specs inside `fdescribes`, only focused specs run. +// * +// */ +// fdescribe('another focused describe', function() { +// fit('is focused and will run', function() { +// expect(true).toBeTruthy(); +// }); + +// it('is not focused and will not run', function() { +// expect(true).toBeFalsy(); +// }); +// }); +// }); +/** + * ## Custom Equality Testers + */ +describe('custom equality', function() { + /** + * You can customize how jasmine determines if two objects are equal by defining your own custom + * equality testers. A custom equality tester is a function that takes two arguments. + */ + let myCustomEquality = function(first: any, second: any) { + /** + * If the custom equality tester knows how to compare the two items, it should return either + * true or false + */ + + if (first === 'abc' && typeof second == 'string') { + return first[0] == second[1]; + } + + /** + * Otherwise, it should return undefined, to tell jasmine's equality tester that it can't + * compare the items + */ + }; + + /** + * Then you register your tester in a `beforeEach` so jasmine knows about it. + */ + beforeEach(function() { + jasmine.addCustomEqualityTester(myCustomEquality); + }); + + /** + * Then when you do comparisons in a spec, custom equality testers will be checked first before + * the default equality logic. + */ + it('should be custom equal', function() { + expect('abc').toEqual('aaa'); + }); + + /** + * If your custom tester returns false, no other equality checking will be done. + */ + it('should be custom not equal', function() { + expect('abc').not.toEqual('abc'); + }); +}); +/** + * + * Often a project will want to encapsulate custom matching code for use across multiple specs. Here + * is how to create a Jasmine-compatible custom matcher. + * + * A custom matcher at its root is a comparison function that takes an `actual` value and `expected` + * value. This factory is passed to Jasmine, ideally in a call to `beforeEach` and will be in scope + * and available for all of the specs inside a given call to `describe`. Custom matchers are torn + * down between specs. The name of the factory will be the name of the matcher exposed on the return + * value of the call to `expect`. + * + */ + +/** + * This object has a custom matcher named "toBeGoofy". + */ +let customMatchers = { + + /** + * ## Matcher Factories + * + * Custom matcher factories are passed two parameters: `util`, which has a set of utility + * functions for matchers to use (see: [`matchersUtil.js`][mu.js] for the current list) and + * `customEqualityTesters` which needs to be passed in if `util.equals` is ever called. These + * parameters are available for use when the matcher is called. + * + * [mu.js]: https://github.com/pivotal/jasmine/blob/master/src/core/matchers/matchersUtil.js + */ + toBeGoofy: function(util: any, customEqualityTesters: any) { + /** + * The factory method should return an object with a `compare` function that will be called to + * check the expectation. + */ + return { + /** + * ## A Function to `compare` + * + * The compare function receives the value passed to `expect()` as the first argument - the + * actual - and the value (if any) passed to the matcher itself as second argument. + */ + compare: function(actual: any, expected: any) { + /** + * `toBeGoofy` takes an optional `expected` argument, so define it here if not passed in. + */ + if (expected === undefined) { + expected = ''; + } + + /** + * ### Result + * + * The `compare` function must return a result object with a `pass` property that is a + * boolean result of the matcher. The `pass` property tells the expectation whether the + * matcher was successful (`true`) or unsuccessful (`false`). If the expectation is + * called/chained with `.not`, the expectation will negate this to determine whether the + * expectation is met. + */ + let result: any = {}; + + /** + * `toBeGoofy` tests for equality of the actual's `hyuk` property to see if it matches the + * expectation. + */ + result.pass = util.equals(actual.hyuk, 'gawrsh' + expected, customEqualityTesters); + + /** + * ### Failure Messages + * + * If left `undefined`, the expectation will attempt to craft a failure message for the + * matcher. However, if the return value has a `message` property it will be used for a + * failed expectation. + */ + if (result.pass) { + /** + * The matcher succeeded, so the custom failure message should be present in the case of a + * negative expectation - when the expectation is used with `.not`. + */ + result.message = 'Expected ' + actual + ' not to be quite so goofy'; + } else { + /** + * The matcher failed, so the custom failure message should be present in the case of a + * positive expectation + */ + result.message = 'Expected ' + actual + ' to be goofy, but it was not very goofy'; + } + + /** + * Return the result of the comparison. + */ + return result; + } + }; + } +}; + +/** + * ### Custom negative comparators + * + * If you need more control over the negative comparison (the `not` case) than the simple boolean + * inversion above, you can also have your matcher factory include another key, `negativeCompare` + * alongside `compare`, for which the value is a function to invoke when `.not` is used. This + * function/key is optional. + */ + +/** + * ## Registration and Usage + */ +describe('Custom matcher: \'toBeGoofy\'', function() { + /** + * Register the custom matchers with Jasmine. All properties on the object passed in will be + * available as custom matchers (e.g., in this case `toBeGoofy`). + */ + beforeEach(function() { + jasmine.addMatchers(customMatchers); + }); + + /** + * Once a custom matcher is registered with Jasmine, it is available on any expectation. + */ + it('is available on an expectation', function() { + (expect({hyuk: 'gawrsh'}) as any).toBeGoofy(); + }); + + it('can take an \'expected\' parameter', function() { + (expect({hyuk: 'gawrsh is fun'}) as any).toBeGoofy(' is fun'); + }); + + it('can be negated', function() { + (expect({hyuk: 'this is fun'}) as any).not.toBeGoofy(); + }); +}); + +describe('failed', () => { + it('should catch failed', () => { + try { + fail('error'); + } catch (error) { + expect(error).toEqual('error'); + } + }); +}); + +describe('timeout', () => { + beforeEach(() => { + setTimeout(() => { + expect(true).toBe(true); + }, 2500); + }, 3000); + + it('beforeEach timeout', () => { + expect(true).toBe(true); + }); + + it('test timeout', (done) => { + setTimeout(() => { + expect(true).toBe(true); + done(); + }, 3100); + }, 3500); +}); diff --git a/test/spec/mocha/jest-bridge.spec.ts b/test/spec/mocha/jest-bridge.spec.ts new file mode 100644 index 000000000..71416d7b2 --- /dev/null +++ b/test/spec/mocha/jest-bridge.spec.ts @@ -0,0 +1,503 @@ +/** + * @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 + */ +const _jestGlobal: any = typeof window !== 'undefined' ? window : global; +_jestGlobal['__zone_symbol__fakeAsyncCheckRemaining'] = false; +describe('extend', () => { + expect.extend({ + toBeDivisibleBy(received: any, argument: any) { + const pass = received % argument == 0; + if (pass) { + return { + message: () => `expected ${received} not to be divisible by ${argument}`, + pass: true, + }; + } else { + return { + message: () => `expected ${received} to be divisible by ${argument}`, + pass: false, + }; + } + }, + }); + + test('even and odd numbers', () => { + (expect(100) as any).toBeDivisibleBy(2); + (expect(101).not as any).toBeDivisibleBy(2); + }); +}); + +describe('expect', () => { + test('anything test', () => { + expect('test').toEqual(expect.anything()); + }); + + test('any(constructor)', () => { + expect('test').toEqual(expect.any(String)); + }); + + describe('arrayContaining', () => { + const expected = ['Alice', 'Bob']; + it('matches even if received contains additional elements', () => { + expect(['Alice', 'Bob', 'Eve']).toEqual(expect.arrayContaining(expected)); + }); + it('does not match if received does not contain expected elements', () => { + expect(['Bob', 'Eve']).not.toEqual(expect.arrayContaining(expected)); + }); + }); + + describe('Beware of a misunderstanding! A sequence of dice rolls', () => { + const expected = [1, 2, 3, 4, 5, 6]; + it('matches even with an unexpected number 7', () => { + expect([4, 1, 6, 7, 3, 5, 2, 5, 4, 6]).toEqual(expect.arrayContaining(expected)); + }); + it('does not match without an expected number 2', () => { + expect([ + 4, 1, 6, 7, 3, 5, 7, 5, 4, 6 + ]).not.toEqual(expect.arrayContaining(expected)); + }); + }); + + describe('assertions', () => { + test('calls both callbacks', () => { + expect.assertions(2); + function callback1(data: any) { + expect(data).toBeTruthy(); + } + function callback2(data: any) { + expect(data).toBeTruthy(); + } + callback1('test'); + callback2('test'); + }); + + test('calls one callback', () => { + expect.hasAssertions(); + function callback1(data: any) { + expect(data).toBeTruthy(); + } + callback1('test'); + }); + }); + + describe('objectContaining', () => { + test('onPress should object containing with the right thing', () => { + const onPress = {x: 100, y: 200, z: 300}; + expect(onPress).toEqual(expect.objectContaining({ + x: expect.any(Number), + y: expect.any(Number), + }), ); + }); + }); + + describe('stringContaining', () => { + test('testStr should contain right string', () => { + expect('test1').toEqual(expect.stringContaining('test')); + }); + }); + + describe('stringMatching in arrayContaining', () => { + const expected = [ + expect.stringMatching(/^Alic/), + expect.stringMatching(/^[BR]ob/), + ]; + it('matches even if received contains additional elements', () => { + expect(['Alicia', 'Roberto', 'Evelina']).toEqual(expect.arrayContaining(expected)); + }); + it('does not match if received does not contain expected elements', () => { + expect(['Roberto', 'Evelina']).not.toEqual(expect.arrayContaining(expected)); + }); + }); + + describe('Promise', () => { + test('resolves to lemon', () => { + // make sure to add a return statement + return expect(Promise.resolve('lemon')).resolves.toBe('lemon'); + }); + + test('resolves to lemon with await', async() => { + await expect(Promise.resolve('lemon')).resolves.toBe('lemon'); + return await expect(Promise.resolve('lemon')).resolves.not.toBe('octopus'); + }); + + test('rejects to octopus', () => { + // make sure to add a return statement + return expect(Promise.reject(new Error('octopus'))).rejects.toThrow('octopus'); + }); + + test('rejects to octopus', async() => { + return await expect(Promise.reject(new Error('octopus'))).rejects.toThrow('octopus'); + }); + }); + + test('instanceof', () => { + class A {} + expect(new A()).toBeInstanceOf(A); + expect(() => {}).toBeInstanceOf(Function); + }); + + test('toContainEqual', () => { + const myBeverage = {delicious: true, sour: false}; + expect([{delicious: true, sour: false}, {delicious: false, sour: true}]) + .toContainEqual(myBeverage); + }); + + test('toHaveLength', () => { + expect([1, 2, 3]).toHaveLength(3); + expect('abc').toHaveLength(3); + expect('').not.toHaveLength(5); + }); + + describe('toMatchObject', () => { + const houseForSale = { + bath: true, + bedrooms: 4, + kitchen: { + amenities: ['oven', 'stove', 'washer'], + area: 20, + wallColor: 'white', + }, + }; + const desiredHouse = { + bath: true, + kitchen: { + amenities: ['oven', 'stove', 'washer'], + wallColor: expect.stringMatching(/white|yellow/), + }, + }; + + test('the house has my desired features', () => { + expect(houseForSale).toMatchObject(desiredHouse); + }); + }); + + describe('toMatchObject applied to arrays arrays', () => { + test('the number of elements must match exactly', () => { + expect([{foo: 'bar'}, {baz: 1}]).toMatchObject([{foo: 'bar'}, {baz: 1}]); + }); + + // .arrayContaining "matches a received array which contains elements that + // are *not* in the expected array" + test('.toMatchObject does not allow extra elements', () => { + expect([{foo: 'bar'}, {baz: 1}]).toMatchObject([{foo: 'bar'}]); + }); + + test('.toMatchObject is called for each elements, so extra object properties are okay', () => { + expect([{foo: 'bar'}, {baz: 1, extra: 'quux'}]).toMatchObject([ + {foo: 'bar'}, + {baz: 1}, + ]); + }); + }); + + + describe('toHaveProperty', () => { + // Object containing house features to be tested + const houseForSale = { + bath: true, + bedrooms: 4, + kitchen: { + amenities: ['oven', 'stove', 'washer'], + area: 20, + wallColor: 'white', + }, + }; + + test('this house has my desired features', () => { + // Simple Referencing + expect(houseForSale).toHaveProperty('bath'); + expect(houseForSale).toHaveProperty('bedrooms', 4); + + expect(houseForSale).not.toHaveProperty('pool'); + + // Deep referencing using dot notation + expect(houseForSale).toHaveProperty('kitchen.area', 20); + expect(houseForSale).toHaveProperty('kitchen.amenities', [ + 'oven', + 'stove', + 'washer', + ]); + + expect(houseForSale).not.toHaveProperty('kitchen.open'); + + // Deep referencing using an array containing the keyPath + expect(houseForSale).toHaveProperty(['kitchen', 'area'], 20); + expect(houseForSale).toHaveProperty(['kitchen', 'amenities'], [ + 'oven', 'stove', 'washer' + ]); + expect(houseForSale).toHaveProperty(['kitchen', 'amenities', 0], 'oven'); + + expect(houseForSale).not.toHaveProperty(['kitchen', 'open']); + }); + }); + + describe('jest.fn', () => { + test('mock.calls', () => { + const mockFn = jest.fn(); + mockFn('arg1', 'arg2'); + mockFn('arg3', 'arg4'); + expect(mockFn.mock.calls).toEqual([['arg1', 'arg2'], ['arg3', 'arg4']]); + }); + + test('mock.instances', () => { + const mockFn = jest.fn(); + + const a = new mockFn(); + const b = new mockFn(); + + expect(mockFn.mock.instances[0]).toBe(a); // true + expect(mockFn.mock.instances[1]).toBe(b); // true + mockFn.mockClear(); + expect(mockFn.mock.instances.length).toBe(0); + }); + + test('mock.mockImplementation', () => { + const mockFn = jest.fn().mockImplementation((scalar: any) => 42 + scalar); + + const a = mockFn(0); + const b = mockFn(1); + + a === 42; // true + b === 43; // true + + expect(mockFn.mock.calls[0][0]).toBe(0); // true + expect(mockFn.mock.calls[1][0]).toBe(1); // true + }); + + test('mock.mockImplementationOnce', () => { + let myMockFn = jest.fn() + .mockImplementationOnce((cb: any) => cb(null, true)) + .mockImplementationOnce((cb: any) => cb(null, false)); + + const logs: any[] = []; + myMockFn((err: any, val: any) => logs.push(val)); // true + myMockFn((err: any, val: any) => logs.push(val)); // false + expect(logs).toEqual([true, false]); + + myMockFn = jest.fn(() => 'default') + .mockImplementationOnce(() => 'first call') + .mockImplementationOnce(() => 'second call'); + + // 'first call', 'second call', 'default', 'default' + logs.length = 0; + logs.push(myMockFn(), myMockFn(), myMockFn(), myMockFn()); + expect(logs).toEqual(['first call', 'second call', 'default', 'default']); + }); + + test('toHaveBeenCalled', () => { + const mockFn = jest.fn(); + mockFn(); + expect(mockFn).toHaveBeenCalled(); + mockFn(1); + expect(mockFn).toHaveBeenCalledWith(1); + }); + + test('mockReturnThis', () => { + const mockFn = jest.fn(); + mockFn.mockReturnThis(); + expect(mockFn()).toBeUndefined(); + }); + + test('mockReturnValue', () => { + const mockFn = jest.fn(); + mockFn.mockReturnValue(30); + expect(mockFn()).toBe(30); + }); + + test('mockReturnValueOnce', () => { + const myMockFn = jest.fn() + .mockReturnValue('default') + .mockReturnValueOnce('first call') + .mockReturnValueOnce('second call'); + + // 'first call', 'second call', 'default', 'default' + const logs: string[] = []; + logs.push(myMockFn(), myMockFn(), myMockFn(), myMockFn()); + expect(logs).toEqual(['first call', 'second call', 'default', 'default']); + }); + + test('mockResolvedValue', async() => { + const asyncMock = jest.fn().mockResolvedValue(43); + + const result = await asyncMock(); // 43 + expect(result).toBe(43); + }); + + test('mockResolvedValueOnce', async() => { + const asyncMock = jest.fn() + .mockResolvedValue('default') + .mockResolvedValueOnce('first call') + .mockResolvedValueOnce('second call'); + + const logs: string[] = []; + logs.push(await asyncMock()); // first call + logs.push(await asyncMock()); // second call + logs.push(await asyncMock()); // default + logs.push(await asyncMock()); // default + expect(logs).toEqual(['first call', 'second call', 'default', 'default']); + }); + + test('mockRejectedValue', async() => { + const asyncMock = jest.fn().mockRejectedValue(new Error('Async error')); + + try { + await asyncMock(); // throws "Async error" + } catch (err) { + expect(err.message).toEqual('Async error'); + } + }); + + test('mockRejectedValueOnce', async() => { + const asyncMock = jest.fn() + .mockResolvedValueOnce('first call') + .mockRejectedValueOnce(new Error('Async error')); + + try { + const first = await asyncMock(); + expect(first).toEqual('first call'); + await asyncMock(); // throws "Async error" + } catch (err) { + expect(err.message).toEqual('Async error'); + } + }); + }); +}); + +describe('clock', () => { + test('clearAllTimeout', fakeAsyncTest(() => { + let timeoutCalledCount = 0; + let intervalCalledCount = 0; + setTimeout(() => { + timeoutCalledCount++; + }, 10); + setInterval(() => { + intervalCalledCount++; + }, 30); + clearAllMacrotasks(); + jest.advanceTimersByTime(30); + expect(timeoutCalledCount).toBe(0); + expect(intervalCalledCount).toBe(0); + })); + + test('runAllTimers', fakeAsyncTest(() => { + let timeoutCalledCount = 0; + let intervalCalledCount = 0; + setTimeout(() => { + timeoutCalledCount++; + }, 10); + setTimeout(() => { + timeoutCalledCount++; + }, 10); + const intervalId = setInterval(() => { + intervalCalledCount++; + }, 30); + expect(() => { + jest.runAllTimers(); + }).toThrow(); + clearInterval(intervalId); + jest.runAllTimers(); + expect(timeoutCalledCount).toBe(2); + expect(intervalCalledCount).toBe(0); + })); + + test('runOnlyPendingTimers', fakeAsyncTest(() => { + let timeoutCalledCount = 0; + let intervalCalledCount = 0; + setTimeout(() => { + timeoutCalledCount++; + }, 10); + setTimeout(() => { + timeoutCalledCount++; + }, 10); + const intervalId = setInterval(() => { + intervalCalledCount++; + }, 30); + jest.runOnlyPendingTimers(); + expect(timeoutCalledCount).toBe(2); + expect(intervalCalledCount).toBe(1); + })); + + test('advanceTimersByTime', fakeAsyncTest(() => { + let timeoutCalledCount = 0; + let intervalCalledCount = 0; + setTimeout(() => { + timeoutCalledCount++; + }, 10); + const intervalId = setInterval(() => { + intervalCalledCount++; + }, 30); + jest.advanceTimersByTime(30); + expect(timeoutCalledCount).toBe(1); + expect(intervalCalledCount).toBe(1); + })); + + test('runAllTicks', fakeAsyncTest(() => { + let thenCalledCount = 0; + Promise.resolve().then(() => { + thenCalledCount++; + }); + expect(thenCalledCount).toBe(0); + jest.runAllTicks(); + expect(thenCalledCount).toBe(1); + })); + + /*test('useFakeTimers', () => { + jest.useFakeTimers(); + let thenCalledCount = 0; + let timeoutCalledCount = 0; + let intervalCalledCount = 0; + Promise.resolve().then(() => { + thenCalledCount++; + }); + expect(thenCalledCount).toBe(0); + setTimeout(() => { + timeoutCalledCount++; + }, 10); + const intervalId = setInterval(() => { + intervalCalledCount++; + }, 30); + jest.advanceTimersByTime(30); + expect(timeoutCalledCount).toBe(1); + expect(intervalCalledCount).toBe(1); + expect(thenCalledCount).toBe(1); + jest.useRealTimers(); + });*/ + + describe('FakeTimer', () => { + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + test('auto jump into fakeAsync', () => { + const fake = Zone.current.get('FakeAsyncTestZoneSpec'); + let thenCalledCount = 0; + let timeoutCalledCount = 0; + let intervalCalledCount = 0; + Promise.resolve().then(() => { + thenCalledCount++; + }); + expect(thenCalledCount).toBe(0); + setTimeout(() => { + timeoutCalledCount++; + }, 10); + const intervalId = setInterval(() => { + intervalCalledCount++; + }, 30); + // jest.advanceTimersByTime(30); + tick(30, (time: number) => { + console.log('fake time tick', time); + }); + expect(timeoutCalledCount).toBe(1); + expect(intervalCalledCount).toBe(1); + expect(thenCalledCount).toBe(1); + }); + }); +}); \ No newline at end of file diff --git a/test/spec/mocha/mocha-browser-karma.ts b/test/spec/mocha/mocha-browser-karma.ts new file mode 100644 index 000000000..74f802705 --- /dev/null +++ b/test/spec/mocha/mocha-browser-karma.ts @@ -0,0 +1,9 @@ +/** + * @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 + */ +(window as any)['__zone_symbol__test_entry_point'] = + '/base/build/test/mocha/mocha-browser-test-entry-point'; \ No newline at end of file diff --git a/test/spec/mocha/mocha-browser-test-entry-point.ts b/test/spec/mocha/mocha-browser-test-entry-point.ts new file mode 100644 index 000000000..60a489419 --- /dev/null +++ b/test/spec/mocha/mocha-browser-test-entry-point.ts @@ -0,0 +1,10 @@ +/** + * @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 '../../lib/browser/browser'; +import '../../lib/testing/zone-testing'; +import './jasmine-bridge.spec'; \ No newline at end of file diff --git a/test/spec/mocha/mocha-node-test-entry-point.ts b/test/spec/mocha/mocha-node-test-entry-point.ts new file mode 100644 index 000000000..c21b31f4c --- /dev/null +++ b/test/spec/mocha/mocha-node-test-entry-point.ts @@ -0,0 +1,14 @@ +/** + * @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 + */ +(global as any)[('__zone_symbol__fakeAsyncPatchLock')] = true; +import '../../../lib/node/rollup-main'; +import '../../../lib/mocha/mocha-node-checker'; +import '../../../lib/testing/zone-testing'; +import './jasmine-bridge.spec'; +import './jest-bridge.spec'; +import './mocha-patch.spec'; diff --git a/test/mocha-patch.spec.ts b/test/spec/mocha/mocha-patch.spec.ts similarity index 79% rename from test/mocha-patch.spec.ts rename to test/spec/mocha/mocha-patch.spec.ts index 54c47bcd0..d2a19ee11 100644 --- a/test/mocha-patch.spec.ts +++ b/test/spec/mocha/mocha-patch.spec.ts @@ -8,21 +8,13 @@ // Extra Mocha-specific typings to make sure typescript compiler is happy // Didn't want to add @types/mocha because of duplication in typings-file with @types/jasmine -declare function suite(description: string, suiteFn: () => void): void; - declare function test(description: string, testFn: () => void): void; - declare function specify(description: string, testFn: () => void): void; - declare function setup(fn: () => void): void; declare function teardown(fn: () => void): void; - declare function suiteSetup(fn: () => void): void; - declare function suiteTeardown(fn: () => void): void; - declare function before(fn: () => void): void; declare function after(fn: () => void): void; - // - - import { - ifEnvSupports - } from './test-util'; +import {ifEnvSupports} from '../test-util'; -ifEnvSupports('Mocha', function() { +function expect(args: any) { + return {toBe: (result: any) => {}, toBeTruthy: () => {}, toEqual: (result: any) => {}}; +}; +ifEnvSupports('Mocha', function() { describe('Mocha BDD-style', () => { let throwOnAsync = false; let beforeEachZone: Zone = null; @@ -40,7 +32,9 @@ ifEnvSupports('Mocha', function() { throwOnAsync = true; } - beforeEach(() => beforeEachZone = Zone.current); + beforeEach(() => { + beforeEachZone = Zone.current; + }); it('should throw on async in describe', () => { expect(Zone.currentTask).toBeTruthy(); @@ -61,7 +55,7 @@ ifEnvSupports('Mocha', function() { }); }); - suite('Mocha TDD-style', () => { + /*suite('Mocha TDD-style', () => { let testZone: Zone = null; let beforeEachZone: Zone = null; let suiteSetupZone: Zone = null; @@ -95,8 +89,7 @@ ifEnvSupports('Mocha', function() { suiteTeardown(() => { expect(suiteSetupZone).toBe(Zone.current); }); - - }); + });*/ describe('return promise', () => { let log: string[]; @@ -117,4 +110,20 @@ ifEnvSupports('Mocha', function() { expect(log).toEqual(['resolved']); }); }); + + xdescribe('timeout', () => { + beforeEach(function() { + this.timeout(10000); + }); + + it('longtime work', (done: DoneFn) => { + setTimeout(done, 6000); + }); + }); + + describe('skip', () => { + it('skip this case', function() { + this.skip(); + }); + }); })(); \ No newline at end of file diff --git a/test/node/Error.spec.ts b/test/spec/node/Error.spec.ts similarity index 100% rename from test/node/Error.spec.ts rename to test/spec/node/Error.spec.ts diff --git a/test/node/console.spec.ts b/test/spec/node/console.spec.ts similarity index 100% rename from test/node/console.spec.ts rename to test/spec/node/console.spec.ts diff --git a/test/node/crypto.spec.ts b/test/spec/node/crypto.spec.ts similarity index 100% rename from test/node/crypto.spec.ts rename to test/spec/node/crypto.spec.ts diff --git a/test/node/events.spec.ts b/test/spec/node/events.spec.ts similarity index 100% rename from test/node/events.spec.ts rename to test/spec/node/events.spec.ts diff --git a/test/node/fs.spec.ts b/test/spec/node/fs.spec.ts similarity index 100% rename from test/node/fs.spec.ts rename to test/spec/node/fs.spec.ts diff --git a/test/node/http.spec.ts b/test/spec/node/http.spec.ts similarity index 100% rename from test/node/http.spec.ts rename to test/spec/node/http.spec.ts diff --git a/test/node/process.spec.ts b/test/spec/node/process.spec.ts similarity index 83% rename from test/node/process.spec.ts rename to test/spec/node/process.spec.ts index 761eba652..eac6bae6d 100644 --- a/test/node/process.spec.ts +++ b/test/spec/node/process.spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {zoneSymbol} from '../../lib/common/utils'; +import {zoneSymbol} from '../../../lib/common/utils'; describe('process related test', () => { let zoneA: Zone, result: any[]; @@ -39,16 +39,18 @@ describe('process related test', () => { it('process.nextTick should be treated as microTask', (done) => { let zoneTick = Zone.current.fork({ name: 'zoneTick', - onScheduleTask: (parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, - task: Task): Task => { - result.push({callback: 'scheduleTask', targetZone: targetZone.name, task: task.source}); - return parentZoneDelegate.scheduleTask(targetZone, task); - }, - onInvokeTask: (parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, - task: Task, applyThis?: any, applyArgs?: any): any => { - result.push({callback: 'invokeTask', targetZone: targetZone.name, task: task.source}); - return parentZoneDelegate.invokeTask(targetZone, task, applyThis, applyArgs); - } + onScheduleTask: ( + parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, task: Task): + Task => { + result.push({callback: 'scheduleTask', targetZone: targetZone.name, task: task.source}); + return parentZoneDelegate.scheduleTask(targetZone, task); + }, + onInvokeTask: + (parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, task: Task, + applyThis?: any, applyArgs?: any): any => { + result.push({callback: 'invokeTask', targetZone: targetZone.name, task: task.source}); + return parentZoneDelegate.invokeTask(targetZone, task, applyThis, applyArgs); + } }); zoneTick.run(() => { process.nextTick(() => { diff --git a/test/patch/IndexedDB.spec.js b/test/spec/patch/IndexedDB.spec.js similarity index 100% rename from test/patch/IndexedDB.spec.js rename to test/spec/patch/IndexedDB.spec.js diff --git a/promise-adapter.js b/test/spec/promise/promise-adapter.js similarity index 83% rename from promise-adapter.js rename to test/spec/promise/promise-adapter.js index ea81bddfb..dfa369e63 100644 --- a/promise-adapter.js +++ b/test/spec/promise/promise-adapter.js @@ -1,4 +1,4 @@ -require('./dist/zone-node.js'); +require('../../../dist/zone-node.js'); Zone[('__zone_symbol__ignoreConsoleErrorUncaughtError')] = true; module.exports.deferred = function() { const p = {}; @@ -14,5 +14,5 @@ module.exports.resolved = (val) => { }; module.exports.rejected = (reason) => { - return Promise.reject(reason); + return Promise.reject(reason); }; diff --git a/test/spec/promise/promise.finally.spec.js b/test/spec/promise/promise.finally.spec.js new file mode 100644 index 000000000..8463fc339 --- /dev/null +++ b/test/spec/promise/promise.finally.spec.js @@ -0,0 +1,392 @@ +'use strict'; + +var assert = require('assert'); +var adapter = require('./promise-adapter'); +var P = global['__zone_symbol__Promise']; + +var someRejectionReason = {message: 'some rejection reason'}; +var anotherReason = {message: 'another rejection reason'}; +process.on('unhandledRejection', function(reason, promise) { + console.log('unhandledRejection', reason); +}); + +describe('mocha promise sanity check', () => { + it('passes with a resolved promise', () => { + return P.resolve(3); + }); + + it('passes with a rejected then resolved promise', () => { + return P.reject(someRejectionReason).catch(x => 'this should be resolved'); + }); + + var ifPromiseIt = P === Promise ? it : it.skip; + ifPromiseIt('is the native Promise', () => { + assert.equal(P, Promise); + }); +}); + +describe('onFinally', () => { + describe('no callback', () => { + specify('from resolved', (done) => { + adapter.resolved(3) + .then((x) => { + assert.strictEqual(x, 3); + return x; + }) + .finally() + .then( + function onFulfilled(x) { + assert.strictEqual(x, 3); + done(); + }, + function onRejected() { + done(new Error('should not be called')); + }); + }); + + specify('from rejected', (done) => { + adapter.rejected(someRejectionReason) + .catch((e) => { + assert.strictEqual(e, someRejectionReason); + throw e; + }) + .finally() + .then( + function onFulfilled() { + done(new Error('should not be called')); + }, + function onRejected(reason) { + assert.strictEqual(reason, someRejectionReason); + done(); + }); + }); + }); + + describe('throws an exception', () => { + specify('from resolved', (done) => { + adapter.resolved(3) + .then((x) => { + assert.strictEqual(x, 3); + return x; + }) + .finally(function onFinally() { + assert(arguments.length === 0); + throw someRejectionReason; + }) + .then( + function onFulfilled() { + done(new Error('should not be called')); + }, + function onRejected(reason) { + assert.strictEqual(reason, someRejectionReason); + done(); + }); + }); + + specify('from rejected', (done) => { + adapter.rejected(anotherReason) + .finally(function onFinally() { + assert(arguments.length === 0); + throw someRejectionReason; + }) + .then( + function onFulfilled() { + done(new Error('should not be called')); + }, + function onRejected(reason) { + assert.strictEqual(reason, someRejectionReason); + done(); + }); + }); + }); + + describe('returns a non-promise', () => { + specify('from resolved', (done) => { + adapter.resolved(3) + .then((x) => { + assert.strictEqual(x, 3); + return x; + }) + .finally(function onFinally() { + assert(arguments.length === 0); + return 4; + }) + .then( + function onFulfilled(x) { + assert.strictEqual(x, 3); + done(); + }, + function onRejected() { + done(new Error('should not be called')); + }); + }); + + specify('from rejected', (done) => { + adapter.rejected(anotherReason) + .catch((e) => { + assert.strictEqual(e, anotherReason); + throw e; + }) + .finally(function onFinally() { + assert(arguments.length === 0); + throw someRejectionReason; + }) + .then( + function onFulfilled() { + done(new Error('should not be called')); + }, + function onRejected(e) { + assert.strictEqual(e, someRejectionReason); + done(); + }); + }); + }); + + describe('returns a pending-forever promise', () => { + specify('from resolved', (done) => { + var timeout; + adapter.resolved(3) + .then((x) => { + assert.strictEqual(x, 3); + return x; + }) + .finally(function onFinally() { + assert(arguments.length === 0); + timeout = setTimeout(done, 0.1e3); + return new P(() => {}); // forever pending + }) + .then( + function onFulfilled(x) { + clearTimeout(timeout); + done(new Error('should not be called')); + }, + function onRejected() { + clearTimeout(timeout); + done(new Error('should not be called')); + }); + }); + + specify('from rejected', (done) => { + var timeout; + adapter.rejected(someRejectionReason) + .catch((e) => { + assert.strictEqual(e, someRejectionReason); + throw e; + }) + .finally(function onFinally() { + assert(arguments.length === 0); + timeout = setTimeout(done, 0.1e3); + return new P(() => {}); // forever pending + }) + .then( + function onFulfilled(x) { + clearTimeout(timeout); + done(new Error('should not be called')); + }, + function onRejected() { + clearTimeout(timeout); + done(new Error('should not be called')); + }); + }); + }); + + describe('returns an immediately-fulfilled promise', () => { + specify('from resolved', (done) => { + adapter.resolved(3) + .then((x) => { + assert.strictEqual(x, 3); + return x; + }) + .finally(function onFinally() { + assert(arguments.length === 0); + return adapter.resolved(4); + }) + .then( + function onFulfilled(x) { + assert.strictEqual(x, 3); + done(); + }, + function onRejected() { + done(new Error('should not be called')); + }); + }); + + specify('from rejected', (done) => { + adapter.rejected(someRejectionReason) + .catch((e) => { + assert.strictEqual(e, someRejectionReason); + throw e; + }) + .finally(function onFinally() { + assert(arguments.length === 0); + return adapter.resolved(4); + }) + .then( + function onFulfilled() { + done(new Error('should not be called')); + }, + function onRejected(e) { + assert.strictEqual(e, someRejectionReason); + done(); + }); + }); + }); + + describe('returns an immediately-rejected promise', () => { + specify('from resolved ', (done) => { + adapter.resolved(3) + .then((x) => { + assert.strictEqual(x, 3); + return x; + }) + .finally(function onFinally() { + assert(arguments.length === 0); + return adapter.rejected(4); + }) + .then( + function onFulfilled(x) { + done(new Error('should not be called')); + }, + function onRejected(e) { + assert.strictEqual(e, 4); + done(); + }); + }); + + specify('from rejected', (done) => { + const newReason = {}; + adapter.rejected(someRejectionReason) + .catch((e) => { + assert.strictEqual(e, someRejectionReason); + throw e; + }) + .finally(function onFinally() { + assert(arguments.length === 0); + return adapter.rejected(newReason); + }) + .then( + function onFulfilled(x) { + done(new Error('should not be called')); + }, + function onRejected(e) { + assert.strictEqual(e, newReason); + done(); + }); + }); + }); + + describe('returns a fulfilled-after-a-second promise', () => { + specify('from resolved', (done) => { + var timeout; + adapter.resolved(3) + .then((x) => { + assert.strictEqual(x, 3); + return x; + }) + .finally(function onFinally() { + assert(arguments.length === 0); + timeout = setTimeout(done, 1.5e3); + return new P((resolve) => { + setTimeout(() => resolve(4), 1e3); + }); + }) + .then( + function onFulfilled(x) { + clearTimeout(timeout); + assert.strictEqual(x, 3); + done(); + }, + function onRejected() { + clearTimeout(timeout); + done(new Error('should not be called')); + }); + }); + + specify('from rejected', (done) => { + var timeout; + adapter.rejected(3) + .catch((e) => { + assert.strictEqual(e, 3); + throw e; + }) + .finally(function onFinally() { + assert(arguments.length === 0); + timeout = setTimeout(done, 1.5e3); + return new P((resolve) => { + setTimeout(() => resolve(4), 1e3); + }); + }) + .then( + function onFulfilled() { + clearTimeout(timeout); + done(new Error('should not be called')); + }, + function onRejected(e) { + clearTimeout(timeout); + assert.strictEqual(e, 3); + done(); + }); + }); + }); + + describe('returns a rejected-after-a-second promise', () => { + specify('from resolved', (done) => { + var timeout; + adapter.resolved(3) + .then((x) => { + assert.strictEqual(x, 3); + return x; + }) + .finally(function onFinally() { + assert(arguments.length === 0); + timeout = setTimeout(done, 1.5e3); + return new P((resolve, reject) => { + setTimeout(() => reject(4), 1e3); + }); + }) + .then( + function onFulfilled() { + clearTimeout(timeout); + done(new Error('should not be called')); + }, + function onRejected(e) { + clearTimeout(timeout); + assert.strictEqual(e, 4); + done(); + }); + }); + + specify('from rejected', (done) => { + var timeout; + adapter.rejected(someRejectionReason) + .catch((e) => { + assert.strictEqual(e, someRejectionReason); + throw e; + }) + .finally(function onFinally() { + assert(arguments.length === 0); + timeout = setTimeout(done, 1.5e3); + return new P((resolve, reject) => { + setTimeout(() => reject(anotherReason), 1e3); + }); + }) + .then( + function onFulfilled() { + clearTimeout(timeout); + done(new Error('should not be called')); + }, + function onRejected(e) { + clearTimeout(timeout); + assert.strictEqual(e, anotherReason); + done(); + }); + }); + }); + + specify('has the correct property descriptor', () => { + var descriptor = Object.getOwnPropertyDescriptor(Promise.prototype, 'finally'); + + assert.strictEqual(descriptor.writable, true); + assert.strictEqual(descriptor.configurable, true); + }); +}); \ No newline at end of file diff --git a/test/rxjs/rxjs.Observable.audit.spec.ts b/test/spec/rxjs/rxjs.Observable.audit.spec.ts similarity index 100% rename from test/rxjs/rxjs.Observable.audit.spec.ts rename to test/spec/rxjs/rxjs.Observable.audit.spec.ts diff --git a/test/rxjs/rxjs.Observable.buffer.spec.ts b/test/spec/rxjs/rxjs.Observable.buffer.spec.ts similarity index 100% rename from test/rxjs/rxjs.Observable.buffer.spec.ts rename to test/spec/rxjs/rxjs.Observable.buffer.spec.ts diff --git a/test/rxjs/rxjs.Observable.catch.spec.ts b/test/spec/rxjs/rxjs.Observable.catch.spec.ts similarity index 100% rename from test/rxjs/rxjs.Observable.catch.spec.ts rename to test/spec/rxjs/rxjs.Observable.catch.spec.ts diff --git a/test/rxjs/rxjs.Observable.collection.spec.ts b/test/spec/rxjs/rxjs.Observable.collection.spec.ts similarity index 100% rename from test/rxjs/rxjs.Observable.collection.spec.ts rename to test/spec/rxjs/rxjs.Observable.collection.spec.ts diff --git a/test/rxjs/rxjs.Observable.combine.spec.ts b/test/spec/rxjs/rxjs.Observable.combine.spec.ts similarity index 100% rename from test/rxjs/rxjs.Observable.combine.spec.ts rename to test/spec/rxjs/rxjs.Observable.combine.spec.ts diff --git a/test/rxjs/rxjs.Observable.concat.spec.ts b/test/spec/rxjs/rxjs.Observable.concat.spec.ts similarity index 100% rename from test/rxjs/rxjs.Observable.concat.spec.ts rename to test/spec/rxjs/rxjs.Observable.concat.spec.ts diff --git a/test/rxjs/rxjs.Observable.count.spec.ts b/test/spec/rxjs/rxjs.Observable.count.spec.ts similarity index 100% rename from test/rxjs/rxjs.Observable.count.spec.ts rename to test/spec/rxjs/rxjs.Observable.count.spec.ts diff --git a/test/rxjs/rxjs.Observable.debounce.spec.ts b/test/spec/rxjs/rxjs.Observable.debounce.spec.ts similarity index 100% rename from test/rxjs/rxjs.Observable.debounce.spec.ts rename to test/spec/rxjs/rxjs.Observable.debounce.spec.ts diff --git a/test/rxjs/rxjs.Observable.default.spec.ts b/test/spec/rxjs/rxjs.Observable.default.spec.ts similarity index 100% rename from test/rxjs/rxjs.Observable.default.spec.ts rename to test/spec/rxjs/rxjs.Observable.default.spec.ts diff --git a/test/rxjs/rxjs.Observable.delay.spec.ts b/test/spec/rxjs/rxjs.Observable.delay.spec.ts similarity index 100% rename from test/rxjs/rxjs.Observable.delay.spec.ts rename to test/spec/rxjs/rxjs.Observable.delay.spec.ts diff --git a/test/rxjs/rxjs.Observable.distinct.spec.ts b/test/spec/rxjs/rxjs.Observable.distinct.spec.ts similarity index 100% rename from test/rxjs/rxjs.Observable.distinct.spec.ts rename to test/spec/rxjs/rxjs.Observable.distinct.spec.ts diff --git a/test/rxjs/rxjs.Observable.do.spec.ts b/test/spec/rxjs/rxjs.Observable.do.spec.ts similarity index 100% rename from test/rxjs/rxjs.Observable.do.spec.ts rename to test/spec/rxjs/rxjs.Observable.do.spec.ts diff --git a/test/rxjs/rxjs.Observable.map.spec.ts b/test/spec/rxjs/rxjs.Observable.map.spec.ts similarity index 100% rename from test/rxjs/rxjs.Observable.map.spec.ts rename to test/spec/rxjs/rxjs.Observable.map.spec.ts diff --git a/test/rxjs/rxjs.Observable.merge.spec.ts b/test/spec/rxjs/rxjs.Observable.merge.spec.ts similarity index 100% rename from test/rxjs/rxjs.Observable.merge.spec.ts rename to test/spec/rxjs/rxjs.Observable.merge.spec.ts diff --git a/test/rxjs/rxjs.Observable.multicast.spec.ts b/test/spec/rxjs/rxjs.Observable.multicast.spec.ts similarity index 100% rename from test/rxjs/rxjs.Observable.multicast.spec.ts rename to test/spec/rxjs/rxjs.Observable.multicast.spec.ts diff --git a/test/rxjs/rxjs.Observable.notification.spec.ts b/test/spec/rxjs/rxjs.Observable.notification.spec.ts similarity index 100% rename from test/rxjs/rxjs.Observable.notification.spec.ts rename to test/spec/rxjs/rxjs.Observable.notification.spec.ts diff --git a/test/rxjs/rxjs.Observable.race.spec.ts b/test/spec/rxjs/rxjs.Observable.race.spec.ts similarity index 100% rename from test/rxjs/rxjs.Observable.race.spec.ts rename to test/spec/rxjs/rxjs.Observable.race.spec.ts diff --git a/test/rxjs/rxjs.Observable.sample.spec.ts b/test/spec/rxjs/rxjs.Observable.sample.spec.ts similarity index 100% rename from test/rxjs/rxjs.Observable.sample.spec.ts rename to test/spec/rxjs/rxjs.Observable.sample.spec.ts diff --git a/test/rxjs/rxjs.Observable.take.spec.ts b/test/spec/rxjs/rxjs.Observable.take.spec.ts similarity index 100% rename from test/rxjs/rxjs.Observable.take.spec.ts rename to test/spec/rxjs/rxjs.Observable.take.spec.ts diff --git a/test/rxjs/rxjs.Observable.timeout.spec.ts b/test/spec/rxjs/rxjs.Observable.timeout.spec.ts similarity index 100% rename from test/rxjs/rxjs.Observable.timeout.spec.ts rename to test/spec/rxjs/rxjs.Observable.timeout.spec.ts diff --git a/test/rxjs/rxjs.Observable.window.spec.ts b/test/spec/rxjs/rxjs.Observable.window.spec.ts similarity index 100% rename from test/rxjs/rxjs.Observable.window.spec.ts rename to test/spec/rxjs/rxjs.Observable.window.spec.ts diff --git a/test/rxjs/rxjs.asap.spec.ts b/test/spec/rxjs/rxjs.asap.spec.ts similarity index 100% rename from test/rxjs/rxjs.asap.spec.ts rename to test/spec/rxjs/rxjs.asap.spec.ts diff --git a/test/rxjs/rxjs.bindCallback.spec.ts b/test/spec/rxjs/rxjs.bindCallback.spec.ts similarity index 100% rename from test/rxjs/rxjs.bindCallback.spec.ts rename to test/spec/rxjs/rxjs.bindCallback.spec.ts diff --git a/test/rxjs/rxjs.bindNodeCallback.spec.ts b/test/spec/rxjs/rxjs.bindNodeCallback.spec.ts similarity index 100% rename from test/rxjs/rxjs.bindNodeCallback.spec.ts rename to test/spec/rxjs/rxjs.bindNodeCallback.spec.ts diff --git a/test/rxjs/rxjs.combineLatest.spec.ts b/test/spec/rxjs/rxjs.combineLatest.spec.ts similarity index 100% rename from test/rxjs/rxjs.combineLatest.spec.ts rename to test/spec/rxjs/rxjs.combineLatest.spec.ts diff --git a/test/rxjs/rxjs.common.spec.ts b/test/spec/rxjs/rxjs.common.spec.ts similarity index 100% rename from test/rxjs/rxjs.common.spec.ts rename to test/spec/rxjs/rxjs.common.spec.ts diff --git a/test/rxjs/rxjs.concat.spec.ts b/test/spec/rxjs/rxjs.concat.spec.ts similarity index 100% rename from test/rxjs/rxjs.concat.spec.ts rename to test/spec/rxjs/rxjs.concat.spec.ts diff --git a/test/rxjs/rxjs.defer.spec.ts b/test/spec/rxjs/rxjs.defer.spec.ts similarity index 100% rename from test/rxjs/rxjs.defer.spec.ts rename to test/spec/rxjs/rxjs.defer.spec.ts diff --git a/test/rxjs/rxjs.empty.spec.ts b/test/spec/rxjs/rxjs.empty.spec.ts similarity index 100% rename from test/rxjs/rxjs.empty.spec.ts rename to test/spec/rxjs/rxjs.empty.spec.ts diff --git a/test/rxjs/rxjs.forkjoin.spec.ts b/test/spec/rxjs/rxjs.forkjoin.spec.ts similarity index 100% rename from test/rxjs/rxjs.forkjoin.spec.ts rename to test/spec/rxjs/rxjs.forkjoin.spec.ts diff --git a/test/rxjs/rxjs.from.spec.ts b/test/spec/rxjs/rxjs.from.spec.ts similarity index 100% rename from test/rxjs/rxjs.from.spec.ts rename to test/spec/rxjs/rxjs.from.spec.ts diff --git a/test/spec/rxjs/rxjs.fromEvent.spec.ts b/test/spec/rxjs/rxjs.fromEvent.spec.ts new file mode 100644 index 000000000..2a4c19f49 --- /dev/null +++ b/test/spec/rxjs/rxjs.fromEvent.spec.ts @@ -0,0 +1,105 @@ +/** + * @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 * as Rx from 'rxjs/Rx'; + +import {isBrowser} from '../../../lib/common/utils'; +import {ifEnvSupports} from '../test-util'; + +function isEventTarget() { + return isBrowser; +} + +(isEventTarget as any).message = 'EventTargetTest'; + +describe('Observable.fromEvent', () => { + let log: string[]; + const constructorZone1: Zone = Zone.current.fork({name: 'Constructor Zone1'}); + const subscriptionZone: Zone = Zone.current.fork({name: 'Subscription Zone'}); + const triggerZone: Zone = Zone.current.fork({name: 'Trigger Zone'}); + let observable1: any; + + beforeEach(() => { + log = []; + }); + + it('fromEvent EventTarget func callback should run in the correct zone', + ifEnvSupports(isEventTarget, () => { + observable1 = constructorZone1.run(() => { + return Rx.Observable.fromEvent(document, 'click'); + }); + + const clickEvent = document.createEvent('Event'); + clickEvent.initEvent('click', true, true); + + subscriptionZone.run(() => { + observable1.subscribe( + (result: any) => { + expect(Zone.current.name).toEqual(subscriptionZone.name); + log.push(result); + }, + () => { + fail('should not call error'); + }, + () => { + expect(Zone.current.name).toEqual(subscriptionZone.name); + log.push('completed'); + }); + }); + + triggerZone.run(() => { + document.dispatchEvent(clickEvent); + }); + + expect(log).toEqual([clickEvent]); + })); + + it('fromEventPattern EventTarget func callback should run in the correct zone', + ifEnvSupports(isEventTarget, () => { + const button = document.createElement('button'); + document.body.appendChild(button); + observable1 = constructorZone1.run(() => { + return Rx.Observable.fromEventPattern( + (handler: any) => { + expect(Zone.current.name).toEqual(constructorZone1.name); + button.addEventListener('click', handler); + log.push('addListener'); + }, + (handler: any) => { + expect(Zone.current.name).toEqual(constructorZone1.name); + button.removeEventListener('click', handler); + document.body.removeChild(button); + log.push('removeListener'); + }); + }); + + const clickEvent = document.createEvent('Event'); + clickEvent.initEvent('click', false, false); + + const subscriper: any = subscriptionZone.run(() => { + return observable1.subscribe( + (result: any) => { + expect(Zone.current.name).toEqual(subscriptionZone.name); + log.push(result); + }, + () => { + fail('should not call error'); + }, + () => { + expect(Zone.current.name).toEqual(subscriptionZone.name); + log.push('completed'); + }); + }); + + triggerZone.run(() => { + button.dispatchEvent(clickEvent); + subscriper.complete(); + }); + expect(log).toEqual(['addListener', clickEvent, 'completed', 'removeListener']); + })); +}); diff --git a/test/rxjs/rxjs.fromPromise.spec.ts b/test/spec/rxjs/rxjs.fromPromise.spec.ts similarity index 100% rename from test/rxjs/rxjs.fromPromise.spec.ts rename to test/spec/rxjs/rxjs.fromPromise.spec.ts diff --git a/test/rxjs/rxjs.interval.spec.ts b/test/spec/rxjs/rxjs.interval.spec.ts similarity index 100% rename from test/rxjs/rxjs.interval.spec.ts rename to test/spec/rxjs/rxjs.interval.spec.ts diff --git a/test/rxjs/rxjs.merge.spec.ts b/test/spec/rxjs/rxjs.merge.spec.ts similarity index 100% rename from test/rxjs/rxjs.merge.spec.ts rename to test/spec/rxjs/rxjs.merge.spec.ts diff --git a/test/rxjs/rxjs.never.spec.ts b/test/spec/rxjs/rxjs.never.spec.ts similarity index 100% rename from test/rxjs/rxjs.never.spec.ts rename to test/spec/rxjs/rxjs.never.spec.ts diff --git a/test/rxjs/rxjs.of.spec.ts b/test/spec/rxjs/rxjs.of.spec.ts similarity index 100% rename from test/rxjs/rxjs.of.spec.ts rename to test/spec/rxjs/rxjs.of.spec.ts diff --git a/test/rxjs/rxjs.range.spec.ts b/test/spec/rxjs/rxjs.range.spec.ts similarity index 100% rename from test/rxjs/rxjs.range.spec.ts rename to test/spec/rxjs/rxjs.range.spec.ts diff --git a/test/rxjs/rxjs.spec.ts b/test/spec/rxjs/rxjs.spec.ts similarity index 98% rename from test/rxjs/rxjs.spec.ts rename to test/spec/rxjs/rxjs.spec.ts index 8a42b0fe2..547b46d00 100644 --- a/test/rxjs/rxjs.spec.ts +++ b/test/spec/rxjs/rxjs.spec.ts @@ -5,7 +5,7 @@ * 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 '../../lib/rxjs/rxjs'; +import '../../../lib/rxjs/rxjs'; import './rxjs.common.spec'; import './rxjs.asap.spec'; import './rxjs.bindCallback.spec'; diff --git a/test/rxjs/rxjs.throw.spec.ts b/test/spec/rxjs/rxjs.throw.spec.ts similarity index 100% rename from test/rxjs/rxjs.throw.spec.ts rename to test/spec/rxjs/rxjs.throw.spec.ts diff --git a/test/rxjs/rxjs.timer.spec.ts b/test/spec/rxjs/rxjs.timer.spec.ts similarity index 100% rename from test/rxjs/rxjs.timer.spec.ts rename to test/spec/rxjs/rxjs.timer.spec.ts diff --git a/test/rxjs/rxjs.zip.spec.ts b/test/spec/rxjs/rxjs.zip.spec.ts similarity index 100% rename from test/rxjs/rxjs.zip.spec.ts rename to test/spec/rxjs/rxjs.zip.spec.ts diff --git a/test/test-util.ts b/test/spec/test-util.ts similarity index 100% rename from test/test-util.ts rename to test/spec/test-util.ts diff --git a/simple-server.js b/test/spec/webdriver/simple-server.js similarity index 80% rename from simple-server.js rename to test/spec/webdriver/simple-server.js index 71a186a09..6c6654f47 100644 --- a/simple-server.js +++ b/test/spec/webdriver/simple-server.js @@ -9,7 +9,7 @@ const http = require('http'); const path = require('path'); const fs = require('fs'); -let server; +let server; const localFolder = __dirname; @@ -20,8 +20,12 @@ function requestHandler(req, res) { process.exit(0); }, 1000); } else { - const file = localFolder + req.url; - + let isZone = false; + if (req.url.indexOf('zone.js') !== -1) { + isZone = true; + } + const file = path.join(localFolder, isZone ? '../../../dist/zone.js' : req.url); + fs.readFile(file, function(err, contents) { if(!err){ res.end(contents); @@ -32,5 +36,5 @@ function requestHandler(req, res) { }); }; }; - + server = http.createServer(requestHandler).listen(8080); \ No newline at end of file diff --git a/test/webdriver/test.html b/test/spec/webdriver/test.html similarity index 65% rename from test/webdriver/test.html rename to test/spec/webdriver/test.html index 0fa9c32ff..c96642559 100644 --- a/test/webdriver/test.html +++ b/test/spec/webdriver/test.html @@ -1,7 +1,7 @@ - +
Hello Zones!
diff --git a/test/webdriver/test.js b/test/spec/webdriver/test.js similarity index 100% rename from test/webdriver/test.js rename to test/spec/webdriver/test.js diff --git a/test/webdriver/test.sauce.js b/test/spec/webdriver/test.sauce.js similarity index 95% rename from test/webdriver/test.sauce.js rename to test/spec/webdriver/test.sauce.js index 7ca9de2ef..0cda43c5d 100644 --- a/test/webdriver/test.sauce.js +++ b/test/spec/webdriver/test.sauce.js @@ -21,12 +21,12 @@ const desiredCapabilities = { edge14: { browserName: 'MicrosoftEdge', platform: 'Windows 10', - version: '14.14393' + version: '14.14393' }, /*edge15: { browserName: 'Edge', platform: 'Windows 10', - version: '15.15063' + version: '15.15063' },*/ chrome48: { browserName: 'chrome', @@ -104,15 +104,15 @@ Object.keys(desiredCapabilities).forEach(key => { key: process.env.SAUCE_ACCESS_KEY, host: 'localhost', port: 4445, - desiredCapabilities: desiredCapabilities[key] + desiredCapabilities: desiredCapabilities[key] }); const p = client .init() .timeouts('script', 60000) - .url('http://localhost:8080/test/webdriver/test.html') + .url('http://localhost:8080/test.html') .executeAsync(function(done) { window.setTimeout(done,1000) }) - .execute(function() + .execute(function() { const elem = document.getElementById('thetext'); const zone = window['Zone'] ? Zone.current.fork({name: 'webdriver'}) : null; diff --git a/test/zone-spec/async-test.spec.ts b/test/spec/zone-spec/async-test.spec.ts similarity index 100% rename from test/zone-spec/async-test.spec.ts rename to test/spec/zone-spec/async-test.spec.ts diff --git a/test/zone-spec/fake-async-test.spec.ts b/test/spec/zone-spec/fake-async-test.spec.ts similarity index 97% rename from test/zone-spec/fake-async-test.spec.ts rename to test/spec/zone-spec/fake-async-test.spec.ts index f92f1b96b..55953edf0 100644 --- a/test/zone-spec/fake-async-test.spec.ts +++ b/test/spec/zone-spec/fake-async-test.spec.ts @@ -7,11 +7,11 @@ */ import 'rxjs/add/operator/delay'; -import '../../lib/rxjs/rxjs-fake-async'; +import '../../../lib/rxjs/rxjs-fake-async'; import {Observable} from 'rxjs/Observable'; -import {isNode, patchMacroTask} from '../../lib/common/utils'; +import {isNode, patchMacroTask} from '../../../lib/common/utils'; import {ifEnvSupports} from '../test-util'; function supportNode() { @@ -309,9 +309,9 @@ describe('FakeAsyncTestZoneSpec', () => { let id: number; id = setInterval(() => { - cycles++; - clearInterval(id); - }, 10) as any as number; + cycles++; + clearInterval(id); + }, 10) as any as number; testZoneSpec.tick(10); expect(cycles).toEqual(1); @@ -620,6 +620,27 @@ describe('FakeAsyncTestZoneSpec', () => { }); }); + it('can flush periodic and non-periodic timers if flushPeriodic is true', () => { + fakeAsyncTestZone.run(() => { + let x = 0; + let y = 0; + + setInterval(() => { + x++; + }, 10); + + setTimeout(() => { + y++; + }, 100); + + let elapsed = testZoneSpec.flush(20, true); + + expect(elapsed).toEqual(100); + expect(x).toEqual(10); + expect(y).toEqual(1); + }); + }); + it('can flush till the last periodic task is processed', () => { fakeAsyncTestZone.run(() => { let x = 0; @@ -774,7 +795,6 @@ describe('FakeAsyncTestZoneSpec', () => { expect(tickRun).toEqual(true); expect(cbArgRun).toEqual(true); }); - }); })); @@ -944,7 +964,6 @@ describe('FakeAsyncTestZoneSpec', () => { expect(unixTimeZero).toBe(0); }); }); - }); describe( @@ -1170,7 +1189,7 @@ class Log { const resolvedPromise = Promise.resolve(null); const ProxyZoneSpec: {assertPresent: () => void} = (Zone as any)['ProxyZoneSpec']; const fakeAsyncTestModule = (Zone as any)[Zone.__symbol__('fakeAsyncTest')]; -const {fakeAsync, tick, discardPeriodicTasks, flush, flushMicrotasks} = fakeAsyncTestModule; +const {fakeAsync, tick, discardPeriodicTasks, flush, flushMicrotasks, clearAllMacrotasks} = fakeAsyncTestModule; { describe('fake async', () => { @@ -1272,7 +1291,6 @@ const {fakeAsync, tick, discardPeriodicTasks, flush, flushMicrotasks} = fakeAsyn })(); }).toThrowError('sync'); }); - }); describe('timers', () => { @@ -1496,6 +1514,22 @@ const {fakeAsync, tick, discardPeriodicTasks, flush, flushMicrotasks} = fakeAsyn discardPeriodicTasks(); })); + + + it('clearAllTimeout', fakeAsync(() => { + let timeoutCalledCount = 0; + let intervalCalledCount = 0; + setTimeout(() => { + timeoutCalledCount++; + }, 10); + setInterval(() => { + intervalCalledCount++; + }, 30); + clearAllMacrotasks(); + tick(30); + expect(timeoutCalledCount).toBe(0); + expect(intervalCalledCount).toBe(0); + })); }); describe('outside of the fakeAsync zone', () => { diff --git a/test/zone-spec/long-stack-trace-zone.spec.ts b/test/spec/zone-spec/long-stack-trace-zone.spec.ts similarity index 93% rename from test/zone-spec/long-stack-trace-zone.spec.ts rename to test/spec/zone-spec/long-stack-trace-zone.spec.ts index 4515c2965..e040afdbb 100644 --- a/test/zone-spec/long-stack-trace-zone.spec.ts +++ b/test/spec/zone-spec/long-stack-trace-zone.spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {isBrowser, zoneSymbol} from '../../lib/common/utils'; +import {isBrowser, zoneSymbol} from '../../../lib/common/utils'; import {ifEnvSupports, isSupportSetErrorStack} from '../test-util'; const defineProperty = (Object as any)[zoneSymbol('defineProperty')] || Object.defineProperty; @@ -21,12 +21,13 @@ describe( beforeEach(function() { lstz = Zone.current.fork(longStackTraceZoneSpec).fork({ name: 'long-stack-trace-zone-test', - onHandleError: (parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, - error: any): boolean => { - parentZoneDelegate.handleError(targetZone, error); - log.push(error); - return false; - } + onHandleError: + (parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, error: any): + boolean => { + parentZoneDelegate.handleError(targetZone, error); + log.push(error); + return false; + } }); log = []; diff --git a/test/zone-spec/proxy.spec.ts b/test/spec/zone-spec/proxy.spec.ts similarity index 100% rename from test/zone-spec/proxy.spec.ts rename to test/spec/zone-spec/proxy.spec.ts diff --git a/test/zone-spec/sync-test.spec.ts b/test/spec/zone-spec/sync-test.spec.ts similarity index 100% rename from test/zone-spec/sync-test.spec.ts rename to test/spec/zone-spec/sync-test.spec.ts diff --git a/test/zone-spec/task-tracking.spec.ts b/test/spec/zone-spec/task-tracking.spec.ts similarity index 100% rename from test/zone-spec/task-tracking.spec.ts rename to test/spec/zone-spec/task-tracking.spec.ts diff --git a/test/test-env-setup-mocha.ts b/test/test-env-setup-mocha.ts deleted file mode 100644 index 96058a0c4..000000000 --- a/test/test-env-setup-mocha.ts +++ /dev/null @@ -1,186 +0,0 @@ -/** - * @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 '../lib/mocha/mocha'; -declare const global: any; - -((context: any) => { - context['jasmine'] = global['jasmine'] || {}; - context['jasmine'].createSpy = function(spyName: string) { - let spy: any = function(...params: any[]) { - spy.countCall++; - spy.callArgs = params; - }; - - spy.countCall = 0; - - return spy; - }; - - function eq(a: any, b: any) { - if (a === b) { - return true; - } else if (Array.isArray(a) && Array.isArray(b)) { - if (a.length !== b.length) { - return false; - } - - let isEqual = true; - - for (let prop in a) { - if (a.hasOwnProperty(prop)) { - if (!eq(a[prop], b[prop])) { - isEqual = false; - break; - } - } - } - - return isEqual; - } else if (typeof a === 'object' && typeof b === 'object') { - if (Object.keys(a).length !== Object.keys(b).length) { - return false; - } - - let isEqual = true; - - for (let prop in a) { - if (a.hasOwnProperty(prop)) { - if (!eq(a[prop], b[prop])) { - isEqual = false; - break; - } - } - } - - return isEqual; - } - - return false; - } - - context['expect'] = function(expected: any) { - return { - toBe: function(actual: any) { - if (expected !== actual) { - throw new Error(`Expected ${expected} to be ${actual}`); - } - }, - toEqual: function(actual: any) { - if (!eq(expected, actual)) { - throw new Error(`Expected ${expected} to be ${actual}`); - } - }, - toBeGreaterThan: function(actual: number) { - if (expected <= actual) { - throw new Error(`Expected ${expected} to be greater than ${actual}`); - } - }, - toBeLessThan: function(actual: number) { - if (expected >= actual) { - throw new Error(`Expected ${expected} to be lesser than ${actual}`); - } - }, - toBeDefined: function() { - if (!expected) { - throw new Error(`Expected ${expected} to be defined`); - } - }, - toThrow: function() { - try { - expected(); - } catch (error) { - return; - } - - throw new Error(`Expected ${expected} to throw`); - }, - toThrowError: function(errorToBeThrow: any) { - try { - expected(); - } catch (error) { - return; - } - - throw Error(`Expected ${expected} to throw: ${errorToBeThrow}`); - }, - toBeTruthy: function() { - if (!expected) { - throw new Error(`Expected ${expected} to be truthy`); - } - }, - toBeFalsy: function(actual: any) { - if (!!actual) { - throw new Error(`Expected ${actual} to be falsy`); - } - }, - toContain: function(actual: any) { - if (expected.indexOf(actual) === -1) { - throw new Error(`Expected ${expected} to contain ${actual}`); - } - }, - toHaveBeenCalled: function() { - if (expected.countCall === 0) { - throw new Error(`Expected ${expected} to been called`); - } - }, - toHaveBeenCalledWith: function(...params: any[]) { - if (!eq(expected.callArgs, params)) { - throw new Error(`Expected ${expected} to been called with ${expected.callArgs - }, called with: ${params}`); - } - }, - toMatch: function(actual: any) { - if (!new RegExp(actual).test(expected)) { - throw new Error(`Expected ${expected} to match ${actual}`); - } - }, - not: { - toBe: function(actual: any) { - if (expected === actual) { - throw new Error(`Expected ${expected} not to be ${actual}`); - } - }, - toHaveBeenCalled: function() { - if (expected.countCall > 0) { - throw new Error(`Expected ${expected} to not been called`); - } - }, - toThrow: function() { - try { - expected(); - } catch (error) { - throw new Error(`Expected ${expected} to not throw`); - } - }, - toThrowError: function() { - try { - expected(); - } catch (error) { - throw Error(`Expected ${expected} to not throw error`); - } - }, - toBeGreaterThan: function(actual: number) { - if (expected > actual) { - throw new Error(`Expected ${expected} not to be greater than ${actual}`); - } - }, - toBeLessThan: function(actual: number) { - if (expected < actual) { - throw new Error(`Expected ${expected} not to be lesser than ${actual}`); - } - }, - toHaveBeenCalledWith: function(params: any[]) { - if (!eq(expected.callArgs, params)) { - throw new Error(`Expected ${expected} to not been called with ${params}`); - } - } - } - }; - }; -})(window); \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index a26216695..dea39c55e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,10 +2,6 @@ # yarn lockfile v1 -"@types/jasmine@2.2.33": - version "2.2.33" - resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-2.2.33.tgz#4715cfd2ca7fbd632fc7f1784f13e637bed028c5" - "@types/node@^6.0.96": version "6.0.101" resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.101.tgz#0c5911cfb434af4a51c0a499931fe6423207d921"