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

fix(@jest/types): infer each types correctly when the table is a tuple or array #13381

Merged
merged 3 commits into from Oct 4, 2022
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 @@ -9,6 +9,7 @@
### Fixes

- `[babel-plugin-jest-hoist]` Ignore `TSTypeQuery` when checking for hoisted references ([#13367](https://github.com/facebook/jest/pull/13367))
- `[@jest/types]` Infer type of `each` table correctly when the table is a tuple or array ([#13381](https://github.com/facebook/jest/pull/13381))

### Chore & Maintenance

Expand Down
49 changes: 23 additions & 26 deletions packages/jest-circus/src/__tests__/hooksError.test.ts
Expand Up @@ -5,31 +5,28 @@
* LICENSE file in the root directory of this source tree.
*/

import type {Circus} from '@jest/types';
import circus from '../';

describe.each([
'beforeEach',
'beforeAll',
'afterEach',
'afterAll',
] as Array<Circus.HookType>)('%s hooks error throwing', fn => {
test.each([
['String'],
[1],
[[]],
[{}],
[Symbol('hello')],
[true],
[null],
[undefined],
])(
`${fn} throws an error when %p is provided as a first argument to it`,
el => {
expect(() => {
// @ts-expect-error: Testing runtime errors here
circus[fn](el);
}).toThrow('Invalid first argument. It must be a callback function.');
},
);
});
describe.each(['beforeEach', 'beforeAll', 'afterEach', 'afterAll'] as const)(
'%s hooks error throwing',
fn => {
test.each([
['String'],
[1],
[[]],
[{}],
[Symbol('hello')],
[true],
[null],
[undefined],
])(
`${fn} throws an error when %p is provided as a first argument to it`,
el => {
expect(() => {
// @ts-expect-error: Testing runtime errors here
circus[fn](el);
}).toThrow('Invalid first argument. It must be a callback function.');
},
);
},
);
50 changes: 23 additions & 27 deletions packages/jest-jasmine2/src/__tests__/hooksError.test.ts
Expand Up @@ -5,30 +5,26 @@
* LICENSE file in the root directory of this source tree.
*/

export type SharedHookType = 'afterAll' | 'beforeAll';
export type HookType = SharedHookType | 'afterEach' | 'beforeEach';

describe.each([
'beforeEach',
'beforeAll',
'afterEach',
'afterAll',
] as Array<HookType>)('%s hooks error throwing', fn => {
test.each([
['String'],
[1],
[[]],
[{}],
[Symbol('hello')],
[true],
[null],
[undefined],
])(
`${fn} throws an error when %p is provided as a first argument to it`,
el => {
expect(() => {
globalThis[fn](el);
}).toThrow('Invalid first argument. It must be a callback function.');
},
);
});
describe.each(['beforeEach', 'beforeAll', 'afterEach', 'afterAll'] as const)(
'%s hooks error throwing',
fn => {
test.each([
['String'],
[1],
[[]],
[{}],
[Symbol('hello')],
[true],
[null],
[undefined],
])(
`${fn} throws an error when %p is provided as a first argument to it`,
el => {
expect(() => {
// @ts-expect-error: Testing runtime errors
globalThis[fn](el);
}).toThrow('Invalid first argument. It must be a callback function.');
},
);
},
);
106 changes: 33 additions & 73 deletions packages/jest-types/__typetests__/each.test.ts
Expand Up @@ -9,7 +9,7 @@ import {expectError, expectType} from 'tsd-lite';
import {describe, test} from '@jest/globals';

const list = [1, 2, 3];
const tupleList: [number, number, string] = [1, 2, 'three'];
const tupleList = ['one', 'two', 'three'] as const;
const table = [
[1, 2, 'three'],
[3, 4, 'seven'],
Expand All @@ -28,57 +28,41 @@ const objectTable = [
// test.each

expectType<void>(
test.each(list)('some test', (a, b, expected) => {
test.each(list)('some test', a => {
expectType<number>(a);
expectType<number>(b);
expectType<number>(expected);
}),
);
expectType<void>(
test.each(list)(
'some test',
(a, b, expected) => {
expectType<number>(a);
expectType<number>(b);
expectType<number>(expected);
},
1000,
),
test.each(list)('some test', a => {
expectType<number>(a);
}),
);

expectType<void>(
test.each(tupleList)('some test', (a, b, expected) => {
expectType<number>(a);
expectType<number>(b);
expectType<string>(expected);
test.each(tupleList)('some test', b => {
expectType<'one' | 'two' | 'three'>(b);
}),
);
expectType<void>(
test.each(tupleList)(
'some test',
(a, b, expected) => {
expectType<number>(a);
expectType<number>(b);
expectType<string>(expected);
b => {
expectType<'one' | 'two' | 'three'>(b);
},
1000,
),
);

expectType<void>(
test.each([3, 4, 'seven'])('some test', (a, b, expected) => {
expectType<number>(a);
expectType<number>(b);
expectType<string>(expected);
test.each([3, 4, 'seven'])('some test', c => {
expectType<string | number>(c);
}),
);
expectType<void>(
test.each([3, 4, 'seven'])(
'some test',
(a, b, expected) => {
expectType<number>(a);
expectType<number>(b);
expectType<string>(expected);
c => {
expectType<string | number>(c);
},
1000,
),
Expand Down Expand Up @@ -261,57 +245,45 @@ expectType<typeof test.each>(test.skip.each);
// test.concurrent.each

expectType<void>(
test.concurrent.each(list)('some test', async (a, b, expected) => {
test.concurrent.each(list)('some test', async a => {
expectType<number>(a);
expectType<number>(b);
expectType<number>(expected);
}),
);
expectType<void>(
test.concurrent.each(list)(
'some test',
async (a, b, expected) => {
async a => {
expectType<number>(a);
expectType<number>(b);
expectType<number>(expected);
},
1000,
),
);

expectType<void>(
test.concurrent.each(tupleList)('some test', async (a, b, expected) => {
expectType<number>(a);
expectType<number>(b);
expectType<string>(expected);
test.concurrent.each(tupleList)('some test', async b => {
expectType<'one' | 'two' | 'three'>(b);
}),
);
expectType<void>(
test.concurrent.each(tupleList)(
'some test',
async (a, b, expected) => {
expectType<number>(a);
expectType<number>(b);
expectType<string>(expected);
async b => {
expectType<'one' | 'two' | 'three'>(b);
},
1000,
),
);

expectType<void>(
test.concurrent.each([3, 4, 'seven'])('some test', async (a, b, expected) => {
expectType<number>(a);
expectType<number>(b);
expectType<string>(expected);
test.concurrent.each([3, 4, 'seven'])('some test', async c => {
expectType<string | number>(c);
}),
);
expectType<void>(
test.concurrent.each([3, 4, 'seven'])(
'some test',
async (a, b, expected) => {
expectType<number>(a);
expectType<number>(b);
expectType<string>(expected);
async c => {
expectType<string | number>(c);
},
1000,
),
Expand Down Expand Up @@ -448,57 +420,45 @@ expectType<typeof test.concurrent.each>(test.concurrent.skip.each);
// describe.each

expectType<void>(
describe.each(list)('describe each', (a, b, expected) => {
describe.each(list)('describe each', a => {
expectType<number>(a);
expectType<number>(b);
expectType<number>(expected);
}),
);
expectType<void>(
describe.each(list)(
'describe each',
(a, b, expected) => {
a => {
expectType<number>(a);
expectType<number>(b);
expectType<number>(expected);
},
1000,
),
);

expectType<void>(
describe.each(tupleList)('describe each', (a, b, expected) => {
expectType<number>(a);
expectType<number>(b);
expectType<string>(expected);
describe.each(tupleList)('describe each', b => {
expectType<'one' | 'two' | 'three'>(b);
}),
);
expectType<void>(
describe.each(tupleList)(
'describe each',
(a, b, expected) => {
expectType<number>(a);
expectType<number>(b);
expectType<string>(expected);
b => {
expectType<'one' | 'two' | 'three'>(b);
},
1000,
),
);

expectType<void>(
describe.each([3, 4, 'seven'])('describe each', (a, b, expected) => {
expectType<number>(a);
expectType<number>(b);
expectType<string>(expected);
describe.each([3, 4, 'seven'])('describe each', c => {
expectType<string | number>(c);
}),
);
expectType<void>(
describe.each([3, 4, 'seven'])(
'describe each',
(a, b, expected) => {
expectType<number>(a);
expectType<number>(b);
expectType<string>(expected);
c => {
expectType<string | number>(c);
},
1000,
),
Expand Down
16 changes: 8 additions & 8 deletions packages/jest-types/src/Global.ts
Expand Up @@ -56,42 +56,42 @@ export type EachTestFn<EachCallback extends TestCallback> = (
) => ReturnType<EachCallback>;

interface Each<EachFn extends TestFn | BlockFn> {
// when the table is an array of object literals
<T extends Record<string, unknown>>(table: ReadonlyArray<T>): (
name: string | NameLike,
fn: (arg: T) => ReturnType<EachFn>,
timeout?: number,
) => void;

// when the table is an array of tuples
<T extends readonly [unknown, ...Array<unknown>]>(table: ReadonlyArray<T>): (
name: string | NameLike,
fn: (...args: T) => ReturnType<EachFn>,
timeout?: number,
) => void;

<T extends readonly [unknown, ...Array<unknown>]>(table: T): (
name: string | NameLike,
fn: (...args: T) => ReturnType<EachFn>,
timeout?: number,
) => void;

// when the table is an array of arrays
<T extends ReadonlyArray<unknown>>(table: ReadonlyArray<T>): (
name: string | NameLike,
fn: (...args: T) => ReturnType<EachFn>,
timeout?: number,
) => void;

<T extends ReadonlyArray<unknown>>(table: T): (
// when the table is a tuple or array
<T>(table: ReadonlyArray<T>): (
name: string | NameLike,
fn: (...args: T) => ReturnType<EachFn>,
fn: (arg: T) => ReturnType<EachFn>,
timeout?: number,
) => void;

// when the table is a template literal
<T = unknown>(strings: TemplateStringsArray, ...expressions: Array<T>): (
name: string | NameLike,
fn: (arg: Record<string, T>) => ReturnType<EachFn>,
timeout?: number,
) => void;

// when the table is a template literal with a type argument
<T extends Record<string, unknown>>(
strings: TemplateStringsArray,
...expressions: Array<unknown>
Expand Down