From e60bdde55b03b478253dbdaa248aecfefc93c70c Mon Sep 17 00:00:00 2001 From: JiaLiPassion Date: Thu, 3 May 2018 14:56:51 +0800 Subject: [PATCH 01/11] feat(test): unify mocha/jasmine test --- .travis.yml | 2 + karma-build-mocha-jasmine-bridge.conf.js | 18 + karma-dist.conf.js | 7 +- lib/jasmine/jasmine.clock.ts | 54 + lib/jasmine/jasmine.ts | 72 +- lib/mocha/jasmine-bridge/jasmine-bridge.ts | 9 + lib/mocha/jasmine-bridge/jasmine.bdd.ts | 45 + lib/mocha/jasmine-bridge/jasmine.clock.ts | 19 + lib/mocha/jasmine-bridge/jasmine.expect.ts | 303 +++++ lib/mocha/jasmine-bridge/jasmine.spy.ts | 335 +++++ lib/mocha/jasmine-bridge/jasmine.ts | 49 + lib/mocha/jasmine-bridge/jasmine.util.ts | 158 +++ lib/mocha/mocha-node-checker.ts | 14 + lib/mocha/mocha-node.ts | 9 + lib/mocha/mocha-patch.ts | 234 ++++ lib/mocha/mocha.ts | 171 +-- lib/testing/fake-async.ts | 71 +- lib/testing/zone-testing.ts | 5 +- package.json | 5 +- test/browser_entry_point.ts | 1 - test/main.ts | 23 +- test/mocha/jasmine-bridge.spec.js | 1255 ++++++++++++++++++ test/mocha/mocha-browser-karma.ts | 9 + test/mocha/mocha-browser-test-entry-point.ts | 10 + test/mocha/mocha-node-test-entry-point.ts | 11 + test/{ => mocha}/mocha-patch.spec.ts | 39 +- test/test-env-setup-mocha.ts | 179 +-- 27 files changed, 2636 insertions(+), 471 deletions(-) create mode 100644 karma-build-mocha-jasmine-bridge.conf.js create mode 100644 lib/jasmine/jasmine.clock.ts create mode 100644 lib/mocha/jasmine-bridge/jasmine-bridge.ts create mode 100644 lib/mocha/jasmine-bridge/jasmine.bdd.ts create mode 100644 lib/mocha/jasmine-bridge/jasmine.clock.ts create mode 100644 lib/mocha/jasmine-bridge/jasmine.expect.ts create mode 100644 lib/mocha/jasmine-bridge/jasmine.spy.ts create mode 100644 lib/mocha/jasmine-bridge/jasmine.ts create mode 100644 lib/mocha/jasmine-bridge/jasmine.util.ts create mode 100644 lib/mocha/mocha-node-checker.ts create mode 100644 lib/mocha/mocha-node.ts create mode 100644 lib/mocha/mocha-patch.ts create mode 100644 test/mocha/jasmine-bridge.spec.js create mode 100644 test/mocha/mocha-browser-karma.ts create mode 100644 test/mocha/mocha-browser-test-entry-point.ts create mode 100644 test/mocha/mocha-node-test-entry-point.ts rename test/{ => mocha}/mocha-patch.spec.ts (74%) diff --git a/.travis.yml b/.travis.yml index 3ec8c6596..7add517ea 100644 --- a/.travis.yml +++ b/.travis.yml @@ -46,6 +46,8 @@ script: - node ./test/webdriver/test.sauce.js - yarn add jasmine@3.0.0 jasmine-core@3.0.0 mocha@5.0.1 - yarn test:phantomjs-single + - yarn test-mocha-jasmine-bridge-node + - yarn test-mocha-jasmine-bridge-browser - 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 - node_modules/.bin/gulp test/node diff --git a/karma-build-mocha-jasmine-bridge.conf.js b/karma-build-mocha-jasmine-bridge.conf.js new file mode 100644 index 000000000..14a51c9c1 --- /dev/null +++ b/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/mocha/mocha-browser-karma.js'); + config.files.push('build/test/wtf_mock.js'); + config.files.push('build/test/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.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-dist.conf.js b/karma-dist.conf.js index 204225c4d..9d40dee62 100644 --- a/karma-dist.conf.js +++ b/karma-dist.conf.js @@ -14,13 +14,8 @@ module.exports = function(config) { 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/zone-testing.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.clock.ts b/lib/jasmine/jasmine.clock.ts new file mode 100644 index 000000000..bba901495 --- /dev/null +++ b/lib/jasmine/jasmine.clock.ts @@ -0,0 +1,54 @@ +/** + * @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, enableClockPatch: boolean) { + 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 + 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; + }; +} diff --git a/lib/jasmine/jasmine.ts b/lib/jasmine/jasmine.ts index 5d279ab36..8b2d1977e 100644 --- a/lib/jasmine/jasmine.ts +++ b/lib/jasmine/jasmine.ts @@ -7,7 +7,8 @@ */ 'use strict'; -(() => { +import {patchJasmineClock} from './jasmine.clock'; +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]; @@ -16,12 +17,13 @@ } 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 (typeof jasmine == 'undefined') { + // not using jasmine, just return; + return; + } if ((jasmine as any)['__zone_patch__']) throw new Error(`'jasmine' has already been patched with 'Zone'.`); (jasmine as any)['__zone_patch__'] = true; @@ -40,7 +42,7 @@ const symbol = Zone.__symbol__; // whether patch jasmine clock when in fakeAsync - const enableClockPatch = _global[symbol('fakeAsyncPatchLock')] === true; + 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(); @@ -68,51 +70,7 @@ }; }); - // 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; - }; - + patchJasmineClock(jasmine, enableClockPatch); /** * Gets a function wrapping the body of a Jasmine `describe` block to execute in a * synchronous-only zone. @@ -127,7 +85,6 @@ 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')]; @@ -177,7 +134,8 @@ (jasmine as any).QueueRunner = (function(_super) { __extends(ZoneQueueRunner, _super); function ZoneQueueRunner(attrs: { - onComplete: Function; userContext?: any; + onComplete: Function; + userContext?: any; timeout?: {setTimeout: Function; clearTimeout: Function}; onException?: (error: any) => void; }) { @@ -188,13 +146,13 @@ ambientZone.scheduleMicroTask('jasmine.onComplete', fn); })(attrs.onComplete); - const nativeSetTimeout = _global['__zone_symbol__setTimeout']; - const nativeClearTimeout = _global['__zone_symbol__clearTimeout']; + 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 + setTimeout: nativeSetTimeout ? nativeSetTimeout : global.setTimeout, + clearTimeout: nativeClearTimeout ? nativeClearTimeout : global.clearTimeout }; } @@ -272,4 +230,4 @@ }; return ZoneQueueRunner; })(QueueRunner); -})(); +}); diff --git a/lib/mocha/jasmine-bridge/jasmine-bridge.ts b/lib/mocha/jasmine-bridge/jasmine-bridge.ts new file mode 100644 index 000000000..1fd4c16c5 --- /dev/null +++ b/lib/mocha/jasmine-bridge/jasmine-bridge.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 './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..10c8a73ff --- /dev/null +++ b/lib/mocha/jasmine-bridge/jasmine.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(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]] : Mocha[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(); + } + }; + } +} \ 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..f1c39dd65 --- /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) { + jasmine.clock = function() { + return { + tick: function() {}, + install: function() {}, + uninstall: function() {}, + mockDate: function() {} + }; + }; + patchJasmineClock(jasmine, true); +} diff --git a/lib/mocha/jasmine-bridge/jasmine.expect.ts b/lib/mocha/jasmine-bridge/jasmine.expect.ts new file mode 100644 index 000000000..ad5ed825c --- /dev/null +++ b/lib/mocha/jasmine-bridge/jasmine.expect.ts @@ -0,0 +1,303 @@ +/** + * @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, customEqualityTesters, eq, ObjectContaining} from './jasmine.util'; + +export function addJasmineExpect(jasmine: any, global: any) { + 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) { + const util = {equals: eq}; + const originalExcept = jasmine['__zone_symbol__expect']; + jasmine.addMatchers = function(customMatcher: any) { + let customMatchers = jasmine['__zone_symbol__customMatchers']; + if (!customMatchers) { + customMatchers = jasmine['__zone_symbol__customMatchers'] = []; + } + customMatchers.push(customMatcher); + global['expect'] = function(expected: any) { + const expectObj = originalExcept.call(this, expected); + customMatchers.forEach((matcher: any) => { + Object.keys(matcher).forEach(key => { + if (matcher.hasOwnProperty(key)) { + const customExpected = matcher[key](util, customEqualityTesters); + expectObj[key] = function(actual: any) { + return customExpected.compare(expected, actual); + }; + expectObj['not'][key] = function(actual: any) { + return !customExpected.compare(expected, actual); + }; + } + }); + }); + return expectObj; + }; + }; +} + +function addExpect(global: any, jasmine: any) { + global['expect'] = jasmine['__zone_symbol__expect'] = function(expected: any) { + return { + nothing: function() {}, + toBe: function(actual: any) { + if (expected !== actual) { + throw new Error(`Expected ${expected} to be ${actual}`); + } + }, + toBeCloseTo: function(actual: 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; + if (Math.round(delta * pow) / pow > maxDelta) { + throw new Error( + `Expected ${expected} to be close to ${actual} with precision ${precision}`); + } + }, + 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}`); + } + }, + toBeGreaterThanOrEqual: function(actual: number) { + if (expected < actual) { + throw new Error(`Expected ${expected} to be greater than or equal ${actual}`); + } + }, + toBeLessThan: function(actual: number) { + if (expected >= actual) { + throw new Error(`Expected ${expected} to be lesser than ${actual}`); + } + }, + toBeLessThanOrEqual: function(actual: number) { + if (expected > actual) { + throw new Error(`Expected ${expected} to be lesser than or equal ${actual}`); + } + }, + toBeDefined: function() { + if (expected === undefined) { + throw new Error(`Expected ${expected} to be defined`); + } + }, + toBeNaN: function() { + if (expected === expected) { + throw new Error(`Expected ${expected} to be NaN`); + } + }, + toBeNegativeInfinity: function() { + if (expected !== Number.NEGATIVE_INFINITY) { + throw new Error(`Expected ${expected} to be -Infinity`); + } + }, + toBeNull: function() { + if (expected !== null) { + throw new Error(`Expected ${expected} to be null`); + } + }, + toBePositiveInfinity: function() { + if (expected !== Number.POSITIVE_INFINITY) { + throw new Error(`Expected ${expected} to be +Infinity`); + } + }, + toBeUndefined: function() { + if (expected !== undefined) { + throw new Error(`Expected ${expected} to be undefined`); + } + }, + 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.calls.count() === 0) { + throw new Error(`Expected ${expected} to been called`); + } + }, + toHaveBeenCalledWith: function(...params: any[]) { + if (expected.calls.allArgs().filter((args: any) => eq(args, params)).length === 0) { + throw new Error(`Expected ${expected} to been 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}`); + } + }, + toBeCloseTo: function(actual: 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; + if (Math.round(delta * pow) / pow <= maxDelta) { + throw new Error( + `Expected ${expected} not to be close to ${actual} with precision ${precision}`); + } + }, + toEqual: function(actual: any) { + if (eq(expected, actual)) { + throw new Error(`Expected ${expected} not to be ${actual}`); + } + }, + 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}`); + } + }, + toBeGreaterThanOrEqual: function(actual: number) { + if (expected >= actual) { + throw new Error(`Expected ${expected} not to be greater than or equal ${actual}`); + } + }, + toBeLessThan: function(actual: number) { + if (expected < actual) { + throw new Error(`Expected ${expected} not to be lesser than ${actual}`); + } + }, + toBeLessThanOrEqual: function(actual: number) { + if (expected <= actual) { + throw new Error(`Expected ${expected} not to be lesser than or equal ${actual}`); + } + }, + toBeDefined: function() { + if (expected !== undefined) { + throw new Error(`Expected ${expected} not to be defined`); + } + }, + toBeNaN: function() { + if (expected !== expected) { + throw new Error(`Expected ${expected} not to be NaN`); + } + }, + toBeNegativeInfinity: function() { + if (expected === Number.NEGATIVE_INFINITY) { + throw new Error(`Expected ${expected} not to be -Infinity`); + } + }, + toBeNull: function() { + if (expected === null) { + throw new Error(`Expected ${expected} not to be null`); + } + }, + toBePositiveInfinity: function() { + if (expected === Number.POSITIVE_INFINITY) { + throw new Error(`Expected ${expected} not to be +Infinity`); + } + }, + toBeUndefined: function() { + if (expected === undefined) { + throw new Error(`Expected ${expected} not to be undefined`); + } + }, + toBeTruthy: function() { + if (!!expected) { + throw new Error(`Expected ${expected} not to be truthy`); + } + }, + toBeFalsy: function() { + if (!expected) { + throw new Error(`Expected ${expected} not to be falsy`); + } + }, + toContain: function(actual: any) { + if (expected.indexOf(actual) !== -1) { + throw new Error(`Expected ${expected} not to contain ${actual}`); + } + }, + toMatch: function(actual: any) { + if (new RegExp(actual).test(expected)) { + throw new Error(`Expected ${expected} not to match ${actual}`); + } + }, + toHaveBeenCalled: function() { + if (expected.calls.count() > 0) { + throw new Error(`Expected ${expected} to not been called`); + } + }, + toHaveBeenCalledWith: function(params: any[]) { + if (expected.calls.allArgs().filter((args: any) => eq(args, params)).length > 0) { + throw new Error(`Expected ${expected} to not been called with ${params}`); + } + } + } + }; + }; +} \ 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..b9095f0fd --- /dev/null +++ b/lib/mocha/jasmine-bridge/jasmine.spy.ts @@ -0,0 +1,335 @@ +/** + * @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))}; + }; + } + + 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); + }; + 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 + }); + } + + 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); + }; + + 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..56d0a4128 --- /dev/null +++ b/lib/mocha/jasmine-bridge/jasmine.ts @@ -0,0 +1,49 @@ +/** + * @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'; + +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'] = {}; + // 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); + + Object.defineProperty(jasmine, 'DEFAULT_TIMEOUT_INTERVAL', { + configurable: true, + enumerable: true, + get: function() { + return global.Mocha.__zone_symbol__TIMEOUT; + }, + set: function(newValue: number) { + global.Mocha.__zone_symbol__TIMEOUT = newValue; + } + }); +}); \ 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..8496577b9 --- /dev/null +++ b/lib/mocha/jasmine-bridge/jasmine.util.ts @@ -0,0 +1,158 @@ +/** + * @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); + }; +} + +export function eq(a: any, b: any) { + 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 (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); + } + + return false; +} diff --git a/lib/mocha/mocha-node-checker.ts b/lib/mocha/mocha-node-checker.ts new file mode 100644 index 000000000..b71fd0d59 --- /dev/null +++ b/lib/mocha/mocha-node-checker.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 + */ +if (global && !(global as any).Mocha) { + try { + (global as any).Mocha = require('mocha'); + } catch (err) { + console.log('err', err); + } +} \ 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..fff6c8506 --- /dev/null +++ b/lib/mocha/mocha-patch.ts @@ -0,0 +1,234 @@ +/** + * @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; + + if (typeof Mocha === 'undefined') { + 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); + + 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() { + 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); + } + }); + 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); + } + } + + 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 asyncTest = function(fn: Function) { + return function(done: Function) { + 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() {}; + + 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)); + }; + + ((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) => { + 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); +}); diff --git a/lib/mocha/mocha.ts b/lib/mocha/mocha.ts index f36f76a4a..75408c55a 100644 --- a/lib/mocha/mocha.ts +++ b/lib/mocha/mocha.ts @@ -6,172 +6,5 @@ * 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-patch'; +import './jasmine-bridge/jasmine-bridge'; \ No newline at end of file diff --git a/lib/testing/fake-async.ts b/lib/testing/fake-async.ts index 72e934f02..28ae6a700 100644 --- a/lib/testing/fake-async.ts +++ b/lib/testing/fake-async.ts @@ -33,35 +33,46 @@ 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 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 +89,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 { 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/package.json b/package.json index b93ffc01b..4d9bbaaf5 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,9 @@ "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\"", + "prepare-mocha-jasmine-bridge": "npm run tsc && cp ./test/mocha/*.js ./build/test/mocha/", + "test-mocha-jasmine-bridge-browser": "npm run prepare-mocha-jasmine-bridge && concurrently \"npm run ws-server\" \"karma start karma-build-mocha-jasmine-bridge.conf.js\"", + "test-mocha-jasmine-bridge-node": "npm run prepare-mocha-jasmine-bridge && mocha ./build/test/mocha/mocha-node-test-entry-point.js", "serve": "python -m SimpleHTTPServer 8000" }, "repository": { @@ -105,4 +108,4 @@ "webdriverio": "^4.8.0", "whatwg-fetch": "^2.0.1" } -} +} \ No newline at end of file diff --git a/test/browser_entry_point.ts b/test/browser_entry_point.ts index 4ec6e5776..73e95d381 100644 --- a/test/browser_entry_point.ts +++ b/test/browser_entry_point.ts @@ -22,6 +22,5 @@ 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/main.ts b/test/main.ts index f8c33f3ea..f9e5e6df8 100644 --- a/test/main.ts +++ b/test/main.ts @@ -32,18 +32,15 @@ if ((window as any)[(Zone as any).__symbol__('setTimeout')]) { } browserPatchedPromise.then(() => { - let testFrameworkPatch = typeof(window as any).Mocha !== 'undefined' ? - '/base/build/test/test-env-setup-mocha' : - '/base/build/test/test-env-setup-jasmine'; // Setup test environment - System.import(testFrameworkPatch).then(() => { - System.import('/base/build/test/browser_entry_point') - .then( - () => { - __karma__.start(); - }, - (error) => { - console.error(error.stack || error); - }); - }); + const entryPoint = + (window as any)['__zone_symbol__test_entry_point'] || '/base/build/test/browser_entry_point'; + System.import(entryPoint) + .then( + () => { + __karma__.start(); + }, + (error) => { + console.error(error.stack || error); + }); }); diff --git a/test/mocha/jasmine-bridge.spec.js b/test/mocha/jasmine-bridge.spec.js new file mode 100644 index 000000000..4ae3bcc0b --- /dev/null +++ b/test/mocha/jasmine-bridge.spec.js @@ -0,0 +1,1255 @@ +/** + * @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 variables declared in a `describe` are + available to any `it` block inside the suite. + */ +describe('A suite is just a function', function() { + var 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() { + var a = 12; + var b = a; + + expect(a).toBe(b); + expect(a).not.toBe(null); + }); + + describe('The \'toEqual\' matcher', function() { + it('works for simple literals and variables', function() { + var a = 12; + expect(a).toEqual(12); + }); + + it('should work for objects', function() { + var foo = {a: 12, b: 34}; + var bar = {a: 12, b: 34}; + expect(foo).toEqual(bar); + }); + }); + + it('The \'toMatch\' matcher is for regular expressions', function() { + var 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() { + var a = {foo: 'foo'}; + + expect(a.foo).toBeDefined(); + expect(a.bar).not.toBeDefined(); + }); + + it('The `toBeUndefined` matcher compares against `undefined`', function() { + var a = {foo: 'foo'}; + + expect(a.foo).not.toBeUndefined(); + expect(a.bar).toBeUndefined(); + }); + + it('The \'toBeNull\' matcher compares against null', function() { + var a = null; + var foo = 'foo'; + + expect(null).toBeNull(); + expect(a).toBeNull(); + expect(foo).not.toBeNull(); + }); + + it('The \'toBeTruthy\' matcher is for boolean casting testing', function() { + var a, foo = 'foo'; + + expect(foo).toBeTruthy(); + expect(a).not.toBeTruthy(); + }); + + it('The \'toBeFalsy\' matcher is for boolean casting testing', function() { + var 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() { + var a = ['foo', 'bar', 'baz']; + + expect(a).toContain('bar'); + expect(a).not.toContain('quux'); + }); + + it('also works for finding a substring', function() { + var a = 'foo bar baz'; + + expect(a).toContain('bar'); + expect(a).not.toContain('quux'); + }); + }); + + it('The \'toBeLessThan\' matcher is for mathematical comparisons', function() { + var pi = 3.1415926, e = 2.78; + + expect(e).toBeLessThan(pi); + expect(pi).not.toBeLessThan(e); + }); + + it('The \'toBeGreaterThan\' matcher is for mathematical comparisons', function() { + var 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() { + var 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() { + var foo = function() { + return 1 + 2; + }; + var bar = function() { + return a + 1; + }; + var baz = function() { + throw 'what'; + }; + + expect(foo).not.toThrow(); + expect(bar).toThrow(); + expect(baz).toThrow('what'); + }); + + it('The \'toThrowError\' matcher is for testing a specific thrown exception', function() { + var 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() { + var foo = 0; + foo += 1; + + expect(foo).toEqual(1); + }); + + it('can have more than one expectation', function() { + var 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 variable 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 variable before continuing. + */ +describe('A spec using beforeEach and afterEach', function() { + var 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() { + var foo; + + 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 variables 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() { + var foo; + + 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() { + var bar; + + 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() { + var foo; + + 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() { + var foo, bar = null; + + beforeEach(function() { + foo = { + setBar: function(value) { + 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() { + var foo, bar, fetchedBar; + + beforeEach(function() { + foo = { + setBar: function(value) { + 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() { + var foo, bar, fetchedBar; + + beforeEach(function() { + foo = { + setBar: function(value) { + 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() { + var foo, bar, fetchedBar; + + beforeEach(function() { + foo = { + setBar: function(value) { + 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, can, be, received) { + 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() { + var foo, bar; + + beforeEach(function() { + foo = { + setBar: function(value) { + 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() { + var foo, bar = null; + + beforeEach(function() { + foo = { + setBar: function(value) { + 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() { + var foo, bar = null; + + beforeEach(function() { + foo = { + setBar: function(value) { + 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() { + var spy = jasmine.createSpy('spy'); + var baz = {fn: spy}; + var 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() { + var whatAmI; + + 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() { + var tape; + + 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() { + var 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() { + var foo; + + 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() { + var 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() { + var timerCallback; + + /** + 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() { + var 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() { + var value; + /** + 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() { + var originalTimeout; + 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. + */ + var myCustomEquality = function(first, second) { + /** + * 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". + */ +var 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, customEqualityTesters) { + /** + * 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, expected) { + /** + * `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. + */ + var result = {}; + + /** + * `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'}).toBeGoofy(); + }); + + it('can take an \'expected\' parameter', function() { + expect({hyuk: 'gawrsh is fun'}).toBeGoofy(' is fun'); + }); + + it('can be negated', function() { + expect({hyuk: 'this is fun'}).not.toBeGoofy(); + }); +}); diff --git a/test/mocha/mocha-browser-karma.ts b/test/mocha/mocha-browser-karma.ts new file mode 100644 index 000000000..74f802705 --- /dev/null +++ b/test/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/mocha/mocha-browser-test-entry-point.ts b/test/mocha/mocha-browser-test-entry-point.ts new file mode 100644 index 000000000..60a489419 --- /dev/null +++ b/test/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/mocha/mocha-node-test-entry-point.ts b/test/mocha/mocha-node-test-entry-point.ts new file mode 100644 index 000000000..e77cf2941 --- /dev/null +++ b/test/mocha/mocha-node-test-entry-point.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 + */ +import '../../lib/node/rollup-main'; +import '../../lib/mocha/mocha-node-checker'; +import '../../lib/testing/zone-testing'; +import './jasmine-bridge.spec'; \ No newline at end of file diff --git a/test/mocha-patch.spec.ts b/test/mocha/mocha-patch.spec.ts similarity index 74% rename from test/mocha-patch.spec.ts rename to test/mocha/mocha-patch.spec.ts index 54c47bcd0..a4f34ffaa 100644 --- a/test/mocha-patch.spec.ts +++ b/test/mocha/mocha-patch.spec.ts @@ -9,20 +9,22 @@ // 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'; +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; -ifEnvSupports('Mocha', function() { +import {ifEnvSupports} from '../test-util'; + +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 +42,10 @@ ifEnvSupports('Mocha', function() { throwOnAsync = true; } - beforeEach(() => beforeEachZone = Zone.current); + beforeEach(() => { + beforeEachZone = Zone.current; + console.log('beforeEach'); + }); it('should throw on async in describe', () => { expect(Zone.currentTask).toBeTruthy(); @@ -61,12 +66,14 @@ ifEnvSupports('Mocha', function() { }); }); - suite('Mocha TDD-style', () => { + /*suite('Mocha TDD-style', () => { + console.log('suite', this && this.constructor); let testZone: Zone = null; let beforeEachZone: Zone = null; let suiteSetupZone: Zone = null; suiteSetup(() => { + console.log('suiteSetup', this && this.constructor); suiteSetupZone = Zone.current; }); @@ -75,6 +82,7 @@ ifEnvSupports('Mocha', function() { }); test('should run in Zone with "test"-syntax in TDD-mode', () => { + console.log('test', this && this.constructor); testZone = Zone.current; expect(Zone.currentTask).toBeTruthy(); expect(testZone.name).toEqual('ProxyZone'); @@ -95,8 +103,7 @@ ifEnvSupports('Mocha', function() { suiteTeardown(() => { expect(suiteSetupZone).toBe(Zone.current); }); - - }); + });*/ describe('return promise', () => { let log: string[]; diff --git a/test/test-env-setup-mocha.ts b/test/test-env-setup-mocha.ts index 96058a0c4..ae0a74275 100644 --- a/test/test-env-setup-mocha.ts +++ b/test/test-env-setup-mocha.ts @@ -6,181 +6,4 @@ * 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 +// import '../lib/mocha'; \ No newline at end of file From 079363716af85809b73d6abc868e31a50f1ef236 Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Sun, 6 May 2018 22:14:08 +0900 Subject: [PATCH 02/11] add jasmine-mocha bridge --- lib/jasmine/jasmine-patch.ts | 237 +++++++++++++++++++++++ lib/jasmine/jasmine.ts | 228 +--------------------- lib/jasmine/mocha-bridge/mocha-bridge.ts | 9 + lib/jasmine/mocha-bridge/mocha.bdd.ts | 27 +++ lib/jasmine/mocha-bridge/mocha.ts | 16 ++ lib/mocha/mocha-patch.ts | 4 + test/main.ts | 21 +- test/test-env-setup-jasmine.ts | 1 - test/test-env-setup-mocha.ts | 4 +- 9 files changed, 311 insertions(+), 236 deletions(-) create mode 100644 lib/jasmine/jasmine-patch.ts create mode 100644 lib/jasmine/mocha-bridge/mocha-bridge.ts create mode 100644 lib/jasmine/mocha-bridge/mocha.bdd.ts create mode 100644 lib/jasmine/mocha-bridge/mocha.ts diff --git a/lib/jasmine/jasmine-patch.ts b/lib/jasmine/jasmine-patch.ts new file mode 100644 index 000000000..c6250a8a8 --- /dev/null +++ b/lib/jasmine/jasmine-patch.ts @@ -0,0 +1,237 @@ +/** + * @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'; +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__isMochaBridge']) { + // 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__; + + // 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); + }; + }); + + patchJasmineClock(jasmine, enableClockPatch); + /** + * 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; + 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); +}); diff --git a/lib/jasmine/jasmine.ts b/lib/jasmine/jasmine.ts index 8b2d1977e..65d515de2 100644 --- a/lib/jasmine/jasmine.ts +++ b/lib/jasmine/jasmine.ts @@ -5,229 +5,5 @@ * 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'; -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_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); - }; - }); - - patchJasmineClock(jasmine, enableClockPatch); - /** - * 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; - 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'; +import './mocha-bridge/mocha-bridge'; \ No newline at end of file diff --git a/lib/jasmine/mocha-bridge/mocha-bridge.ts b/lib/jasmine/mocha-bridge/mocha-bridge.ts new file mode 100644 index 000000000..faed6d5bb --- /dev/null +++ b/lib/jasmine/mocha-bridge/mocha-bridge.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'; diff --git a/lib/jasmine/mocha-bridge/mocha.bdd.ts b/lib/jasmine/mocha-bridge/mocha.bdd.ts new file mode 100644 index 000000000..a3c5b3636 --- /dev/null +++ b/lib/jasmine/mocha-bridge/mocha.bdd.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 + */ + +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: 'it', Mocha: 'it'}, {jasmine: 'it', Mocha: 'specify'}, {jasmine: 'it', Mocha: 'test'}, + {jasmine: 'describe', Mocha: 'suite'}, {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]; + } + }); +} \ 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..c62f75efe --- /dev/null +++ b/lib/jasmine/mocha-bridge/mocha.ts @@ -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 + */ +import {mappingBDD} from './mocha.bdd'; +Zone.__load_patch('mocha-bridge', (global: any) => { + if (global.Mocha) { + return; + } + global.Mocha = {}; + global.Mocha['__zone_symbol__isJasmineBridge'] = true; + mappingBDD(global.Mocha, global.jasmine, global); +}); \ No newline at end of file diff --git a/lib/mocha/mocha-patch.ts b/lib/mocha/mocha-patch.ts index fff6c8506..f60e404f4 100644 --- a/lib/mocha/mocha-patch.ts +++ b/lib/mocha/mocha-patch.ts @@ -15,6 +15,10 @@ Zone.__load_patch('Mocha', (global: any, Zone: ZoneType, api: _ZonePrivate) => { return; } + if (Mocha['__zone_symbol__isJasmineBridge']) { + return; + } + if (typeof Zone === 'undefined') { throw new Error('Missing Zone.js'); } diff --git a/test/main.ts b/test/main.ts index f9e5e6df8..69e25a3c5 100644 --- a/test/main.ts +++ b/test/main.ts @@ -32,15 +32,20 @@ if ((window as any)[(Zone as any).__symbol__('setTimeout')]) { } browserPatchedPromise.then(() => { + let testFrameworkPatch = typeof (window as any).Mocha !== 'undefined' ? + '/base/build/test/test-env-setup-mocha' : + '/base/build/test/test-env-setup-jasmine'; // Setup test environment const entryPoint = (window as any)['__zone_symbol__test_entry_point'] || '/base/build/test/browser_entry_point'; - System.import(entryPoint) - .then( - () => { - __karma__.start(); - }, - (error) => { - console.error(error.stack || error); - }); + System.import(testFrameworkPatch).then(() => { + System.import(entryPoint) + .then( + () => { + __karma__.start(); + }, + (error) => { + console.error(error.stack || error); + }); + }); }); diff --git a/test/test-env-setup-jasmine.ts b/test/test-env-setup-jasmine.ts index 8cee81df5..85ea4d9d9 100644 --- a/test/test-env-setup-jasmine.ts +++ b/test/test-env-setup-jasmine.ts @@ -7,4 +7,3 @@ */ (jasmine).DEFAULT_TIMEOUT_INTERVAL = 5000; -import '../lib/jasmine/jasmine'; diff --git a/test/test-env-setup-mocha.ts b/test/test-env-setup-mocha.ts index ae0a74275..8184c6e7b 100644 --- a/test/test-env-setup-mocha.ts +++ b/test/test-env-setup-mocha.ts @@ -6,4 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ -// import '../lib/mocha'; \ No newline at end of file +if (global && (global as any).Mocha) { + (global as any).Mocha.__zone_symbol__TIMEOUT = 5000; +} \ No newline at end of file From f1f56ef8e617c92cf4e47610a136f4ef002c3aee Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Mon, 7 May 2018 01:13:09 +0900 Subject: [PATCH 03/11] add more bdd API and timeout/pending support to mocha --- lib/jasmine/jasmine-patch.ts | 31 +++++++++++++++++++++++---- lib/jasmine/mocha-bridge/mocha.bdd.ts | 5 +++-- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/lib/jasmine/jasmine-patch.ts b/lib/jasmine/jasmine-patch.ts index c6250a8a8..518c5404a 100644 --- a/lib/jasmine/jasmine-patch.ts +++ b/lib/jasmine/jasmine-patch.ts @@ -61,16 +61,26 @@ Zone.__load_patch('jasmine', (global: any) => { jasmineEnv[symbol(methodName)] = originalJasmineFn; jasmineEnv[methodName] = function( description: string, specDefinitions: Function, timeout: number) { - arguments[1] = wrapTestInZone(specDefinitions); - return originalJasmineFn.apply(this, arguments); + if (typeof timeout !== 'number') { + timeout = (jasmine as any)[symbol('mochaTimeout')]; + } + const wrappedSpecDef = wrapTestInZone(specDefinitions); + return originalJasmineFn.apply( + this, + typeof timeout === 'number' ? [description, wrappedSpecDef, timeout] : + [description, wrappedSpecDef]); }; }); ['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); + if (typeof timeout !== 'number') { + timeout = (jasmine as any)[symbol('mochaTimeout')]; + } + const wrappedSpecDef = wrapTestInZone(specDefinitions); + return originalJasmineFn.apply( + this, typeof timeout === 'number' ? [wrappedSpecDef, timeout] : [wrappedSpecDef]); }; }); @@ -81,6 +91,19 @@ Zone.__load_patch('jasmine', (global: any) => { */ function wrapDescribeInZone(describeBody: Function): Function { return function() { + (jasmine as any)[symbol('mochaTimeout')] = null; + if (this && !this.timeout) { + this.timeout = function(timeout: number) { + (jasmine as any)[symbol('mochaTimeout')] = timeout; + } + } + if (this && !this.skip) { + this.skip = function() { + if (typeof global['pending'] === 'function') { + global['pending'](); + } + } + } return syncZone.run(describeBody, this, (arguments as any) as any[]); }; } diff --git a/lib/jasmine/mocha-bridge/mocha.bdd.ts b/lib/jasmine/mocha-bridge/mocha.bdd.ts index a3c5b3636..ac6be8099 100644 --- a/lib/jasmine/mocha-bridge/mocha.bdd.ts +++ b/lib/jasmine/mocha-bridge/mocha.bdd.ts @@ -9,9 +9,10 @@ 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: 'beforeAll', Mocha: 'suiteSetup'}, - {jasmine: 'afterAll', Mocha: 'suiteTeardown'}, + {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'} From cc57de271513b1fa58c003fd519a0326c5538265 Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Mon, 7 May 2018 11:39:46 +0900 Subject: [PATCH 04/11] change jasmine-bridge spec from js->ts --- package.json | 5 +- ...-bridge.spec.js => jasmine-bridge.spec.ts} | 140 +++++++++--------- 2 files changed, 70 insertions(+), 75 deletions(-) rename test/mocha/{jasmine-bridge.spec.js => jasmine-bridge.spec.ts} (93%) diff --git a/package.json b/package.json index 4d9bbaaf5..da9a05043 100644 --- a/package.json +++ b/package.json @@ -45,9 +45,8 @@ "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\"", - "prepare-mocha-jasmine-bridge": "npm run tsc && cp ./test/mocha/*.js ./build/test/mocha/", - "test-mocha-jasmine-bridge-browser": "npm run prepare-mocha-jasmine-bridge && concurrently \"npm run ws-server\" \"karma start karma-build-mocha-jasmine-bridge.conf.js\"", - "test-mocha-jasmine-bridge-node": "npm run prepare-mocha-jasmine-bridge && mocha ./build/test/mocha/mocha-node-test-entry-point.js", + "test-mocha-jasmine-bridge-browser": "npm run tsc && concurrently \"npm run ws-server\" \"karma start karma-build-mocha-jasmine-bridge.conf.js\"", + "test-mocha-jasmine-bridge-node": "npm run tsc && mocha ./build/test/mocha/mocha-node-test-entry-point.js", "serve": "python -m SimpleHTTPServer 8000" }, "repository": { diff --git a/test/mocha/jasmine-bridge.spec.js b/test/mocha/jasmine-bridge.spec.ts similarity index 93% rename from test/mocha/jasmine-bridge.spec.js rename to test/mocha/jasmine-bridge.spec.ts index 4ae3bcc0b..53c85d84d 100644 --- a/test/mocha/jasmine-bridge.spec.js +++ b/test/mocha/jasmine-bridge.spec.ts @@ -41,11 +41,11 @@ describe('A suite', function() { /** ### 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 variables declared in a `describe` are + 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() { - var a; + let a; it('and so is a spec', function() { a = true; @@ -90,28 +90,28 @@ describe('The \'toBe\' matcher compares with ===', function() { describe('Included matchers:', function() { it('The \'toBe\' matcher compares with ===', function() { - var a = 12; - var b = a; + 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 variables', function() { - var a = 12; + it('works for simple literals and letiables', function() { + let a = 12; expect(a).toEqual(12); }); it('should work for objects', function() { - var foo = {a: 12, b: 34}; - var bar = {a: 12, b: 34}; + 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() { - var message = 'foo bar baz'; + let message = 'foo bar baz'; expect(message).toMatch(/bar/); expect(message).toMatch('bar'); @@ -119,22 +119,22 @@ describe('Included matchers:', function() { }); it('The \'toBeDefined\' matcher compares against `undefined`', function() { - var a = {foo: 'foo'}; + let a: any = {foo: 'foo'}; expect(a.foo).toBeDefined(); expect(a.bar).not.toBeDefined(); }); it('The `toBeUndefined` matcher compares against `undefined`', function() { - var a = {foo: 'foo'}; + let a: any = {foo: 'foo'}; expect(a.foo).not.toBeUndefined(); expect(a.bar).toBeUndefined(); }); it('The \'toBeNull\' matcher compares against null', function() { - var a = null; - var foo = 'foo'; + let a = null; + let foo = 'foo'; expect(null).toBeNull(); expect(a).toBeNull(); @@ -142,14 +142,14 @@ describe('Included matchers:', function() { }); it('The \'toBeTruthy\' matcher is for boolean casting testing', function() { - var a, foo = 'foo'; + let a, foo = 'foo'; expect(foo).toBeTruthy(); expect(a).not.toBeTruthy(); }); it('The \'toBeFalsy\' matcher is for boolean casting testing', function() { - var a, foo = 'foo'; + let a, foo = 'foo'; expect(a).toBeFalsy(); expect(foo).not.toBeFalsy(); @@ -157,14 +157,14 @@ describe('Included matchers:', function() { describe('The \'toContain\' matcher', function() { it('works for finding an item in an Array', function() { - var a = ['foo', 'bar', 'baz']; + let a = ['foo', 'bar', 'baz']; expect(a).toContain('bar'); expect(a).not.toContain('quux'); }); it('also works for finding a substring', function() { - var a = 'foo bar baz'; + let a = 'foo bar baz'; expect(a).toContain('bar'); expect(a).not.toContain('quux'); @@ -172,44 +172,40 @@ describe('Included matchers:', function() { }); it('The \'toBeLessThan\' matcher is for mathematical comparisons', function() { - var pi = 3.1415926, e = 2.78; + 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() { - var pi = 3.1415926, e = 2.78; + 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() { - var pi = 3.1415926, e = 2.78; + 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() { - var foo = function() { + let foo = function() { return 1 + 2; }; - var bar = function() { - return a + 1; - }; - var baz = function() { + let baz = function() { throw 'what'; }; expect(foo).not.toThrow(); - expect(bar).toThrow(); expect(baz).toThrow('what'); }); it('The \'toThrowError\' matcher is for testing a specific thrown exception', function() { - var foo = function() { + let foo = function() { throw new TypeError('foo bar baz'); }; @@ -229,14 +225,14 @@ describe('Included matchers:', function() { */ describe('A spec', function() { it('is just a function, so it can contain any code', function() { - var foo = 0; + let foo = 0; foo += 1; expect(foo).toEqual(1); }); it('can have more than one expectation', function() { - var foo = 0; + let foo = 0; foo += 1; expect(foo).toEqual(1); @@ -255,12 +251,12 @@ describe('A spec', function() { 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 variable under test is defined + 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 variable before continuing. + `beforeEach` function. The `afterEach` function resets the letiable before continuing. */ describe('A spec using beforeEach and afterEach', function() { - var foo = 0; + let foo = 0; beforeEach(function() { foo += 1; @@ -292,7 +288,7 @@ describe('A spec using beforeEach and afterEach', function() { * is easy to accidentally leak state between your specs so that they erroneously pass or fail. */ describe('A spec using beforeAll and afterAll', function() { - var foo; + let foo: any; beforeAll(function() { foo = 1; @@ -315,7 +311,7 @@ describe('A spec using beforeAll and afterAll', function() { // /** // ### The `this` keyword -// Another way to share variables between a `beforeEach`, `it`, and `afterEach` is through the +// 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`. // */ @@ -344,7 +340,7 @@ describe('A spec', function() { the `afterEach` functions similarly. */ describe('A spec', function() { - var foo; + let foo: any; beforeEach(function() { foo = 0; @@ -365,7 +361,7 @@ describe('A spec', function() { }); describe('nested inside a second describe', function() { - var bar; + let bar: any; beforeEach(function() { bar = 1; @@ -383,7 +379,7 @@ describe('A spec', function() { skipped when run and thus their results will not appear in the results. */ xdescribe('A spec', function() { - var foo; + let foo: any; beforeEach(function() { foo = 0; @@ -438,11 +434,11 @@ describe('Pending specs', function() { */ describe('A spy', function() { - var foo, bar = null; + let foo: any, bar: any = null; beforeEach(function() { foo = { - setBar: function(value) { + setBar: function(value: any) { bar = value; } }; @@ -473,11 +469,11 @@ describe('A spy', function() { addition it will delegate to the actual implementation. */ describe('A spy, when configured to call through', function() { - var foo, bar, fetchedBar; + let foo: any, bar: any, fetchedBar: any; beforeEach(function() { foo = { - setBar: function(value) { + setBar: function(value: any) { bar = value; }, getBar: function() { @@ -510,11 +506,11 @@ describe('A spy, when configured to call through', function() { value. */ describe('A spy, when configured to fake a return value', function() { - var foo, bar, fetchedBar; + let foo: any, bar: any, fetchedBar: any; beforeEach(function() { foo = { - setBar: function(value) { + setBar: function(value: any) { bar = value; }, getBar: function() { @@ -547,11 +543,11 @@ describe('A spy, when configured to fake a return value', function() { function. */ describe('A spy, when configured with an alternate implementation', function() { - var foo, bar, fetchedBar; + let foo: any, bar: any, fetchedBar: any; beforeEach(function() { foo = { - setBar: function(value) { + setBar: function(value: any) { bar = value; }, getBar: function() { @@ -564,7 +560,7 @@ describe('A spy, when configured with an alternate implementation', function() { as * well */ - spyOn(foo, 'getBar').and.callFake(function(arguments, can, be, received) { + spyOn(foo, 'getBar').and.callFake(function(arguments: any, can: any, be: any, received: any) { return 1001; }); @@ -592,11 +588,11 @@ describe('A spy, when configured with an alternate implementation', function() { as an error. */ describe('A spy, when configured to throw an error', function() { - var foo, bar; + let foo: any, bar: any; beforeEach(function() { foo = { - setBar: function(value) { + setBar: function(value: any) { bar = value; } }; @@ -617,11 +613,11 @@ describe('A spy, when configured to throw an error', function() { time with `and.stub`. */ describe('A spy', function() { - var foo, bar = null; + let foo: any, bar: any = null; beforeEach(function() { foo = { - setBar: function(value) { + setBar: function(value: any) { bar = value; } }; @@ -647,11 +643,11 @@ describe('A spy', function() { Every call to a spy is tracked and exposed on the `calls` property. */ describe('A spy', function() { - var foo, bar = null; + let foo: any, bar: any = null; beforeEach(function() { foo = { - setBar: function(value) { + setBar: function(value: any) { bar = value; } }; @@ -743,9 +739,9 @@ describe('A spy', function() { * set to the value of `this` when the spy was called. */ it('tracks the context', function() { - var spy = jasmine.createSpy('spy'); - var baz = {fn: spy}; - var quux = {fn: spy}; + let spy = jasmine.createSpy('spy'); + let baz = {fn: spy}; + let quux = {fn: spy}; baz.fn(123); quux.fn(456); @@ -776,7 +772,7 @@ describe('A spy', function() { it. Spies are JavaScript objects and can be used as such. */ describe('A spy, when created manually', function() { - var whatAmI; + let whatAmI: any; beforeEach(function() { whatAmI = jasmine.createSpy('whatAmI'); @@ -811,7 +807,7 @@ describe('A spy, when created manually', function() { strings. It returns an object that has a property for each string that is a spy. */ describe('Multiple spies, when created manually', function() { - var tape; + let tape: any; beforeEach(function() { tape = jasmine.createSpyObj('tape', ['play', 'pause', 'stop', 'rewind']); @@ -854,7 +850,7 @@ describe('jasmine.any', function() { describe('when used with a spy', function() { it('is useful for comparing arguments', function() { - var foo = jasmine.createSpy('foo'); + let foo = jasmine.createSpy('foo'); foo(12, function() { return true; }); @@ -871,7 +867,7 @@ describe('jasmine.any', function() { */ describe('jasmine.objectContaining', function() { - var foo; + let foo: any; beforeEach(function() { foo = {a: 1, b: 2, bar: 'baz'}; @@ -884,7 +880,7 @@ describe('jasmine.objectContaining', function() { describe('when used with a spy', function() { it('is useful for comparing arguments', function() { - var callback = jasmine.createSpy('callback'); + let callback = jasmine.createSpy('callback'); callback({bar: 'baz'}); @@ -901,7 +897,7 @@ describe('jasmine.objectContaining', function() { The Jasmine Clock is available for testing time dependent code. */ describe('Manually ticking the Jasmine Clock', function() { - var timerCallback; + let timerCallback: any; /** It is installed with a call to `jasmine.clock().install` in a spec or suite that needs to @@ -962,7 +958,7 @@ describe('Manually ticking the Jasmine Clock', function() { */ describe('Mocking the Date object', function() { it('mocks the Date object and sets it to a given time', function() { - var baseTime = new Date(2013, 9, 23); + 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); @@ -979,7 +975,7 @@ describe('Manually ticking the Jasmine Clock', function() { Jasmine also has support for running specs that require testing asynchronous operations. */ describe('Asynchronous specs', function() { - var value; + 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. @@ -1014,7 +1010,7 @@ describe('Asynchronous specs', function() { set globally, outside of any given `describe`. */ describe('long asynchronous specs', function() { - var originalTimeout; + let originalTimeout: any; beforeEach(function() { originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL; jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000; @@ -1084,7 +1080,7 @@ 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. */ - var myCustomEquality = function(first, second) { + 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 @@ -1138,7 +1134,7 @@ describe('custom equality', function() { /** * This object has a custom matcher named "toBeGoofy". */ -var customMatchers = { +let customMatchers = { /** * ## Matcher Factories @@ -1150,7 +1146,7 @@ var customMatchers = { * * [mu.js]: https://github.com/pivotal/jasmine/blob/master/src/core/matchers/matchersUtil.js */ - toBeGoofy: function(util, customEqualityTesters) { + 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. @@ -1162,7 +1158,7 @@ var customMatchers = { * 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, expected) { + compare: function(actual: any, expected: any) { /** * `toBeGoofy` takes an optional `expected` argument, so define it here if not passed in. */ @@ -1179,7 +1175,7 @@ var customMatchers = { * called/chained with `.not`, the expectation will negate this to determine whether the * expectation is met. */ - var result = {}; + let result: any = {}; /** * `toBeGoofy` tests for equality of the actual's `hyuk` property to see if it matches the @@ -1242,14 +1238,14 @@ describe('Custom matcher: \'toBeGoofy\'', function() { * Once a custom matcher is registered with Jasmine, it is available on any expectation. */ it('is available on an expectation', function() { - expect({hyuk: 'gawrsh'}).toBeGoofy(); + (expect({hyuk: 'gawrsh'}) as any).toBeGoofy(); }); it('can take an \'expected\' parameter', function() { - expect({hyuk: 'gawrsh is fun'}).toBeGoofy(' is fun'); + (expect({hyuk: 'gawrsh is fun'}) as any).toBeGoofy(' is fun'); }); it('can be negated', function() { - expect({hyuk: 'this is fun'}).not.toBeGoofy(); + (expect({hyuk: 'this is fun'}) as any).not.toBeGoofy(); }); }); From e7d43fed2ab8462e9a4e0a6a122f73732470a7e9 Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Tue, 8 May 2018 00:03:35 +0900 Subject: [PATCH 05/11] refactor(test): refactor folder of test/build/ci --- .travis.yml | 17 +- README.md | 8 +- MODULE.md => doc/design/MODULE.md | 0 .../design/NON-STANDARD-APIS.md | 0 .../design/STANDARD-APIS.md | 0 doc/{ => lifecycle/png}/error.png | Bin doc/{ => lifecycle/png}/eventtask.png | Bin doc/{ => lifecycle/png}/microtask.png | Bin .../png}/non-periodical-macrotask.png | Bin doc/{ => lifecycle/png}/override-task.png | Bin .../png}/periodical-macrotask.png | Bin doc/{ => lifecycle/png}/reschedule-task.png | Bin doc/{ => lifecycle/puml}/error.puml | 0 doc/{ => lifecycle/puml}/eventtask.puml | 0 doc/{ => lifecycle/puml}/microtask.puml | 0 .../puml}/non-periodical-macrotask.puml | 0 doc/{ => lifecycle/puml}/override-task.puml | 0 .../puml}/periodical-macrotask.puml | 0 doc/{ => lifecycle/puml}/reschedule-task.puml | 0 doc/{ => lifecycle}/task.md | 0 gulpfile.js | 13 +- karma-dist-sauce-jasmine3.conf.js | 12 - lib/jasmine/jasmine-patch.ts | 30 +- lib/jasmine/mocha-bridge/mocha.bdd.ts | 17 + lib/jasmine/mocha-bridge/mocha.ts | 3 +- lib/mocha/jasmine-bridge/jasmine.bdd.ts | 2 +- lib/mocha/jasmine-bridge/jasmine.ts | 1 + lib/mocha/mocha-patch.ts | 2 +- package.json | 26 +- promise.finally.spec.js | 345 --------------- scripts/closure/closure_flagfile | 2 +- .../size/check-file-size.js | 0 .../size/file-size-limit.json | 0 test/browser-zone-setup.ts | 22 - test/browser_entry_point.ts | 26 -- test/common_tests.ts | 26 -- test/env/browser/browser-zone-setup.ts | 22 + test/env/browser/browser_entry_point.ts | 25 ++ test/{ => env/browser}/main.ts | 10 +- test/env/common/common_tests.ts | 28 ++ test/{ => env/config}/saucelabs.js | 0 .../test-env-setup-jasmine-no-patch-clock.ts | 0 .../config}/test-env-setup-jasmine.ts | 0 test/{ => env/config}/test-env-setup-mocha.ts | 0 test/env/node/node_bluebird_entry_point.ts | 32 ++ test/{ => env/node}/node_entry_point.ts | 22 +- test/env/node/node_tests.ts | 15 + .../{ => env/polyfills}/test_fake_polyfill.ts | 0 test/{ => env/polyfills}/wtf_mock.ts | 0 test/{ => env/websocket}/ws-client.js | 0 test/{ => env/websocket}/ws-server.js | 0 .../websocket}/ws-webworker-context.ts | 6 +- .../websocket}/zone_worker_entry_point.ts | 2 +- .../ci/build/karma-build-sauce-mocha.conf.js | 6 +- .../karma-build-sauce-selenium3-mocha.conf.js | 6 +- .../ci/dist/karma-dist-sauce-jasmine.conf.js | 6 +- .../ci/dist/karma-dist-sauce-jasmine3.conf.js | 16 + ...karma-dist-sauce-selenium3-jasmine.conf.js | 6 +- .../karma/ci/sauce-selenium3.conf.js | 29 +- sauce.conf.js => test/karma/ci/sauce.conf.js | 2 +- .../karma-build-jasmine-phantomjs.conf.js | 2 +- .../common/build/karma-build-jasmine.conf.js | 3 +- .../karma-build-mocha-jasmine-bridge.conf.js | 10 +- .../common/build/karma-build-mocha.conf.js | 0 .../karma/common/build/karma-build.conf.js | 10 +- .../common/dist/karma-dist-jasmine.conf.js | 2 +- .../common/dist/karma-dist-mocha.conf.js | 0 .../karma/common/dist/karma-dist.conf.js | 9 +- .../karma/common/karma-base.conf.js | 32 +- test/node_bluebird_entry_point.ts | 32 -- test/node_tests.ts | 15 - test/rxjs/rxjs.fromEvent.spec.ts | 121 ------ test/{ => spec}/browser/FileReader.spec.ts | 0 test/{ => spec}/browser/HTMLImports.spec.ts | 0 test/{ => spec}/browser/MediaQuery.spec.ts | 4 +- .../browser/MutationObserver.spec.ts | 0 test/{ => spec}/browser/Notification.spec.ts | 4 +- test/{ => spec}/browser/WebSocket.spec.ts | 4 +- test/{ => spec}/browser/Worker.spec.ts | 2 +- .../{ => spec}/browser/XMLHttpRequest.spec.ts | 0 test/{ => spec}/browser/browser.spec.ts | 8 +- .../browser/define-property.spec.ts | 0 test/{ => spec}/browser/element.spec.ts | 0 .../browser/geolocation.spec.manual.ts | 0 .../browser/registerElement.spec.ts | 0 .../browser/requestAnimationFrame.spec.ts | 0 test/{ => spec}/closure/zone.closure.ts | 0 test/{ => spec}/common/Error.spec.ts | 0 test/{ => spec}/common/Promise.spec.ts | 78 ++-- test/{ => spec}/common/microtasks.spec.ts | 0 test/{ => spec}/common/setInterval.spec.ts | 4 +- test/{ => spec}/common/setTimeout.spec.ts | 3 +- test/{ => spec}/common/task.spec.ts | 0 test/{ => spec}/common/toString.spec.ts | 5 +- test/{ => spec}/common/util.spec.ts | 3 +- test/{ => spec}/common/zone.spec.ts | 20 +- test/{ => spec}/extra/bluebird.spec.ts | 97 +++-- test/{ => spec}/extra/cordova.spec.ts | 0 test/{ => spec/jasmine}/jasmine-patch.spec.ts | 16 +- test/{ => spec}/mocha/jasmine-bridge.spec.ts | 2 +- test/{ => spec}/mocha/mocha-browser-karma.ts | 0 .../mocha/mocha-browser-test-entry-point.ts | 0 .../mocha/mocha-node-test-entry-point.ts | 6 +- test/{ => spec}/mocha/mocha-patch.spec.ts | 20 +- test/{ => spec}/node/Error.spec.ts | 0 test/{ => spec}/node/console.spec.ts | 0 test/{ => spec}/node/crypto.spec.ts | 0 test/{ => spec}/node/events.spec.ts | 0 test/{ => spec}/node/fs.spec.ts | 0 test/{ => spec}/node/http.spec.ts | 0 test/{ => spec}/node/process.spec.ts | 24 +- test/{ => spec}/patch/IndexedDB.spec.js | 0 .../spec/promise/promise-adapter.js | 4 +- test/spec/promise/promise.finally.spec.js | 392 ++++++++++++++++++ .../rxjs/rxjs.Observable.audit.spec.ts | 0 .../rxjs/rxjs.Observable.buffer.spec.ts | 0 .../rxjs/rxjs.Observable.catch.spec.ts | 0 .../rxjs/rxjs.Observable.collection.spec.ts | 0 .../rxjs/rxjs.Observable.combine.spec.ts | 0 .../rxjs/rxjs.Observable.concat.spec.ts | 0 .../rxjs/rxjs.Observable.count.spec.ts | 0 .../rxjs/rxjs.Observable.debounce.spec.ts | 0 .../rxjs/rxjs.Observable.default.spec.ts | 0 .../rxjs/rxjs.Observable.delay.spec.ts | 0 .../rxjs/rxjs.Observable.distinct.spec.ts | 0 .../rxjs/rxjs.Observable.do.spec.ts | 0 .../rxjs/rxjs.Observable.map.spec.ts | 0 .../rxjs/rxjs.Observable.merge.spec.ts | 0 .../rxjs/rxjs.Observable.multicast.spec.ts | 0 .../rxjs/rxjs.Observable.notification.spec.ts | 0 .../rxjs/rxjs.Observable.race.spec.ts | 0 .../rxjs/rxjs.Observable.sample.spec.ts | 0 .../rxjs/rxjs.Observable.take.spec.ts | 0 .../rxjs/rxjs.Observable.timeout.spec.ts | 0 .../rxjs/rxjs.Observable.window.spec.ts | 0 test/{ => spec}/rxjs/rxjs.asap.spec.ts | 0 .../{ => spec}/rxjs/rxjs.bindCallback.spec.ts | 0 .../rxjs/rxjs.bindNodeCallback.spec.ts | 0 .../rxjs/rxjs.combineLatest.spec.ts | 0 test/{ => spec}/rxjs/rxjs.common.spec.ts | 0 test/{ => spec}/rxjs/rxjs.concat.spec.ts | 0 test/{ => spec}/rxjs/rxjs.defer.spec.ts | 0 test/{ => spec}/rxjs/rxjs.empty.spec.ts | 0 test/{ => spec}/rxjs/rxjs.forkjoin.spec.ts | 0 test/{ => spec}/rxjs/rxjs.from.spec.ts | 0 test/spec/rxjs/rxjs.fromEvent.spec.ts | 105 +++++ test/{ => spec}/rxjs/rxjs.fromPromise.spec.ts | 0 test/{ => spec}/rxjs/rxjs.interval.spec.ts | 0 test/{ => spec}/rxjs/rxjs.merge.spec.ts | 0 test/{ => spec}/rxjs/rxjs.never.spec.ts | 0 test/{ => spec}/rxjs/rxjs.of.spec.ts | 0 test/{ => spec}/rxjs/rxjs.range.spec.ts | 0 test/{ => spec}/rxjs/rxjs.spec.ts | 2 +- test/{ => spec}/rxjs/rxjs.throw.spec.ts | 0 test/{ => spec}/rxjs/rxjs.timer.spec.ts | 0 test/{ => spec}/rxjs/rxjs.zip.spec.ts | 0 test/{ => spec}/test-util.ts | 0 .../spec/webdriver/simple-server.js | 12 +- test/{ => spec}/webdriver/test.html | 2 +- test/{ => spec}/webdriver/test.js | 0 test/{ => spec}/webdriver/test.sauce.js | 10 +- test/{ => spec}/zone-spec/async-test.spec.ts | 0 .../zone-spec/fake-async-test.spec.ts | 13 +- .../zone-spec/long-stack-trace-zone.spec.ts | 15 +- test/{ => spec}/zone-spec/proxy.spec.ts | 0 test/{ => spec}/zone-spec/sync-test.spec.ts | 0 .../zone-spec/task-tracking.spec.ts | 0 167 files changed, 974 insertions(+), 902 deletions(-) rename MODULE.md => doc/design/MODULE.md (100%) rename NON-STANDARD-APIS.md => doc/design/NON-STANDARD-APIS.md (100%) rename STANDARD-APIS.md => doc/design/STANDARD-APIS.md (100%) rename doc/{ => lifecycle/png}/error.png (100%) rename doc/{ => lifecycle/png}/eventtask.png (100%) rename doc/{ => lifecycle/png}/microtask.png (100%) rename doc/{ => lifecycle/png}/non-periodical-macrotask.png (100%) rename doc/{ => lifecycle/png}/override-task.png (100%) rename doc/{ => lifecycle/png}/periodical-macrotask.png (100%) rename doc/{ => lifecycle/png}/reschedule-task.png (100%) rename doc/{ => lifecycle/puml}/error.puml (100%) rename doc/{ => lifecycle/puml}/eventtask.puml (100%) rename doc/{ => lifecycle/puml}/microtask.puml (100%) rename doc/{ => lifecycle/puml}/non-periodical-macrotask.puml (100%) rename doc/{ => lifecycle/puml}/override-task.puml (100%) rename doc/{ => lifecycle/puml}/periodical-macrotask.puml (100%) rename doc/{ => lifecycle/puml}/reschedule-task.puml (100%) rename doc/{ => lifecycle}/task.md (100%) delete mode 100644 karma-dist-sauce-jasmine3.conf.js delete mode 100644 promise.finally.spec.js rename check-file-size.js => scripts/size/check-file-size.js (100%) rename file-size-limit.json => scripts/size/file-size-limit.json (100%) delete mode 100644 test/browser-zone-setup.ts delete mode 100644 test/browser_entry_point.ts delete mode 100644 test/common_tests.ts create mode 100644 test/env/browser/browser-zone-setup.ts create mode 100644 test/env/browser/browser_entry_point.ts rename test/{ => env/browser}/main.ts (78%) create mode 100644 test/env/common/common_tests.ts rename test/{ => env/config}/saucelabs.js (100%) rename test/{ => env/config}/test-env-setup-jasmine-no-patch-clock.ts (100%) rename test/{ => env/config}/test-env-setup-jasmine.ts (100%) rename test/{ => env/config}/test-env-setup-mocha.ts (100%) create mode 100644 test/env/node/node_bluebird_entry_point.ts rename test/{ => env/node}/node_entry_point.ts (56%) create mode 100644 test/env/node/node_tests.ts rename test/{ => env/polyfills}/test_fake_polyfill.ts (100%) rename test/{ => env/polyfills}/wtf_mock.ts (100%) rename test/{ => env/websocket}/ws-client.js (100%) rename test/{ => env/websocket}/ws-server.js (100%) rename test/{ => env/websocket}/ws-webworker-context.ts (58%) rename test/{ => env/websocket}/zone_worker_entry_point.ts (92%) rename karma-build-sauce-mocha.conf.js => test/karma/ci/build/karma-build-sauce-mocha.conf.js (60%) rename karma-build-sauce-selenium3-mocha.conf.js => test/karma/ci/build/karma-build-sauce-selenium3-mocha.conf.js (56%) rename karma-dist-sauce-jasmine.conf.js => test/karma/ci/dist/karma-dist-sauce-jasmine.conf.js (57%) create mode 100644 test/karma/ci/dist/karma-dist-sauce-jasmine3.conf.js rename karma-dist-sauce-selenium3-jasmine.conf.js => test/karma/ci/dist/karma-dist-sauce-selenium3-jasmine.conf.js (58%) rename sauce-selenium3.conf.js => test/karma/ci/sauce-selenium3.conf.js (57%) rename sauce.conf.js => test/karma/ci/sauce.conf.js (98%) rename karma-build-jasmine-phantomjs.conf.js => test/karma/common/build/karma-build-jasmine-phantomjs.conf.js (86%) rename karma-build-jasmine.conf.js => test/karma/common/build/karma-build-jasmine.conf.js (77%) rename karma-build-mocha-jasmine-bridge.conf.js => test/karma/common/build/karma-build-mocha-jasmine-bridge.conf.js (60%) rename karma-build-mocha.conf.js => test/karma/common/build/karma-build-mocha.conf.js (100%) rename karma-build.conf.js => test/karma/common/build/karma-build.conf.js (57%) rename karma-dist-jasmine.conf.js => test/karma/common/dist/karma-dist-jasmine.conf.js (78%) rename karma-dist-mocha.conf.js => test/karma/common/dist/karma-dist-mocha.conf.js (100%) rename karma-dist.conf.js => test/karma/common/dist/karma-dist.conf.js (68%) rename karma-base.conf.js => test/karma/common/karma-base.conf.js (68%) delete mode 100644 test/node_bluebird_entry_point.ts delete mode 100644 test/node_tests.ts delete mode 100644 test/rxjs/rxjs.fromEvent.spec.ts rename test/{ => spec}/browser/FileReader.spec.ts (100%) rename test/{ => spec}/browser/HTMLImports.spec.ts (100%) rename test/{ => spec}/browser/MediaQuery.spec.ts (89%) rename test/{ => spec}/browser/MutationObserver.spec.ts (100%) rename test/{ => spec}/browser/Notification.spec.ts (89%) rename test/{ => spec}/browser/WebSocket.spec.ts (95%) rename test/{ => spec}/browser/Worker.spec.ts (95%) rename test/{ => spec}/browser/XMLHttpRequest.spec.ts (100%) rename test/{ => spec}/browser/browser.spec.ts (99%) rename test/{ => spec}/browser/define-property.spec.ts (100%) rename test/{ => spec}/browser/element.spec.ts (100%) rename test/{ => spec}/browser/geolocation.spec.manual.ts (100%) rename test/{ => spec}/browser/registerElement.spec.ts (100%) rename test/{ => spec}/browser/requestAnimationFrame.spec.ts (100%) rename test/{ => spec}/closure/zone.closure.ts (100%) rename test/{ => spec}/common/Error.spec.ts (100%) rename test/{ => spec}/common/Promise.spec.ts (90%) rename test/{ => spec}/common/microtasks.spec.ts (100%) rename test/{ => spec}/common/setInterval.spec.ts (98%) rename test/{ => spec}/common/setTimeout.spec.ts (98%) rename test/{ => spec}/common/task.spec.ts (100%) rename test/{ => spec}/common/toString.spec.ts (94%) rename test/{ => spec}/common/util.spec.ts (99%) rename test/{ => spec}/common/zone.spec.ts (97%) rename test/{ => spec}/extra/bluebird.spec.ts (91%) rename test/{ => spec}/extra/cordova.spec.ts (100%) rename test/{ => spec/jasmine}/jasmine-patch.spec.ts (80%) rename test/{ => spec}/mocha/jasmine-bridge.spec.ts (99%) rename test/{ => spec}/mocha/mocha-browser-karma.ts (100%) rename test/{ => spec}/mocha/mocha-browser-test-entry-point.ts (100%) rename test/{ => spec}/mocha/mocha-node-test-entry-point.ts (63%) rename test/{ => spec}/mocha/mocha-patch.spec.ts (92%) rename test/{ => spec}/node/Error.spec.ts (100%) rename test/{ => spec}/node/console.spec.ts (100%) rename test/{ => spec}/node/crypto.spec.ts (100%) rename test/{ => spec}/node/events.spec.ts (100%) rename test/{ => spec}/node/fs.spec.ts (100%) rename test/{ => spec}/node/http.spec.ts (100%) rename test/{ => spec}/node/process.spec.ts (83%) rename test/{ => spec}/patch/IndexedDB.spec.js (100%) rename promise-adapter.js => test/spec/promise/promise-adapter.js (83%) create mode 100644 test/spec/promise/promise.finally.spec.js rename test/{ => spec}/rxjs/rxjs.Observable.audit.spec.ts (100%) rename test/{ => spec}/rxjs/rxjs.Observable.buffer.spec.ts (100%) rename test/{ => spec}/rxjs/rxjs.Observable.catch.spec.ts (100%) rename test/{ => spec}/rxjs/rxjs.Observable.collection.spec.ts (100%) rename test/{ => spec}/rxjs/rxjs.Observable.combine.spec.ts (100%) rename test/{ => spec}/rxjs/rxjs.Observable.concat.spec.ts (100%) rename test/{ => spec}/rxjs/rxjs.Observable.count.spec.ts (100%) rename test/{ => spec}/rxjs/rxjs.Observable.debounce.spec.ts (100%) rename test/{ => spec}/rxjs/rxjs.Observable.default.spec.ts (100%) rename test/{ => spec}/rxjs/rxjs.Observable.delay.spec.ts (100%) rename test/{ => spec}/rxjs/rxjs.Observable.distinct.spec.ts (100%) rename test/{ => spec}/rxjs/rxjs.Observable.do.spec.ts (100%) rename test/{ => spec}/rxjs/rxjs.Observable.map.spec.ts (100%) rename test/{ => spec}/rxjs/rxjs.Observable.merge.spec.ts (100%) rename test/{ => spec}/rxjs/rxjs.Observable.multicast.spec.ts (100%) rename test/{ => spec}/rxjs/rxjs.Observable.notification.spec.ts (100%) rename test/{ => spec}/rxjs/rxjs.Observable.race.spec.ts (100%) rename test/{ => spec}/rxjs/rxjs.Observable.sample.spec.ts (100%) rename test/{ => spec}/rxjs/rxjs.Observable.take.spec.ts (100%) rename test/{ => spec}/rxjs/rxjs.Observable.timeout.spec.ts (100%) rename test/{ => spec}/rxjs/rxjs.Observable.window.spec.ts (100%) rename test/{ => spec}/rxjs/rxjs.asap.spec.ts (100%) rename test/{ => spec}/rxjs/rxjs.bindCallback.spec.ts (100%) rename test/{ => spec}/rxjs/rxjs.bindNodeCallback.spec.ts (100%) rename test/{ => spec}/rxjs/rxjs.combineLatest.spec.ts (100%) rename test/{ => spec}/rxjs/rxjs.common.spec.ts (100%) rename test/{ => spec}/rxjs/rxjs.concat.spec.ts (100%) rename test/{ => spec}/rxjs/rxjs.defer.spec.ts (100%) rename test/{ => spec}/rxjs/rxjs.empty.spec.ts (100%) rename test/{ => spec}/rxjs/rxjs.forkjoin.spec.ts (100%) rename test/{ => spec}/rxjs/rxjs.from.spec.ts (100%) create mode 100644 test/spec/rxjs/rxjs.fromEvent.spec.ts rename test/{ => spec}/rxjs/rxjs.fromPromise.spec.ts (100%) rename test/{ => spec}/rxjs/rxjs.interval.spec.ts (100%) rename test/{ => spec}/rxjs/rxjs.merge.spec.ts (100%) rename test/{ => spec}/rxjs/rxjs.never.spec.ts (100%) rename test/{ => spec}/rxjs/rxjs.of.spec.ts (100%) rename test/{ => spec}/rxjs/rxjs.range.spec.ts (100%) rename test/{ => spec}/rxjs/rxjs.spec.ts (98%) rename test/{ => spec}/rxjs/rxjs.throw.spec.ts (100%) rename test/{ => spec}/rxjs/rxjs.timer.spec.ts (100%) rename test/{ => spec}/rxjs/rxjs.zip.spec.ts (100%) rename test/{ => spec}/test-util.ts (100%) rename simple-server.js => test/spec/webdriver/simple-server.js (80%) rename test/{ => spec}/webdriver/test.html (65%) rename test/{ => spec}/webdriver/test.js (100%) rename test/{ => spec}/webdriver/test.sauce.js (95%) rename test/{ => spec}/zone-spec/async-test.spec.ts (100%) rename test/{ => spec}/zone-spec/fake-async-test.spec.ts (99%) rename test/{ => spec}/zone-spec/long-stack-trace-zone.spec.ts (93%) rename test/{ => spec}/zone-spec/proxy.spec.ts (100%) rename test/{ => spec}/zone-spec/sync-test.spec.ts (100%) rename test/{ => spec}/zone-spec/task-tracking.spec.ts (100%) diff --git a/.travis.yml b/.travis.yml index 7add517ea..28ef8cf91 100644 --- a/.travis.yml +++ b/.travis.yml @@ -35,20 +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 - yarn test-mocha-jasmine-bridge-node - - yarn test-mocha-jasmine-bridge-browser - - 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 + - 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/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..ee0956804 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -404,9 +404,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 +432,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 +497,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 +509,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/lib/jasmine/jasmine-patch.ts b/lib/jasmine/jasmine-patch.ts index 518c5404a..f11ed2843 100644 --- a/lib/jasmine/jasmine-patch.ts +++ b/lib/jasmine/jasmine-patch.ts @@ -8,6 +8,7 @@ '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) @@ -24,7 +25,7 @@ Zone.__load_patch('jasmine', (global: any) => { // not using jasmine, just return; return; } - if ((jasmine as any)['__zone_symbol__isMochaBridge']) { + if ((jasmine as any)['__zone_symbol__isBridge']) { // jasmine is a mock bridge return; } @@ -61,9 +62,6 @@ Zone.__load_patch('jasmine', (global: any) => { jasmineEnv[symbol(methodName)] = originalJasmineFn; jasmineEnv[methodName] = function( description: string, specDefinitions: Function, timeout: number) { - if (typeof timeout !== 'number') { - timeout = (jasmine as any)[symbol('mochaTimeout')]; - } const wrappedSpecDef = wrapTestInZone(specDefinitions); return originalJasmineFn.apply( this, @@ -71,13 +69,10 @@ Zone.__load_patch('jasmine', (global: any) => { [description, wrappedSpecDef]); }; }); - ['beforeEach', 'afterEach'].forEach(methodName => { + ['beforeAll', 'afterAll', 'beforeEach', 'afterEach'].forEach(methodName => { let originalJasmineFn: Function = jasmineEnv[methodName]; jasmineEnv[symbol(methodName)] = originalJasmineFn; jasmineEnv[methodName] = function(specDefinitions: Function, timeout: number) { - if (typeof timeout !== 'number') { - timeout = (jasmine as any)[symbol('mochaTimeout')]; - } const wrappedSpecDef = wrapTestInZone(specDefinitions); return originalJasmineFn.apply( this, typeof timeout === 'number' ? [wrappedSpecDef, timeout] : [wrappedSpecDef]); @@ -91,19 +86,7 @@ Zone.__load_patch('jasmine', (global: any) => { */ function wrapDescribeInZone(describeBody: Function): Function { return function() { - (jasmine as any)[symbol('mochaTimeout')] = null; - if (this && !this.timeout) { - this.timeout = function(timeout: number) { - (jasmine as any)[symbol('mochaTimeout')] = timeout; - } - } - if (this && !this.skip) { - this.skip = function() { - if (typeof global['pending'] === 'function') { - global['pending'](); - } - } - } + mappingMochaMethods(jasmine, global, this); return syncZone.run(describeBody, this, (arguments as any) as any[]); }; } @@ -119,6 +102,7 @@ Zone.__load_patch('jasmine', (global: any) => { testBody = fakeAsyncModule.fakeAsync(testBody); } } + mappingMochaMethods(jasmine, global, applyThis); if (done) { return testProxyZone.run(testBody, applyThis, [done]); } else { @@ -170,6 +154,10 @@ Zone.__load_patch('jasmine', (global: any) => { // 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); diff --git a/lib/jasmine/mocha-bridge/mocha.bdd.ts b/lib/jasmine/mocha-bridge/mocha.bdd.ts index ac6be8099..287932ce1 100644 --- a/lib/jasmine/mocha-bridge/mocha.bdd.ts +++ b/lib/jasmine/mocha-bridge/mocha.bdd.ts @@ -25,4 +25,21 @@ export function mappingBDD(Mocha: any, jasmine: any, global: any) { 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 index c62f75efe..92106048a 100644 --- a/lib/jasmine/mocha-bridge/mocha.ts +++ b/lib/jasmine/mocha-bridge/mocha.ts @@ -11,6 +11,7 @@ Zone.__load_patch('mocha-bridge', (global: any) => { return; } global.Mocha = {}; - global.Mocha['__zone_symbol__isJasmineBridge'] = true; + // 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/lib/mocha/jasmine-bridge/jasmine.bdd.ts b/lib/mocha/jasmine-bridge/jasmine.bdd.ts index 10c8a73ff..b07e16082 100644 --- a/lib/mocha/jasmine-bridge/jasmine.bdd.ts +++ b/lib/mocha/jasmine-bridge/jasmine.bdd.ts @@ -18,7 +18,7 @@ export function mappingBDD(jasmine: any, Mocha: any, global: any) { const chains = mocha.split('.'); let mochaMethod: any = null; for (let i = 0; i < chains.length; i++) { - mochaMethod = mochaMethod ? mochaMethod[chains[i]] : Mocha[chains[i]]; + mochaMethod = mochaMethod ? mochaMethod[chains[i]] : global[chains[i]]; } global[map.jasmine] = jasmine[map.jasmine] = function() { const args = Array.prototype.slice.call(arguments); diff --git a/lib/mocha/jasmine-bridge/jasmine.ts b/lib/mocha/jasmine-bridge/jasmine.ts index 56d0a4128..84e071b3e 100644 --- a/lib/mocha/jasmine-bridge/jasmine.ts +++ b/lib/mocha/jasmine-bridge/jasmine.ts @@ -22,6 +22,7 @@ Zone.__load_patch('jasmine2mocha', (global: any) => { } // create a jasmine global object jasmine = global['jasmine'] = {}; + jasmine['__zone_symbol__isBridge'] = true; // BDD mapping mappingBDD(jasmine, global.Mocha, global); diff --git a/lib/mocha/mocha-patch.ts b/lib/mocha/mocha-patch.ts index f60e404f4..72cf60878 100644 --- a/lib/mocha/mocha-patch.ts +++ b/lib/mocha/mocha-patch.ts @@ -15,7 +15,7 @@ Zone.__load_patch('Mocha', (global: any, Zone: ZoneType, api: _ZonePrivate) => { return; } - if (Mocha['__zone_symbol__isJasmineBridge']) { + if (Mocha['__zone_symbol__isBridge']) { return; } diff --git a/package.json b/package.json index da9a05043..0511c2cd6 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,12 +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-jasmine-bridge-browser": "npm run tsc && concurrently \"npm run ws-server\" \"karma start karma-build-mocha-jasmine-bridge.conf.js\"", - "test-mocha-jasmine-bridge-node": "npm run tsc && mocha ./build/test/mocha/mocha-node-test-entry-point.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": { 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 73e95d381..000000000 --- a/test/browser_entry_point.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 - */ - -// 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 './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 78% rename from test/main.ts rename to test/env/browser/main.ts index 69e25a3c5..7b4ca188c 100644 --- a/test/main.ts +++ b/test/env/browser/main.ts @@ -28,16 +28,16 @@ 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'; + '/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/browser_entry_point'; + const entryPoint = (window as any)['__zone_symbol__test_entry_point'] || + '/base/build/test/env/browser/browser_entry_point'; System.import(testFrameworkPatch).then(() => { System.import(entryPoint) .then( diff --git a/test/env/common/common_tests.ts b/test/env/common/common_tests.ts new file mode 100644 index 000000000..b5b3a4ccf --- /dev/null +++ b/test/env/common/common_tests.ts @@ -0,0 +1,28 @@ +/** + * @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'; +import '../../spec/mocha/mocha-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 100% rename from test/test-env-setup-jasmine.ts rename to test/env/config/test-env-setup-jasmine.ts diff --git a/test/test-env-setup-mocha.ts b/test/env/config/test-env-setup-mocha.ts similarity index 100% rename from test/test-env-setup-mocha.ts rename to test/env/config/test-env-setup-mocha.ts 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-build-sauce-mocha.conf.js b/test/karma/ci/build/karma-build-sauce-mocha.conf.js similarity index 60% rename from karma-build-sauce-mocha.conf.js rename to test/karma/ci/build/karma-build-sauce-mocha.conf.js index c35699113..b1573d41c 100644 --- a/karma-build-sauce-mocha.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-mocha.conf.js')(config); - require('./sauce.conf')(config); +module.exports = function(config) { + require('../../common/dist/karma-dist-mocha.conf.js')(config); + require('../sauce.conf')(config); }; diff --git a/karma-build-sauce-selenium3-mocha.conf.js b/test/karma/ci/build/karma-build-sauce-selenium3-mocha.conf.js similarity index 56% rename from karma-build-sauce-selenium3-mocha.conf.js rename to test/karma/ci/build/karma-build-sauce-selenium3-mocha.conf.js index d23462147..eef7b4ea0 100644 --- a/karma-build-sauce-selenium3-mocha.conf.js +++ b/test/karma/ci/build/karma-build-sauce-selenium3-mocha.conf.js @@ -6,7 +6,7 @@ * 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']); +module.exports = function(config) { + require('../../common/dist/karma-dist-mocha.conf.js')(config); + require('../sauce-selenium3.conf')(config, ['SL_IE9']); }; diff --git a/karma-dist-sauce-jasmine.conf.js b/test/karma/ci/dist/karma-dist-sauce-jasmine.conf.js similarity index 57% rename from karma-dist-sauce-jasmine.conf.js rename to test/karma/ci/dist/karma-dist-sauce-jasmine.conf.js index 44f6be74a..f23f5fc29 100644 --- a/karma-dist-sauce-jasmine.conf.js +++ b/test/karma/ci/dist/karma-dist-sauce-jasmine.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-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/karma-dist-sauce-selenium3-jasmine.conf.js b/test/karma/ci/dist/karma-dist-sauce-selenium3-jasmine.conf.js similarity index 58% rename from karma-dist-sauce-selenium3-jasmine.conf.js rename to test/karma/ci/dist/karma-dist-sauce-selenium3-jasmine.conf.js index d5c65da90..4b1172e04 100644 --- a/karma-dist-sauce-selenium3-jasmine.conf.js +++ b/test/karma/ci/dist/karma-dist-sauce-selenium3-jasmine.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-selenium3.conf')(config); +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/karma-build-mocha-jasmine-bridge.conf.js b/test/karma/common/build/karma-build-mocha-jasmine-bridge.conf.js similarity index 60% rename from karma-build-mocha-jasmine-bridge.conf.js rename to test/karma/common/build/karma-build-mocha-jasmine-bridge.conf.js index 14a51c9c1..9fd3a802d 100644 --- a/karma-build-mocha-jasmine-bridge.conf.js +++ b/test/karma/common/build/karma-build-mocha-jasmine-bridge.conf.js @@ -1,13 +1,13 @@ module.exports = function(config) { - require('./karma-base.conf.js')(config); - config.files.push('build/test/mocha/mocha-browser-karma.js'); - config.files.push('build/test/wtf_mock.js'); - config.files.push('build/test/test_fake_polyfill.js'); + 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/main.js'); + config.files.push('build/test/env/browser/main.js'); config.plugins.push(require('karma-mocha')); config.frameworks.push('mocha'); 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/karma-dist.conf.js b/test/karma/common/dist/karma-dist.conf.js similarity index 68% rename from karma-dist.conf.js rename to test/karma/common/dist/karma-dist.conf.js index 9d40dee62..74454bc1e 100644 --- a/karma-dist.conf.js +++ b/test/karma/common/dist/karma-dist.conf.js @@ -7,15 +7,14 @@ */ 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'); + 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/main.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..32aeb12d4 100644 --- a/test/browser/WebSocket.spec.ts +++ b/test/spec/browser/WebSocket.spec.ts @@ -28,7 +28,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 +42,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..72f462f69 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; @@ -135,7 +135,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 80% rename from test/jasmine-patch.spec.ts rename to test/spec/jasmine/jasmine-patch.spec.ts index c7fc9be7d..3df23f3fc 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,6 +33,18 @@ ifEnvSupports(supportJasmineSpec, () => { throwOnAsync = true; } + 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', () => { diff --git a/test/mocha/jasmine-bridge.spec.ts b/test/spec/mocha/jasmine-bridge.spec.ts similarity index 99% rename from test/mocha/jasmine-bridge.spec.ts rename to test/spec/mocha/jasmine-bridge.spec.ts index 53c85d84d..99f2b43df 100644 --- a/test/mocha/jasmine-bridge.spec.ts +++ b/test/spec/mocha/jasmine-bridge.spec.ts @@ -602,7 +602,7 @@ describe('A spy, when configured to throw an error', function() { it('throws the value', function() { expect(function() { - foo.setBar(123) + foo.setBar(123); }).toThrowError('quux'); }); }); diff --git a/test/mocha/mocha-browser-karma.ts b/test/spec/mocha/mocha-browser-karma.ts similarity index 100% rename from test/mocha/mocha-browser-karma.ts rename to test/spec/mocha/mocha-browser-karma.ts diff --git a/test/mocha/mocha-browser-test-entry-point.ts b/test/spec/mocha/mocha-browser-test-entry-point.ts similarity index 100% rename from test/mocha/mocha-browser-test-entry-point.ts rename to test/spec/mocha/mocha-browser-test-entry-point.ts diff --git a/test/mocha/mocha-node-test-entry-point.ts b/test/spec/mocha/mocha-node-test-entry-point.ts similarity index 63% rename from test/mocha/mocha-node-test-entry-point.ts rename to test/spec/mocha/mocha-node-test-entry-point.ts index e77cf2941..7c0174ec0 100644 --- a/test/mocha/mocha-node-test-entry-point.ts +++ b/test/spec/mocha/mocha-node-test-entry-point.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/node/rollup-main'; -import '../../lib/mocha/mocha-node-checker'; -import '../../lib/testing/zone-testing'; +import '../../../lib/node/rollup-main'; +import '../../../lib/mocha/mocha-node-checker'; +import '../../../lib/testing/zone-testing'; import './jasmine-bridge.spec'; \ No newline at end of file diff --git a/test/mocha/mocha-patch.spec.ts b/test/spec/mocha/mocha-patch.spec.ts similarity index 92% rename from test/mocha/mocha-patch.spec.ts rename to test/spec/mocha/mocha-patch.spec.ts index a4f34ffaa..cd5e70598 100644 --- a/test/mocha/mocha-patch.spec.ts +++ b/test/spec/mocha/mocha-patch.spec.ts @@ -44,7 +44,6 @@ ifEnvSupports('Mocha', function() { beforeEach(() => { beforeEachZone = Zone.current; - console.log('beforeEach'); }); it('should throw on async in describe', () => { @@ -67,13 +66,11 @@ ifEnvSupports('Mocha', function() { }); /*suite('Mocha TDD-style', () => { - console.log('suite', this && this.constructor); let testZone: Zone = null; let beforeEachZone: Zone = null; let suiteSetupZone: Zone = null; suiteSetup(() => { - console.log('suiteSetup', this && this.constructor); suiteSetupZone = Zone.current; }); @@ -82,7 +79,6 @@ ifEnvSupports('Mocha', function() { }); test('should run in Zone with "test"-syntax in TDD-mode', () => { - console.log('test', this && this.constructor); testZone = Zone.current; expect(Zone.currentTask).toBeTruthy(); expect(testZone.name).toEqual('ProxyZone'); @@ -124,4 +120,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 99% rename from test/zone-spec/fake-async-test.spec.ts rename to test/spec/zone-spec/fake-async-test.spec.ts index f92f1b96b..e6199da32 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); @@ -774,7 +774,6 @@ describe('FakeAsyncTestZoneSpec', () => { expect(tickRun).toEqual(true); expect(cbArgRun).toEqual(true); }); - }); })); @@ -944,7 +943,6 @@ describe('FakeAsyncTestZoneSpec', () => { expect(unixTimeZero).toBe(0); }); }); - }); describe( @@ -1272,7 +1270,6 @@ const {fakeAsync, tick, discardPeriodicTasks, flush, flushMicrotasks} = fakeAsyn })(); }).toThrowError('sync'); }); - }); describe('timers', () => { 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 From ad098b0b0cbb7cf63413ad8f59237c82788ba603 Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Thu, 10 May 2018 01:00:54 +0900 Subject: [PATCH 06/11] feat(test): add jest support to Mocha runner --- lib/mocha/jasmine-bridge/jasmine.bdd.ts | 7 + lib/mocha/jasmine-bridge/jasmine.expect.ts | 484 +++++++++++------- lib/mocha/jasmine-bridge/jasmine.spy.ts | 16 + lib/mocha/jasmine-bridge/jasmine.ts | 3 +- lib/mocha/jasmine-bridge/jasmine.util.ts | 5 + lib/mocha/jest-bridge/jest-bridge.ts | 9 + lib/mocha/jest-bridge/jest.bdd.ts | 25 + lib/mocha/jest-bridge/jest.expect.ts | 287 +++++++++++ lib/mocha/jest-bridge/jest.spy.ts | 115 +++++ lib/mocha/jest-bridge/jest.ts | 29 ++ lib/mocha/mocha-patch.ts | 17 + lib/mocha/mocha.ts | 3 +- test/spec/mocha/jasmine-bridge.spec.ts | 8 + test/spec/mocha/jest-bridge.spec.ts | 387 ++++++++++++++ .../spec/mocha/mocha-node-test-entry-point.ts | 3 +- test/spec/mocha/mocha-patch.spec.ts | 12 +- 16 files changed, 1198 insertions(+), 212 deletions(-) create mode 100644 lib/mocha/jest-bridge/jest-bridge.ts create mode 100644 lib/mocha/jest-bridge/jest.bdd.ts create mode 100644 lib/mocha/jest-bridge/jest.expect.ts create mode 100644 lib/mocha/jest-bridge/jest.spy.ts create mode 100644 lib/mocha/jest-bridge/jest.ts create mode 100644 test/spec/mocha/jest-bridge.spec.ts diff --git a/lib/mocha/jasmine-bridge/jasmine.bdd.ts b/lib/mocha/jasmine-bridge/jasmine.bdd.ts index b07e16082..897aa0b75 100644 --- a/lib/mocha/jasmine-bridge/jasmine.bdd.ts +++ b/lib/mocha/jasmine-bridge/jasmine.bdd.ts @@ -42,4 +42,11 @@ export function mappingBDD(jasmine: any, Mocha: any, global: any) { } }; } + + 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.expect.ts b/lib/mocha/jasmine-bridge/jasmine.expect.ts index ad5ed825c..3925c48de 100644 --- a/lib/mocha/jasmine-bridge/jasmine.expect.ts +++ b/lib/mocha/jasmine-bridge/jasmine.expect.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 {addCustomEqualityTester, Any, customEqualityTesters, eq, ObjectContaining} from './jasmine.util'; +import {addCustomEqualityTester, Any, customEqualityTesters, eq, ObjectContaining, toMatch} from './jasmine.util'; export function addJasmineExpect(jasmine: any, global: any) { addExpect(global, jasmine); @@ -28,7 +28,6 @@ function addObjectContaining(jasmine: any) { } function addCustomMatchers(jasmine: any, global: any) { - const util = {equals: eq}; const originalExcept = jasmine['__zone_symbol__expect']; jasmine.addMatchers = function(customMatcher: any) { let customMatchers = jasmine['__zone_symbol__customMatchers']; @@ -36,33 +35,173 @@ function addCustomMatchers(jasmine: any, global: any) { customMatchers = jasmine['__zone_symbol__customMatchers'] = []; } customMatchers.push(customMatcher); - global['expect'] = function(expected: any) { - const expectObj = originalExcept.call(this, expected); - customMatchers.forEach((matcher: any) => { - Object.keys(matcher).forEach(key => { - if (matcher.hasOwnProperty(key)) { - const customExpected = matcher[key](util, customEqualityTesters); - expectObj[key] = function(actual: any) { - return customExpected.compare(expected, actual); - }; - expectObj['not'][key] = function(actual: any) { - return !customExpected.compare(expected, actual); - }; - } - }); - }); - return expectObj; - }; }; } -function addExpect(global: any, jasmine: any) { - global['expect'] = jasmine['__zone_symbol__expect'] = function(expected: any) { - return { - nothing: function() {}, +function buildCustomMatchers(expectObj: any, jasmine: any, expected: any) { + const util = {equals: eq, toMatch: toMatch}; + let customMatchers: any = jasmine['__zone_symbol__customMatchers']; + if (!customMatchers) { + return; + } + customMatchers.forEach((matcher: any) => { + Object.keys(matcher).forEach(key => { + if (matcher.hasOwnProperty(key)) { + const customExpected = matcher[key](util, customEqualityTesters); + expectObj[key] = function(...actuals: any[]) { + // TODO: @JiaLiPassion use result.message + if (!customExpected.compare(expected, actuals[0], actuals[1]).pass) { + throw new Error(`${key} failed, expect: ${expected}, actual is: ${actuals[0]}`); + } + }; + expectObj['not'][key] = function(...actuals: any[]) { + if (customExpected.compare(expected, actuals[0], actuals[1]).pass) { + throw new Error(`${key} failed, not expect: ${expected}, actual is: ${actuals[0]}`); + } + }; + } + }); + }); + return expectObj; +} + +function getMatchers(expected: any) { + return { + nothing: function() {}, + toBe: function(actual: any) { + if (expected !== actual) { + throw new Error(`Expected ${expected} to be ${actual}`); + } + }, + toBeCloseTo: function(actual: 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; + if (Math.round(delta * pow) / pow > maxDelta) { + throw new Error( + `Expected ${expected} to be close to ${actual} with precision ${precision}`); + } + }, + 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}`); + } + }, + toBeGreaterThanOrEqual: function(actual: number) { + if (expected < actual) { + throw new Error(`Expected ${expected} to be greater than or equal ${actual}`); + } + }, + toBeLessThan: function(actual: number) { + if (expected >= actual) { + throw new Error(`Expected ${expected} to be lesser than ${actual}`); + } + }, + toBeLessThanOrEqual: function(actual: number) { + if (expected > actual) { + throw new Error(`Expected ${expected} to be lesser than or equal ${actual}`); + } + }, + toBeDefined: function() { + if (expected === undefined) { + throw new Error(`Expected ${expected} to be defined`); + } + }, + toBeNaN: function() { + if (expected === expected) { + throw new Error(`Expected ${expected} to be NaN`); + } + }, + toBeNegativeInfinity: function() { + if (expected !== Number.NEGATIVE_INFINITY) { + throw new Error(`Expected ${expected} to be -Infinity`); + } + }, + toBeNull: function() { + if (expected !== null) { + throw new Error(`Expected ${expected} to be null`); + } + }, + toBePositiveInfinity: function() { + if (expected !== Number.POSITIVE_INFINITY) { + throw new Error(`Expected ${expected} to be +Infinity`); + } + }, + toBeUndefined: function() { + if (expected !== undefined) { + throw new Error(`Expected ${expected} to be undefined`); + } + }, + 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}`); + } + }, + toBeCalled: function() { + if (expected.calls.count() === 0) { + throw new Error(`Expected ${expected} to been called`); + } + }, + toHaveBeenCalled: function() { + if (expected.calls.count() === 0) { + throw new Error(`Expected ${expected} to been called`); + } + }, + toBeCalledWith: function(...params: any[]) { + if (expected.calls.allArgs().filter((args: any) => eq(args, params)).length === 0) { + throw new Error(`Expected ${expected.calls.allArgs()} to been called with ${params}`); + } + }, + toHaveBeenCalledWith: function(...params: any[]) { + if (expected.calls.allArgs().filter((args: any) => eq(args, params)).length === 0) { + throw new Error(`Expected ${expected.calls.allArgs()} to been called with ${params}`); + } + }, + toMatch: function(actual: any) { + if (!toMatch(actual, expected)) { + throw new Error(`Expected ${expected} to match ${actual}`); + } + }, + not: { toBe: function(actual: any) { - if (expected !== actual) { - throw new Error(`Expected ${expected} to be ${actual}`); + if (expected === actual) { + throw new Error(`Expected ${expected} not to be ${actual}`); } }, toBeCloseTo: function(actual: any, precision: any) { @@ -72,232 +211,181 @@ function addExpect(global: any, jasmine: any) { const pow = Math.pow(10, precision + 1); const delta = Math.abs(expected - actual); const maxDelta = Math.pow(10, -precision) / 2; - if (Math.round(delta * pow) / pow > maxDelta) { + if (Math.round(delta * pow) / pow <= maxDelta) { throw new Error( - `Expected ${expected} to be close to ${actual} with precision ${precision}`); + `Expected ${expected} not to be close to ${actual} with precision ${precision}`); } }, toEqual: function(actual: any) { - if (!eq(expected, actual)) { - throw new Error(`Expected ${expected} to be ${actual}`); + if (eq(expected, actual)) { + throw new Error(`Expected ${expected} not to be ${actual}`); + } + }, + 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} to be greater than ${actual}`); + if (expected > actual) { + throw new Error(`Expected ${expected} not to be greater than ${actual}`); } }, toBeGreaterThanOrEqual: function(actual: number) { - if (expected < actual) { - throw new Error(`Expected ${expected} to be greater than or equal ${actual}`); + if (expected >= actual) { + throw new Error(`Expected ${expected} not to be greater than or equal ${actual}`); } }, toBeLessThan: function(actual: number) { - if (expected >= actual) { - throw new Error(`Expected ${expected} to be lesser than ${actual}`); + if (expected < actual) { + throw new Error(`Expected ${expected} not to be lesser than ${actual}`); } }, toBeLessThanOrEqual: function(actual: number) { - if (expected > actual) { - throw new Error(`Expected ${expected} to be lesser than or equal ${actual}`); + if (expected <= actual) { + throw new Error(`Expected ${expected} not to be lesser than or equal ${actual}`); } }, toBeDefined: function() { - if (expected === undefined) { - throw new Error(`Expected ${expected} to be defined`); + if (expected !== undefined) { + throw new Error(`Expected ${expected} not to be defined`); } }, toBeNaN: function() { - if (expected === expected) { - throw new Error(`Expected ${expected} to be NaN`); + if (expected !== expected) { + throw new Error(`Expected ${expected} not to be NaN`); } }, toBeNegativeInfinity: function() { - if (expected !== Number.NEGATIVE_INFINITY) { - throw new Error(`Expected ${expected} to be -Infinity`); + if (expected === Number.NEGATIVE_INFINITY) { + throw new Error(`Expected ${expected} not to be -Infinity`); } }, toBeNull: function() { - if (expected !== null) { - throw new Error(`Expected ${expected} to be null`); + if (expected === null) { + throw new Error(`Expected ${expected} not to be null`); } }, toBePositiveInfinity: function() { - if (expected !== Number.POSITIVE_INFINITY) { - throw new Error(`Expected ${expected} to be +Infinity`); + if (expected === Number.POSITIVE_INFINITY) { + throw new Error(`Expected ${expected} not to be +Infinity`); } }, toBeUndefined: function() { - if (expected !== undefined) { - throw new Error(`Expected ${expected} to be undefined`); - } - }, - toThrow: function() { - try { - expected(); - } catch (error) { - return; + if (expected === undefined) { + throw new Error(`Expected ${expected} not to be undefined`); } - - throw new Error(`Expected ${expected} to throw`); }, - toThrowError: function(errorToBeThrow: any) { - try { - expected(); - } catch (error) { - return; + toBeTruthy: function() { + if (!!expected) { + throw new Error(`Expected ${expected} not to be truthy`); } - - throw Error(`Expected ${expected} to throw: ${errorToBeThrow}`); }, - toBeTruthy: function() { + toBeFalsy: function() { if (!expected) { - throw new Error(`Expected ${expected} to be truthy`); + throw new Error(`Expected ${expected} not to be falsy`); } }, - 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} not to contain ${actual}`); } }, - toContain: function(actual: any) { - if (expected.indexOf(actual) === -1) { - throw new Error(`Expected ${expected} to contain ${actual}`); + toMatch: function(actual: any) { + if (toMatch(actual, expected)) { + throw new Error(`Expected ${expected} not to match ${actual}`); } }, - toHaveBeenCalled: function() { - if (expected.calls.count() === 0) { - throw new Error(`Expected ${expected} to been called`); + toBeCalled: function() { + if (expected.calls.count() > 0) { + throw new Error(`Expected ${expected} to not been called`); } }, - toHaveBeenCalledWith: function(...params: any[]) { - if (expected.calls.allArgs().filter((args: any) => eq(args, params)).length === 0) { - throw new Error(`Expected ${expected} to been called with ${params}`); + toHaveBeenCalled: function() { + if (expected.calls.count() > 0) { + throw new Error(`Expected ${expected} to not been called`); } }, - toMatch: function(actual: any) { - if (!new RegExp(actual).test(expected)) { - throw new Error(`Expected ${expected} to match ${actual}`); + toBeCalledWith: function(params: any[]) { + if (expected.calls.allArgs().filter((args: any) => eq(args, params)).length > 0) { + throw new Error(`Expected ${expected.calls.allArgs()} to not been called with ${params}`); } }, - not: { - toBe: function(actual: any) { - if (expected === actual) { - throw new Error(`Expected ${expected} not to be ${actual}`); - } - }, - toBeCloseTo: function(actual: 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; - if (Math.round(delta * pow) / pow <= maxDelta) { - throw new Error( - `Expected ${expected} not to be close to ${actual} with precision ${precision}`); - } - }, - toEqual: function(actual: any) { - if (eq(expected, actual)) { - throw new Error(`Expected ${expected} not to be ${actual}`); - } - }, - 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}`); - } - }, - toBeGreaterThanOrEqual: function(actual: number) { - if (expected >= actual) { - throw new Error(`Expected ${expected} not to be greater than or equal ${actual}`); - } - }, - toBeLessThan: function(actual: number) { - if (expected < actual) { - throw new Error(`Expected ${expected} not to be lesser than ${actual}`); - } - }, - toBeLessThanOrEqual: function(actual: number) { - if (expected <= actual) { - throw new Error(`Expected ${expected} not to be lesser than or equal ${actual}`); - } - }, - toBeDefined: function() { - if (expected !== undefined) { - throw new Error(`Expected ${expected} not to be defined`); - } - }, - toBeNaN: function() { - if (expected !== expected) { - throw new Error(`Expected ${expected} not to be NaN`); - } - }, - toBeNegativeInfinity: function() { - if (expected === Number.NEGATIVE_INFINITY) { - throw new Error(`Expected ${expected} not to be -Infinity`); - } - }, - toBeNull: function() { - if (expected === null) { - throw new Error(`Expected ${expected} not to be null`); - } - }, - toBePositiveInfinity: function() { - if (expected === Number.POSITIVE_INFINITY) { - throw new Error(`Expected ${expected} not to be +Infinity`); - } - }, - toBeUndefined: function() { - if (expected === undefined) { - throw new Error(`Expected ${expected} not to be undefined`); - } - }, - toBeTruthy: function() { - if (!!expected) { - throw new Error(`Expected ${expected} not to be truthy`); - } - }, - toBeFalsy: function() { - if (!expected) { - throw new Error(`Expected ${expected} not to be falsy`); - } - }, - toContain: function(actual: any) { - if (expected.indexOf(actual) !== -1) { - throw new Error(`Expected ${expected} not to contain ${actual}`); - } - }, - toMatch: function(actual: any) { - if (new RegExp(actual).test(expected)) { - throw new Error(`Expected ${expected} not to match ${actual}`); - } - }, - toHaveBeenCalled: function() { - if (expected.calls.count() > 0) { - throw new Error(`Expected ${expected} to not been called`); - } - }, - toHaveBeenCalledWith: function(params: any[]) { - if (expected.calls.allArgs().filter((args: any) => eq(args, params)).length > 0) { - throw new Error(`Expected ${expected} to not been called with ${params}`); - } + toHaveBeenCalledWith: function(params: any[]) { + if (expected.calls.allArgs().filter((args: any) => eq(args, params)).length > 0) { + throw new Error(`Expected ${expected.calls.allArgs()} to not been called with ${params}`); } } + } + }; +} + +function buildResolveRejects(key: string, matchers: any, expected: any, isNot = false) { + if (matchers.hasOwnProperty(key)) { + const resolveFnFactory = function(isNot = false) { + return function() { + const self = this; + const args = Array.prototype.slice.call(arguments); + return expected.then( + (value: any) => { + const newMatchers: any = getMatchers(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 expected.then((value: any) => {}, (error: any) => { + const newMatchers: any = getMatchers(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; + global['expect'] = jasmine['__zone_symbol__expect'] = function(expected: any) { + jasmine.__zone_symbol__expect_assertions++; + const matchers: any = getMatchers(expected); + if (expected && typeof expected.then === 'function') { + // expected maybe a promise + matchers.resolves = {not: {}}; + matchers.rejects = {not: {}}; + Object.keys(matchers).forEach(key => { + buildResolveRejects(key, matchers, expected); + }); + Object.keys(matchers.not).forEach(key => { + buildResolveRejects(key, matchers, expected, true); + }); + } + buildCustomMatchers(matchers, jasmine, expected); + 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 index b9095f0fd..9487324c8 100644 --- a/lib/mocha/jasmine-bridge/jasmine.spy.ts +++ b/lib/mocha/jasmine-bridge/jasmine.spy.ts @@ -170,6 +170,19 @@ export function addJasmineSpy(jasmine: any, Mocha: any, global: any) { }; } + 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) { @@ -234,6 +247,9 @@ export function addJasmineSpy(jasmine: any, Mocha: any, global: any) { wrapper.withArgs = function() { return spyStrategyDispatcher.withArgs.apply(spyStrategyDispatcher, arguments); }; + wrapper.updateArgs = function(newArgs: SpyStrategyOptions) { + spyStrategyDispatcher.updateArgs(newArgs); + }; return wrapper; } diff --git a/lib/mocha/jasmine-bridge/jasmine.ts b/lib/mocha/jasmine-bridge/jasmine.ts index 84e071b3e..65b35d5d7 100644 --- a/lib/mocha/jasmine-bridge/jasmine.ts +++ b/lib/mocha/jasmine-bridge/jasmine.ts @@ -41,9 +41,10 @@ Zone.__load_patch('jasmine2mocha', (global: any) => { configurable: true, enumerable: true, get: function() { - return global.Mocha.__zone_symbol__TIMEOUT; + return jasmine.__zone_symbol__TIMEOUT || 2000; }, set: function(newValue: number) { + jasmine.__zone_symbol__TIMEOUT = newValue; global.Mocha.__zone_symbol__TIMEOUT = newValue; } }); diff --git a/lib/mocha/jasmine-bridge/jasmine.util.ts b/lib/mocha/jasmine-bridge/jasmine.util.ts index 8496577b9..0b9162c45 100644 --- a/lib/mocha/jasmine-bridge/jasmine.util.ts +++ b/lib/mocha/jasmine-bridge/jasmine.util.ts @@ -156,3 +156,8 @@ export function eq(a: any, b: any) { return false; } + +export function toMatch(actual: any, expected: any) { + const regExp = actual instanceof RegExp ? actual : new RegExp(actual); + return regExp.test(expected); +} diff --git a/lib/mocha/jest-bridge/jest-bridge.ts b/lib/mocha/jest-bridge/jest-bridge.ts new file mode 100644 index 000000000..6c5b0251f --- /dev/null +++ b/lib/mocha/jest-bridge/jest-bridge.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 './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.expect.ts b/lib/mocha/jest-bridge/jest.expect.ts new file mode 100644 index 000000000..ab50064d1 --- /dev/null +++ b/lib/mocha/jest-bridge/jest.expect.ts @@ -0,0 +1,287 @@ +/** + * @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'; +declare namespace jasmine { + interface Expect { + anything: () => any; + any: (expectedObject: any) => any; + arrayContaining: (expectedArray: string[]) => any; + objectContaining: (expectedObject: any) => any; + stringContaining: (expectedString: string) => any; + stringMatching: (expectedMatcher: RegExp|string) => any; + extend: (extendedMatchers: any) => any; + assertions: (numbers: number) => void; + hasAssertions: () => void; + } + + interface Matchers { + toHaveBeenCalledTimes: (expected: number) => boolean; + lastCalledWith: (...params: any[]) => boolean; + toHaveBeenLastCalledWith: (...params: any[]) => boolean; + toBeInstanceOf: (expected: any) => boolean; + toContainEqual: (expected: any) => boolean; + toHaveLength: (expected: number) => boolean; + toHaveProperty: (expected: any, value: any) => boolean; + toMatchObject: (expected: any) => boolean; + } +} + +export function expandExpect(global: any) { + const jasmine = global.jasmine; + const expect: jasmine.Expect = 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(b.expectedObject[key], a[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(b.expectedMatcher, astr); + } + }); + + 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: expected.calls.count() === actual}; + } + }; + }, + lastCalledWith: function(util: any, customEqualityTester: any) { + return { + compare: function(actual: any, expected: any) { + return {pass: util.equals(actual, expected.calls.last().args)}; + } + }; + }, + toHaveBeenLastCalledWith: function(util: any, customEqualityTester: any) { + return { + compare: function(actual: any, expected: any) { + return {pass: util.equals(actual, expected.calls.last().args)}; + } + }; + }, + 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..151c60df3 --- /dev/null +++ b/lib/mocha/jest-bridge/jest.spy.ts @@ -0,0 +1,115 @@ +/** + * @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) { + function createSpy(spyFactory: (implFn?: Function) => any, implFn?: Function) { + const spy = jasmine.createSpy('jestSpy', 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); + }; + + 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)); + }; +} diff --git a/lib/mocha/jest-bridge/jest.ts b/lib/mocha/jest-bridge/jest.ts new file mode 100644 index 000000000..b232ea924 --- /dev/null +++ b/lib/mocha/jest-bridge/jest.ts @@ -0,0 +1,29 @@ +/** + * @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 {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 + if (global.Mocha['__zone_symbol__isBridge']) { + 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); +}); \ No newline at end of file diff --git a/lib/mocha/mocha-patch.ts b/lib/mocha/mocha-patch.ts index 72cf60878..bc52a0b84 100644 --- a/lib/mocha/mocha-patch.ts +++ b/lib/mocha/mocha-patch.ts @@ -8,6 +8,16 @@ 'use strict'; +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; + Zone.__load_patch('Mocha', (global: any, Zone: ZoneType, api: _ZonePrivate) => { const Mocha = global.Mocha; @@ -85,6 +95,11 @@ Zone.__load_patch('Mocha', (global: any, Zone: ZoneType, api: _ZonePrivate) => { 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; @@ -106,6 +121,8 @@ Zone.__load_patch('Mocha', (global: any, Zone: ZoneType, api: _ZonePrivate) => { 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; } } diff --git a/lib/mocha/mocha.ts b/lib/mocha/mocha.ts index 75408c55a..384f1ef59 100644 --- a/lib/mocha/mocha.ts +++ b/lib/mocha/mocha.ts @@ -7,4 +7,5 @@ */ import './mocha-patch'; -import './jasmine-bridge/jasmine-bridge'; \ No newline at end of file +import './jasmine-bridge/jasmine-bridge'; +import './jest-bridge/jest-bridge'; \ No newline at end of file diff --git a/test/spec/mocha/jasmine-bridge.spec.ts b/test/spec/mocha/jasmine-bridge.spec.ts index 99f2b43df..c7cb56808 100644 --- a/test/spec/mocha/jasmine-bridge.spec.ts +++ b/test/spec/mocha/jasmine-bridge.spec.ts @@ -1249,3 +1249,11 @@ describe('Custom matcher: \'toBeGoofy\'', function() { (expect({hyuk: 'this is fun'}) as any).not.toBeGoofy(); }); }); + +describe('failed', () => { + try { + fail('error'); + } catch (error) { + expect(error).toEqual('error'); + } +}); \ No newline at end of file diff --git a/test/spec/mocha/jest-bridge.spec.ts b/test/spec/mocha/jest-bridge.spec.ts new file mode 100644 index 000000000..3cf172c90 --- /dev/null +++ b/test/spec/mocha/jest-bridge.spec.ts @@ -0,0 +1,387 @@ +/** + * @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 + */ +declare let jest: any; +declare function test(description: string, testFn: () => void): void; + +describe('extend', () => { + (expect as any).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 as any).anything()); + }); + + test('any(constructor)', () => { + expect('test').toEqual((expect as any).any(String)); + }); + + describe('arrayContaining', () => { + const expected = ['Alice', 'Bob']; + it('matches even if received contains additional elements', () => { + expect(['Alice', 'Bob', 'Eve']).toEqual((expect as any).arrayContaining(expected)); + }); + it('does not match if received does not contain expected elements', () => { + expect(['Bob', 'Eve']).not.toEqual((expect as any).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 as any).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 as any).arrayContaining(expected), + ); + }); + }); + + describe('assertions', () => { + test('calls both callbacks', () => { + (expect as any).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 as any).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) as any) + .toEqual( + (expect as any).objectContaining({ + x: (expect as any).any(Number), + y: (expect as any).any(Number), + }), + ); + }); + }); + + describe('stringContaining', () => { + test('testStr should contain right string', () => { + expect('test1').toEqual((expect as any).stringContaining('test')); + }); + }); + + describe('stringMatching in arrayContaining', () => { + const expected = [ + (expect as any).stringMatching(/^Alic/), + (expect as any).stringMatching(/^[BR]ob/), + ]; + it('matches even if received contains additional elements', () => { + expect(['Alicia', 'Roberto', 'Evelina']) + .toEqual( + (expect as any).arrayContaining(expected), + ); + }); + it('does not match if received does not contain expected elements', () => { + expect(['Roberto', 'Evelina']) + .not.toEqual( + (expect as any).arrayContaining(expected), + ); + }); + }); + + describe('Promise', () => { + test('resolves to lemon', () => { + // make sure to add a return statement + return (expect(Promise.resolve('lemon')) as any).resolves.toBe('lemon'); + }); + + test('resolves to lemon with await', async () => { + await (expect(Promise.resolve('lemon')) as any).resolves.toBe('lemon'); + await (expect(Promise.resolve('lemon')) as any).resolves.not.toBe('octopus'); + }); + + test('rejects to octopus', () => { + // make sure to add a return statement + return (expect(Promise.reject(new Error('octopus'))) as any) + .rejects.toThrow( + 'octopus', + ); + }); + + test('rejects to octopus', async () => { + await (expect(Promise.reject(new Error('octopus'))) as any).rejects.toThrow('octopus'); + }); + }); + + test('instanceof', () => { + class A {} + (expect(new A()) as any).toBeInstanceOf(A); + (expect(() => {}) as any).toBeInstanceOf(Function); + }); + + test('toContainEqual', () => { + const myBeverage = {delicious: true, sour: false}; + (expect([{delicious: true, sour: false}, {delicious: false, sour: true}]) as any) + .toContainEqual(myBeverage); + }); + + test('toHaveLength', () => { + (expect([1, 2, 3]) as any).toHaveLength(3); + (expect('abc') as any).toHaveLength(3); + (expect('').not as any).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 as any).stringMatching(/white|yellow/), + }, + }; + + test('the house has my desired features', () => { + (expect(houseForSale) as any).toMatchObject(desiredHouse); + }); + }); + + describe('toMatchObject applied to arrays arrays', () => { + test('the number of elements must match exactly', () => { + (expect([{foo: 'bar'}, {baz: 1}]) as any).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}]) as any).toMatchObject([{foo: 'bar'}]); + }); + + test('.toMatchObject is called for each elements, so extra object properties are okay', () => { + (expect([{foo: 'bar'}, {baz: 1, extra: 'quux'}]) as any).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) as any).toHaveProperty('bath'); + (expect(houseForSale) as any).toHaveProperty('bedrooms', 4); + + (expect(houseForSale).not as any).toHaveProperty('pool'); + + // Deep referencing using dot notation + (expect(houseForSale) as any).toHaveProperty('kitchen.area', 20); + (expect(houseForSale) as any).toHaveProperty('kitchen.amenities', [ + 'oven', + 'stove', + 'washer', + ]); + + (expect(houseForSale).not as any).toHaveProperty('kitchen.open'); + + // Deep referencing using an array containing the keyPath + (expect(houseForSale) as any).toHaveProperty(['kitchen', 'area'], 20); + (expect(houseForSale) as any) + .toHaveProperty( + ['kitchen', 'amenities'], + ['oven', 'stove', 'washer'], + ); + (expect(houseForSale) as any).toHaveProperty(['kitchen', 'amenities', 0], 'oven'); + + (expect(houseForSale).not as any).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'); + } + }); + }); +}); \ 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 index 7c0174ec0..7dad9ced7 100644 --- a/test/spec/mocha/mocha-node-test-entry-point.ts +++ b/test/spec/mocha/mocha-node-test-entry-point.ts @@ -8,4 +8,5 @@ import '../../../lib/node/rollup-main'; import '../../../lib/mocha/mocha-node-checker'; import '../../../lib/testing/zone-testing'; -import './jasmine-bridge.spec'; \ No newline at end of file +import './jasmine-bridge.spec'; +import './jest-bridge.spec'; \ No newline at end of file diff --git a/test/spec/mocha/mocha-patch.spec.ts b/test/spec/mocha/mocha-patch.spec.ts index cd5e70598..d2a19ee11 100644 --- a/test/spec/mocha/mocha-patch.spec.ts +++ b/test/spec/mocha/mocha-patch.spec.ts @@ -8,16 +8,6 @@ // 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'; function expect(args: any) { @@ -122,7 +112,7 @@ ifEnvSupports('Mocha', function() { }); xdescribe('timeout', () => { - beforeEach(function () { + beforeEach(function() { this.timeout(10000); }); From dc1a083d4a2799e8d241f1b6f1dda4668533fa1e Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Thu, 10 May 2018 11:24:28 +0900 Subject: [PATCH 07/11] doc(test): add draft doc of zone-testing --- doc/design/TEST.md | 25 ++ doc/design/TEST_RUNNER.md | 256 +++++++++++++++++++++ lib/mocha/jasmine-bridge/jasmine.bdd.ts | 2 +- lib/mocha/jasmine-bridge/jasmine.expect.ts | 4 +- lib/mocha/jest-bridge/jest.expect.ts | 27 +-- 5 files changed, 295 insertions(+), 19 deletions(-) create mode 100644 doc/design/TEST.md create mode 100644 doc/design/TEST_RUNNER.md diff --git a/doc/design/TEST.md b/doc/design/TEST.md new file mode 100644 index 000000000..6b68a569f --- /dev/null +++ b/doc/design/TEST.md @@ -0,0 +1,25 @@ +# Zone Testing + +`zone.js` has a `zone-testing.js` bundle, which provides a lot of functionalities for testing. +include: + +1. FakeAsyncTestZoneSpec: simulate system timer to make `async` test faster and stable. +2. AsyncTestZoneSpec: automatically wait for all async tasks to finish. +3. SyncTestZoneSpec: force all tests to be synchronized. +4. Jasmine/Mocha/Jest supports. + +## FakeAsyncTestZoneSpec + +Add `fakeAsync` document later. + +## AsyncTestZoneSpec + +Add `async` document later. + +## SyncTestZoneSpec + +Add `sync` document later. + +## Unify jasmine/mocha/jest + +`zone-testing` support `jasmine` and `mocha` runner, when `zone-testing` is loaded, it will detect current test environment(jasmine or mocha) and monkey-patch current runner accordingly. For detail, please check this document [test runner](./TEST_RUNNER.md). diff --git a/doc/design/TEST_RUNNER.md b/doc/design/TEST_RUNNER.md new file mode 100644 index 000000000..de3f0c86a --- /dev/null +++ b/doc/design/TEST_RUNNER.md @@ -0,0 +1,256 @@ +# 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`; +// TODO: this is too complex to load fakeAsyncTest, should expose as global function. +const {resetFakeAsyncZone, flushMicrotasks, discardPeriodicTasks, tick, flush, fakeAsync} = (Zone as any)[Zone.__symbol__('fakeAsyncTest')]; + +// 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. + +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 also use `jest style expect and mock`. + +```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. 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 mock +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/lib/mocha/jasmine-bridge/jasmine.bdd.ts b/lib/mocha/jasmine-bridge/jasmine.bdd.ts index 897aa0b75..a8b9edd24 100644 --- a/lib/mocha/jasmine-bridge/jasmine.bdd.ts +++ b/lib/mocha/jasmine-bridge/jasmine.bdd.ts @@ -47,6 +47,6 @@ export function mappingBDD(jasmine: any, Mocha: any, global: any) { 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.expect.ts b/lib/mocha/jasmine-bridge/jasmine.expect.ts index 3925c48de..e791bffee 100644 --- a/lib/mocha/jasmine-bridge/jasmine.expect.ts +++ b/lib/mocha/jasmine-bridge/jasmine.expect.ts @@ -344,7 +344,7 @@ function buildResolveRejects(key: string, matchers: any, expected: any, isNot = (error: any) => { throw error; }); - } + }; }; if (isNot) { matchers.resolves.not[key] = resolveFnFactory(true); @@ -360,7 +360,7 @@ function buildResolveRejects(key: string, matchers: any, expected: any, isNot = return isNot ? newMatchers.not[key].apply(self, args) : newMatchers[key].apply(self, args); }); - } + }; }; if (isNot) { matchers.rejects.not[key] = rejectFnFactory(true); diff --git a/lib/mocha/jest-bridge/jest.expect.ts b/lib/mocha/jest-bridge/jest.expect.ts index ab50064d1..9716ab40d 100644 --- a/lib/mocha/jest-bridge/jest.expect.ts +++ b/lib/mocha/jest-bridge/jest.expect.ts @@ -49,37 +49,33 @@ export function expandExpect(global: any) { constructor(public expectedArray: any[]) {} } - expect.arrayContaining = - function(expectedArray: string[]) { + expect.arrayContaining = function(expectedArray: string[]) { return new ArrayContaining(expectedArray); - } + }; class ObjectContaining { constructor(public expectedObject: any) {} } - expect.objectContaining = - function(expectedObject: any) { + expect.objectContaining = function(expectedObject: any) { return new ObjectContaining(expectedObject); - } + }; class StringContaining { constructor(public expectedString: string) {} } - expect.stringContaining = - function(expectedString: string) { + expect.stringContaining = function(expectedString: string) { return new StringContaining(expectedString); - } + }; class StringMatching { constructor(public expectedMatcher: RegExp|string) {} } - expect.stringMatching = - function(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 = []; @@ -150,7 +146,7 @@ export function expandExpect(global: any) { return matcher(actual, expected); } }; - } + }; } }); jasmine.addMatchers(jasmineMatchers); @@ -258,11 +254,10 @@ export function expandExpect(global: any) { assertions.push({test: currentTest, numbers}); }; - expect.hasAssertions = - function() { + 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 = []; From f58494559920f3e1d1401a2de7642228983aada1 Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Fri, 11 May 2018 02:12:56 +0900 Subject: [PATCH 08/11] feat(test): add jest timers patch, refactor jasmine.expect --- lib/mocha/jasmine-bridge/jasmine.expect.ts | 447 ++++++++------------- lib/mocha/jasmine-bridge/jasmine.spy.ts | 12 + lib/mocha/jasmine-bridge/jasmine.util.ts | 42 +- lib/mocha/jest-bridge/jest.clock.ts | 95 +++++ lib/mocha/jest-bridge/jest.expect.ts | 10 +- lib/mocha/jest-bridge/jest.spy.ts | 27 +- lib/mocha/jest-bridge/jest.ts | 10 + test/spec/mocha/jest-bridge.spec.ts | 2 +- 8 files changed, 345 insertions(+), 300 deletions(-) create mode 100644 lib/mocha/jest-bridge/jest.clock.ts diff --git a/lib/mocha/jasmine-bridge/jasmine.expect.ts b/lib/mocha/jasmine-bridge/jasmine.expect.ts index e791bffee..c876bf37a 100644 --- a/lib/mocha/jasmine-bridge/jasmine.expect.ts +++ b/lib/mocha/jasmine-bridge/jasmine.expect.ts @@ -5,9 +5,10 @@ * 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, customEqualityTesters, eq, ObjectContaining, toMatch} from './jasmine.util'; +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); @@ -28,364 +29,230 @@ function addObjectContaining(jasmine: any) { } function addCustomMatchers(jasmine: any, global: any) { - const originalExcept = jasmine['__zone_symbol__expect']; jasmine.addMatchers = function(customMatcher: any) { - let customMatchers = jasmine['__zone_symbol__customMatchers']; - if (!customMatchers) { - customMatchers = jasmine['__zone_symbol__customMatchers'] = []; - } + let customMatchers = getCustomMatchers(jasmine); customMatchers.push(customMatcher); }; } -function buildCustomMatchers(expectObj: any, jasmine: any, expected: any) { - const util = {equals: eq, toMatch: toMatch}; - let customMatchers: any = jasmine['__zone_symbol__customMatchers']; - if (!customMatchers) { - return; - } +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 customExpected = matcher[key](util, customEqualityTesters); - expectObj[key] = function(...actuals: any[]) { - // TODO: @JiaLiPassion use result.message - if (!customExpected.compare(expected, actuals[0], actuals[1]).pass) { - throw new Error(`${key} failed, expect: ${expected}, actual is: ${actuals[0]}`); + const customMatcher = matcher[key](util, customEqualityTesters); + matchers[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, false, actual, expects); + throw new Error(message); } }; - expectObj['not'][key] = function(...actuals: any[]) { - if (customExpected.compare(expected, actuals[0], actuals[1]).pass) { - throw new Error(`${key} failed, not expect: ${expected}, actual is: ${actuals[0]}`); + 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 expectObj; + return matchers; } -function getMatchers(expected: any) { +function getMatchers() { return { - nothing: function() {}, - toBe: function(actual: any) { - if (expected !== actual) { - throw new Error(`Expected ${expected} to be ${actual}`); - } + nothing: function() { + return {compare: (actual: any) => ({pass: true})}; }, - toBeCloseTo: function(actual: 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; - if (Math.round(delta * pow) / pow > maxDelta) { - throw new Error( - `Expected ${expected} to be close to ${actual} with precision ${precision}`); - } + toBe: function() { + return {compare: (actual: any, expected: any) => ({pass: actual === expected})}; }, - toEqual: function(actual: any) { - if (!eq(expected, actual)) { - throw new Error(`Expected ${expected} to be ${actual}`); - } + 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}; + } + }; }, - toBeGreaterThan: function(actual: number) { - if (expected <= actual) { - throw new Error(`Expected ${expected} to be greater than ${actual}`); - } + toEqual: function() { + return {compare: (actual: any, expected: any) => ({pass: eq(actual, expected)})}; }, - toBeGreaterThanOrEqual: function(actual: number) { - if (expected < actual) { - throw new Error(`Expected ${expected} to be greater than or equal ${actual}`); - } + toBeGreaterThan: function() { + return {compare: (actual: any, expected: any) => ({pass: actual > expected})}; }, - toBeLessThan: function(actual: number) { - if (expected >= actual) { - throw new Error(`Expected ${expected} to be lesser than ${actual}`); - } + toBeGreaterThanOrEqual: function() { + return {compare: (actual: any, expected: any) => ({pass: actual >= expected})}; }, - toBeLessThanOrEqual: function(actual: number) { - if (expected > actual) { - throw new Error(`Expected ${expected} to be lesser than or equal ${actual}`); - } + toBeLessThan: function() { + return {compare: (actual: any, expected: any) => ({pass: actual < expected})}; + }, + toBeLessThanOrEqual: function() { + return {compare: (actual: any, expected: any) => ({pass: actual <= expected})}; }, toBeDefined: function() { - if (expected === undefined) { - throw new Error(`Expected ${expected} to be defined`); - } + return {compare: (actual: any) => ({pass: actual !== undefined})}; }, toBeNaN: function() { - if (expected === expected) { - throw new Error(`Expected ${expected} to be NaN`); - } + return {compare: (actual: any) => ({pass: actual !== actual})}; }, toBeNegativeInfinity: function() { - if (expected !== Number.NEGATIVE_INFINITY) { - throw new Error(`Expected ${expected} to be -Infinity`); - } + return {compare: (actual: any) => ({pass: actual === Number.NEGATIVE_INFINITY})}; }, toBeNull: function() { - if (expected !== null) { - throw new Error(`Expected ${expected} to be null`); - } + return {compare: (actual: any) => ({pass: actual === null})}; }, toBePositiveInfinity: function() { - if (expected !== Number.POSITIVE_INFINITY) { - throw new Error(`Expected ${expected} to be +Infinity`); - } + return {compare: (actual: any) => ({pass: actual === Number.POSITIVE_INFINITY})}; }, toBeUndefined: function() { - if (expected !== undefined) { - throw new Error(`Expected ${expected} to be undefined`); - } + return {compare: (actual: any) => ({pass: actual === undefined})}; }, toThrow: function() { - try { - expected(); - } catch (error) { - return; - } - - throw new Error(`Expected ${expected} to throw`); + return { + compare: (actual: any, expected: any) => { + let pass = false; + try { + if (typeof actual === 'function') { + actual(); + } else { + pass = eq(actual, expected); + } + } catch (error) { + pass = eq(error, expected); + } + return {pass}; + } + }; }, - toThrowError: function(errorToBeThrow: any) { - try { - expected(); - } catch (error) { - return; - } - - throw Error(`Expected ${expected} to throw: ${errorToBeThrow}`); + 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() { - if (!expected) { - throw new Error(`Expected ${expected} to be truthy`); - } + return {compare: (actual: any) => ({pass: !!actual})}; }, - toBeFalsy: function(actual: any) { - if (!!actual) { - throw new Error(`Expected ${actual} to be falsy`); - } + toBeFalsy: function() { + return {compare: (actual: any) => ({pass: !actual})}; }, - toContain: function(actual: any) { - if (expected.indexOf(actual) === -1) { - throw new Error(`Expected ${expected} to contain ${actual}`); - } + toContain: function() { + return {compare: (actual: any, expected: any) => ({pass: actual.indexOf(expected) !== -1})}; }, toBeCalled: function() { - if (expected.calls.count() === 0) { - throw new Error(`Expected ${expected} to been called`); - } + return {compare: (actual: any) => ({pass: actual.calls.count() > 0})}; }, toHaveBeenCalled: function() { - if (expected.calls.count() === 0) { - throw new Error(`Expected ${expected} to been called`); - } + return {compare: (actual: any) => ({pass: actual.calls.count() > 0})}; }, - toBeCalledWith: function(...params: any[]) { - if (expected.calls.allArgs().filter((args: any) => eq(args, params)).length === 0) { - throw new Error(`Expected ${expected.calls.allArgs()} to been called with ${params}`); - } - }, - toHaveBeenCalledWith: function(...params: any[]) { - if (expected.calls.allArgs().filter((args: any) => eq(args, params)).length === 0) { - throw new Error(`Expected ${expected.calls.allArgs()} to been called with ${params}`); - } + toBeCalledWith: function() { + return { + compare: (actual: any, ...expected: any[]) => + ({pass: actual.calls.allArgs().filter((args: any) => eq(args, expected)).length > 0}) + }; }, - toMatch: function(actual: any) { - if (!toMatch(actual, expected)) { - throw new Error(`Expected ${expected} to match ${actual}`); - } + toHaveBeenCalledWith: function() { + return { + compare: (actual: any, ...expected: any[]) => + ({pass: actual.calls.allArgs().filter((args: any) => eq(args, expected)).length > 0}) + }; }, - not: { - toBe: function(actual: any) { - if (expected === actual) { - throw new Error(`Expected ${expected} not to be ${actual}`); - } - }, - toBeCloseTo: function(actual: 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; - if (Math.round(delta * pow) / pow <= maxDelta) { - throw new Error( - `Expected ${expected} not to be close to ${actual} with precision ${precision}`); - } - }, - toEqual: function(actual: any) { - if (eq(expected, actual)) { - throw new Error(`Expected ${expected} not to be ${actual}`); - } - }, - 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}`); - } - }, - toBeGreaterThanOrEqual: function(actual: number) { - if (expected >= actual) { - throw new Error(`Expected ${expected} not to be greater than or equal ${actual}`); - } - }, - toBeLessThan: function(actual: number) { - if (expected < actual) { - throw new Error(`Expected ${expected} not to be lesser than ${actual}`); - } - }, - toBeLessThanOrEqual: function(actual: number) { - if (expected <= actual) { - throw new Error(`Expected ${expected} not to be lesser than or equal ${actual}`); - } - }, - toBeDefined: function() { - if (expected !== undefined) { - throw new Error(`Expected ${expected} not to be defined`); - } - }, - toBeNaN: function() { - if (expected !== expected) { - throw new Error(`Expected ${expected} not to be NaN`); - } - }, - toBeNegativeInfinity: function() { - if (expected === Number.NEGATIVE_INFINITY) { - throw new Error(`Expected ${expected} not to be -Infinity`); - } - }, - toBeNull: function() { - if (expected === null) { - throw new Error(`Expected ${expected} not to be null`); - } - }, - toBePositiveInfinity: function() { - if (expected === Number.POSITIVE_INFINITY) { - throw new Error(`Expected ${expected} not to be +Infinity`); - } - }, - toBeUndefined: function() { - if (expected === undefined) { - throw new Error(`Expected ${expected} not to be undefined`); - } - }, - toBeTruthy: function() { - if (!!expected) { - throw new Error(`Expected ${expected} not to be truthy`); - } - }, - toBeFalsy: function() { - if (!expected) { - throw new Error(`Expected ${expected} not to be falsy`); - } - }, - toContain: function(actual: any) { - if (expected.indexOf(actual) !== -1) { - throw new Error(`Expected ${expected} not to contain ${actual}`); - } - }, - toMatch: function(actual: any) { - if (toMatch(actual, expected)) { - throw new Error(`Expected ${expected} not to match ${actual}`); - } - }, - toBeCalled: function() { - if (expected.calls.count() > 0) { - throw new Error(`Expected ${expected} to not been called`); - } - }, - toHaveBeenCalled: function() { - if (expected.calls.count() > 0) { - throw new Error(`Expected ${expected} to not been called`); - } - }, - toBeCalledWith: function(params: any[]) { - if (expected.calls.allArgs().filter((args: any) => eq(args, params)).length > 0) { - throw new Error(`Expected ${expected.calls.allArgs()} to not been called with ${params}`); - } - }, - toHaveBeenCalledWith: function(params: any[]) { - if (expected.calls.allArgs().filter((args: any) => eq(args, params)).length > 0) { - throw new Error(`Expected ${expected.calls.allArgs()} to not been called with ${params}`); - } - } + toMatch: function() { + return {compare: (actual: any, expected: any) => ({pass: toMatch(actual, expected)})}; } }; } -function buildResolveRejects(key: string, matchers: any, expected: any, isNot = false) { - if (matchers.hasOwnProperty(key)) { - const resolveFnFactory = function(isNot = false) { - return function() { - const self = this; - const args = Array.prototype.slice.call(arguments); - return expected.then( - (value: any) => { - const newMatchers: any = getMatchers(value); - return isNot ? newMatchers.not[key].apply(self, args) : - newMatchers[key].apply(self, args); - }, - (error: any) => { - throw error; - }); - }; +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 expected.then((value: any) => {}, (error: any) => { - const newMatchers: any = getMatchers(error); - return isNot ? newMatchers.not[key].apply(self, args) : - newMatchers[key].apply(self, args); - }); - }; + }; + 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(); - } + }; + 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; - global['expect'] = jasmine['__zone_symbol__expect'] = function(expected: any) { + 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: any = getMatchers(expected); - if (expected && typeof expected.then === 'function') { + 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 => { - buildResolveRejects(key, matchers, expected); + if (matchers.hasOwnProperty(key)) { + buildResolveRejects(key, matchers, actual); + } }); Object.keys(matchers.not).forEach(key => { - buildResolveRejects(key, matchers, expected, true); + if (matchers.not.hasOwnProperty(key)) { + buildResolveRejects(key, matchers, actual, true); + } }); } - buildCustomMatchers(matchers, jasmine, expected); 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 index 9487324c8..c87ef722c 100644 --- a/lib/mocha/jasmine-bridge/jasmine.spy.ts +++ b/lib/mocha/jasmine-bridge/jasmine.spy.ts @@ -265,6 +265,14 @@ export function addJasmineSpy(jasmine: any, Mocha: any, global: any) { }); } + 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; @@ -289,6 +297,10 @@ export function addJasmineSpy(jasmine: any, Mocha: any, global: any) { spyRegistry.clearSpies(testInfo); }; + Mocha.clearAllSpies = function() { + spyRegistry.clearAllSpies(); + }; + jasmine.createSpy = function(spyName: string|Function, originalFn?: Function) { if (typeof spyName === 'function') { originalFn = spyName; diff --git a/lib/mocha/jasmine-bridge/jasmine.util.ts b/lib/mocha/jasmine-bridge/jasmine.util.ts index 0b9162c45..25475340b 100644 --- a/lib/mocha/jasmine-bridge/jasmine.util.ts +++ b/lib/mocha/jasmine-bridge/jasmine.util.ts @@ -135,7 +135,9 @@ export function eq(a: any, b: any) { if (Object.keys(a).length !== Object.keys(b).length) { return false; } - + if (a instanceof Error && b instanceof Error) { + return a.message === b.message; + } let isEqual = true; for (let prop in a) { @@ -154,10 +156,44 @@ export function eq(a: any, b: any) { return b.eq(a); } + if (a instanceof Error && typeof b === 'string') { + return a.message === b; + } + if (b instanceof Error && typeof a === 'string') { + return a === b.message; + } + return false; } export function toMatch(actual: any, expected: any) { - const regExp = actual instanceof RegExp ? actual : new RegExp(actual); - return regExp.test(expected); + 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) { + const stringify = Mocha.utils && Mocha.utils.stringify; + return stringify(obj); +} + +export function buildFailureMessage( + matcherName: string, isNot: boolean, actual: any, ...expected: any[]) { + const englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { + return ' ' + s.toLowerCase(); + }); + + var message = 'Expected ' + formatObject(actual) + (isNot ? ' not ' : ' ') + englishyPredicate; + + if (expected.length > 0) { + for (var i = 0; i < expected.length; i++) { + if (i > 0) { + message += ','; + } + message += ' ' + formatObject(expected[i]); + } + } + + return message + '.'; } diff --git a/lib/mocha/jest-bridge/jest.clock.ts b/lib/mocha/jest-bridge/jest.clock.ts new file mode 100644 index 000000000..89953a3c2 --- /dev/null +++ b/lib/mocha/jest-bridge/jest.clock.ts @@ -0,0 +1,95 @@ +/** + * @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() { + const zs = getFakeAsyncTestZoneSpec(); + if (!zs) { + return; + } + // TODO: @JiaLiPassion, add clear method in fakeAsyncZoneSpec + // flush(); + }; + + jest.runAllTimers = function() { + const zs = getFakeAsyncTestZoneSpec(); + if (!zs) { + return; + } + // TODO: @JiaLiPassion, now flush can only flush + // non periodic timers, should flush periodic too. + flush(); + }; + + jest.runAllImmediates = function() { + const zs = getFakeAsyncTestZoneSpec(); + if (!zs) { + return; + } + // TODO: @JiaLiPassion, should we support this one? + flush(); + }; + + jest.runOnlyPendingTimers = function() { + const zs = getFakeAsyncTestZoneSpec(); + if (!zs) { + return; + } + // TODO: @JiaLiPassion, should we support this one? + flush(); + }; + + jest.advanceTimersByTime = function(msToRun: number) { + const zs = getFakeAsyncTestZoneSpec(); + if (!zs) { + return; + } + tick(msToRun); + }; + + + jest.runAllTicks = function() { + const zs = getFakeAsyncTestZoneSpec(); + if (!zs) { + return; + } + flushMicrotasks(); + }; + + jest.useFakeTimers = function() { + const zs = getFakeAsyncTestZoneSpec(); + if (zs) { + return; + } + const fakeAsyncTestZoneSpec = new FakeAsyncTestZoneSpec() + const proxyZoneSpec = ProxyZoneSpec.get(); + jest.__zone_symbol__last_delegate_spec = proxyZoneSpec.getDelegate(); + proxyZoneSpec.setDelegate(fakeAsyncTestZoneSpec); + fakeAsyncTestZoneSpec.lockDatePatch(); + }; + + jest.useRealTimers = function() { + const zs = getFakeAsyncTestZoneSpec(); + if (!zs) { + throw new Error('Must use real timers in the same block with useFakeTimers'); + } + const proxyZoneSpec = ProxyZoneSpec.get(); + const lastDelegate = jest.__zone_symbol__last_delegate_spec; + jest.__zone_symbol__last_delegate_spec = null; + proxyZoneSpec.setDelegate(lastDelegate); + zs.unlockDatePatch(); + } +} \ No newline at end of file diff --git a/lib/mocha/jest-bridge/jest.expect.ts b/lib/mocha/jest-bridge/jest.expect.ts index 9716ab40d..fbfec67bb 100644 --- a/lib/mocha/jest-bridge/jest.expect.ts +++ b/lib/mocha/jest-bridge/jest.expect.ts @@ -109,7 +109,7 @@ export function expandExpect(global: any) { if (b instanceof ObjectContaining) { Object.keys(b.expectedObject).forEach(key => { if (b.expectedObject.hasOwnProperty(key)) { - if (!eq(a[key], b.expectedObject[key]) || !toMatch(b.expectedObject[key], a[key])) { + if (!eq(a[key], b.expectedObject[key]) || !toMatch(a[key], b.expectedObject[key])) { return false; } } @@ -131,7 +131,7 @@ export function expandExpect(global: any) { if (typeof a !== 'string') { astr = Object.prototype.toString.call(a); } - return toMatch(b.expectedMatcher, astr); + return toMatch(astr, b.expectedMatcher); } }); @@ -156,21 +156,21 @@ export function expandExpect(global: any) { toHaveBeenCalledTimes: function(util: any, customEqualityTester: any) { return { compare: function(actual: any, expected: any) { - return {pass: expected.calls.count() === actual}; + return {pass: actual.calls.count() === expected}; } }; }, lastCalledWith: function(util: any, customEqualityTester: any) { return { compare: function(actual: any, expected: any) { - return {pass: util.equals(actual, expected.calls.last().args)}; + 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, expected.calls.last().args)}; + return {pass: util.equals(actual.calls.last().args, expected)}; } }; }, diff --git a/lib/mocha/jest-bridge/jest.spy.ts b/lib/mocha/jest-bridge/jest.spy.ts index 151c60df3..879a05cec 100644 --- a/lib/mocha/jest-bridge/jest.spy.ts +++ b/lib/mocha/jest-bridge/jest.spy.ts @@ -6,8 +6,9 @@ * 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 = jasmine.createSpy('jestSpy', implFn); + const spy = spyFactory(implFn); spy.defaultFn = implFn; const instances: any[] = []; const mockFn: any = function MockFn() { @@ -101,6 +102,7 @@ export function mappingSpy(jest: any, jasmine: any, global: any) { global.Mocha.clearSpies(global.Mocha.__zone_symbol__current_ctx); }; + jest.__zone_symbol__mocks.push(mockFn); return mockFn; } @@ -112,4 +114,27 @@ export function mappingSpy(jest: any, jasmine: any, global: any) { 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 index b232ea924..b65e0a1e3 100644 --- a/lib/mocha/jest-bridge/jest.ts +++ b/lib/mocha/jest-bridge/jest.ts @@ -6,6 +6,7 @@ * 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'; @@ -16,6 +17,7 @@ Zone.__load_patch('jest2mocha', (global: any) => { return; } // TODO: @JiaLiPassion, now we only support jest in Mocha runner + // support jasmine later. if (global.Mocha['__zone_symbol__isBridge']) { return; } @@ -26,4 +28,12 @@ Zone.__load_patch('jest2mocha', (global: any) => { 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/test/spec/mocha/jest-bridge.spec.ts b/test/spec/mocha/jest-bridge.spec.ts index 3cf172c90..ef1356366 100644 --- a/test/spec/mocha/jest-bridge.spec.ts +++ b/test/spec/mocha/jest-bridge.spec.ts @@ -147,7 +147,7 @@ describe('expect', () => { }); test('rejects to octopus', async () => { - await (expect(Promise.reject(new Error('octopus'))) as any).rejects.toThrow('octopus'); + return await (expect(Promise.reject(new Error('octopus'))) as any).rejects.toThrow('octopus'); }); }); From d95f5b76b7d1be9bb065e9b11aebd3d77c5c2bf3 Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Sat, 12 May 2018 00:50:22 +0900 Subject: [PATCH 09/11] feat(test): add zone-testing typing --- .gitignore | 1 + gulpfile.js | 6 + lib/jasmine/jasmine.ts | 3 +- lib/mocha/jasmine-bridge/jasmine.expect.ts | 9 +- lib/mocha/jasmine-bridge/jasmine.ts | 5 + lib/mocha/jasmine-bridge/jasmine.util.ts | 25 +- lib/mocha/jest-bridge/jest.clock.ts | 6 +- lib/mocha/jest-bridge/jest.expect.ts | 27 +- lib/mocha/jest-bridge/jest.ts | 2 +- lib/mocha/mocha-patch.ts | 14 +- lib/testing/zone-testing.typing.ts | 271 +++++++++++++++++++++ package.json | 1 - test/spec/browser/WebSocket.spec.ts | 1 - test/spec/browser/browser.spec.ts | 1 - test/spec/jasmine/jasmine-patch.spec.ts | 2 +- test/spec/mocha/jasmine-bridge.spec.ts | 33 ++- test/spec/mocha/jest-bridge.spec.ts | 67 ++--- yarn.lock | 4 - 18 files changed, 365 insertions(+), 113 deletions(-) create mode 100644 lib/testing/zone-testing.typing.ts 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/gulpfile.js b/gulpfile.js index ee0956804..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); diff --git a/lib/jasmine/jasmine.ts b/lib/jasmine/jasmine.ts index 65d515de2..197905f52 100644 --- a/lib/jasmine/jasmine.ts +++ b/lib/jasmine/jasmine.ts @@ -6,4 +6,5 @@ * found in the LICENSE file at https://angular.io/license */ import './jasmine-patch'; -import './mocha-bridge/mocha-bridge'; \ No newline at end of file +// TODO: @JiaLiPassion, add mocha/jest bridge for jasmine later +// import './mocha-bridge/mocha-bridge'; \ No newline at end of file diff --git a/lib/mocha/jasmine-bridge/jasmine.expect.ts b/lib/mocha/jasmine-bridge/jasmine.expect.ts index c876bf37a..a24178f76 100644 --- a/lib/mocha/jasmine-bridge/jasmine.expect.ts +++ b/lib/mocha/jasmine-bridge/jasmine.expect.ts @@ -48,16 +48,17 @@ function buildCustomMatchers(jasmine: any, actual: any) { if (matcher.hasOwnProperty(key)) { const customMatcher = matcher[key](util, customEqualityTesters); matchers[key] = function(...expects: any[]) { - const args = expects ? expects : []; + const args = expects ? [...expects] : []; args.unshift(actual); const result = customMatcher.compare.apply(null, args); if (!result.pass) { + console.log('compare ', args); const message = result.messge || util.buildFailureMessage(key, false, actual, expects); throw new Error(message); } }; matchers['not'][key] = function(...expects: any[]) { - const args = expects ? expects : []; + const args = expects ? [...expects] : []; args.unshift(actual); const result = customMatcher.compare.apply(null, args); if (result.pass) { @@ -133,10 +134,10 @@ function getMatchers() { if (typeof actual === 'function') { actual(); } else { - pass = eq(actual, expected); + pass = (!expected && actual instanceof Error) || eq(actual, expected); } } catch (error) { - pass = eq(error, expected); + pass = !expected || eq(error, expected); } return {pass}; } diff --git a/lib/mocha/jasmine-bridge/jasmine.ts b/lib/mocha/jasmine-bridge/jasmine.ts index 65b35d5d7..7f3348cc5 100644 --- a/lib/mocha/jasmine-bridge/jasmine.ts +++ b/lib/mocha/jasmine-bridge/jasmine.ts @@ -9,6 +9,7 @@ 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') { @@ -48,4 +49,8 @@ Zone.__load_patch('jasmine2mocha', (global: any) => { 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 index 25475340b..81a04d52c 100644 --- a/lib/mocha/jasmine-bridge/jasmine.util.ts +++ b/lib/mocha/jasmine-bridge/jasmine.util.ts @@ -97,7 +97,11 @@ export function addCustomEqualityTester(jasmine: any) { }; } -export function eq(a: any, b: any) { +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) { @@ -132,12 +136,13 @@ export function eq(a: any, b: any) { 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; } - if (a instanceof Error && b instanceof Error) { - return a.message === b.message; - } let isEqual = true; for (let prop in a) { @@ -156,11 +161,11 @@ export function eq(a: any, b: any) { return b.eq(a); } - if (a instanceof Error && typeof b === 'string') { - return a.message === b; + if (a instanceof Error) { + return eq(getErrorMessage(a), b) || toMatch(getErrorMessage(a), b); } - if (b instanceof Error && typeof a === 'string') { - return a === b.message; + if (b instanceof Error) { + return eq(a, getErrorMessage(b)) || toMatch(a, getErrorMessage(b)); } return false; @@ -184,10 +189,10 @@ export function buildFailureMessage( return ' ' + s.toLowerCase(); }); - var message = 'Expected ' + formatObject(actual) + (isNot ? ' not ' : ' ') + englishyPredicate; + let message = 'Expected ' + formatObject(actual) + (isNot ? ' not ' : ' ') + englishyPredicate; if (expected.length > 0) { - for (var i = 0; i < expected.length; i++) { + for (let i = 0; i < expected.length; i++) { if (i > 0) { message += ','; } diff --git a/lib/mocha/jest-bridge/jest.clock.ts b/lib/mocha/jest-bridge/jest.clock.ts index 89953a3c2..0ac34733b 100644 --- a/lib/mocha/jest-bridge/jest.clock.ts +++ b/lib/mocha/jest-bridge/jest.clock.ts @@ -74,14 +74,14 @@ export function addJestTimer(jest: any, global: any) { if (zs) { return; } - const fakeAsyncTestZoneSpec = new FakeAsyncTestZoneSpec() + const fakeAsyncTestZoneSpec = new FakeAsyncTestZoneSpec(); const proxyZoneSpec = ProxyZoneSpec.get(); jest.__zone_symbol__last_delegate_spec = proxyZoneSpec.getDelegate(); proxyZoneSpec.setDelegate(fakeAsyncTestZoneSpec); fakeAsyncTestZoneSpec.lockDatePatch(); }; - jest.useRealTimers = function() { + jest.useRealTimers = function () { const zs = getFakeAsyncTestZoneSpec(); if (!zs) { throw new Error('Must use real timers in the same block with useFakeTimers'); @@ -91,5 +91,5 @@ export function addJestTimer(jest: any, global: any) { jest.__zone_symbol__last_delegate_spec = null; proxyZoneSpec.setDelegate(lastDelegate); zs.unlockDatePatch(); - } + }; } \ No newline at end of file diff --git a/lib/mocha/jest-bridge/jest.expect.ts b/lib/mocha/jest-bridge/jest.expect.ts index fbfec67bb..e34c13198 100644 --- a/lib/mocha/jest-bridge/jest.expect.ts +++ b/lib/mocha/jest-bridge/jest.expect.ts @@ -6,34 +6,9 @@ * found in the LICENSE file at https://angular.io/license */ import {Any, eq, toMatch} from '../jasmine-bridge/jasmine.util'; -declare namespace jasmine { - interface Expect { - anything: () => any; - any: (expectedObject: any) => any; - arrayContaining: (expectedArray: string[]) => any; - objectContaining: (expectedObject: any) => any; - stringContaining: (expectedString: string) => any; - stringMatching: (expectedMatcher: RegExp|string) => any; - extend: (extendedMatchers: any) => any; - assertions: (numbers: number) => void; - hasAssertions: () => void; - } - - interface Matchers { - toHaveBeenCalledTimes: (expected: number) => boolean; - lastCalledWith: (...params: any[]) => boolean; - toHaveBeenLastCalledWith: (...params: any[]) => boolean; - toBeInstanceOf: (expected: any) => boolean; - toContainEqual: (expected: any) => boolean; - toHaveLength: (expected: number) => boolean; - toHaveProperty: (expected: any, value: any) => boolean; - toMatchObject: (expected: any) => boolean; - } -} - export function expandExpect(global: any) { const jasmine = global.jasmine; - const expect: jasmine.Expect = global.expect; + const expect: any = global.expect; class Anything {} diff --git a/lib/mocha/jest-bridge/jest.ts b/lib/mocha/jest-bridge/jest.ts index b65e0a1e3..10ec36781 100644 --- a/lib/mocha/jest-bridge/jest.ts +++ b/lib/mocha/jest-bridge/jest.ts @@ -18,7 +18,7 @@ Zone.__load_patch('jest2mocha', (global: any) => { } // TODO: @JiaLiPassion, now we only support jest in Mocha runner // support jasmine later. - if (global.Mocha['__zone_symbol__isBridge']) { + if (!global.Mocha || global.Mocha['__zone_symbol__isBridge']) { return; } // create a jasmine global object diff --git a/lib/mocha/mocha-patch.ts b/lib/mocha/mocha-patch.ts index bc52a0b84..0d70f9697 100644 --- a/lib/mocha/mocha-patch.ts +++ b/lib/mocha/mocha-patch.ts @@ -8,16 +8,6 @@ 'use strict'; -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; - Zone.__load_patch('Mocha', (global: any, Zone: ZoneType, api: _ZonePrivate) => { const Mocha = global.Mocha; @@ -148,8 +138,12 @@ Zone.__load_patch('Mocha', (global: any, Zone: ZoneType, api: _ZonePrivate) => { } 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]); }; diff --git a/lib/testing/zone-testing.typing.ts b/lib/testing/zone-testing.typing.ts new file mode 100644 index 000000000..670622357 --- /dev/null +++ b/lib/testing/zone-testing.typing.ts @@ -0,0 +1,271 @@ +/** + * @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 + */ +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 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 runAllImmediates(): typeof jest; + 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; + } +} + +declare function pending(reason?: string): void; diff --git a/package.json b/package.json index 0511c2cd6..0493dd433 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,6 @@ }, "dependencies": {}, "devDependencies": { - "@types/jasmine": "2.2.33", "@types/node": "^6.0.96", "@types/systemjs": "^0.19.30", "assert": "^1.4.1", diff --git a/test/spec/browser/WebSocket.spec.ts b/test/spec/browser/WebSocket.spec.ts index 32aeb12d4..3f4aa1f19 100644 --- a/test/spec/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() { diff --git a/test/spec/browser/browser.spec.ts b/test/spec/browser/browser.spec.ts index 72f462f69..37db17f40 100644 --- a/test/spec/browser/browser.spec.ts +++ b/test/spec/browser/browser.spec.ts @@ -122,7 +122,6 @@ describe('Zone', function() { return parentZoneDelegate.scheduleTask(targetZone, task); } }); - beforeEach(function() { mouseEvent.initEvent('mousedown', true, true); hookSpy = jasmine.createSpy('hook'); diff --git a/test/spec/jasmine/jasmine-patch.spec.ts b/test/spec/jasmine/jasmine-patch.spec.ts index 3df23f3fc..9f5e83cb0 100644 --- a/test/spec/jasmine/jasmine-patch.spec.ts +++ b/test/spec/jasmine/jasmine-patch.spec.ts @@ -45,7 +45,7 @@ ifEnvSupports(supportJasmineSpec, () => { expect(beforeAllCalledCount).toBe(1); }); - beforeEach(() => beforeEachZone = Zone.current); + 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 index c7cb56808..d93902f26 100644 --- a/test/spec/mocha/jasmine-bridge.spec.ts +++ b/test/spec/mocha/jasmine-bridge.spec.ts @@ -1251,9 +1251,30 @@ describe('Custom matcher: \'toBeGoofy\'', function() { }); describe('failed', () => { - try { - fail('error'); - } catch (error) { - expect(error).toEqual('error'); - } -}); \ No newline at end of file + 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 index ef1356366..79c7cd351 100644 --- a/test/spec/mocha/jest-bridge.spec.ts +++ b/test/spec/mocha/jest-bridge.spec.ts @@ -5,9 +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 */ -declare let jest: any; -declare function test(description: string, testFn: () => void): void; - describe('extend', () => { (expect as any).extend({ toBeDivisibleBy(received: any, argument: any) { @@ -54,16 +51,12 @@ describe('expect', () => { 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 as any).arrayContaining(expected), - ); + expect([4, 1, 6, 7, 3, 5, 2, 5, 4, 6]).toEqual((expect as any).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 as any).arrayContaining(expected), - ); + expect([ + 4, 1, 6, 7, 3, 5, 7, 5, 4, 6 + ]).not.toEqual((expect as any).arrayContaining(expected), ); }); }); @@ -92,13 +85,10 @@ describe('expect', () => { describe('objectContaining', () => { test('onPress should object containing with the right thing', () => { const onPress = {x: 100, y: 200, z: 300}; - (expect(onPress) as any) - .toEqual( - (expect as any).objectContaining({ - x: (expect as any).any(Number), - y: (expect as any).any(Number), - }), - ); + (expect(onPress) as any).toEqual((expect as any).objectContaining({ + x: (expect as any).any(Number), + y: (expect as any).any(Number), + }), ); }); }); @@ -114,16 +104,10 @@ describe('expect', () => { (expect as any).stringMatching(/^[BR]ob/), ]; it('matches even if received contains additional elements', () => { - expect(['Alicia', 'Roberto', 'Evelina']) - .toEqual( - (expect as any).arrayContaining(expected), - ); + expect(['Alicia', 'Roberto', 'Evelina']).toEqual((expect as any).arrayContaining(expected), ); }); it('does not match if received does not contain expected elements', () => { - expect(['Roberto', 'Evelina']) - .not.toEqual( - (expect as any).arrayContaining(expected), - ); + expect(['Roberto', 'Evelina']).not.toEqual((expect as any).arrayContaining(expected), ); }); }); @@ -133,21 +117,18 @@ describe('expect', () => { return (expect(Promise.resolve('lemon')) as any).resolves.toBe('lemon'); }); - test('resolves to lemon with await', async () => { - await (expect(Promise.resolve('lemon')) as any).resolves.toBe('lemon'); - await (expect(Promise.resolve('lemon')) as any).resolves.not.toBe('octopus'); + test('resolves to lemon with await', async() => { + await(expect(Promise.resolve('lemon')) as any).resolves.toBe('lemon'); + await(expect(Promise.resolve('lemon')) as any).resolves.not.toBe('octopus'); }); test('rejects to octopus', () => { // make sure to add a return statement - return (expect(Promise.reject(new Error('octopus'))) as any) - .rejects.toThrow( - 'octopus', - ); + return (expect(Promise.reject(new Error('octopus'))) as any).rejects.toThrow('octopus'); }); - test('rejects to octopus', async () => { - return await (expect(Promise.reject(new Error('octopus'))) as any).rejects.toThrow('octopus'); + test('rejects to octopus', async() => { + return await(expect(Promise.reject(new Error('octopus'))) as any).rejects.toThrow('octopus'); }); }); @@ -243,11 +224,9 @@ describe('expect', () => { // Deep referencing using an array containing the keyPath (expect(houseForSale) as any).toHaveProperty(['kitchen', 'area'], 20); - (expect(houseForSale) as any) - .toHaveProperty( - ['kitchen', 'amenities'], - ['oven', 'stove', 'washer'], - ); + (expect(houseForSale) as any).toHaveProperty(['kitchen', 'amenities'], [ + 'oven', 'stove', 'washer' + ], ); (expect(houseForSale) as any).toHaveProperty(['kitchen', 'amenities', 0], 'oven'); (expect(houseForSale).not as any).toHaveProperty(['kitchen', 'open']); @@ -339,14 +318,14 @@ describe('expect', () => { expect(logs).toEqual(['first call', 'second call', 'default', 'default']); }); - test('mockResolvedValue', async () => { + test('mockResolvedValue', async() => { const asyncMock = jest.fn().mockResolvedValue(43); const result = await asyncMock(); // 43 expect(result).toBe(43); }); - test('mockResolvedValueOnce', async () => { + test('mockResolvedValueOnce', async() => { const asyncMock = jest.fn() .mockResolvedValue('default') .mockResolvedValueOnce('first call') @@ -360,7 +339,7 @@ describe('expect', () => { expect(logs).toEqual(['first call', 'second call', 'default', 'default']); }); - test('mockRejectedValue', async () => { + test('mockRejectedValue', async() => { const asyncMock = jest.fn().mockRejectedValue(new Error('Async error')); try { @@ -370,7 +349,7 @@ describe('expect', () => { } }); - test('mockRejectedValueOnce', async () => { + test('mockRejectedValueOnce', async() => { const asyncMock = jest.fn() .mockResolvedValueOnce('first call') .mockRejectedValueOnce(new Error('Async error')); 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" From 26a2b611aa07e71d4af734f620f29a802b61ebc3 Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Sun, 13 May 2018 19:15:49 +0900 Subject: [PATCH 10/11] feat(test): add global interface of zone-testing about async/fakeAsync --- lib/jasmine/jasmine-patch.ts | 9 +- lib/jasmine/jasmine.clock.ts | 27 ++-- lib/mocha/jasmine-bridge/jasmine.clock.ts | 4 +- lib/mocha/jasmine-bridge/jasmine.expect.ts | 1 - lib/mocha/jasmine-bridge/jasmine.ts | 2 +- lib/mocha/jest-bridge/jest.clock.ts | 53 +++---- lib/testing/async-testing.ts | 2 +- lib/testing/fake-async.ts | 47 +++++- lib/testing/zone-testing.typing.ts | 22 ++- lib/zone-spec/fake-async-test.ts | 12 ++ test/spec/mocha/jest-bridge.spec.ts | 137 ++++++++++++++++++ .../spec/mocha/mocha-node-test-entry-point.ts | 1 + test/spec/zone-spec/fake-async-test.spec.ts | 39 ++++- 13 files changed, 287 insertions(+), 69 deletions(-) diff --git a/lib/jasmine/jasmine-patch.ts b/lib/jasmine/jasmine-patch.ts index f11ed2843..54908cb8a 100644 --- a/lib/jasmine/jasmine-patch.ts +++ b/lib/jasmine/jasmine-patch.ts @@ -46,9 +46,6 @@ Zone.__load_patch('jasmine', (global: any) => { 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 => { @@ -79,7 +76,7 @@ Zone.__load_patch('jasmine', (global: any) => { }; }); - patchJasmineClock(jasmine, enableClockPatch); + patchJasmineClock(jasmine, global); /** * Gets a function wrapping the body of a Jasmine `describe` block to execute in a * synchronous-only zone. @@ -92,10 +89,10 @@ Zone.__load_patch('jasmine', (global: any) => { } function runInTestZone(testBody: Function, applyThis: any, queueRunner: any, done?: Function) { - const isClockInstalled = !!(jasmine as any)[symbol('clockInstalled')]; + const isClockInstalled = (jasmine as any)[symbol('clockInstalled')] === true; const testProxyZoneSpec = queueRunner.testProxyZoneSpec; const testProxyZone = queueRunner.testProxyZone; - if (isClockInstalled && enableClockPatch) { + if (isClockInstalled) { // auto run a fakeAsync const fakeAsyncModule = (Zone as any)[Zone.__symbol__('fakeAsyncTest')]; if (fakeAsyncModule && typeof fakeAsyncModule.fakeAsync === 'function') { diff --git a/lib/jasmine/jasmine.clock.ts b/lib/jasmine/jasmine.clock.ts index bba901495..879cb65c6 100644 --- a/lib/jasmine/jasmine.clock.ts +++ b/lib/jasmine/jasmine.clock.ts @@ -7,7 +7,7 @@ */ // need to patch jasmine.clock().mockDate and jasmine.clock().tick() so // they can work properly in FakeAsyncTest -export function patchJasmineClock(jasmine: any, enableClockPatch: boolean) { +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() { @@ -35,19 +35,18 @@ export function patchJasmineClock(jasmine: any, enableClockPatch: boolean) { 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); - }; - }); - } + ['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/mocha/jasmine-bridge/jasmine.clock.ts b/lib/mocha/jasmine-bridge/jasmine.clock.ts index f1c39dd65..4339adb5e 100644 --- a/lib/mocha/jasmine-bridge/jasmine.clock.ts +++ b/lib/mocha/jasmine-bridge/jasmine.clock.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ import {patchJasmineClock} from '../../jasmine/jasmine.clock'; -export function addJasmineClock(jasmine: any) { +export function addJasmineClock(jasmine: any, global: any) { jasmine.clock = function() { return { tick: function() {}, @@ -15,5 +15,5 @@ export function addJasmineClock(jasmine: any) { mockDate: function() {} }; }; - patchJasmineClock(jasmine, true); + patchJasmineClock(jasmine, global); } diff --git a/lib/mocha/jasmine-bridge/jasmine.expect.ts b/lib/mocha/jasmine-bridge/jasmine.expect.ts index a24178f76..07b961609 100644 --- a/lib/mocha/jasmine-bridge/jasmine.expect.ts +++ b/lib/mocha/jasmine-bridge/jasmine.expect.ts @@ -52,7 +52,6 @@ function buildCustomMatchers(jasmine: any, actual: any) { args.unshift(actual); const result = customMatcher.compare.apply(null, args); if (!result.pass) { - console.log('compare ', args); const message = result.messge || util.buildFailureMessage(key, false, actual, expects); throw new Error(message); } diff --git a/lib/mocha/jasmine-bridge/jasmine.ts b/lib/mocha/jasmine-bridge/jasmine.ts index 7f3348cc5..3237679dd 100644 --- a/lib/mocha/jasmine-bridge/jasmine.ts +++ b/lib/mocha/jasmine-bridge/jasmine.ts @@ -36,7 +36,7 @@ Zone.__load_patch('jasmine2mocha', (global: any) => { addJasmineSpy(jasmine, global.Mocha, global); // Add jasmine clock functionality - addJasmineClock(jasmine); + addJasmineClock(jasmine, global); Object.defineProperty(jasmine, 'DEFAULT_TIMEOUT_INTERVAL', { configurable: true, diff --git a/lib/mocha/jest-bridge/jest.clock.ts b/lib/mocha/jest-bridge/jest.clock.ts index 0ac34733b..29322ea07 100644 --- a/lib/mocha/jest-bridge/jest.clock.ts +++ b/lib/mocha/jest-bridge/jest.clock.ts @@ -16,12 +16,7 @@ export function addJestTimer(jest: any, global: any) { } jest.clearAllTimers = function() { - const zs = getFakeAsyncTestZoneSpec(); - if (!zs) { - return; - } - // TODO: @JiaLiPassion, add clear method in fakeAsyncZoneSpec - // flush(); + clearAllMacrotasks(); }; jest.runAllTimers = function() { @@ -29,18 +24,11 @@ export function addJestTimer(jest: any, global: any) { if (!zs) { return; } - // TODO: @JiaLiPassion, now flush can only flush - // non periodic timers, should flush periodic too. - flush(); - }; - - jest.runAllImmediates = function() { - const zs = getFakeAsyncTestZoneSpec(); - if (!zs) { - return; + if (zs.pendingPeriodicTimers.length > 0) { + throw new Error('Can not runAllTimers when having interval timers.'); } - // TODO: @JiaLiPassion, should we support this one? - flush(); + // flush non-perodic-tasks with 10000 maxTurns + flush(10000); }; jest.runOnlyPendingTimers = function() { @@ -48,19 +36,18 @@ export function addJestTimer(jest: any, global: any) { if (!zs) { return; } - // TODO: @JiaLiPassion, should we support this one? - flush(); + // flush both periodic tasks and non-perodic-tasks + flush(10000, true); }; - jest.advanceTimersByTime = function(msToRun: number) { + jest.advanceTimersByTime = function(msToRun: number, doTick?: (elapsed: number) => void) { const zs = getFakeAsyncTestZoneSpec(); if (!zs) { return; } - tick(msToRun); + tick(msToRun, doTick); }; - jest.runAllTicks = function() { const zs = getFakeAsyncTestZoneSpec(); if (!zs) { @@ -74,22 +61,16 @@ export function addJestTimer(jest: any, global: any) { if (zs) { return; } - const fakeAsyncTestZoneSpec = new FakeAsyncTestZoneSpec(); - const proxyZoneSpec = ProxyZoneSpec.get(); - jest.__zone_symbol__last_delegate_spec = proxyZoneSpec.getDelegate(); - proxyZoneSpec.setDelegate(fakeAsyncTestZoneSpec); - fakeAsyncTestZoneSpec.lockDatePatch(); + /** + * 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 () { - const zs = getFakeAsyncTestZoneSpec(); - if (!zs) { - throw new Error('Must use real timers in the same block with useFakeTimers'); - } - const proxyZoneSpec = ProxyZoneSpec.get(); - const lastDelegate = jest.__zone_symbol__last_delegate_spec; - jest.__zone_symbol__last_delegate_spec = null; - proxyZoneSpec.setDelegate(lastDelegate); - zs.unlockDatePatch(); + global['__zone_symbol__fakeAsyncPatchLock'] = global['__zone_symbol__originFakeAsyncPatchLock']; + global.jasmine && global.jasmine.clock().uninstall(); }; } \ 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 28ae6a700..c7b34e3b2 100644 --- a/lib/testing/fake-async.ts +++ b/lib/testing/fake-async.ts @@ -55,6 +55,9 @@ Zone.__load_patch('fakeasync', (global: any, Zone: ZoneType, api: _ZonePrivate) 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')) { @@ -132,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); } /** @@ -146,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); } /** @@ -169,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.typing.ts b/lib/testing/zone-testing.typing.ts index 670622357..55d6f4bb0 100644 --- a/lib/testing/zone-testing.typing.ts +++ b/lib/testing/zone-testing.typing.ts @@ -8,6 +8,9 @@ /** * 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; @@ -37,6 +40,23 @@ 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 { @@ -229,7 +249,6 @@ declare namespace jest { function fn(implementation: (...args: any[]) => T): Mock; function fn(implementation?: (...args: any[]) => any): Mock; function isMockFunction(fn: any): fn is Mock; - function runAllImmediates(): typeof jest; function runAllTicks(): typeof jest; function runAllTimers(): typeof jest; function runOnlyPendingTimers(): typeof jest; @@ -268,4 +287,3 @@ declare namespace jest { } } -declare function pending(reason?: string): void; 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/test/spec/mocha/jest-bridge.spec.ts b/test/spec/mocha/jest-bridge.spec.ts index 79c7cd351..25c4d5f65 100644 --- a/test/spec/mocha/jest-bridge.spec.ts +++ b/test/spec/mocha/jest-bridge.spec.ts @@ -5,6 +5,8 @@ * 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 as any).extend({ toBeDivisibleBy(received: any, argument: any) { @@ -363,4 +365,139 @@ describe('expect', () => { } }); }); +}); + +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-node-test-entry-point.ts b/test/spec/mocha/mocha-node-test-entry-point.ts index 7dad9ced7..944a61fbd 100644 --- a/test/spec/mocha/mocha-node-test-entry-point.ts +++ b/test/spec/mocha/mocha-node-test-entry-point.ts @@ -5,6 +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 */ +(global as any)[('__zone_symbol__fakeAsyncPatchLock')] = true; import '../../../lib/node/rollup-main'; import '../../../lib/mocha/mocha-node-checker'; import '../../../lib/testing/zone-testing'; diff --git a/test/spec/zone-spec/fake-async-test.spec.ts b/test/spec/zone-spec/fake-async-test.spec.ts index e6199da32..55953edf0 100644 --- a/test/spec/zone-spec/fake-async-test.spec.ts +++ b/test/spec/zone-spec/fake-async-test.spec.ts @@ -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; @@ -1168,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', () => { @@ -1493,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', () => { From 87805700b8f6a381c5b7fb95956ea6a24d29d8c6 Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Sun, 13 May 2018 19:36:36 +0900 Subject: [PATCH 11/11] doc(test): add more test document --- doc/design/TEST.md | 266 +++++++++++++++++- doc/design/TEST_RUNNER.md | 256 ----------------- lib/mocha/jasmine-bridge/jasmine.expect.ts | 2 +- lib/mocha/jasmine-bridge/jasmine.util.ts | 7 +- lib/mocha/jest-bridge/jest.ts | 4 + lib/mocha/mocha-node-checker.ts | 19 +- lib/mocha/mocha-patch.ts | 118 +++++--- lib/mocha/mocha.ts | 1 + test/env/common/common_tests.ts | 1 - test/spec/mocha/jest-bridge.spec.ts | 88 +++--- .../spec/mocha/mocha-node-test-entry-point.ts | 3 +- 11 files changed, 396 insertions(+), 369 deletions(-) delete mode 100644 doc/design/TEST_RUNNER.md diff --git a/doc/design/TEST.md b/doc/design/TEST.md index 6b68a569f..e1c3b3cd0 100644 --- a/doc/design/TEST.md +++ b/doc/design/TEST.md @@ -1,25 +1,261 @@ -# Zone Testing +# Test Runner -`zone.js` has a `zone-testing.js` bundle, which provides a lot of functionalities for testing. -include: +`zone-testing` monkey-patch `jasmine` and `mocha` runner to provide the following functionalities. -1. FakeAsyncTestZoneSpec: simulate system timer to make `async` test faster and stable. -2. AsyncTestZoneSpec: automatically wait for all async tasks to finish. -3. SyncTestZoneSpec: force all tests to be synchronized. -4. Jasmine/Mocha/Jest supports. +1. All `describe/xdescribe/fdescribe` are guaranteed to run in `SyncTestZone`, so no `async` code are allowed under `describe` directly. -## FakeAsyncTestZoneSpec +```javascript +describe('test', () => { + // setTimeout(() => {}, 100); // not allowed +}); +``` -Add `fakeAsync` document later. +2. Support `fakeAsync` in `test`. -## AsyncTestZoneSpec +```javascript +import 'zone.js`; +import 'zone.js/dist/zone-testing`; -Add `async` document later. +// 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); + })); -## SyncTestZoneSpec + it ('Promise', fakeAsync(() => { + let thenRan = false; + Promise.resolve(null).then((_) => { + thenRan = true; + }); -Add `sync` document later. + expect(thenRan).toEqual(false); + flushMicrotasks(); + expect(thenRan).toEqual(true); + })); +}); +``` -## Unify jasmine/mocha/jest +Will add more examples later. -`zone-testing` support `jasmine` and `mocha` runner, when `zone-testing` is loaded, it will detect current test environment(jasmine or mocha) and monkey-patch current runner accordingly. For detail, please check this document [test runner](./TEST_RUNNER.md). +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/design/TEST_RUNNER.md b/doc/design/TEST_RUNNER.md deleted file mode 100644 index de3f0c86a..000000000 --- a/doc/design/TEST_RUNNER.md +++ /dev/null @@ -1,256 +0,0 @@ -# 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`; -// TODO: this is too complex to load fakeAsyncTest, should expose as global function. -const {resetFakeAsyncZone, flushMicrotasks, discardPeriodicTasks, tick, flush, fakeAsync} = (Zone as any)[Zone.__symbol__('fakeAsyncTest')]; - -// 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. - -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 also use `jest style expect and mock`. - -```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. 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 mock -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/lib/mocha/jasmine-bridge/jasmine.expect.ts b/lib/mocha/jasmine-bridge/jasmine.expect.ts index 07b961609..ee26f6959 100644 --- a/lib/mocha/jasmine-bridge/jasmine.expect.ts +++ b/lib/mocha/jasmine-bridge/jasmine.expect.ts @@ -48,7 +48,7 @@ function buildCustomMatchers(jasmine: any, actual: any) { if (matcher.hasOwnProperty(key)) { const customMatcher = matcher[key](util, customEqualityTesters); matchers[key] = function(...expects: any[]) { - const args = expects ? [...expects] : []; + const args = expects ? expects.slice() : []; args.unshift(actual); const result = customMatcher.compare.apply(null, args); if (!result.pass) { diff --git a/lib/mocha/jasmine-bridge/jasmine.util.ts b/lib/mocha/jasmine-bridge/jasmine.util.ts index 81a04d52c..7b113d465 100644 --- a/lib/mocha/jasmine-bridge/jasmine.util.ts +++ b/lib/mocha/jasmine-bridge/jasmine.util.ts @@ -179,19 +179,18 @@ export function toMatch(actual: any, expected: any) { const Mocha: any = typeof window === 'undefined' ? (global as any).Mocha : (window as any).Mocha; export function formatObject(obj: any) { - const stringify = Mocha.utils && Mocha.utils.stringify; - return stringify(obj); + return Object.prototype.toString.call(obj); } export function buildFailureMessage( - matcherName: string, isNot: boolean, actual: any, ...expected: any[]) { + 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.length > 0) { + if (expected && expected.length > 0) { for (let i = 0; i < expected.length; i++) { if (i > 0) { message += ','; diff --git a/lib/mocha/jest-bridge/jest.ts b/lib/mocha/jest-bridge/jest.ts index 10ec36781..8cae92778 100644 --- a/lib/mocha/jest-bridge/jest.ts +++ b/lib/mocha/jest-bridge/jest.ts @@ -21,6 +21,10 @@ Zone.__load_patch('jest2mocha', (global: any) => { 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; diff --git a/lib/mocha/mocha-node-checker.ts b/lib/mocha/mocha-node-checker.ts index b71fd0d59..c042b8c7a 100644 --- a/lib/mocha/mocha-node-checker.ts +++ b/lib/mocha/mocha-node-checker.ts @@ -5,10 +5,17 @@ * 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) { - try { - (global as any).Mocha = require('mocha'); - } catch (err) { - console.log('err', err); +(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) { + } } -} \ No newline at end of file +}(typeof window === 'undefined' ? global : window)); \ No newline at end of file diff --git a/lib/mocha/mocha-patch.ts b/lib/mocha/mocha-patch.ts index 0d70f9697..adc43a13b 100644 --- a/lib/mocha/mocha-patch.ts +++ b/lib/mocha/mocha-patch.ts @@ -10,6 +10,7 @@ Zone.__load_patch('Mocha', (global: any, Zone: ZoneType, api: _ZonePrivate) => { const Mocha = global.Mocha; + const jasmine = global.jasmine; if (typeof Mocha === 'undefined') { return; @@ -19,6 +20,10 @@ Zone.__load_patch('Mocha', (global: any, Zone: ZoneType, api: _ZonePrivate) => { return; } + if (jasmine && !jasmine['__zone_symbol__isBridge']) { + return; + } + if (typeof Zone === 'undefined') { throw new Error('Missing Zone.js'); } @@ -44,15 +49,6 @@ Zone.__load_patch('Mocha', (global: any, Zone: ZoneType, api: _ZonePrivate) => { let suiteZoneSpec: ZoneSpec = new ProxyZoneSpec(); const suiteZone = rootZone.fork(suiteZoneSpec); - 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]; @@ -183,47 +179,87 @@ Zone.__load_patch('Mocha', (global: any, Zone: ZoneType, api: _ZonePrivate) => { Mocha.clearSpies = function() {}; - global.describe = global.suite = Mocha.describe = function() { - return mochaOriginal.describe.apply(this, wrapDescribeInZone(arguments)); - }; + 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.xdescribe = global.suite.skip = Mocha.describe.skip = function() { - return mochaOriginal.describe.skip.apply(this, wrapDescribeInZone(arguments)); - }; + global.describe = global.suite = Mocha.describe = function () { + return mochaOriginal.describe.apply(this, wrapDescribeInZone(arguments)); + }; - global.describe.only = global.suite.only = Mocha.describe.only = function() { - return mochaOriginal.describe.only.apply(this, wrapDescribeInZone(arguments)); - }; + global.xdescribe = global.suite.skip = Mocha.describe.skip = function () { + return mochaOriginal.describe.skip.apply(this, wrapDescribeInZone(arguments)); + }; - global.it = global.specify = global.test = Mocha.it = function() { - return mochaOriginal.it.apply(this, wrapTestInZone(arguments)); - }; + global.describe.only = global.suite.only = Mocha.describe.only = function () { + return mochaOriginal.describe.only.apply(this, wrapDescribeInZone(arguments)); + }; - global.xit = global.xspecify = Mocha.it.skip = function() { - return mochaOriginal.it.skip.apply(this, wrapTestInZone(arguments)); - }; + global.it = global.specify = global.test = Mocha.it = function () { + return mochaOriginal.it.apply(this, wrapTestInZone(arguments)); + }; - global.it.only = global.test.only = Mocha.it.only = function() { - return mochaOriginal.it.only.apply(this, wrapTestInZone(arguments)); - }; + global.xit = global.xspecify = Mocha.it.skip = function () { + return mochaOriginal.it.skip.apply(this, wrapTestInZone(arguments)); + }; - global.after = global.suiteTeardown = Mocha.after = function() { - return mochaOriginal.after.apply(this, wrapSuiteInZone(arguments)); - }; + global.it.only = global.test.only = Mocha.it.only = function () { + return mochaOriginal.it.only.apply(this, wrapTestInZone(arguments)); + }; - global.afterEach = global.teardown = Mocha.afterEach = function() { - return mochaOriginal.afterEach.apply(this, wrapTestInZone(arguments)); - }; + global.after = global.suiteTeardown = Mocha.after = function () { + return mochaOriginal.after.apply(this, wrapSuiteInZone(arguments)); + }; - global.before = global.suiteSetup = Mocha.before = function() { - return mochaOriginal.before.apply(this, wrapSuiteInZone(arguments)); - }; + global.afterEach = global.teardown = Mocha.afterEach = function () { + return mochaOriginal.afterEach.apply(this, wrapTestInZone(arguments)); + }; - global.beforeEach = global.setup = Mocha.beforeEach = function() { - return mochaOriginal.beforeEach.apply(this, wrapTestInZone(arguments)); - }; + global.before = global.suiteSetup = Mocha.before = function () { + return mochaOriginal.before.apply(this, wrapSuiteInZone(arguments)); + }; - ((originalRunTest, originalRun) => { + 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); @@ -245,5 +281,5 @@ Zone.__load_patch('Mocha', (global: any, Zone: ZoneType, api: _ZonePrivate) => { return originalRun.call(this, fn); }; - })(Mocha.Runner.prototype.runTest, Mocha.Runner.prototype.run); + })(Mocha.Runner.prototype.runTest, Mocha.Runner.prototype.run, Mocha.prototype.run); }); diff --git a/lib/mocha/mocha.ts b/lib/mocha/mocha.ts index 384f1ef59..fbd4f168c 100644 --- a/lib/mocha/mocha.ts +++ b/lib/mocha/mocha.ts @@ -6,6 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ +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/test/env/common/common_tests.ts b/test/env/common/common_tests.ts index b5b3a4ccf..966ab17c6 100644 --- a/test/env/common/common_tests.ts +++ b/test/env/common/common_tests.ts @@ -23,6 +23,5 @@ import '../../spec/zone-spec/proxy.spec'; import '../../spec/zone-spec/task-tracking.spec'; import '../../spec/rxjs/rxjs.spec'; import '../../spec/jasmine/jasmine-patch.spec'; -import '../../spec/mocha/mocha-patch.spec'; Error.stackTraceLimit = Number.POSITIVE_INFINITY; diff --git a/test/spec/mocha/jest-bridge.spec.ts b/test/spec/mocha/jest-bridge.spec.ts index 25c4d5f65..71416d7b2 100644 --- a/test/spec/mocha/jest-bridge.spec.ts +++ b/test/spec/mocha/jest-bridge.spec.ts @@ -8,7 +8,7 @@ const _jestGlobal: any = typeof window !== 'undefined' ? window : global; _jestGlobal['__zone_symbol__fakeAsyncCheckRemaining'] = false; describe('extend', () => { - (expect as any).extend({ + expect.extend({ toBeDivisibleBy(received: any, argument: any) { const pass = received % argument == 0; if (pass) { @@ -33,38 +33,38 @@ describe('extend', () => { describe('expect', () => { test('anything test', () => { - expect('test').toEqual((expect as any).anything()); + expect('test').toEqual(expect.anything()); }); test('any(constructor)', () => { - expect('test').toEqual((expect as any).any(String)); + 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 as any).arrayContaining(expected)); + 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 as any).arrayContaining(expected)); + 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 as any).arrayContaining(expected), ); + 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 as any).arrayContaining(expected), ); + ]).not.toEqual(expect.arrayContaining(expected)); }); }); describe('assertions', () => { test('calls both callbacks', () => { - (expect as any).assertions(2); + expect.assertions(2); function callback1(data: any) { expect(data).toBeTruthy(); } @@ -76,7 +76,7 @@ describe('expect', () => { }); test('calls one callback', () => { - (expect as any).hasAssertions(); + expect.hasAssertions(); function callback1(data: any) { expect(data).toBeTruthy(); } @@ -87,69 +87,69 @@ describe('expect', () => { describe('objectContaining', () => { test('onPress should object containing with the right thing', () => { const onPress = {x: 100, y: 200, z: 300}; - (expect(onPress) as any).toEqual((expect as any).objectContaining({ - x: (expect as any).any(Number), - y: (expect as any).any(Number), + 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 as any).stringContaining('test')); + expect('test1').toEqual(expect.stringContaining('test')); }); }); describe('stringMatching in arrayContaining', () => { const expected = [ - (expect as any).stringMatching(/^Alic/), - (expect as any).stringMatching(/^[BR]ob/), + expect.stringMatching(/^Alic/), + expect.stringMatching(/^[BR]ob/), ]; it('matches even if received contains additional elements', () => { - expect(['Alicia', 'Roberto', 'Evelina']).toEqual((expect as any).arrayContaining(expected), ); + 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 as any).arrayContaining(expected), ); + 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')) as any).resolves.toBe('lemon'); + return expect(Promise.resolve('lemon')).resolves.toBe('lemon'); }); test('resolves to lemon with await', async() => { - await(expect(Promise.resolve('lemon')) as any).resolves.toBe('lemon'); - await(expect(Promise.resolve('lemon')) as any).resolves.not.toBe('octopus'); + 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'))) as any).rejects.toThrow('octopus'); + return expect(Promise.reject(new Error('octopus'))).rejects.toThrow('octopus'); }); test('rejects to octopus', async() => { - return await(expect(Promise.reject(new Error('octopus'))) as any).rejects.toThrow('octopus'); + return await expect(Promise.reject(new Error('octopus'))).rejects.toThrow('octopus'); }); }); test('instanceof', () => { class A {} - (expect(new A()) as any).toBeInstanceOf(A); - (expect(() => {}) as any).toBeInstanceOf(Function); + 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}]) as any) + expect([{delicious: true, sour: false}, {delicious: false, sour: true}]) .toContainEqual(myBeverage); }); test('toHaveLength', () => { - (expect([1, 2, 3]) as any).toHaveLength(3); - (expect('abc') as any).toHaveLength(3); - (expect('').not as any).toHaveLength(5); + expect([1, 2, 3]).toHaveLength(3); + expect('abc').toHaveLength(3); + expect('').not.toHaveLength(5); }); describe('toMatchObject', () => { @@ -166,28 +166,28 @@ describe('expect', () => { bath: true, kitchen: { amenities: ['oven', 'stove', 'washer'], - wallColor: (expect as any).stringMatching(/white|yellow/), + wallColor: expect.stringMatching(/white|yellow/), }, }; test('the house has my desired features', () => { - (expect(houseForSale) as any).toMatchObject(desiredHouse); + expect(houseForSale).toMatchObject(desiredHouse); }); }); describe('toMatchObject applied to arrays arrays', () => { test('the number of elements must match exactly', () => { - (expect([{foo: 'bar'}, {baz: 1}]) as any).toMatchObject([{foo: 'bar'}, {baz: 1}]); + 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}]) as any).toMatchObject([{foo: 'bar'}]); + 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'}]) as any).toMatchObject([ + expect([{foo: 'bar'}, {baz: 1, extra: 'quux'}]).toMatchObject([ {foo: 'bar'}, {baz: 1}, ]); @@ -209,29 +209,29 @@ describe('expect', () => { test('this house has my desired features', () => { // Simple Referencing - (expect(houseForSale) as any).toHaveProperty('bath'); - (expect(houseForSale) as any).toHaveProperty('bedrooms', 4); + expect(houseForSale).toHaveProperty('bath'); + expect(houseForSale).toHaveProperty('bedrooms', 4); - (expect(houseForSale).not as any).toHaveProperty('pool'); + expect(houseForSale).not.toHaveProperty('pool'); // Deep referencing using dot notation - (expect(houseForSale) as any).toHaveProperty('kitchen.area', 20); - (expect(houseForSale) as any).toHaveProperty('kitchen.amenities', [ + expect(houseForSale).toHaveProperty('kitchen.area', 20); + expect(houseForSale).toHaveProperty('kitchen.amenities', [ 'oven', 'stove', 'washer', ]); - (expect(houseForSale).not as any).toHaveProperty('kitchen.open'); + expect(houseForSale).not.toHaveProperty('kitchen.open'); // Deep referencing using an array containing the keyPath - (expect(houseForSale) as any).toHaveProperty(['kitchen', 'area'], 20); - (expect(houseForSale) as any).toHaveProperty(['kitchen', 'amenities'], [ + expect(houseForSale).toHaveProperty(['kitchen', 'area'], 20); + expect(houseForSale).toHaveProperty(['kitchen', 'amenities'], [ 'oven', 'stove', 'washer' - ], ); - (expect(houseForSale) as any).toHaveProperty(['kitchen', 'amenities', 0], 'oven'); + ]); + expect(houseForSale).toHaveProperty(['kitchen', 'amenities', 0], 'oven'); - (expect(houseForSale).not as any).toHaveProperty(['kitchen', 'open']); + expect(houseForSale).not.toHaveProperty(['kitchen', 'open']); }); }); diff --git a/test/spec/mocha/mocha-node-test-entry-point.ts b/test/spec/mocha/mocha-node-test-entry-point.ts index 944a61fbd..c21b31f4c 100644 --- a/test/spec/mocha/mocha-node-test-entry-point.ts +++ b/test/spec/mocha/mocha-node-test-entry-point.ts @@ -10,4 +10,5 @@ import '../../../lib/node/rollup-main'; import '../../../lib/mocha/mocha-node-checker'; import '../../../lib/testing/zone-testing'; import './jasmine-bridge.spec'; -import './jest-bridge.spec'; \ No newline at end of file +import './jest-bridge.spec'; +import './mocha-patch.spec';