-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtrpc-stub.ts
102 lines (94 loc) · 3.16 KB
/
trpc-stub.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
import {
AnyProcedure,
AnyRootTypes,
AnyRouter,
CombinedDataTransformer,
RouterRecord,
createFlatProxy,
createRecursiveProxy,
defaultTransformer,
inferProcedureOutput,
} from '@trpc/server/unstable-core-do-not-import';
// since this is only used in the test environment, it should be safe to import these
// it may make upgrading in the future harder, but it's only one location you have to update
import { PartialDeep } from 'type-fest';
type CypressTRPCMock<$Value extends AnyProcedure> = {
// saving as comments incase they're useful
// input: inferProcedureInput<$Value>;
// output: inferTransformedProcedureOutput<TRoot, $Value>;
// transformer: TRoot['transformer'];
// errorShape: TRoot['errorShape'];
returns: (value: inferProcedureOutput<$Value>) => Cypress.Chainable<null>;
// helpful utility if you don't want to mock the full response, can be deleted if not needed
returnsPartial: (value: PartialDeep<inferProcedureOutput<$Value>>) => Cypress.Chainable<null>;
intercept: (
transformValue?: (value: inferProcedureOutput<$Value>) => any,
) => Cypress.Chainable<null>;
wait: (options?: Parameters<typeof cy.wait>[1]) => Cypress.Chainable<null>;
path: string;
// add any more cypress methods or other methods here
};
type TRPCMockStub = keyof CypressTRPCMock<any>;
type DecorateRouterRecord<TRoot extends AnyRootTypes, TRecord extends RouterRecord> = {
[TKey in keyof TRecord]: TRecord[TKey] extends infer $Value
? $Value extends RouterRecord
? DecorateRouterRecord<TRoot, $Value>
: $Value extends AnyProcedure
? CypressTRPCMock<$Value>
: never
: never;
};
type TRPCStub<TRouter extends AnyRouter> = DecorateRouterRecord<
TRouter['_def']['_config']['$types'],
TRouter['_def']['record']
>;
interface StubOptions {
transformer?: CombinedDataTransformer;
}
export function stubTRPC<T extends AnyRouter, Stub = TRPCStub<T>>(options?: StubOptions) {
const transformer = options?.transformer ?? defaultTransformer;
const proxy = createFlatProxy<Stub>((key) => {
return createRecursiveProxy((opts) => {
const pathCopy = [key, ...opts.path];
const lastArg: TRPCMockStub = pathCopy.pop() as TRPCMockStub;
const path = pathCopy.join('.');
if (lastArg === 'path') {
return path;
}
const typedOpts = opts.args as Parameters<CypressTRPCMock<any>[typeof lastArg]>;
if (lastArg === 'returns' || lastArg === 'returnsPartial') {
return cy
.intercept(`/api/trpc/${path}*`, {
statusCode: 200,
body: {
result: {
data: { json: transformer.output.serialize(typedOpts[0]) },
},
},
})
.as(path);
}
if (lastArg === 'intercept') {
return cy
.intercept(`/api/trpc/${path}*`, (req) => {
req.continue((res) => {
if (typedOpts[0]) {
const output =
res?.body?.result?.data && transformer.output.deserialize(res.body.result.data);
const transformedOutput = typedOpts[0](output);
res.body.result.data = transformer.output.serialize(transformedOutput);
}
});
})
.as(path);
}
if (lastArg === 'wait') {
const timeout = typedOpts[0]?.timeout;
return cy.wait(`@${path}`, {
timeout,
});
}
});
});
return proxy;
}