graphql-data-generator 0.3.0-alpha.2 → 0.3.0-alpha.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/index.js +1 -1
- package/esm/init.js +34 -13
- package/esm/jest.js +160 -56
- package/esm/proxy.js +50 -6
- package/package.json +11 -1
- package/script/index.js +2 -1
- package/script/init.js +57 -13
- package/script/jest.js +164 -57
- package/script/proxy.js +51 -7
- package/types/index.d.ts +2 -2
- package/types/init.d.ts +10 -0
- package/types/jest.d.ts +29 -3
- package/types/proxy.d.ts +5 -0
- package/types/types.d.ts +6 -0
package/esm/index.js
CHANGED
package/esm/init.js
CHANGED
|
@@ -1,11 +1,24 @@
|
|
|
1
|
+
import * as dntShim from "./_dnt.shims.js";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
1
3
|
import { readFileSync } from "node:fs";
|
|
2
4
|
import { Kind, parse } from "graphql";
|
|
3
|
-
import { dirname, join } from "node:path";
|
|
4
5
|
import { gqlPluckFromCodeStringSync } from "@graphql-tools/graphql-tag-pluck";
|
|
5
6
|
import { operation, proxy, withGetDefaultPatch } from "./proxy.js";
|
|
6
7
|
import { toObject } from "./util.js";
|
|
8
|
+
import { dirname, resolve } from "node:path";
|
|
9
|
+
globalThis.require ??= createRequire(dntShim.Deno.cwd());
|
|
7
10
|
const files = {};
|
|
8
|
-
const loadFile = (path) =>
|
|
11
|
+
const loadFile = (path) => {
|
|
12
|
+
if (files[path])
|
|
13
|
+
return files[path];
|
|
14
|
+
const raw = files[path] = readFileSync(path, "utf-8");
|
|
15
|
+
const imports = Array.from(raw.matchAll(/#import "(.*)"/gm), ([, importPath]) => loadFile(require.resolve(importPath.startsWith(".")
|
|
16
|
+
? resolve(dntShim.Deno.cwd(), dirname(path), importPath)
|
|
17
|
+
: importPath, { paths: [dntShim.Deno.cwd()] })));
|
|
18
|
+
if (!imports.length)
|
|
19
|
+
return raw;
|
|
20
|
+
return [raw, ...imports].join("\n\n");
|
|
21
|
+
};
|
|
9
22
|
const getOperationContentMap = {};
|
|
10
23
|
const getOperationContent = (path, operationName) => {
|
|
11
24
|
const existing = getOperationContentMap[path];
|
|
@@ -14,14 +27,14 @@ const getOperationContent = (path, operationName) => {
|
|
|
14
27
|
return existing;
|
|
15
28
|
return getOperationContentMap[path][operationName];
|
|
16
29
|
}
|
|
17
|
-
const fileContent =
|
|
30
|
+
const fileContent = loadFile(path);
|
|
18
31
|
try {
|
|
19
32
|
const sources = gqlPluckFromCodeStringSync(path, fileContent);
|
|
20
33
|
getOperationContentMap[path] = Object.fromEntries(sources.map((s) => {
|
|
21
34
|
const document = parse(s);
|
|
22
35
|
const firstOp = document.definitions.find((d) => d.kind === Kind.OPERATION_DEFINITION);
|
|
23
36
|
if (!firstOp)
|
|
24
|
-
throw new Error(`
|
|
37
|
+
throw new Error(`Could not find an operation in ${path}`);
|
|
25
38
|
return [firstOp.name?.value, document];
|
|
26
39
|
}));
|
|
27
40
|
}
|
|
@@ -30,7 +43,16 @@ const getOperationContent = (path, operationName) => {
|
|
|
30
43
|
}
|
|
31
44
|
return getOperationContent(path, operationName);
|
|
32
45
|
};
|
|
33
|
-
|
|
46
|
+
/**
|
|
47
|
+
* Initialize the data builder.
|
|
48
|
+
* @param schema The plain text of your schema.
|
|
49
|
+
* @param queries List of queries exported from generated types.
|
|
50
|
+
* @param mutations List of mutations exported from generated types.
|
|
51
|
+
* @param subscriptions List of subscriptions exported from generated types.
|
|
52
|
+
* @param types List of types exported from generated types.
|
|
53
|
+
* @param inputs List of types exported from generated types.
|
|
54
|
+
* @param scalars A mapping to generate scalar values. Function values will be invoked with their `__typename`.
|
|
55
|
+
*/
|
|
34
56
|
export const init = (schema, queries, mutations, subscriptions, types, inputs, scalars, options) => (fn) => {
|
|
35
57
|
const doc = parse(schema);
|
|
36
58
|
const build = {};
|
|
@@ -104,7 +126,6 @@ export const init = (schema, queries, mutations, subscriptions, types, inputs, s
|
|
|
104
126
|
: variables,
|
|
105
127
|
});
|
|
106
128
|
Error.captureStackTrace(mock, obj.variables);
|
|
107
|
-
mock.stack = mock.stack?.slice(6);
|
|
108
129
|
return mock;
|
|
109
130
|
},
|
|
110
131
|
},
|
|
@@ -125,7 +146,6 @@ export const init = (schema, queries, mutations, subscriptions, types, inputs, s
|
|
|
125
146
|
: data,
|
|
126
147
|
});
|
|
127
148
|
Error.captureStackTrace(mock, obj.data);
|
|
128
|
-
mock.stack = mock.stack?.slice(6);
|
|
129
149
|
return mock;
|
|
130
150
|
},
|
|
131
151
|
},
|
|
@@ -146,7 +166,6 @@ export const init = (schema, queries, mutations, subscriptions, types, inputs, s
|
|
|
146
166
|
const builder = build[operation];
|
|
147
167
|
const mock = builder(prevInput, patch);
|
|
148
168
|
Error.captureStackTrace(mock, obj[name]);
|
|
149
|
-
mock.stack = mock.stack?.slice(6);
|
|
150
169
|
return mock;
|
|
151
170
|
},
|
|
152
171
|
}])));
|
|
@@ -158,14 +177,16 @@ export const init = (schema, queries, mutations, subscriptions, types, inputs, s
|
|
|
158
177
|
if (transforms[name] && "default" in transforms[name]) {
|
|
159
178
|
patches = [transforms[name].default, ...patches];
|
|
160
179
|
}
|
|
161
|
-
const {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
180
|
+
const { mock, parsedQuery } = withGetDefaultPatch((type) => transforms[type]?.default, () => {
|
|
181
|
+
const { request: { query: parsedQuery, ...request }, ...raw } = operation(doc.definitions, scalars, query, ...patches);
|
|
182
|
+
const mock = toObject({
|
|
183
|
+
request: { ...request },
|
|
184
|
+
...raw,
|
|
185
|
+
});
|
|
186
|
+
return { mock, parsedQuery };
|
|
165
187
|
});
|
|
166
188
|
mock.request.query = parsedQuery;
|
|
167
189
|
Error.captureStackTrace(mock, builder);
|
|
168
|
-
mock.stack = mock.stack?.slice(6);
|
|
169
190
|
return addOperationTransforms(name, options?.finalizeOperation
|
|
170
191
|
? options.finalizeOperation(mock)
|
|
171
192
|
: mock);
|
package/esm/jest.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import * as dntShim from "./_dnt.shims.js";
|
|
2
2
|
import React, { useMemo } from "react";
|
|
3
|
-
import { ApolloLink } from "@apollo/client";
|
|
4
|
-
import {
|
|
5
|
-
import { MockedProvider, MockLink, } from "@apollo/client/testing
|
|
3
|
+
import { ApolloLink, useApolloClient, } from "@apollo/client";
|
|
4
|
+
import { onError } from "@apollo/client/link/error";
|
|
5
|
+
import { MockedProvider as ApolloMockedProvider, MockLink, } from "@apollo/client/testing";
|
|
6
6
|
import { waitFor } from "@testing-library/dom";
|
|
7
|
-
import { print } from "graphql";
|
|
7
|
+
import { Kind, print } from "graphql";
|
|
8
8
|
import { diff as jestDiff } from "jest-diff";
|
|
9
9
|
let currentSpecResult;
|
|
10
10
|
jasmine.getEnv().addReporter({
|
|
@@ -12,25 +12,26 @@ jasmine.getEnv().addReporter({
|
|
|
12
12
|
});
|
|
13
13
|
const afterTest = [];
|
|
14
14
|
afterEach(async () => {
|
|
15
|
-
|
|
15
|
+
const hooks = afterTest.splice(0);
|
|
16
|
+
for (const hook of hooks)
|
|
16
17
|
await hook();
|
|
17
|
-
afterTest.splice(0);
|
|
18
18
|
});
|
|
19
|
-
const diff = (a, b) => jestDiff(a, b, {
|
|
20
|
-
omitAnnotationLines: true,
|
|
21
|
-
// aColor: green,
|
|
22
|
-
// bColor: red,
|
|
23
|
-
// changeColor: inverse,
|
|
24
|
-
// commonColor: dim,
|
|
25
|
-
// patchColor: yellow,
|
|
26
|
-
})
|
|
19
|
+
const diff = (a, b) => jestDiff(a, b, { omitAnnotationLines: true })
|
|
27
20
|
?.replace(/\w+ \{/g, "{") // Remove class names
|
|
28
21
|
.replace(/\w+ \[/g, "["); // Remove array class names
|
|
22
|
+
const getOperationDefinition = (document) => document.definitions.find((d) => d.kind === Kind.OPERATION_DEFINITION);
|
|
23
|
+
const getOperationType = (operation) => getOperationDefinition(operation.query)?.operation ??
|
|
24
|
+
"<unknown operation type>";
|
|
25
|
+
const getOperationName = (document) => getOperationDefinition(document)?.name?.value;
|
|
26
|
+
const getOperationInfo = (document) => {
|
|
27
|
+
const def = getOperationDefinition(document);
|
|
28
|
+
return {
|
|
29
|
+
name: def?.name?.value ?? "<unknown operation>",
|
|
30
|
+
operationType: def?.operation ?? "<unknown operation type>",
|
|
31
|
+
};
|
|
32
|
+
};
|
|
29
33
|
const getErrorMessage = (operation, mockLink) => {
|
|
30
|
-
const
|
|
31
|
-
const operationType = definition.kind === "OperationDefinition"
|
|
32
|
-
? definition.operation
|
|
33
|
-
: "<unknown operation type>";
|
|
34
|
+
const operationType = getOperationType(operation);
|
|
34
35
|
const key = JSON.stringify({
|
|
35
36
|
query: print(operation.query),
|
|
36
37
|
});
|
|
@@ -38,7 +39,7 @@ const getErrorMessage = (operation, mockLink) => {
|
|
|
38
39
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
39
40
|
const alts = (mockLink
|
|
40
41
|
.mockedResponsesByKey[key] ?? []);
|
|
41
|
-
let errorMessage = `Expected
|
|
42
|
+
let errorMessage = `Expected ${operationType} ${operation.operationName} to have been mocked`;
|
|
42
43
|
if (alts.length || Object.keys(operation.variables).length > 0) {
|
|
43
44
|
errorMessage += ` with variables ${dntShim.Deno.inspect(operation.variables, { depth: Infinity, colors: true })}`;
|
|
44
45
|
}
|
|
@@ -61,59 +62,162 @@ const getErrorMessage = (operation, mockLink) => {
|
|
|
61
62
|
: undefined,
|
|
62
63
|
};
|
|
63
64
|
};
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
65
|
+
let _failRefetchWarnings = false;
|
|
66
|
+
/**
|
|
67
|
+
* In older versions of `@apollo/client`, refetches with missing mocks trigger
|
|
68
|
+
* warnings instead of being treated as standard missing mocks.
|
|
69
|
+
* This utility converts those warnings into failures and ensures watch queries
|
|
70
|
+
* are installed for queries with `watch: true`. This must be set when
|
|
71
|
+
* `MockProvider` is mounted.
|
|
72
|
+
*
|
|
73
|
+
* This is not required on modern version of `@apollo/client`.
|
|
74
|
+
*/
|
|
75
|
+
export const failRefetchWarnings = (value = true) => _failRefetchWarnings = value;
|
|
76
|
+
let _allowMissingMocks = false;
|
|
77
|
+
/**
|
|
78
|
+
* Allows missing mocks, resulting in tests passing. Usage is intended to ease
|
|
79
|
+
* migration.
|
|
80
|
+
*/
|
|
81
|
+
export const allowMissingMocks = (value) => _allowMissingMocks = value;
|
|
82
|
+
const AutoWatch = ({ mocks }) => {
|
|
83
|
+
const client = useApolloClient();
|
|
84
|
+
for (const mock of mocks)
|
|
85
|
+
if (mock.watch)
|
|
86
|
+
client.watchQuery(mock.request);
|
|
87
|
+
return null;
|
|
88
|
+
};
|
|
89
|
+
let lastMocks = [];
|
|
90
|
+
// deno-lint-ignore ban-types
|
|
91
|
+
const getStack = (to) => {
|
|
92
|
+
const obj = {};
|
|
93
|
+
Error.captureStackTrace(obj, to);
|
|
94
|
+
return obj.stack;
|
|
95
|
+
};
|
|
96
|
+
const _waitForMocks = async (mocks, cause) => {
|
|
97
|
+
for (const mock of mocks) {
|
|
98
|
+
if (mock.optional || mock.error)
|
|
99
|
+
continue;
|
|
100
|
+
await waitFor(() => {
|
|
72
101
|
if (currentSpecResult.failedExpectations.length)
|
|
73
102
|
return;
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
throw err;
|
|
89
|
-
}
|
|
90
|
-
}, { onTimeout: (e) => e });
|
|
103
|
+
if (mock.result.mock.calls.length === 0) {
|
|
104
|
+
const { name, operationType } = getOperationInfo(mock.request.query);
|
|
105
|
+
const err = new Error(`Expected to have used ${operationType} ${name}${mock.request.variables
|
|
106
|
+
? ` with variables ${dntShim.Deno.inspect(mock.request.variables, {
|
|
107
|
+
depth: Infinity,
|
|
108
|
+
colors: true,
|
|
109
|
+
})}`
|
|
110
|
+
: ""}`);
|
|
111
|
+
if (mock.stack) {
|
|
112
|
+
err.stack = `${mock.stack}${cause ? `\nCaused by: ${cause}` : ""}`;
|
|
113
|
+
}
|
|
114
|
+
else if (cause)
|
|
115
|
+
err.stack = cause;
|
|
116
|
+
throw err;
|
|
91
117
|
}
|
|
92
118
|
});
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
/**
|
|
122
|
+
* Wait for mocks to have been used.
|
|
123
|
+
* @param mock If `undefined`, waits for all mocks. If a number, waits fort he first `mocks` mocks. If a string, waits for all mocks up until and including that mock.
|
|
124
|
+
* @param offset If `mocks` is a string, grabs the `offset`th mock of that name (e.g., the third `getReport` mock)
|
|
125
|
+
*/
|
|
126
|
+
export const waitForMocks = async (mock = lastMocks.length, offset = 0) => {
|
|
127
|
+
if (typeof mock === "string") {
|
|
128
|
+
const matches = lastMocks.map((m, i) => [m, i])
|
|
129
|
+
.filter(([m]) => getOperationName(m.request.query) === mock);
|
|
130
|
+
if (matches.length <= offset) {
|
|
131
|
+
fail({
|
|
132
|
+
name: "Error",
|
|
133
|
+
message: `Expected mock ${mock} to have been mocked`,
|
|
134
|
+
stack: getStack(waitForMocks),
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
expect(matches.length).toBeGreaterThan(offset);
|
|
138
|
+
mock = matches[offset][1] + 1;
|
|
139
|
+
}
|
|
140
|
+
await _waitForMocks(lastMocks.slice(0, mock), getStack(waitForMocks));
|
|
141
|
+
};
|
|
142
|
+
/**
|
|
143
|
+
* A wrapper for `@apollo/client/testing`, this component will assert all
|
|
144
|
+
* requests have matching mocks and all defined mocks are used unless marked
|
|
145
|
+
* `optional`.
|
|
146
|
+
*/
|
|
147
|
+
export const MockedProvider = ({ mocks, stack: renderStack, children, link: passedLink, ...rest }) => {
|
|
148
|
+
const observableMocks = useMemo(() => {
|
|
149
|
+
const observableMocks = mocks.flatMap((m) => [
|
|
150
|
+
{
|
|
151
|
+
...m,
|
|
152
|
+
stack: m.stack,
|
|
153
|
+
result: Object.assign(jest.fn(() => m.result), m.result),
|
|
154
|
+
},
|
|
155
|
+
...(m.watch
|
|
156
|
+
? [{
|
|
157
|
+
...m,
|
|
158
|
+
stack: m.stack,
|
|
159
|
+
result: Object.assign(jest.fn(() => m.result), m.result),
|
|
160
|
+
watch: false,
|
|
161
|
+
}]
|
|
162
|
+
: []),
|
|
163
|
+
]);
|
|
164
|
+
lastMocks = observableMocks;
|
|
165
|
+
afterTest.push(() => _waitForMocks(lastMocks, renderStack));
|
|
93
166
|
return observableMocks;
|
|
94
167
|
}, [mocks]);
|
|
95
168
|
const link = useMemo(() => {
|
|
96
169
|
const mockLink = new MockLink(observableMocks);
|
|
97
170
|
mockLink.showWarnings = false;
|
|
98
|
-
const errorLoggingLink =
|
|
99
|
-
if (
|
|
171
|
+
const errorLoggingLink = onError(({ networkError, operation }) => {
|
|
172
|
+
if (_allowMissingMocks ||
|
|
173
|
+
!networkError?.message?.includes("No more mocked responses"))
|
|
100
174
|
return;
|
|
101
175
|
const { message, stack: altStack } = getErrorMessage(operation, mockLink);
|
|
102
176
|
try {
|
|
103
177
|
networkError.message = message;
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
178
|
+
if (altStack) {
|
|
179
|
+
networkError.stack = renderStack
|
|
180
|
+
? `${altStack}\nCaused By: ${renderStack}`
|
|
181
|
+
: altStack;
|
|
182
|
+
}
|
|
183
|
+
else if (renderStack)
|
|
184
|
+
networkError.stack = renderStack;
|
|
185
|
+
fail({ name: "Error", message, stack: networkError.stack });
|
|
110
186
|
}
|
|
111
187
|
catch {
|
|
112
188
|
// fail both throws and marks the test as failed in jest; we only need the latter
|
|
113
189
|
}
|
|
114
190
|
});
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
191
|
+
return ApolloLink.from([
|
|
192
|
+
errorLoggingLink,
|
|
193
|
+
mockLink,
|
|
194
|
+
...(passedLink ? [passedLink] : []),
|
|
195
|
+
]);
|
|
196
|
+
}, [observableMocks, renderStack]);
|
|
197
|
+
if (_failRefetchWarnings) {
|
|
198
|
+
const oldWarn = console.warn.bind(console.warn);
|
|
199
|
+
console.warn = (message, operation, ...etc) => {
|
|
200
|
+
if (message !==
|
|
201
|
+
'Unknown query named "%s" requested in refetchQueries in options.include array') {
|
|
202
|
+
return oldWarn(message, operation, ...etc);
|
|
203
|
+
}
|
|
204
|
+
try {
|
|
205
|
+
fail({
|
|
206
|
+
name: "Error",
|
|
207
|
+
message: `Expected query ${operation} requested in refetchQueries options.include array to have bene mocked`,
|
|
208
|
+
stack: renderStack,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
// eat
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
afterTest.push(() => {
|
|
216
|
+
console.warn = oldWarn;
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
return (React.createElement(ApolloMockedProvider, { ...rest, link: link },
|
|
220
|
+
React.createElement(React.Fragment, null,
|
|
221
|
+
React.createElement(AutoWatch, { mocks: observableMocks }),
|
|
222
|
+
children)));
|
|
119
223
|
};
|
package/esm/proxy.js
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import { Kind, Location, parse } from "graphql";
|
|
2
2
|
import { absurd } from "./util.js";
|
|
3
|
+
/**
|
|
4
|
+
* Updates the value to the base value, bypassing default transformations.
|
|
5
|
+
* Useful to clear optional inputs, resulting in `undefined` rather than `null`.
|
|
6
|
+
*/
|
|
7
|
+
export const clear = Symbol("clear");
|
|
8
|
+
/**
|
|
9
|
+
* Called to insert default patches at the front of nested objects (props &
|
|
10
|
+
* arrays). `build` will insert default patches for top-level objects.
|
|
11
|
+
*/
|
|
3
12
|
let getDefaultPatch = (__typename) => undefined;
|
|
4
13
|
export const withGetDefaultPatch = (newGetDefaultPatch, fn) => {
|
|
5
14
|
const prev = getDefaultPatch;
|
|
@@ -131,7 +140,7 @@ const resolveConcreteType = (definitions, definition, patch, prev) => {
|
|
|
131
140
|
};
|
|
132
141
|
const resolveValue = (definitions, scalars, type, ctx) => {
|
|
133
142
|
if (type.kind !== Kind.NON_NULL_TYPE)
|
|
134
|
-
return null;
|
|
143
|
+
return ctx.input ? undefined : null;
|
|
135
144
|
if (type.type.kind === Kind.LIST_TYPE)
|
|
136
145
|
return [];
|
|
137
146
|
type = type.type;
|
|
@@ -161,7 +170,7 @@ const resolveValue = (definitions, scalars, type, ctx) => {
|
|
|
161
170
|
? rawDefaultPatch(base)
|
|
162
171
|
: rawDefaultPatch;
|
|
163
172
|
if (defaultPatch) {
|
|
164
|
-
return _proxy(definitions, scalars, fieldTypeDefinitions[0].name.value, [base, defaultPatch]);
|
|
173
|
+
return _proxy(definitions, scalars, fieldTypeDefinitions[0].name.value, [base, defaultPatch], { selectionSet: ctx.selectionSet });
|
|
165
174
|
}
|
|
166
175
|
return base;
|
|
167
176
|
}
|
|
@@ -258,7 +267,7 @@ const selectionSetToKeys = (definitions, selectionSet, typename) => {
|
|
|
258
267
|
}
|
|
259
268
|
return keys;
|
|
260
269
|
};
|
|
261
|
-
|
|
270
|
+
let _proxy = (definitions, scalars, path, patches, { prev, resolvedType = resolveType(definitions, path), selectionSet, } = {}) => {
|
|
262
271
|
const { parent = path, definition } = resolvedType;
|
|
263
272
|
let type = resolvedType.type;
|
|
264
273
|
if (!prev && patches.length) {
|
|
@@ -270,9 +279,18 @@ const _proxy = (definitions, scalars, path, patches, { prev, resolvedType = reso
|
|
|
270
279
|
const patch = typeof rawPatch === "function"
|
|
271
280
|
? prev ? rawPatch(prev) : undefined
|
|
272
281
|
: rawPatch;
|
|
282
|
+
if (patch === clear) {
|
|
283
|
+
return _proxy(definitions, scalars, path, [], { selectionSet });
|
|
284
|
+
}
|
|
273
285
|
if (type.kind !== Kind.NON_NULL_TYPE) {
|
|
274
|
-
if (!patches.length)
|
|
275
|
-
return (prev ??
|
|
286
|
+
if (!patches.length) {
|
|
287
|
+
return (prev ??
|
|
288
|
+
(resolveType(definitions, path.split(".").slice(0, -1).join("."))
|
|
289
|
+
.definition?.kind ===
|
|
290
|
+
Kind.INPUT_OBJECT_TYPE_DEFINITION
|
|
291
|
+
? undefined
|
|
292
|
+
: null));
|
|
293
|
+
}
|
|
276
294
|
}
|
|
277
295
|
else
|
|
278
296
|
type = type.type;
|
|
@@ -280,6 +298,9 @@ const _proxy = (definitions, scalars, path, patches, { prev, resolvedType = reso
|
|
|
280
298
|
if (!patches.length)
|
|
281
299
|
return (prev ?? []);
|
|
282
300
|
const value = (patches.at(-1) ?? []);
|
|
301
|
+
if (value === clear) {
|
|
302
|
+
return _proxy(definitions, scalars, path, [], { selectionSet });
|
|
303
|
+
}
|
|
283
304
|
const previousArray = (prev ?? []);
|
|
284
305
|
const lastIndex = Math.max(previousArray.length - 1, 0);
|
|
285
306
|
const nextIndex = previousArray.length;
|
|
@@ -297,7 +318,14 @@ const _proxy = (definitions, scalars, path, patches, { prev, resolvedType = reso
|
|
|
297
318
|
}));
|
|
298
319
|
const arr = [];
|
|
299
320
|
for (let i = 0; i < length; i++) {
|
|
321
|
+
const nestType = type.type.kind === Kind.NAMED_TYPE
|
|
322
|
+
? type.type.name.value
|
|
323
|
+
: type.type.kind === Kind.NON_NULL_TYPE &&
|
|
324
|
+
type.type.type.kind === Kind.NAMED_TYPE
|
|
325
|
+
? type.type.type.name.value
|
|
326
|
+
: undefined;
|
|
300
327
|
arr[i] = _proxy(definitions, scalars, `${path}.${i}`, [
|
|
328
|
+
nestType && !previousArray[i] ? getDefaultPatch(nestType) : undefined,
|
|
301
329
|
value[i],
|
|
302
330
|
i === lastIndex ? value.last : undefined,
|
|
303
331
|
i === nextIndex ? value.next : undefined,
|
|
@@ -335,6 +363,9 @@ const _proxy = (definitions, scalars, path, patches, { prev, resolvedType = reso
|
|
|
335
363
|
const value = typeof rawValue === "function"
|
|
336
364
|
? rawValue(prev)
|
|
337
365
|
: rawValue;
|
|
366
|
+
if (value === clear) {
|
|
367
|
+
return target[prop] = _proxy(definitions, scalars, `${path}.${prop}`, [], { selectionSet });
|
|
368
|
+
}
|
|
338
369
|
if (value && typeof value === "object") {
|
|
339
370
|
const nonNullFieldType = field.type.kind === Kind.NON_NULL_TYPE
|
|
340
371
|
? field.type.type
|
|
@@ -373,6 +404,7 @@ const _proxy = (definitions, scalars, path, patches, { prev, resolvedType = reso
|
|
|
373
404
|
selectionSet: selectionSet
|
|
374
405
|
? getSelectionSetSelection(definitions, selectionSet, prop)
|
|
375
406
|
: undefined,
|
|
407
|
+
input: definition.kind === Kind.INPUT_OBJECT_TYPE_DEFINITION,
|
|
376
408
|
});
|
|
377
409
|
};
|
|
378
410
|
const keys = [
|
|
@@ -466,7 +498,7 @@ const _proxy = (definitions, scalars, path, patches, { prev, resolvedType = reso
|
|
|
466
498
|
mock.variables[name] = mockPrev.variables[name];
|
|
467
499
|
continue;
|
|
468
500
|
}
|
|
469
|
-
const result = resolveValue(definitions, scalars, variableDefinition.type, { hostType: `${path}Variables`, prop: name });
|
|
501
|
+
const result = resolveValue(definitions, scalars, variableDefinition.type, { hostType: `${path}Variables`, prop: name, input: false });
|
|
470
502
|
if (result != null) {
|
|
471
503
|
if (!mock.variables)
|
|
472
504
|
mock.variables = {};
|
|
@@ -489,6 +521,10 @@ const _proxy = (definitions, scalars, path, patches, { prev, resolvedType = reso
|
|
|
489
521
|
// Query, Mutation, Subscription
|
|
490
522
|
const operationKey = definition.operation[0].toUpperCase() +
|
|
491
523
|
definition.operation.slice(1);
|
|
524
|
+
if (value === clear) {
|
|
525
|
+
mock.data[prop] = _proxy(definitions, scalars, `${operationKey}.${selection.name.value}`, [], { selectionSet: selection.selectionSet });
|
|
526
|
+
continue;
|
|
527
|
+
}
|
|
492
528
|
mock.data[prop] = _proxy(definitions, scalars, `${operationKey}.${selection.name.value}`, value != null ? [value] : [], {
|
|
493
529
|
prev: mockPrev?.data?.[prop],
|
|
494
530
|
selectionSet: selection.selectionSet,
|
|
@@ -534,6 +570,14 @@ const _proxy = (definitions, scalars, path, patches, { prev, resolvedType = reso
|
|
|
534
570
|
const value = typeof scalar === "function" ? scalar(parent) : scalar;
|
|
535
571
|
return value;
|
|
536
572
|
}
|
|
573
|
+
case Kind.ENUM_TYPE_DEFINITION: {
|
|
574
|
+
const patch = patches[patches.length - 1];
|
|
575
|
+
if (patch != null)
|
|
576
|
+
return patch;
|
|
577
|
+
if (prev != null)
|
|
578
|
+
return prev;
|
|
579
|
+
return definition.values?.[0]?.name.value;
|
|
580
|
+
}
|
|
537
581
|
default:
|
|
538
582
|
throw new Error(`Unhandled definition kind '${definition.kind}'`);
|
|
539
583
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "graphql-data-generator",
|
|
3
|
-
"version": "0.3.0-alpha.
|
|
3
|
+
"version": "0.3.0-alpha.20",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/voces/graphql-data-generator.git"
|
|
@@ -35,6 +35,16 @@
|
|
|
35
35
|
"bin": {
|
|
36
36
|
"graphql-data-generator": "./esm/cli.js"
|
|
37
37
|
},
|
|
38
|
+
"typesVersions": {
|
|
39
|
+
"*": {
|
|
40
|
+
"script/jest": [
|
|
41
|
+
"types/jest.d.ts"
|
|
42
|
+
],
|
|
43
|
+
"script/plugin": [
|
|
44
|
+
"types/plugin.d.ts"
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
},
|
|
38
48
|
"dependencies": {
|
|
39
49
|
"@graphql-codegen/visitor-plugin-common": "*",
|
|
40
50
|
"fast-glob": "*",
|
package/script/index.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.toObject = exports.formatCode = exports.proxy = exports.codegen = exports.init = void 0;
|
|
3
|
+
exports.toObject = exports.formatCode = exports.proxy = exports.clear = exports.codegen = exports.init = void 0;
|
|
4
4
|
var init_js_1 = require("./init.js");
|
|
5
5
|
Object.defineProperty(exports, "init", { enumerable: true, get: function () { return init_js_1.init; } });
|
|
6
6
|
var codegen_js_1 = require("./codegen.js");
|
|
7
7
|
Object.defineProperty(exports, "codegen", { enumerable: true, get: function () { return codegen_js_1.codegen; } });
|
|
8
8
|
var proxy_js_1 = require("./proxy.js");
|
|
9
|
+
Object.defineProperty(exports, "clear", { enumerable: true, get: function () { return proxy_js_1.clear; } });
|
|
9
10
|
Object.defineProperty(exports, "proxy", { enumerable: true, get: function () { return proxy_js_1.proxy; } });
|
|
10
11
|
var util_js_1 = require("./util.js");
|
|
11
12
|
Object.defineProperty(exports, "formatCode", { enumerable: true, get: function () { return util_js_1.formatCode; } });
|
package/script/init.js
CHANGED
|
@@ -1,14 +1,50 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
2
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
26
|
exports.init = void 0;
|
|
27
|
+
const dntShim = __importStar(require("./_dnt.shims.js"));
|
|
28
|
+
const node_module_1 = require("node:module");
|
|
4
29
|
const node_fs_1 = require("node:fs");
|
|
5
30
|
const graphql_1 = require("graphql");
|
|
6
|
-
const node_path_1 = require("node:path");
|
|
7
31
|
const graphql_tag_pluck_1 = require("@graphql-tools/graphql-tag-pluck");
|
|
8
32
|
const proxy_js_1 = require("./proxy.js");
|
|
9
33
|
const util_js_1 = require("./util.js");
|
|
34
|
+
const node_path_1 = require("node:path");
|
|
35
|
+
globalThis.require ??= (0, node_module_1.createRequire)(dntShim.Deno.cwd());
|
|
10
36
|
const files = {};
|
|
11
|
-
const loadFile = (path) =>
|
|
37
|
+
const loadFile = (path) => {
|
|
38
|
+
if (files[path])
|
|
39
|
+
return files[path];
|
|
40
|
+
const raw = files[path] = (0, node_fs_1.readFileSync)(path, "utf-8");
|
|
41
|
+
const imports = Array.from(raw.matchAll(/#import "(.*)"/gm), ([, importPath]) => loadFile(require.resolve(importPath.startsWith(".")
|
|
42
|
+
? (0, node_path_1.resolve)(dntShim.Deno.cwd(), (0, node_path_1.dirname)(path), importPath)
|
|
43
|
+
: importPath, { paths: [dntShim.Deno.cwd()] })));
|
|
44
|
+
if (!imports.length)
|
|
45
|
+
return raw;
|
|
46
|
+
return [raw, ...imports].join("\n\n");
|
|
47
|
+
};
|
|
12
48
|
const getOperationContentMap = {};
|
|
13
49
|
const getOperationContent = (path, operationName) => {
|
|
14
50
|
const existing = getOperationContentMap[path];
|
|
@@ -17,14 +53,14 @@ const getOperationContent = (path, operationName) => {
|
|
|
17
53
|
return existing;
|
|
18
54
|
return getOperationContentMap[path][operationName];
|
|
19
55
|
}
|
|
20
|
-
const fileContent =
|
|
56
|
+
const fileContent = loadFile(path);
|
|
21
57
|
try {
|
|
22
58
|
const sources = (0, graphql_tag_pluck_1.gqlPluckFromCodeStringSync)(path, fileContent);
|
|
23
59
|
getOperationContentMap[path] = Object.fromEntries(sources.map((s) => {
|
|
24
60
|
const document = (0, graphql_1.parse)(s);
|
|
25
61
|
const firstOp = document.definitions.find((d) => d.kind === graphql_1.Kind.OPERATION_DEFINITION);
|
|
26
62
|
if (!firstOp)
|
|
27
|
-
throw new Error(`
|
|
63
|
+
throw new Error(`Could not find an operation in ${path}`);
|
|
28
64
|
return [firstOp.name?.value, document];
|
|
29
65
|
}));
|
|
30
66
|
}
|
|
@@ -33,7 +69,16 @@ const getOperationContent = (path, operationName) => {
|
|
|
33
69
|
}
|
|
34
70
|
return getOperationContent(path, operationName);
|
|
35
71
|
};
|
|
36
|
-
|
|
72
|
+
/**
|
|
73
|
+
* Initialize the data builder.
|
|
74
|
+
* @param schema The plain text of your schema.
|
|
75
|
+
* @param queries List of queries exported from generated types.
|
|
76
|
+
* @param mutations List of mutations exported from generated types.
|
|
77
|
+
* @param subscriptions List of subscriptions exported from generated types.
|
|
78
|
+
* @param types List of types exported from generated types.
|
|
79
|
+
* @param inputs List of types exported from generated types.
|
|
80
|
+
* @param scalars A mapping to generate scalar values. Function values will be invoked with their `__typename`.
|
|
81
|
+
*/
|
|
37
82
|
const init = (schema, queries, mutations, subscriptions, types, inputs, scalars, options) => (fn) => {
|
|
38
83
|
const doc = (0, graphql_1.parse)(schema);
|
|
39
84
|
const build = {};
|
|
@@ -107,7 +152,6 @@ const init = (schema, queries, mutations, subscriptions, types, inputs, scalars,
|
|
|
107
152
|
: variables,
|
|
108
153
|
});
|
|
109
154
|
Error.captureStackTrace(mock, obj.variables);
|
|
110
|
-
mock.stack = mock.stack?.slice(6);
|
|
111
155
|
return mock;
|
|
112
156
|
},
|
|
113
157
|
},
|
|
@@ -128,7 +172,6 @@ const init = (schema, queries, mutations, subscriptions, types, inputs, scalars,
|
|
|
128
172
|
: data,
|
|
129
173
|
});
|
|
130
174
|
Error.captureStackTrace(mock, obj.data);
|
|
131
|
-
mock.stack = mock.stack?.slice(6);
|
|
132
175
|
return mock;
|
|
133
176
|
},
|
|
134
177
|
},
|
|
@@ -149,7 +192,6 @@ const init = (schema, queries, mutations, subscriptions, types, inputs, scalars,
|
|
|
149
192
|
const builder = build[operation];
|
|
150
193
|
const mock = builder(prevInput, patch);
|
|
151
194
|
Error.captureStackTrace(mock, obj[name]);
|
|
152
|
-
mock.stack = mock.stack?.slice(6);
|
|
153
195
|
return mock;
|
|
154
196
|
},
|
|
155
197
|
}])));
|
|
@@ -161,14 +203,16 @@ const init = (schema, queries, mutations, subscriptions, types, inputs, scalars,
|
|
|
161
203
|
if (transforms[name] && "default" in transforms[name]) {
|
|
162
204
|
patches = [transforms[name].default, ...patches];
|
|
163
205
|
}
|
|
164
|
-
const {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
206
|
+
const { mock, parsedQuery } = (0, proxy_js_1.withGetDefaultPatch)((type) => transforms[type]?.default, () => {
|
|
207
|
+
const { request: { query: parsedQuery, ...request }, ...raw } = (0, proxy_js_1.operation)(doc.definitions, scalars, query, ...patches);
|
|
208
|
+
const mock = (0, util_js_1.toObject)({
|
|
209
|
+
request: { ...request },
|
|
210
|
+
...raw,
|
|
211
|
+
});
|
|
212
|
+
return { mock, parsedQuery };
|
|
168
213
|
});
|
|
169
214
|
mock.request.query = parsedQuery;
|
|
170
215
|
Error.captureStackTrace(mock, builder);
|
|
171
|
-
mock.stack = mock.stack?.slice(6);
|
|
172
216
|
return addOperationTransforms(name, options?.finalizeOperation
|
|
173
217
|
? options.finalizeOperation(mock)
|
|
174
218
|
: mock);
|
package/script/jest.js
CHANGED
|
@@ -23,12 +23,12 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
23
23
|
return result;
|
|
24
24
|
};
|
|
25
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
-
exports.
|
|
26
|
+
exports.MockedProvider = exports.waitForMocks = exports.allowMissingMocks = exports.failRefetchWarnings = void 0;
|
|
27
27
|
const dntShim = __importStar(require("./_dnt.shims.js"));
|
|
28
28
|
const react_1 = __importStar(require("react"));
|
|
29
29
|
const client_1 = require("@apollo/client");
|
|
30
|
-
const
|
|
31
|
-
const
|
|
30
|
+
const error_1 = require("@apollo/client/link/error");
|
|
31
|
+
const testing_1 = require("@apollo/client/testing");
|
|
32
32
|
const dom_1 = require("@testing-library/dom");
|
|
33
33
|
const graphql_1 = require("graphql");
|
|
34
34
|
const jest_diff_1 = require("jest-diff");
|
|
@@ -38,25 +38,26 @@ jasmine.getEnv().addReporter({
|
|
|
38
38
|
});
|
|
39
39
|
const afterTest = [];
|
|
40
40
|
afterEach(async () => {
|
|
41
|
-
|
|
41
|
+
const hooks = afterTest.splice(0);
|
|
42
|
+
for (const hook of hooks)
|
|
42
43
|
await hook();
|
|
43
|
-
afterTest.splice(0);
|
|
44
44
|
});
|
|
45
|
-
const diff = (a, b) => (0, jest_diff_1.diff)(a, b, {
|
|
46
|
-
omitAnnotationLines: true,
|
|
47
|
-
// aColor: green,
|
|
48
|
-
// bColor: red,
|
|
49
|
-
// changeColor: inverse,
|
|
50
|
-
// commonColor: dim,
|
|
51
|
-
// patchColor: yellow,
|
|
52
|
-
})
|
|
45
|
+
const diff = (a, b) => (0, jest_diff_1.diff)(a, b, { omitAnnotationLines: true })
|
|
53
46
|
?.replace(/\w+ \{/g, "{") // Remove class names
|
|
54
47
|
.replace(/\w+ \[/g, "["); // Remove array class names
|
|
48
|
+
const getOperationDefinition = (document) => document.definitions.find((d) => d.kind === graphql_1.Kind.OPERATION_DEFINITION);
|
|
49
|
+
const getOperationType = (operation) => getOperationDefinition(operation.query)?.operation ??
|
|
50
|
+
"<unknown operation type>";
|
|
51
|
+
const getOperationName = (document) => getOperationDefinition(document)?.name?.value;
|
|
52
|
+
const getOperationInfo = (document) => {
|
|
53
|
+
const def = getOperationDefinition(document);
|
|
54
|
+
return {
|
|
55
|
+
name: def?.name?.value ?? "<unknown operation>",
|
|
56
|
+
operationType: def?.operation ?? "<unknown operation type>",
|
|
57
|
+
};
|
|
58
|
+
};
|
|
55
59
|
const getErrorMessage = (operation, mockLink) => {
|
|
56
|
-
const
|
|
57
|
-
const operationType = definition.kind === "OperationDefinition"
|
|
58
|
-
? definition.operation
|
|
59
|
-
: "<unknown operation type>";
|
|
60
|
+
const operationType = getOperationType(operation);
|
|
60
61
|
const key = JSON.stringify({
|
|
61
62
|
query: (0, graphql_1.print)(operation.query),
|
|
62
63
|
});
|
|
@@ -64,7 +65,7 @@ const getErrorMessage = (operation, mockLink) => {
|
|
|
64
65
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
65
66
|
const alts = (mockLink
|
|
66
67
|
.mockedResponsesByKey[key] ?? []);
|
|
67
|
-
let errorMessage = `Expected
|
|
68
|
+
let errorMessage = `Expected ${operationType} ${operation.operationName} to have been mocked`;
|
|
68
69
|
if (alts.length || Object.keys(operation.variables).length > 0) {
|
|
69
70
|
errorMessage += ` with variables ${dntShim.Deno.inspect(operation.variables, { depth: Infinity, colors: true })}`;
|
|
70
71
|
}
|
|
@@ -87,60 +88,166 @@ const getErrorMessage = (operation, mockLink) => {
|
|
|
87
88
|
: undefined,
|
|
88
89
|
};
|
|
89
90
|
};
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
91
|
+
let _failRefetchWarnings = false;
|
|
92
|
+
/**
|
|
93
|
+
* In older versions of `@apollo/client`, refetches with missing mocks trigger
|
|
94
|
+
* warnings instead of being treated as standard missing mocks.
|
|
95
|
+
* This utility converts those warnings into failures and ensures watch queries
|
|
96
|
+
* are installed for queries with `watch: true`. This must be set when
|
|
97
|
+
* `MockProvider` is mounted.
|
|
98
|
+
*
|
|
99
|
+
* This is not required on modern version of `@apollo/client`.
|
|
100
|
+
*/
|
|
101
|
+
const failRefetchWarnings = (value = true) => _failRefetchWarnings = value;
|
|
102
|
+
exports.failRefetchWarnings = failRefetchWarnings;
|
|
103
|
+
let _allowMissingMocks = false;
|
|
104
|
+
/**
|
|
105
|
+
* Allows missing mocks, resulting in tests passing. Usage is intended to ease
|
|
106
|
+
* migration.
|
|
107
|
+
*/
|
|
108
|
+
const allowMissingMocks = (value) => _allowMissingMocks = value;
|
|
109
|
+
exports.allowMissingMocks = allowMissingMocks;
|
|
110
|
+
const AutoWatch = ({ mocks }) => {
|
|
111
|
+
const client = (0, client_1.useApolloClient)();
|
|
112
|
+
for (const mock of mocks)
|
|
113
|
+
if (mock.watch)
|
|
114
|
+
client.watchQuery(mock.request);
|
|
115
|
+
return null;
|
|
116
|
+
};
|
|
117
|
+
let lastMocks = [];
|
|
118
|
+
// deno-lint-ignore ban-types
|
|
119
|
+
const getStack = (to) => {
|
|
120
|
+
const obj = {};
|
|
121
|
+
Error.captureStackTrace(obj, to);
|
|
122
|
+
return obj.stack;
|
|
123
|
+
};
|
|
124
|
+
const _waitForMocks = async (mocks, cause) => {
|
|
125
|
+
for (const mock of mocks) {
|
|
126
|
+
if (mock.optional || mock.error)
|
|
127
|
+
continue;
|
|
128
|
+
await (0, dom_1.waitFor)(() => {
|
|
98
129
|
if (currentSpecResult.failedExpectations.length)
|
|
99
130
|
return;
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
throw err;
|
|
115
|
-
}
|
|
116
|
-
}, { onTimeout: (e) => e });
|
|
131
|
+
if (mock.result.mock.calls.length === 0) {
|
|
132
|
+
const { name, operationType } = getOperationInfo(mock.request.query);
|
|
133
|
+
const err = new Error(`Expected to have used ${operationType} ${name}${mock.request.variables
|
|
134
|
+
? ` with variables ${dntShim.Deno.inspect(mock.request.variables, {
|
|
135
|
+
depth: Infinity,
|
|
136
|
+
colors: true,
|
|
137
|
+
})}`
|
|
138
|
+
: ""}`);
|
|
139
|
+
if (mock.stack) {
|
|
140
|
+
err.stack = `${mock.stack}${cause ? `\nCaused by: ${cause}` : ""}`;
|
|
141
|
+
}
|
|
142
|
+
else if (cause)
|
|
143
|
+
err.stack = cause;
|
|
144
|
+
throw err;
|
|
117
145
|
}
|
|
118
146
|
});
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
/**
|
|
150
|
+
* Wait for mocks to have been used.
|
|
151
|
+
* @param mock If `undefined`, waits for all mocks. If a number, waits fort he first `mocks` mocks. If a string, waits for all mocks up until and including that mock.
|
|
152
|
+
* @param offset If `mocks` is a string, grabs the `offset`th mock of that name (e.g., the third `getReport` mock)
|
|
153
|
+
*/
|
|
154
|
+
const waitForMocks = async (mock = lastMocks.length, offset = 0) => {
|
|
155
|
+
if (typeof mock === "string") {
|
|
156
|
+
const matches = lastMocks.map((m, i) => [m, i])
|
|
157
|
+
.filter(([m]) => getOperationName(m.request.query) === mock);
|
|
158
|
+
if (matches.length <= offset) {
|
|
159
|
+
fail({
|
|
160
|
+
name: "Error",
|
|
161
|
+
message: `Expected mock ${mock} to have been mocked`,
|
|
162
|
+
stack: getStack(exports.waitForMocks),
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
expect(matches.length).toBeGreaterThan(offset);
|
|
166
|
+
mock = matches[offset][1] + 1;
|
|
167
|
+
}
|
|
168
|
+
await _waitForMocks(lastMocks.slice(0, mock), getStack(exports.waitForMocks));
|
|
169
|
+
};
|
|
170
|
+
exports.waitForMocks = waitForMocks;
|
|
171
|
+
/**
|
|
172
|
+
* A wrapper for `@apollo/client/testing`, this component will assert all
|
|
173
|
+
* requests have matching mocks and all defined mocks are used unless marked
|
|
174
|
+
* `optional`.
|
|
175
|
+
*/
|
|
176
|
+
const MockedProvider = ({ mocks, stack: renderStack, children, link: passedLink, ...rest }) => {
|
|
177
|
+
const observableMocks = (0, react_1.useMemo)(() => {
|
|
178
|
+
const observableMocks = mocks.flatMap((m) => [
|
|
179
|
+
{
|
|
180
|
+
...m,
|
|
181
|
+
stack: m.stack,
|
|
182
|
+
result: Object.assign(jest.fn(() => m.result), m.result),
|
|
183
|
+
},
|
|
184
|
+
...(m.watch
|
|
185
|
+
? [{
|
|
186
|
+
...m,
|
|
187
|
+
stack: m.stack,
|
|
188
|
+
result: Object.assign(jest.fn(() => m.result), m.result),
|
|
189
|
+
watch: false,
|
|
190
|
+
}]
|
|
191
|
+
: []),
|
|
192
|
+
]);
|
|
193
|
+
lastMocks = observableMocks;
|
|
194
|
+
afterTest.push(() => _waitForMocks(lastMocks, renderStack));
|
|
119
195
|
return observableMocks;
|
|
120
196
|
}, [mocks]);
|
|
121
197
|
const link = (0, react_1.useMemo)(() => {
|
|
122
|
-
const mockLink = new
|
|
198
|
+
const mockLink = new testing_1.MockLink(observableMocks);
|
|
123
199
|
mockLink.showWarnings = false;
|
|
124
|
-
const errorLoggingLink =
|
|
125
|
-
if (
|
|
200
|
+
const errorLoggingLink = (0, error_1.onError)(({ networkError, operation }) => {
|
|
201
|
+
if (_allowMissingMocks ||
|
|
202
|
+
!networkError?.message?.includes("No more mocked responses"))
|
|
126
203
|
return;
|
|
127
204
|
const { message, stack: altStack } = getErrorMessage(operation, mockLink);
|
|
128
205
|
try {
|
|
129
206
|
networkError.message = message;
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
207
|
+
if (altStack) {
|
|
208
|
+
networkError.stack = renderStack
|
|
209
|
+
? `${altStack}\nCaused By: ${renderStack}`
|
|
210
|
+
: altStack;
|
|
211
|
+
}
|
|
212
|
+
else if (renderStack)
|
|
213
|
+
networkError.stack = renderStack;
|
|
214
|
+
fail({ name: "Error", message, stack: networkError.stack });
|
|
136
215
|
}
|
|
137
216
|
catch {
|
|
138
217
|
// fail both throws and marks the test as failed in jest; we only need the latter
|
|
139
218
|
}
|
|
140
219
|
});
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
220
|
+
return client_1.ApolloLink.from([
|
|
221
|
+
errorLoggingLink,
|
|
222
|
+
mockLink,
|
|
223
|
+
...(passedLink ? [passedLink] : []),
|
|
224
|
+
]);
|
|
225
|
+
}, [observableMocks, renderStack]);
|
|
226
|
+
if (_failRefetchWarnings) {
|
|
227
|
+
const oldWarn = console.warn.bind(console.warn);
|
|
228
|
+
console.warn = (message, operation, ...etc) => {
|
|
229
|
+
if (message !==
|
|
230
|
+
'Unknown query named "%s" requested in refetchQueries in options.include array') {
|
|
231
|
+
return oldWarn(message, operation, ...etc);
|
|
232
|
+
}
|
|
233
|
+
try {
|
|
234
|
+
fail({
|
|
235
|
+
name: "Error",
|
|
236
|
+
message: `Expected query ${operation} requested in refetchQueries options.include array to have bene mocked`,
|
|
237
|
+
stack: renderStack,
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
// eat
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
afterTest.push(() => {
|
|
245
|
+
console.warn = oldWarn;
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
return (react_1.default.createElement(testing_1.MockedProvider, { ...rest, link: link },
|
|
249
|
+
react_1.default.createElement(react_1.default.Fragment, null,
|
|
250
|
+
react_1.default.createElement(AutoWatch, { mocks: observableMocks }),
|
|
251
|
+
children)));
|
|
145
252
|
};
|
|
146
|
-
exports.
|
|
253
|
+
exports.MockedProvider = MockedProvider;
|
package/script/proxy.js
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.operation = exports.proxy = exports.withGetDefaultPatch = void 0;
|
|
3
|
+
exports.operation = exports.proxy = exports.withGetDefaultPatch = exports.clear = void 0;
|
|
4
4
|
const graphql_1 = require("graphql");
|
|
5
5
|
const util_js_1 = require("./util.js");
|
|
6
|
+
/**
|
|
7
|
+
* Updates the value to the base value, bypassing default transformations.
|
|
8
|
+
* Useful to clear optional inputs, resulting in `undefined` rather than `null`.
|
|
9
|
+
*/
|
|
10
|
+
exports.clear = Symbol("clear");
|
|
11
|
+
/**
|
|
12
|
+
* Called to insert default patches at the front of nested objects (props &
|
|
13
|
+
* arrays). `build` will insert default patches for top-level objects.
|
|
14
|
+
*/
|
|
6
15
|
let getDefaultPatch = (__typename) => undefined;
|
|
7
16
|
const withGetDefaultPatch = (newGetDefaultPatch, fn) => {
|
|
8
17
|
const prev = getDefaultPatch;
|
|
@@ -135,7 +144,7 @@ const resolveConcreteType = (definitions, definition, patch, prev) => {
|
|
|
135
144
|
};
|
|
136
145
|
const resolveValue = (definitions, scalars, type, ctx) => {
|
|
137
146
|
if (type.kind !== graphql_1.Kind.NON_NULL_TYPE)
|
|
138
|
-
return null;
|
|
147
|
+
return ctx.input ? undefined : null;
|
|
139
148
|
if (type.type.kind === graphql_1.Kind.LIST_TYPE)
|
|
140
149
|
return [];
|
|
141
150
|
type = type.type;
|
|
@@ -165,7 +174,7 @@ const resolveValue = (definitions, scalars, type, ctx) => {
|
|
|
165
174
|
? rawDefaultPatch(base)
|
|
166
175
|
: rawDefaultPatch;
|
|
167
176
|
if (defaultPatch) {
|
|
168
|
-
return _proxy(definitions, scalars, fieldTypeDefinitions[0].name.value, [base, defaultPatch]);
|
|
177
|
+
return _proxy(definitions, scalars, fieldTypeDefinitions[0].name.value, [base, defaultPatch], { selectionSet: ctx.selectionSet });
|
|
169
178
|
}
|
|
170
179
|
return base;
|
|
171
180
|
}
|
|
@@ -262,7 +271,7 @@ const selectionSetToKeys = (definitions, selectionSet, typename) => {
|
|
|
262
271
|
}
|
|
263
272
|
return keys;
|
|
264
273
|
};
|
|
265
|
-
|
|
274
|
+
let _proxy = (definitions, scalars, path, patches, { prev, resolvedType = resolveType(definitions, path), selectionSet, } = {}) => {
|
|
266
275
|
const { parent = path, definition } = resolvedType;
|
|
267
276
|
let type = resolvedType.type;
|
|
268
277
|
if (!prev && patches.length) {
|
|
@@ -274,9 +283,18 @@ const _proxy = (definitions, scalars, path, patches, { prev, resolvedType = reso
|
|
|
274
283
|
const patch = typeof rawPatch === "function"
|
|
275
284
|
? prev ? rawPatch(prev) : undefined
|
|
276
285
|
: rawPatch;
|
|
286
|
+
if (patch === exports.clear) {
|
|
287
|
+
return _proxy(definitions, scalars, path, [], { selectionSet });
|
|
288
|
+
}
|
|
277
289
|
if (type.kind !== graphql_1.Kind.NON_NULL_TYPE) {
|
|
278
|
-
if (!patches.length)
|
|
279
|
-
return (prev ??
|
|
290
|
+
if (!patches.length) {
|
|
291
|
+
return (prev ??
|
|
292
|
+
(resolveType(definitions, path.split(".").slice(0, -1).join("."))
|
|
293
|
+
.definition?.kind ===
|
|
294
|
+
graphql_1.Kind.INPUT_OBJECT_TYPE_DEFINITION
|
|
295
|
+
? undefined
|
|
296
|
+
: null));
|
|
297
|
+
}
|
|
280
298
|
}
|
|
281
299
|
else
|
|
282
300
|
type = type.type;
|
|
@@ -284,6 +302,9 @@ const _proxy = (definitions, scalars, path, patches, { prev, resolvedType = reso
|
|
|
284
302
|
if (!patches.length)
|
|
285
303
|
return (prev ?? []);
|
|
286
304
|
const value = (patches.at(-1) ?? []);
|
|
305
|
+
if (value === exports.clear) {
|
|
306
|
+
return _proxy(definitions, scalars, path, [], { selectionSet });
|
|
307
|
+
}
|
|
287
308
|
const previousArray = (prev ?? []);
|
|
288
309
|
const lastIndex = Math.max(previousArray.length - 1, 0);
|
|
289
310
|
const nextIndex = previousArray.length;
|
|
@@ -301,7 +322,14 @@ const _proxy = (definitions, scalars, path, patches, { prev, resolvedType = reso
|
|
|
301
322
|
}));
|
|
302
323
|
const arr = [];
|
|
303
324
|
for (let i = 0; i < length; i++) {
|
|
325
|
+
const nestType = type.type.kind === graphql_1.Kind.NAMED_TYPE
|
|
326
|
+
? type.type.name.value
|
|
327
|
+
: type.type.kind === graphql_1.Kind.NON_NULL_TYPE &&
|
|
328
|
+
type.type.type.kind === graphql_1.Kind.NAMED_TYPE
|
|
329
|
+
? type.type.type.name.value
|
|
330
|
+
: undefined;
|
|
304
331
|
arr[i] = _proxy(definitions, scalars, `${path}.${i}`, [
|
|
332
|
+
nestType && !previousArray[i] ? getDefaultPatch(nestType) : undefined,
|
|
305
333
|
value[i],
|
|
306
334
|
i === lastIndex ? value.last : undefined,
|
|
307
335
|
i === nextIndex ? value.next : undefined,
|
|
@@ -339,6 +367,9 @@ const _proxy = (definitions, scalars, path, patches, { prev, resolvedType = reso
|
|
|
339
367
|
const value = typeof rawValue === "function"
|
|
340
368
|
? rawValue(prev)
|
|
341
369
|
: rawValue;
|
|
370
|
+
if (value === exports.clear) {
|
|
371
|
+
return target[prop] = _proxy(definitions, scalars, `${path}.${prop}`, [], { selectionSet });
|
|
372
|
+
}
|
|
342
373
|
if (value && typeof value === "object") {
|
|
343
374
|
const nonNullFieldType = field.type.kind === graphql_1.Kind.NON_NULL_TYPE
|
|
344
375
|
? field.type.type
|
|
@@ -377,6 +408,7 @@ const _proxy = (definitions, scalars, path, patches, { prev, resolvedType = reso
|
|
|
377
408
|
selectionSet: selectionSet
|
|
378
409
|
? getSelectionSetSelection(definitions, selectionSet, prop)
|
|
379
410
|
: undefined,
|
|
411
|
+
input: definition.kind === graphql_1.Kind.INPUT_OBJECT_TYPE_DEFINITION,
|
|
380
412
|
});
|
|
381
413
|
};
|
|
382
414
|
const keys = [
|
|
@@ -470,7 +502,7 @@ const _proxy = (definitions, scalars, path, patches, { prev, resolvedType = reso
|
|
|
470
502
|
mock.variables[name] = mockPrev.variables[name];
|
|
471
503
|
continue;
|
|
472
504
|
}
|
|
473
|
-
const result = resolveValue(definitions, scalars, variableDefinition.type, { hostType: `${path}Variables`, prop: name });
|
|
505
|
+
const result = resolveValue(definitions, scalars, variableDefinition.type, { hostType: `${path}Variables`, prop: name, input: false });
|
|
474
506
|
if (result != null) {
|
|
475
507
|
if (!mock.variables)
|
|
476
508
|
mock.variables = {};
|
|
@@ -493,6 +525,10 @@ const _proxy = (definitions, scalars, path, patches, { prev, resolvedType = reso
|
|
|
493
525
|
// Query, Mutation, Subscription
|
|
494
526
|
const operationKey = definition.operation[0].toUpperCase() +
|
|
495
527
|
definition.operation.slice(1);
|
|
528
|
+
if (value === exports.clear) {
|
|
529
|
+
mock.data[prop] = _proxy(definitions, scalars, `${operationKey}.${selection.name.value}`, [], { selectionSet: selection.selectionSet });
|
|
530
|
+
continue;
|
|
531
|
+
}
|
|
496
532
|
mock.data[prop] = _proxy(definitions, scalars, `${operationKey}.${selection.name.value}`, value != null ? [value] : [], {
|
|
497
533
|
prev: mockPrev?.data?.[prop],
|
|
498
534
|
selectionSet: selection.selectionSet,
|
|
@@ -538,6 +574,14 @@ const _proxy = (definitions, scalars, path, patches, { prev, resolvedType = reso
|
|
|
538
574
|
const value = typeof scalar === "function" ? scalar(parent) : scalar;
|
|
539
575
|
return value;
|
|
540
576
|
}
|
|
577
|
+
case graphql_1.Kind.ENUM_TYPE_DEFINITION: {
|
|
578
|
+
const patch = patches[patches.length - 1];
|
|
579
|
+
if (patch != null)
|
|
580
|
+
return patch;
|
|
581
|
+
if (prev != null)
|
|
582
|
+
return prev;
|
|
583
|
+
return definition.values?.[0]?.name.value;
|
|
584
|
+
}
|
|
541
585
|
default:
|
|
542
586
|
throw new Error(`Unhandled definition kind '${definition.kind}'`);
|
|
543
587
|
}
|
package/types/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { init } from "./init.js";
|
|
2
|
-
export type { DeepPartial, Patch } from "./types.js";
|
|
2
|
+
export type { DeepPartial, OperationMock, Patch } from "./types.js";
|
|
3
3
|
export { codegen } from "./codegen.js";
|
|
4
|
-
export { proxy } from "./proxy.js";
|
|
4
|
+
export { clear, proxy } from "./proxy.js";
|
|
5
5
|
export { formatCode, toObject } from "./util.js";
|
package/types/init.d.ts
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import type { Build, MapObjectsToTransforms, MapOperationsToTransforms } from "./extendedTypes.js";
|
|
2
2
|
import type { ContravariantEmpty, OperationMock } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Initialize the data builder.
|
|
5
|
+
* @param schema The plain text of your schema.
|
|
6
|
+
* @param queries List of queries exported from generated types.
|
|
7
|
+
* @param mutations List of mutations exported from generated types.
|
|
8
|
+
* @param subscriptions List of subscriptions exported from generated types.
|
|
9
|
+
* @param types List of types exported from generated types.
|
|
10
|
+
* @param inputs List of types exported from generated types.
|
|
11
|
+
* @param scalars A mapping to generate scalar values. Function values will be invoked with their `__typename`.
|
|
12
|
+
*/
|
|
3
13
|
export declare const init: <Query extends Record<string, {
|
|
4
14
|
data: Record<string, unknown>;
|
|
5
15
|
variables?: Record<string, unknown>;
|
package/types/jest.d.ts
CHANGED
|
@@ -1,7 +1,33 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
+
import { MockedProviderProps } from "@apollo/client/testing";
|
|
2
3
|
import type { OperationMock } from "./types.js";
|
|
3
|
-
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* In older versions of `@apollo/client`, refetches with missing mocks trigger
|
|
6
|
+
* warnings instead of being treated as standard missing mocks.
|
|
7
|
+
* This utility converts those warnings into failures and ensures watch queries
|
|
8
|
+
* are installed for queries with `watch: true`. This must be set when
|
|
9
|
+
* `MockProvider` is mounted.
|
|
10
|
+
*
|
|
11
|
+
* This is not required on modern version of `@apollo/client`.
|
|
12
|
+
*/
|
|
13
|
+
export declare const failRefetchWarnings: (value?: boolean) => boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Allows missing mocks, resulting in tests passing. Usage is intended to ease
|
|
16
|
+
* migration.
|
|
17
|
+
*/
|
|
18
|
+
export declare const allowMissingMocks: (value: true) => true;
|
|
19
|
+
/**
|
|
20
|
+
* Wait for mocks to have been used.
|
|
21
|
+
* @param mock If `undefined`, waits for all mocks. If a number, waits fort he first `mocks` mocks. If a string, waits for all mocks up until and including that mock.
|
|
22
|
+
* @param offset If `mocks` is a string, grabs the `offset`th mock of that name (e.g., the third `getReport` mock)
|
|
23
|
+
*/
|
|
24
|
+
export declare const waitForMocks: (mock?: number | string, offset?: number) => Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* A wrapper for `@apollo/client/testing`, this component will assert all
|
|
27
|
+
* requests have matching mocks and all defined mocks are used unless marked
|
|
28
|
+
* `optional`.
|
|
29
|
+
*/
|
|
30
|
+
export declare const MockedProvider: ({ mocks, stack: renderStack, children, link: passedLink, ...rest }: Omit<MockedProviderProps, "mocks"> & {
|
|
31
|
+
mocks: ReadonlyArray<OperationMock>;
|
|
5
32
|
stack?: string;
|
|
6
|
-
children?: React.ReactNode;
|
|
7
33
|
}) => React.JSX.Element;
|
package/types/proxy.d.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import type { DefinitionNode, DocumentNode, GraphQLError } from "graphql";
|
|
2
2
|
import type { OperationMock, Patch, SimpleOperationMock } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Updates the value to the base value, bypassing default transformations.
|
|
5
|
+
* Useful to clear optional inputs, resulting in `undefined` rather than `null`.
|
|
6
|
+
*/
|
|
7
|
+
export declare const clear: undefined;
|
|
3
8
|
export declare const withGetDefaultPatch: <T>(newGetDefaultPatch: <U>(__typename: string) => Patch<U> | ((prev: U) => Patch<U> | undefined) | undefined, fn: () => T) => T;
|
|
4
9
|
export declare const proxy: <T>(definitions: readonly DefinitionNode[], scalars: Record<string, unknown | ((typename: string) => unknown)>, type: string, ...patches: (Patch<T> | ((prev: T) => Patch<T>))[]) => T;
|
|
5
10
|
export declare const operation: <O extends SimpleOperationMock, Extra = object>(definitions: readonly DefinitionNode[], scalars: Record<string, unknown | ((typename: string) => unknown)>, query: string | DocumentNode, ...patches: (Patch<Omit<O, "error" | "errors">> & {
|
package/types/types.d.ts
CHANGED
|
@@ -28,12 +28,16 @@ export type OperationMock<Data extends Record<string, unknown> = Record<string,
|
|
|
28
28
|
};
|
|
29
29
|
error?: Error;
|
|
30
30
|
stack?: string;
|
|
31
|
+
watch?: boolean;
|
|
32
|
+
optional?: boolean;
|
|
31
33
|
};
|
|
32
34
|
export type SimpleOperationMock<Data extends Record<string, unknown> = Record<string, unknown>, Variables = Record<string, unknown> | never> = {
|
|
33
35
|
data: Data;
|
|
34
36
|
variables?: Variables;
|
|
35
37
|
error?: Error;
|
|
36
38
|
errors?: GraphQLError[];
|
|
39
|
+
watch?: boolean;
|
|
40
|
+
optional?: boolean;
|
|
37
41
|
};
|
|
38
42
|
export type SimpleOperationPatch<Data extends Record<string, unknown> = Record<string, unknown>, Variables = Record<string, unknown> | never> = Patch<{
|
|
39
43
|
data: Data;
|
|
@@ -41,5 +45,7 @@ export type SimpleOperationPatch<Data extends Record<string, unknown> = Record<s
|
|
|
41
45
|
}> & {
|
|
42
46
|
error?: Error;
|
|
43
47
|
errors?: GraphQLError[];
|
|
48
|
+
watch?: boolean;
|
|
49
|
+
optional?: boolean;
|
|
44
50
|
};
|
|
45
51
|
export {};
|