Skip to content

Commit 00cae63

Browse files
authored
Add Mock.Of<F> type (#141)
1 parent a0f7b98 commit 00cae63

File tree

7 files changed

+52
-27
lines changed

7 files changed

+52
-27
lines changed

.changeset/happy-apples-impress.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'earljs': patch
3+
---
4+
5+
Function mocks can now be typed as Mock.Of<TFunctionType>

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"chai": "^4.2.0",
3535
"chai-as-promised": "^7.1.1",
3636
"cross-spawn-with-kill": "^1.0.0",
37+
"conditional-type-checks": "^1.0.5",
3738
"eslint": "^7.29.0",
3839
"eslint-config-typestrict": "^1.0.2",
3940
"eslint-plugin-import": "^2.23.4",

packages/docs/guides/mocks.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ To create a mock do:
1515
```typescript
1616
import { mockFn } from 'earljs'
1717

18+
// mock: Mock<[number, number], number>
1819
const mock = mockFn<[number, number], number>()
1920
```
2021

@@ -25,6 +26,7 @@ that's why you need to pass type arguments between angle brackets `<`, `>`.
2526
Alternatively, you can pass function type as a type argument:
2627

2728
```typescript
29+
// mock: Mock.Of<(a: number, b: number) => number>
2830
const mock = mockFn<(a: number, b: number) => number>()
2931
```
3032

packages/earljs/src/mocks/mockFn.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,7 @@ interface Override {
3838
spec: Spec
3939
}
4040

41-
export function mockFn<FUNCTION_SIG extends (...args: any) => any>(
42-
defaultImpl?: FUNCTION_SIG,
43-
): Mock<Parameters<FUNCTION_SIG>, ReturnType<FUNCTION_SIG>>
41+
export function mockFn<FUNCTION_SIG extends (...args: any) => any>(defaultImpl?: FUNCTION_SIG): Mock.Of<FUNCTION_SIG>
4442
export function mockFn<ARGS extends any[], RETURN = any>(defaultImpl?: (...args: ARGS) => RETURN): Mock<ARGS, RETURN>
4543
export function mockFn<ARGS extends any[], RETURN = any>(defaultImpl?: (...args: ARGS) => RETURN): Mock<ARGS, RETURN> {
4644
let spec: Spec = {

packages/earljs/src/mocks/types.ts

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
1-
export interface MockCall<ARGS, RETURN> {
2-
args: ARGS
3-
result: { type: 'return'; value: RETURN } | { type: 'throw'; error: any }
1+
export interface MockCall<TArgs, TReturn> {
2+
args: TArgs
3+
result: { type: 'return'; value: TReturn } | { type: 'throw'; error: any }
44
}
55

66
export type Awaited<T> = T extends PromiseLike<infer PT> ? PT : never
77

8-
export interface Mock<ARGS extends any[], RETURN> {
8+
export interface Mock<TArgs extends any[], TReturn> {
99
/**
1010
* Calls the mock function.
1111
*/
12-
(...args: ARGS): RETURN
12+
(...args: TArgs): TReturn
1313

1414
/**
1515
* An array containing all the performed calls.
1616
*/
17-
calls: MockCall<ARGS, RETURN>[]
17+
calls: MockCall<TArgs, TReturn>[]
1818

1919
/**
2020
* Checks if all the expected calls to the mock have been performed.
@@ -26,106 +26,110 @@ export interface Mock<ARGS extends any[], RETURN> {
2626
* Overrides any previous configuration.
2727
* @param value value to be returned.
2828
*/
29-
returns(value: RETURN): Mock<ARGS, RETURN>
29+
returns(value: TReturn): Mock<TArgs, TReturn>
3030
/**
3131
* Schedules the mock to return a value the next time it's called.
3232
* If anything is already scheduled it will be used first.
3333
* @param value value to be returned.
3434
*/
35-
returnsOnce(value: RETURN): Mock<ARGS, RETURN>
35+
returnsOnce(value: TReturn): Mock<TArgs, TReturn>
3636

3737
/**
3838
* Sets the error thrown by calls to the Mock.
3939
* Overrides any previous configuration.
4040
* @param error error to be thrown.
4141
*/
42-
throws(error: any): Mock<ARGS, RETURN>
42+
throws(error: any): Mock<TArgs, TReturn>
4343
/**
4444
* Schedules the mock to throw an error the next time it's called.
4545
* If anything is already scheduled it will be used first.
4646
* @param error error to be thrown.
4747
*/
48-
throwsOnce(error: any): Mock<ARGS, RETURN>
48+
throwsOnce(error: any): Mock<TArgs, TReturn>
4949

5050
/**
5151
* Sets the underlying implementation of the Mock.
5252
* Overrides any previous configuration.
5353
* @param implementation function to execute.
5454
*/
55-
executes(implementation: (...args: ARGS[]) => RETURN): Mock<ARGS, RETURN>
55+
executes(implementation: (...args: TArgs[]) => TReturn): Mock<TArgs, TReturn>
5656
/**
5757
* Schedules the mock to use the provided implementation the next time it's called.
5858
* If anything is already scheduled it will be used first.
5959
* @param implementation function to execute.
6060
*/
61-
executesOnce(implementation: (...args: ARGS[]) => RETURN): Mock<ARGS, RETURN>
61+
executesOnce(implementation: (...args: TArgs[]) => TReturn): Mock<TArgs, TReturn>
6262

6363
/**
6464
* Sets the return value wrapped in Promise.resolve of calls to the Mock.
6565
* @param value value to be returned.
6666
*/
67-
resolvesTo(value: Awaited<RETURN>): Mock<ARGS, RETURN>
67+
resolvesTo(value: Awaited<TReturn>): Mock<TArgs, TReturn>
6868
/**
6969
* Schedules the mock to return value wrapped in Promise.resolve the next time it's called.
7070
* If anything is already scheduled it will be used first.
7171
* @param value value to be returned.
7272
*/
73-
resolvesToOnce(value: Awaited<RETURN>): Mock<ARGS, RETURN>
73+
resolvesToOnce(value: Awaited<TReturn>): Mock<TArgs, TReturn>
7474

7575
/**
7676
* Sets the error rejected by calls to the Mock.
7777
* @param error error to be thrown.
7878
*/
79-
rejectsWith(error: any): Mock<ARGS, RETURN>
79+
rejectsWith(error: any): Mock<TArgs, TReturn>
8080
/**
8181
* Schedules the mock to reject with value the next time it's called.
8282
* If anything is already scheduled it will be used first.
8383
* @param error error to be thrown.
8484
*/
85-
rejectsWithOnce(error: any): Mock<ARGS, any>
85+
rejectsWithOnce(error: any): Mock<TArgs, any>
8686

8787
/**
8888
* Specifies a different behavior when other arguments are given
8989
* @param args arguments to match
9090
*/
91-
given<B extends ARGS>(
91+
given<B extends TArgs>(
9292
...args: B
9393
): {
9494
/**
9595
* Schedules the mock to return a value the next time it's called.
9696
* If anything is already scheduled it will be used first.
9797
* @param value value to be returned.
9898
*/
99-
returnsOnce(value: RETURN): Mock<ARGS, RETURN>
99+
returnsOnce(value: TReturn): Mock<TArgs, TReturn>
100100

101101
/**
102102
* Schedules the mock to throw an error the next time it's called.
103103
* If anything is already scheduled it will be used first.
104104
* @param error error to be thrown.
105105
*/
106-
throwsOnce(error: any): Mock<ARGS, RETURN>
106+
throwsOnce(error: any): Mock<TArgs, TReturn>
107107

108108
/**
109109
* Schedules the mock use the provided implementation the next time it's called.
110110
* If anything is already scheduled it will be used first.
111111
* @param implementation function to execute.
112112
*/
113-
executesOnce(implementation: (...args: B) => RETURN): Mock<ARGS, RETURN>
113+
executesOnce(implementation: (...args: B) => TReturn): Mock<TArgs, TReturn>
114114

115115
/**
116116
* Schedules the mock to return value wrapped in Promise.resolve the next time it's called.
117117
* If anything is already scheduled it will be used first.
118118
* @param value value to be returned.
119119
*/
120-
resolvesToOnce(value: Awaited<RETURN>): Mock<ARGS, RETURN>
120+
resolvesToOnce(value: Awaited<TReturn>): Mock<TArgs, TReturn>
121121

122122
/**
123123
* Schedules the mock to reject with value the next time it's called.
124124
* If anything is already scheduled it will be used first.
125125
* @param error error to be thrown.
126126
*/
127-
rejectsWithOnce(error: any): Mock<ARGS, RETURN>
127+
rejectsWithOnce(error: any): Mock<TArgs, TReturn>
128128
}
129129
}
130130

131-
export type MockArgs<T> = T extends Mock<infer ARGS, any> ? ARGS : never
131+
export type MockArgs<T> = T extends Mock<infer Args, any> ? Args : never
132+
133+
export declare namespace Mock {
134+
export type Of<T extends (...args: any[]) => any> = Mock<Parameters<T>, ReturnType<T>>
135+
}

packages/earljs/test/mocks/mockFn.test.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { expect } from 'chai'
2+
import { AssertTrue, IsExact } from 'conditional-type-checks'
23

34
import { expect as earl } from '../../src'
4-
import { mockFn, MockNotConfiguredError } from '../../src/mocks'
5+
import { Mock, mockFn, MockNotConfiguredError } from '../../src/mocks'
56
import { noop } from '../common'
67

78
const sum = (a: number, b: number) => a + b
@@ -135,6 +136,15 @@ describe('Mock', () => {
135136

136137
expect(fn(2, 2)).to.eq(4)
137138
})
139+
140+
it('infers types correctly with functional generic parameter', () => {
141+
type Operation = (a: number, b: number) => number
142+
const fn = mockFn<Operation>((a, b) => a + b)
143+
144+
expect(fn(2, 2)).to.eq(4)
145+
146+
type _ = AssertTrue<IsExact<Mock<[number, number], number>, Mock.Of<Operation>>>
147+
})
138148
})
139149

140150
describe('.executesOnce', () => {

yarn.lock

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4579,6 +4579,11 @@ concordance@^4.0.0:
45794579
semver "^5.5.1"
45804580
well-known-symbols "^2.0.0"
45814581

4582+
conditional-type-checks@^1.0.5:
4583+
version "1.0.5"
4584+
resolved "https://registry.yarnpkg.com/conditional-type-checks/-/conditional-type-checks-1.0.5.tgz#310ecd13c46c3963fbe9a2f8f93511d88cdcc973"
4585+
integrity sha512-DkfkvmjXVe4ye4llJ1JADtO3dNvqqcQM08cA9BhNt9Oe8pyRW8X1CZyBg9Qst05bDV9BJM01KLmnFh78NcJgNg==
4586+
45824587
config-chain@^1.1.11:
45834588
version "1.1.12"
45844589
resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.12.tgz#0fde8d091200eb5e808caf25fe618c02f48e4efa"

0 commit comments

Comments
 (0)