Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(jest-jasmine2): remove usage of Function type #10216

Merged
merged 2 commits into from Jul 5, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -15,6 +15,7 @@

- `[jest-jasmine2]` Convert `PCancelable` to TypeScript ([#10215](https://github.com/facebook/jest/pull/10215))
- `[jest-jasmine2]` Refine typings of `queueRunner` ([#10215](https://github.com/facebook/jest/pull/10215))
- `[jest-jasmine2]` Remove usage of `Function` type ([#10216](https://github.com/facebook/jest/pull/10216))
- `[jest-resolve]` Improve types ([#10239](https://github.com/facebook/jest/pull/10239))

### Performance
Expand Down
32 changes: 25 additions & 7 deletions packages/jest-jasmine2/src/jasmine/Env.ts
Expand Up @@ -41,7 +41,13 @@ import queueRunner, {
import treeProcessor, {TreeNode} from '../treeProcessor';
import isError from '../isError';
import assertionErrorMessage from '../assertionErrorMessage';
import type {AssertionErrorWithStack, Jasmine, Reporter, Spy} from '../types';
import type {
AssertionErrorWithStack,
Jasmine,
Reporter,
SpecDefinitionsFn,
Spy,
} from '../types';
import type {default as Spec, SpecResult} from './Spec';
import type Suite from './Suite';

Expand All @@ -64,7 +70,10 @@ export default function (j$: Jasmine) {
runnablesToRun?: Array<string>,
suiteTree?: Suite,
) => Promise<void>;
fdescribe: (description: string, specDefinitions: Function) => Suite;
fdescribe: (
description: string,
specDefinitions: SpecDefinitionsFn,
) => Suite;
spyOn: (
obj: Record<string, Spy>,
methodName: string,
Expand All @@ -78,15 +87,21 @@ export default function (j$: Jasmine) {
clearReporters: () => void;
addReporter: (reporterToAdd: Reporter) => void;
it: (description: string, fn: QueueableFn['fn'], timeout?: number) => Spec;
xdescribe: (description: string, specDefinitions: Function) => Suite;
xdescribe: (
description: string,
specDefinitions: SpecDefinitionsFn,
) => Suite;
xit: (description: string, fn: QueueableFn['fn'], timeout?: number) => Spec;
beforeAll: (beforeAllFunction: QueueableFn['fn'], timeout?: number) => void;
todo: () => Spec;
provideFallbackReporter: (reporterToAdd: Reporter) => void;
allowRespy: (allow: boolean) => void;
describe: (description: string, specDefinitions: Function) => Suite;
describe: (
description: string,
specDefinitions: SpecDefinitionsFn,
) => Suite;

constructor(_options?: object) {
constructor(_options?: Record<string, unknown>) {
G-Rath marked this conversation as resolved.
Show resolved Hide resolved
let totalSpecsDefined = 0;

let catchExceptions = true;
Expand Down Expand Up @@ -411,13 +426,16 @@ export default function (j$: Jasmine) {
return suite;
};

const addSpecsToSuite = (suite: Suite, specDefinitions: Function) => {
const addSpecsToSuite = (
suite: Suite,
specDefinitions: SpecDefinitionsFn,
) => {
const parentSuite = currentDeclarationSuite;
parentSuite.addChild(suite);
currentDeclarationSuite = suite;

let declarationError: undefined | Error = undefined;
let describeReturnValue: undefined | Error = undefined;
let describeReturnValue: unknown | Error;
try {
describeReturnValue = specDefinitions.call(suite);
} catch (e) {
Expand Down
4 changes: 2 additions & 2 deletions packages/jest-jasmine2/src/jasmine/Spec.ts
Expand Up @@ -171,7 +171,7 @@ export default class Spec {
}
}

execute(onComplete: Function, enabled: boolean) {
execute(onComplete?: () => void, enabled?: boolean) {
const self = this;

this.onStart(this);
Expand Down Expand Up @@ -203,7 +203,7 @@ export default class Spec {

this.currentRun.then(() => complete(true));

function complete(enabledAgain: boolean) {
function complete(enabledAgain?: boolean) {
self.result.status = self.status(enabledAgain);
self.resultCallback(self.result);

Expand Down
10 changes: 5 additions & 5 deletions packages/jest-jasmine2/src/jasmine/jasmineLight.ts
Expand Up @@ -30,7 +30,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/* eslint-disable sort-keys */

import type {Jasmine} from '../types';
import type {Jasmine, SpecDefinitionsFn} from '../types';
import createSpy from './createSpy';
import Env from './Env';
import JsApiReporter from './JsApiReporter';
Expand All @@ -45,7 +45,7 @@ export const create = function (createOptions: Record<string, any>): Jasmine {

j$._DEFAULT_TIMEOUT_INTERVAL = createOptions.testTimeout || 5000;

j$.getEnv = function (options?: object) {
j$.getEnv = function (options?: Record<string, unknown>) {
const env = (j$.currentEnv_ = j$.currentEnv_ || new j$.Env(options));
//jasmine. singletons in here (setTimeout blah blah).
return env;
Expand All @@ -66,15 +66,15 @@ export const create = function (createOptions: Record<string, any>): Jasmine {
// Interface is a reserved word in strict mode, so can't export it as ESM
export const _interface = function (jasmine: Jasmine, env: any) {
const jasmineInterface = {
describe(description: string, specDefinitions: Function) {
describe(description: string, specDefinitions: SpecDefinitionsFn) {
return env.describe(description, specDefinitions);
},

xdescribe(description: string, specDefinitions: Function) {
xdescribe(description: string, specDefinitions: SpecDefinitionsFn) {
return env.xdescribe(description, specDefinitions);
},

fdescribe(description: string, specDefinitions: Function) {
fdescribe(description: string, specDefinitions: SpecDefinitionsFn) {
return env.fdescribe(description, specDefinitions);
},

Expand Down
41 changes: 27 additions & 14 deletions packages/jest-jasmine2/src/jasmineAsyncInstall.ts
Expand Up @@ -17,26 +17,30 @@ import throat from 'throat';
import isError from './isError';
import type {Jasmine} from './types';
import type Spec from './jasmine/Spec';
import type {QueueableFn} from './queueRunner';
import type {DoneFn, QueueableFn} from './queueRunner';

interface DoneFn {
(): void;
fail: (error: Error) => void;
}

function isPromise(obj: any) {
function isPromise(obj: any): obj is PromiseLike<unknown> {
return obj && typeof obj.then === 'function';
}

const doneFnNoop = () => {};

doneFnNoop.fail = () => {};

function promisifyLifeCycleFunction(
originalFn: Function,
originalFn: (beforeAllFunction: QueueableFn['fn'], timeout?: number) => void,
env: Jasmine['currentEnv_'],
) {
return function <T>(
fn: Function | (() => Promise<T>) | GeneratorFunction | undefined,
fn:
| ((done: DoneFn) => void | PromiseLike<T>)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be an error to both take a done function and return a promise

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will cause some major type problems, as everywhere else is expecting only (done: DoneFn) => void.

For the record, by unioning the functions TS can't support both of them when doing .call, so it favors (done: Done) => void, meaning that returnValue.then is invalid as it has type void.

This can be resolved by using an interface:

interface OneOrTheOther {
  (done: DoneFn): void;
  (): PromiseLike<void>;
}

This then takes us back to where we were before, but this time it's the opposite: TS is complaining asyncJestTest doesn't fit because it's of type (done: DoneFn) => void instead of OneOrTheOther 😬

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😞

| (() => Promise<T>)
| GeneratorFunction
| undefined,
timeout?: number,
) {
): void {
if (!fn) {
// @ts-expect-error: missing fn arg is handled by originalFn
return originalFn.call(env);
}

Expand All @@ -59,7 +63,7 @@ function promisifyLifeCycleFunction(
// didn't return a promise.
const asyncJestLifecycle = function (done: DoneFn) {
G-Rath marked this conversation as resolved.
Show resolved Hide resolved
const wrappedFn = isGeneratorFn(fn) ? co.wrap(fn) : fn;
const returnValue = wrappedFn.call({});
const returnValue = wrappedFn.call({}, doneFnNoop);

if (isPromise(returnValue)) {
returnValue.then(done.bind(null, null), (error: Error) => {
Expand All @@ -82,12 +86,21 @@ function promisifyLifeCycleFunction(
// Similar to promisifyLifeCycleFunction but throws an error
// when the return value is neither a Promise nor `undefined`
function promisifyIt(
originalFn: Function,
originalFn: (
description: string,
fn: QueueableFn['fn'],
timeout?: number,
) => Spec,
env: Jasmine['currentEnv_'],
jasmine: Jasmine,
) {
return function (specName: string, fn: Function, timeout?: number) {
return function (
specName: string,
fn?: (done: DoneFn) => void | PromiseLike<void>,
timeout?: number,
): Spec {
if (!fn) {
// @ts-expect-error: missing fn arg is handled by originalFn
const spec = originalFn.call(env, specName);
spec.pend('not implemented');
return spec;
Expand All @@ -109,7 +122,7 @@ function promisifyIt(

const asyncJestTest = function (done: DoneFn) {
const wrappedFn = isGeneratorFn(fn) ? co.wrap(fn) : fn;
const returnValue = wrappedFn.call({});
const returnValue = wrappedFn.call({}, doneFnNoop);

if (isPromise(returnValue)) {
returnValue.then(done.bind(null, null), (error: Error) => {
Expand Down
10 changes: 1 addition & 9 deletions packages/jest-jasmine2/src/jestExpect.ts
Expand Up @@ -14,18 +14,10 @@ import {
toThrowErrorMatchingInlineSnapshot,
toThrowErrorMatchingSnapshot,
} from 'jest-snapshot';
import type {Jasmine, RawMatcherFn} from './types';
import type {Jasmine, JasmineMatchersObject, RawMatcherFn} from './types';

declare const global: Global.Global;

type JasmineMatcher = {
(matchersUtil: any, context: any): JasmineMatcher;
compare: () => RawMatcherFn;
negativeCompare: () => RawMatcherFn;
};

type JasmineMatchersObject = {[id: string]: JasmineMatcher};

export default (config: {expand: boolean}): void => {
global.expect = expect;
expect.setState({expand: config.expand});
Expand Down
7 changes: 6 additions & 1 deletion packages/jest-jasmine2/src/queueRunner.ts
Expand Up @@ -20,8 +20,13 @@ export type Options = {
userContext: any;
};

export interface DoneFn {
(error?: any): void;
fail: (error: Error) => void;
}

export type QueueableFn = {
fn: (done: (error?: any) => void) => void;
fn: (done: DoneFn) => void;
timeout?: () => number;
initError?: Error;
};
Expand Down
16 changes: 14 additions & 2 deletions packages/jest-jasmine2/src/types.ts
Expand Up @@ -20,6 +20,8 @@ import type {default as Suite, SuiteResult} from './jasmine/Suite';
import type SpyStrategy from './jasmine/SpyStrategy';
import type CallTracker from './jasmine/CallTracker';

export type SpecDefinitionsFn = () => void;

export interface AssertionErrorWithStack extends AssertionError {
stack: string;
}
Expand Down Expand Up @@ -64,11 +66,21 @@ export interface Spy extends Record<string, any> {
restoreObjectToOriginalState?: () => void;
}

type JasmineMatcher = {
(matchersUtil: unknown, context: unknown): JasmineMatcher;
compare: () => RawMatcherFn;
negativeCompare: () => RawMatcherFn;
};

export type JasmineMatchersObject = {[id: string]: JasmineMatcher};

export type Jasmine = {
_DEFAULT_TIMEOUT_INTERVAL: number;
DEFAULT_TIMEOUT_INTERVAL: number;
currentEnv_: ReturnType<typeof Env>['prototype'];
getEnv: (options?: object) => ReturnType<typeof Env>['prototype'];
getEnv: (
options?: Record<string, unknown>,
) => ReturnType<typeof Env>['prototype'];
createSpy: typeof createSpy;
Env: ReturnType<typeof Env>;
JsApiReporter: typeof JsApiReporter;
Expand All @@ -79,7 +91,7 @@ export type Jasmine = {
Timer: typeof Timer;
version: string;
testPath: Config.Path;
addMatchers: Function;
addMatchers: (matchers: JasmineMatchersObject) => void;
} & typeof expect &
NodeJS.Global;

Expand Down