`zone.js` supports jest `test.each()` methods, but it
introduces a bug, which is the `done()` function will not be handled correctly.
```
it('should work with done', done => {
  // done will be undefined.
});
```
The reason is the logic of monkey patching `test` method is different from `jasmine` patch
// jasmine patch
```
return testBody.length === 0
   ? () => testProxyZone.run(testBody, null)
   : done => testProxyZone.run(testBody, null, [done]);
```
// jest patch
```
 return function(...args) {
   return testProxyZone.run(testBody, null, args);
 };
```
the purpose of this change is to handle the following cases.
```
test.each([1, 2])('test.each', (arg1, arg2) => {
  expect(arg1).toBe(1);
  expect(arg2).toBe(2);
});
```
so in jest, it is a little complex, because the `testBody`'s parameter may be bigger than 1, so the
logic in `jasmine`
```
return testBody.length === 0
   ? () => testProxyZone.run(testBody, null)
   : done => testProxyZone.run(testBody, null, [done]);
```
will not work for `test.each` in jest.
So in this PR, I created a dynamic `Function` to return the correct length of paramters (which is required by jest core), to handle
1. normal `test` with or without `done`.
2. each with parameters with or without done.
PR Close #36022
		
	
			
		
			
				
	
	
		
			129 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			129 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
/**
 | 
						|
 * @license
 | 
						|
 * Copyright Google Inc. All Rights Reserved.
 | 
						|
 *
 | 
						|
 * Use of this source code is governed by an MIT-style license that can be
 | 
						|
 * found in the LICENSE file at https://angular.io/license
 | 
						|
 */
 | 
						|
 | 
						|
'use strict';
 | 
						|
 | 
						|
Zone.__load_patch('jest', (context: any, Zone: ZoneType) => {
 | 
						|
  if (typeof jest === 'undefined' || jest['__zone_patch__']) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  jest['__zone_patch__'] = true;
 | 
						|
 | 
						|
 | 
						|
  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');
 | 
						|
  }
 | 
						|
 | 
						|
  const rootZone = Zone.current;
 | 
						|
  const syncZone = rootZone.fork(new SyncTestZoneSpec('jest.describe'));
 | 
						|
  const proxyZone = rootZone.fork(new ProxyZoneSpec());
 | 
						|
 | 
						|
  function wrapDescribeFactoryInZone(originalJestFn: Function) {
 | 
						|
    return function(this: unknown, ...tableArgs: any[]) {
 | 
						|
      const originalDescribeFn = originalJestFn.apply(this, tableArgs);
 | 
						|
      return function(this: unknown, ...args: any[]) {
 | 
						|
        args[1] = wrapDescribeInZone(args[1]);
 | 
						|
        return originalDescribeFn.apply(this, args);
 | 
						|
      };
 | 
						|
    };
 | 
						|
  }
 | 
						|
 | 
						|
  function wrapTestFactoryInZone(originalJestFn: Function) {
 | 
						|
    return function(this: unknown, ...tableArgs: any[]) {
 | 
						|
      return function(this: unknown, ...args: any[]) {
 | 
						|
        args[1] = wrapTestInZone(args[1]);
 | 
						|
        return originalJestFn.apply(this, tableArgs).apply(this, args);
 | 
						|
      };
 | 
						|
    };
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Gets a function wrapping the body of a jest `describe` block to execute in a
 | 
						|
   * synchronous-only zone.
 | 
						|
   */
 | 
						|
  function wrapDescribeInZone(describeBody: Function): Function {
 | 
						|
    return function(this: unknown, ...args: any[]) {
 | 
						|
      return syncZone.run(describeBody, this, args);
 | 
						|
    };
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Gets a function wrapping the body of a jest `it/beforeEach/afterEach` block to
 | 
						|
   * execute in a ProxyZone zone.
 | 
						|
   * This will run in the `proxyZone`.
 | 
						|
   */
 | 
						|
  function wrapTestInZone(testBody: Function): Function {
 | 
						|
    if (typeof testBody !== 'function') {
 | 
						|
      return testBody;
 | 
						|
    }
 | 
						|
    const wrappedFunc = function() {
 | 
						|
      return proxyZone.run(testBody, null, arguments as any);
 | 
						|
    };
 | 
						|
    // Update the length of wrappedFunc to be the same as the length of the testBody
 | 
						|
    // So jest core can handle whether the test function has `done()` or not correctly
 | 
						|
    Object.defineProperty(
 | 
						|
        wrappedFunc, 'length', {configurable: true, writable: true, enumerable: false});
 | 
						|
    wrappedFunc.length = testBody.length;
 | 
						|
    return wrappedFunc;
 | 
						|
  }
 | 
						|
 | 
						|
  ['describe', 'xdescribe', 'fdescribe'].forEach(methodName => {
 | 
						|
    let originalJestFn: Function = context[methodName];
 | 
						|
    if (context[Zone.__symbol__(methodName)]) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    context[Zone.__symbol__(methodName)] = originalJestFn;
 | 
						|
    context[methodName] = function(this: unknown, ...args: any[]) {
 | 
						|
      args[1] = wrapDescribeInZone(args[1]);
 | 
						|
      return originalJestFn.apply(this, args);
 | 
						|
    };
 | 
						|
    context[methodName].each = wrapDescribeFactoryInZone((originalJestFn as any).each);
 | 
						|
  });
 | 
						|
  context.describe.only = context.fdescribe;
 | 
						|
  context.describe.skip = context.xdescribe;
 | 
						|
 | 
						|
  ['it', 'xit', 'fit', 'test', 'xtest'].forEach(methodName => {
 | 
						|
    let originalJestFn: Function = context[methodName];
 | 
						|
    if (context[Zone.__symbol__(methodName)]) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    context[Zone.__symbol__(methodName)] = originalJestFn;
 | 
						|
    context[methodName] = function(this: unknown, ...args: any[]) {
 | 
						|
      args[1] = wrapTestInZone(args[1]);
 | 
						|
      return originalJestFn.apply(this, args);
 | 
						|
    };
 | 
						|
    context[methodName].each = wrapTestFactoryInZone((originalJestFn as any).each);
 | 
						|
    context[methodName].todo = (originalJestFn as any).todo;
 | 
						|
  });
 | 
						|
 | 
						|
  context.it.only = context.fit;
 | 
						|
  context.it.skip = context.xit;
 | 
						|
  context.test.only = context.fit;
 | 
						|
  context.test.skip = context.xit;
 | 
						|
 | 
						|
  ['beforeEach', 'afterEach', 'beforeAll', 'afterAll'].forEach(methodName => {
 | 
						|
    let originalJestFn: Function = context[methodName];
 | 
						|
    if (context[Zone.__symbol__(methodName)]) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    context[Zone.__symbol__(methodName)] = originalJestFn;
 | 
						|
    context[methodName] = function(this: unknown, ...args: any[]) {
 | 
						|
      args[0] = wrapTestInZone(args[0]);
 | 
						|
      return originalJestFn.apply(this, args);
 | 
						|
    };
 | 
						|
  });
 | 
						|
});
 |