@hyper-fetch/graphql 7.5.2 → 7.5.3

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.
@@ -0,0 +1,15 @@
1
+ import gql from "graphql-tag";
2
+
3
+ export const loginMutation = gql`
4
+ mutation Login($username: String!, $password: String!) {
5
+ login(username: $username, password: $password) {
6
+ username
7
+ password
8
+ }
9
+ }
10
+ `;
11
+
12
+ export type LoginMutationVariables = {
13
+ username: string;
14
+ password: string;
15
+ };
@@ -0,0 +1,24 @@
1
+ import gql from "graphql-tag";
2
+
3
+ export const getUserQueryString = `
4
+ query GetUser {
5
+ username {
6
+ username
7
+ firstName
8
+ }
9
+ }
10
+ `;
11
+
12
+ export const getUserQuery = gql`
13
+ query GetUser {
14
+ username {
15
+ username
16
+ firstName
17
+ }
18
+ }
19
+ `;
20
+
21
+ export type GetUserQueryResponse = {
22
+ username: string;
23
+ firstName: string;
24
+ };
@@ -0,0 +1,169 @@
1
+ import { Client, getErrorMessage } from "@hyper-fetch/core";
2
+ import { createGraphqlMockingServer } from "@hyper-fetch/testing";
3
+
4
+ import { GraphqlAdapter } from "adapter";
5
+ import { GetUserQueryResponse, getUserQuery, getUserQueryString } from "../../constants/queries.constants";
6
+ import { LoginMutationVariables, loginMutation } from "../../constants/mutations.constants";
7
+
8
+ const { startServer, stopServer, resetMocks, mockRequest } = createGraphqlMockingServer();
9
+
10
+ describe("Graphql Adapter [ Browser ]", () => {
11
+ let client = new Client({ url: "https://shared-base-url/graphql" }).setAdapter(GraphqlAdapter());
12
+ let request = client.createRequest<{ response: GetUserQueryResponse }>()({ endpoint: getUserQuery });
13
+ let mutation = client.createRequest<{ response: GetUserQueryResponse; payload: LoginMutationVariables }>()({
14
+ endpoint: loginMutation,
15
+ });
16
+
17
+ beforeAll(() => {
18
+ startServer();
19
+ });
20
+
21
+ beforeEach(() => {
22
+ client = new Client({ url: "https://shared-base-url/graphql" }).setAdapter(GraphqlAdapter());
23
+ request = client.createRequest<{ response: GetUserQueryResponse }>()({ endpoint: getUserQuery });
24
+ mutation = client.createRequest<{ response: GetUserQueryResponse; payload: LoginMutationVariables }>()({
25
+ endpoint: loginMutation,
26
+ });
27
+
28
+ resetMocks();
29
+ jest.resetAllMocks();
30
+ });
31
+
32
+ afterAll(() => {
33
+ stopServer();
34
+ });
35
+
36
+ it("should make a request and return success data with status", async () => {
37
+ const expected = mockRequest(request, { data: { username: "prc", firstName: "Maciej" } });
38
+
39
+ const { data, error, status, extra } = await request.send();
40
+
41
+ expect(expected.data).toStrictEqual(data);
42
+ expect(status).toBe(200);
43
+ expect(error).toBe(null);
44
+ expect(extra).toStrictEqual({
45
+ headers: { "content-type": "application/json", "content-length": "48" },
46
+ extensions: {},
47
+ });
48
+ });
49
+
50
+ it("should make a request with string endpoint", async () => {
51
+ const expected = mockRequest(request, { data: { username: "prc", firstName: "Maciej" } });
52
+
53
+ const { data, error, status, extra } = await client.createRequest()({ endpoint: getUserQueryString }).send({});
54
+
55
+ expect(expected.data).toStrictEqual(data);
56
+ expect(status).toBe(200);
57
+ expect(error).toBe(null);
58
+ expect(extra).toStrictEqual({
59
+ headers: { "content-type": "application/json", "content-length": "48" },
60
+ extensions: {},
61
+ });
62
+ });
63
+
64
+ it("should make a request and return error data with status", async () => {
65
+ const expected = mockRequest(request, { status: 400 });
66
+
67
+ const { data, error, status, extra } = await request.send();
68
+
69
+ expect(data).toBe(null);
70
+ expect(status).toBe(400);
71
+ expect(error).toStrictEqual(expected.errors);
72
+ expect(extra).toStrictEqual({
73
+ headers: { "content-type": "application/json", "content-length": "42" },
74
+ extensions: {},
75
+ });
76
+ });
77
+
78
+ it("should allow to make mutation request", async () => {
79
+ const expected = mockRequest(
80
+ mutation.setPayload({
81
+ username: "Kacper",
82
+ password: "Kacper1234",
83
+ }),
84
+ { data: { username: "prc", firstName: "Maciej" } },
85
+ );
86
+
87
+ const { data, error, status, extra } = await mutation
88
+ .setPayload({
89
+ username: "Kacper",
90
+ password: "Kacper1234",
91
+ })
92
+ .send();
93
+
94
+ expect(expected.data).toStrictEqual(data);
95
+ expect(status).toBe(200);
96
+ expect(error).toBe(null);
97
+ expect(extra).toStrictEqual({
98
+ headers: { "content-type": "application/json", "content-length": "48" },
99
+ extensions: {},
100
+ });
101
+ });
102
+
103
+ it("should allow to cancel request and return error", async () => {
104
+ mockRequest(request, { delay: 5 });
105
+
106
+ setTimeout(() => {
107
+ request.abort();
108
+ }, 2);
109
+
110
+ const { data, error } = await request.send();
111
+
112
+ expect(data).toBe(null);
113
+ expect(error).toStrictEqual([getErrorMessage("abort")]);
114
+ });
115
+
116
+ // it("should not throw when XMLHttpRequest is not available on window", async () => {
117
+ // const expected = mockRequest(request, { delay: 20 });
118
+ // const xml = window.XMLHttpRequest;
119
+ // window.XMLHttpRequest = undefined as any;
120
+
121
+ // const { data, error, status, extra } = await request.send();
122
+
123
+ // expect(expected.data).toStrictEqual(data);
124
+ // expect(status).toBe(200);
125
+ // expect(error).toBe(null);
126
+ // expect(extra).toMatchObject({
127
+ // headers: { "content-type": "application/json", "content-length": "11" },
128
+ // extensions: {},
129
+ // });
130
+ // window.XMLHttpRequest = xml;
131
+ // });
132
+
133
+ it("should allow to set options", async () => {
134
+ const xml = window.XMLHttpRequest;
135
+ let instance: null | XMLHttpRequest = null;
136
+ class ExtendedXml extends XMLHttpRequest {
137
+ constructor() {
138
+ super();
139
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
140
+ instance = this;
141
+ }
142
+ }
143
+
144
+ window.XMLHttpRequest = ExtendedXml;
145
+
146
+ const timeoutRequest = request.setOptions({ timeout: 50 });
147
+ mockRequest(timeoutRequest, { delay: 20 });
148
+ await timeoutRequest.send();
149
+ expect(instance!.timeout).toBe(50);
150
+
151
+ window.XMLHttpRequest = xml;
152
+ });
153
+
154
+ it("should handle undefined response data as null", async () => {
155
+ mockRequest(request, { data: null });
156
+ const { data } = await request.send();
157
+ expect(data).toBe(null);
158
+ });
159
+
160
+ it("should use default error message when errors are null", async () => {
161
+ mockRequest(request, {
162
+ status: 400,
163
+ error: null,
164
+ });
165
+
166
+ const { error } = await request.send();
167
+ expect(error).toStrictEqual([getErrorMessage()]);
168
+ });
169
+ });
@@ -0,0 +1,241 @@
1
+ /**
2
+ * @jest-environment node
3
+ */
4
+ import { Client, getErrorMessage } from "@hyper-fetch/core";
5
+ import { createGraphqlMockingServer } from "@hyper-fetch/testing";
6
+ import https from "https";
7
+
8
+ import { GraphqlAdapter, GraphqlMethod } from "../../../src/adapter";
9
+ import { GetUserQueryResponse, getUserQuery, getUserQueryString } from "../../constants/queries.constants";
10
+ import { LoginMutationVariables, loginMutation } from "../../constants/mutations.constants";
11
+
12
+ const { startServer, stopServer, resetMocks, mockRequest } = createGraphqlMockingServer();
13
+
14
+ describe("Graphql Adapter [ Server ]", () => {
15
+ const requestId = "test";
16
+ const requestCopy = https.request;
17
+ let clientHttp = new Client({ url: "http://shared-base-url/graphql" }).setAdapter(GraphqlAdapter()).setDebug(true);
18
+ let client = new Client({ url: "https://shared-base-url/graphql" }).setAdapter(GraphqlAdapter()).setDebug(true);
19
+ let requestHttp = clientHttp.createRequest<{ response: GetUserQueryResponse }>()({ endpoint: getUserQuery });
20
+ let request = client.createRequest<{ response: GetUserQueryResponse }>()({ endpoint: getUserQuery });
21
+ let mutation = client.createRequest<{ response: GetUserQueryResponse; payload: LoginMutationVariables }>()({
22
+ endpoint: loginMutation,
23
+ });
24
+
25
+ beforeAll(() => {
26
+ startServer();
27
+ });
28
+
29
+ beforeEach(() => {
30
+ clientHttp = new Client({ url: "http://shared-base-url/graphql" }).setAdapter(GraphqlAdapter());
31
+ client = new Client({ url: "https://shared-base-url/graphql" }).setAdapter(GraphqlAdapter());
32
+ requestHttp = clientHttp.createRequest<{ response: GetUserQueryResponse }>()({ endpoint: getUserQuery });
33
+ request = client.createRequest<{ response: GetUserQueryResponse }>()({ endpoint: getUserQuery });
34
+ mutation = client.createRequest<{ response: GetUserQueryResponse; payload: LoginMutationVariables }>()({
35
+ endpoint: loginMutation,
36
+ });
37
+ client.appManager.isBrowser = false;
38
+ });
39
+
40
+ afterEach(() => {
41
+ client.clear();
42
+ jest.resetAllMocks();
43
+ jest.clearAllMocks();
44
+ jest.restoreAllMocks();
45
+ https.globalAgent.destroy();
46
+ resetMocks();
47
+ });
48
+
49
+ afterAll(() => {
50
+ stopServer();
51
+ });
52
+
53
+ it("should pick correct adapter and not throw", async () => {
54
+ mockRequest(request);
55
+ client.appManager.isBrowser = true;
56
+
57
+ const adapter = GraphqlAdapter();
58
+
59
+ expect(adapter).toBeDefined();
60
+ });
61
+
62
+ it("should pick http module", async () => {
63
+ mockRequest(requestHttp);
64
+ clientHttp.requestManager.addAbortController(requestHttp.abortKey, requestId);
65
+
66
+ const res = await clientHttp.adapter.fetch(
67
+ clientHttp.createRequest<{ response: GetUserQueryResponse }>()({ endpoint: getUserQuery }),
68
+ requestId,
69
+ );
70
+
71
+ expect(res.data).toBeDefined();
72
+ });
73
+
74
+ it("should pick https module", async () => {
75
+ mockRequest(request);
76
+ client.requestManager.addAbortController(request.abortKey, requestId);
77
+
78
+ const res = await clientHttp.adapter.fetch(request, requestId);
79
+
80
+ expect(res.data).toBeDefined();
81
+ });
82
+
83
+ it("should make a request and return success data with status", async () => {
84
+ const expected = mockRequest(request, { delay: 10, data: { username: "prc", firstName: "Maciej" } });
85
+
86
+ const { data, error, status, extra } = await request.send();
87
+
88
+ expect(error).toBe(null);
89
+ expect(expected.data).toStrictEqual(data);
90
+ expect(status).toBe(200);
91
+ expect(extra).toMatchObject({
92
+ headers: { "content-type": "application/json", "content-length": "48" },
93
+ extensions: {},
94
+ });
95
+ });
96
+
97
+ it("should make a request with string endpoint", async () => {
98
+ const expected = mockRequest(request, { data: { username: "prc", firstName: "Maciej" } });
99
+
100
+ const { data, error, status, extra } = await client.createRequest()({ endpoint: getUserQueryString }).send({});
101
+
102
+ expect(expected.data).toStrictEqual(data);
103
+ expect(status).toBe(200);
104
+ expect(error).toBe(null);
105
+ expect(extra).toMatchObject({
106
+ headers: { "content-type": "application/json", "content-length": "48" },
107
+ extensions: {},
108
+ });
109
+ });
110
+ it("should make a get request with string endpoint", async () => {
111
+ const req = client.createRequest<{ response: GetUserQueryResponse }>()({
112
+ endpoint: getUserQueryString,
113
+ method: GraphqlMethod.GET,
114
+ });
115
+ const expected = mockRequest(req, { data: { username: "prc", firstName: "Maciej" } });
116
+
117
+ const { data, error, status, extra } = await req.send({});
118
+
119
+ expect(expected.data).toStrictEqual(data);
120
+ expect(status).toBe(200);
121
+ expect(error).toBe(null);
122
+ expect(extra).toMatchObject({
123
+ headers: { "content-type": "application/json", "content-length": "48" },
124
+ extensions: {},
125
+ });
126
+ });
127
+
128
+ it("should make a request and return error data with status", async () => {
129
+ const expected = mockRequest(request, { status: 400 });
130
+
131
+ const { data, error, status, extra } = await request.send();
132
+
133
+ expect(data).toBe(null);
134
+ expect(status).toBe(400);
135
+ expect(error).toStrictEqual(expected.errors);
136
+ expect(extra).toMatchObject({
137
+ headers: { "content-type": "application/json", "content-length": "42" },
138
+ extensions: {},
139
+ });
140
+ });
141
+
142
+ it("should allow to make mutation request", async () => {
143
+ const req = mutation.setPayload({
144
+ username: "Kacper",
145
+ password: "Kacper1234",
146
+ });
147
+ const expected = mockRequest(req, { data: { username: "prc", firstName: "Maciej" } });
148
+
149
+ const { data, error, status, extra } = await req.send();
150
+
151
+ expect(data).toStrictEqual(expected.data);
152
+ expect(status).toBe(200);
153
+ expect(error).toBe(null);
154
+ expect(extra).toMatchObject({
155
+ headers: { "content-type": "application/json", "content-length": "48" },
156
+ extensions: {},
157
+ });
158
+ });
159
+
160
+ it("should handle undefined response data as null", async () => {
161
+ mockRequest(request, { data: null });
162
+ const { data } = await request.send();
163
+ expect(data).toBe(null);
164
+ });
165
+
166
+ it("should use default error message when errors are null", async () => {
167
+ mockRequest(request, {
168
+ status: 400,
169
+ error: null,
170
+ });
171
+
172
+ const { error } = await request.send();
173
+ expect(error).toStrictEqual([getErrorMessage()]);
174
+ });
175
+
176
+ it("should allow to set options", async () => {
177
+ let receivedOptions: any;
178
+
179
+ jest.spyOn(https, "request").mockImplementation((url, options, callback) => {
180
+ receivedOptions = options;
181
+ return requestCopy(options, callback);
182
+ });
183
+
184
+ const timeoutRequest = request.setOptions({ timeout: 10 });
185
+ mockRequest(timeoutRequest, { delay: 20 });
186
+
187
+ await timeoutRequest.send();
188
+
189
+ expect(receivedOptions.timeout).toBe(10);
190
+ });
191
+
192
+ // it("should allow to calculate payload size", async () => {
193
+ // let receivedOptions: any;
194
+
195
+ // jest.spyOn(https, "request").mockImplementation((url, options, callback) => {
196
+ // receivedOptions = options;
197
+ // return requestCopy(options, callback);
198
+ // });
199
+ // mockRequest(mutation);
200
+
201
+ // await mutation.send({
202
+ // payload: {
203
+ // username: "Kacper",
204
+ // password: "Kacper1234",
205
+ // },
206
+ // });
207
+
208
+ // expect(receivedOptions.headers["Content-Length"]).toBeGreaterThan(0);
209
+ // });
210
+
211
+ // it("should allow to calculate Buffer size", async () => {
212
+ // let receivedOptions: any;
213
+
214
+ // jest.spyOn(https, "request").mockImplementation((url, options, callback) => {
215
+ // receivedOptions = options;
216
+ // return requestCopy(options, callback);
217
+ // });
218
+ // mockRequest(mutation);
219
+
220
+ // const buffer = Buffer.from("test");
221
+
222
+ // await mutation.send({
223
+ // payload: buffer as any,
224
+ // });
225
+
226
+ // expect(receivedOptions.headers["Content-Length"]).toBeGreaterThan(0);
227
+ // });
228
+
229
+ it("should allow to cancel request and return error", async () => {
230
+ mockRequest(request, { delay: 5 });
231
+
232
+ setTimeout(() => {
233
+ request.abort();
234
+ }, 2);
235
+
236
+ const { data, error } = await request.send();
237
+
238
+ expect(data).toBe(null);
239
+ expect(error).toStrictEqual([getErrorMessage("abort")]);
240
+ });
241
+ });
@@ -0,0 +1,53 @@
1
+ import { Client } from "@hyper-fetch/core";
2
+
3
+ import { GraphqlMethod, getRequestValues, GraphqlAdapter } from "adapter";
4
+ import { getUserQuery, getUserQueryString } from "../../constants/queries.constants";
5
+
6
+ describe("Graphql Adapter [ Utils ]", () => {
7
+ let client = new Client({ url: "https://shared-base-url/graphql" }).setAdapter(GraphqlAdapter());
8
+ let request = client.createRequest<{ response: any; payload: any }>()({
9
+ endpoint: getUserQuery,
10
+ method: GraphqlMethod.POST,
11
+ });
12
+ let requestGet = client.createRequest<{ response: any; payload: any }>()({
13
+ endpoint: getUserQueryString,
14
+ method: GraphqlMethod.GET,
15
+ });
16
+
17
+ beforeEach(() => {
18
+ client = new Client({ url: "https://shared-base-url/graphql" }).setAdapter(GraphqlAdapter());
19
+ request = client.createRequest<{ response: any; payload: any }>()({
20
+ endpoint: getUserQuery,
21
+ method: GraphqlMethod.POST,
22
+ });
23
+ requestGet = client.createRequest<{ response: any; payload: any }>()({
24
+ endpoint: getUserQueryString,
25
+ method: GraphqlMethod.GET,
26
+ });
27
+
28
+ jest.resetAllMocks();
29
+ jest.clearAllMocks();
30
+ jest.restoreAllMocks();
31
+ });
32
+
33
+ describe("When using getRequestValues", () => {
34
+ it("should generate values", async () => {
35
+ const { fullUrl, payload } = getRequestValues(request);
36
+
37
+ expect(fullUrl).toBe("https://shared-base-url/graphql");
38
+ expect(payload).toBe('{"query":"query GetUser {\\n username {\\n username\\n firstName\\n }\\n}"}');
39
+ expect(request.method).toBe("POST");
40
+ expect(request.endpoint).toBeString();
41
+ });
42
+ it("should generate values for GET", async () => {
43
+ const { fullUrl, payload } = getRequestValues(requestGet);
44
+
45
+ expect(fullUrl).toBe(
46
+ "https://shared-base-url/graphql?query=%0Aquery%20GetUser%20%7B%0A%20%20username%20%7B%0A%20%20%20%20username%0A%20%20%20%20firstName%0A%20%20%7D%0A%7D%0A",
47
+ );
48
+ expect(payload).toBeNull();
49
+ expect(requestGet.method).toBe("GET");
50
+ expect(requestGet.endpoint).toBeString();
51
+ });
52
+ });
53
+ });
@@ -0,0 +1 @@
1
+ import "jest-extended";
@@ -0,0 +1 @@
1
+ import "jest-extended";
@@ -0,0 +1,9 @@
1
+ export const testCallbacksExecution = (callbacks: Array<jest.Mock<any, any>>) => {
2
+ callbacks.forEach((spyFn, index) => {
3
+ const nextSpy = callbacks[index + 1];
4
+ if (nextSpy) {
5
+ expect(spyFn).toHaveBeenCalledBefore(nextSpy);
6
+ }
7
+ expect(spyFn).toHaveBeenCalledTimes(1);
8
+ });
9
+ };
@@ -0,0 +1,2 @@
1
+ export * from "./callbacks.shared";
2
+ export * from "./progress.shared";
@@ -0,0 +1,39 @@
1
+ import { RequestInstance, getProgressValue, getRequestEta } from "request";
2
+
3
+ export const testProgressSpy = (props: {
4
+ spy: () => void;
5
+ requestId: string;
6
+ request: RequestInstance;
7
+ startTimestamp: number;
8
+ progressTimestamp?: number;
9
+ total?: number;
10
+ loaded?: number;
11
+ timeLeft?: number;
12
+ }) => {
13
+ const { spy, requestId, request, startTimestamp, total, loaded, timeLeft, progressTimestamp } = props;
14
+
15
+ const totalValue = total ?? 1;
16
+ const loadedValue = loaded ?? 0;
17
+
18
+ const estimatedTime = getRequestEta(new Date(startTimestamp), new Date(progressTimestamp || startTimestamp), {
19
+ total: totalValue,
20
+ loaded: loadedValue,
21
+ }).timeLeft;
22
+ const timeToSend = loadedValue ? estimatedTime : null;
23
+
24
+ expect(spy).toHaveBeenCalledTimes(1);
25
+ expect(spy).toHaveBeenCalledWith(
26
+ {
27
+ loaded: loadedValue,
28
+ progress: getProgressValue({ total: totalValue, loaded: loadedValue }),
29
+ sizeLeft: totalValue - loadedValue,
30
+ startTimestamp,
31
+ timeLeft: timeLeft ?? timeToSend,
32
+ total: totalValue,
33
+ },
34
+ {
35
+ request,
36
+ requestId,
37
+ },
38
+ );
39
+ };
@@ -0,0 +1,13 @@
1
+ {
2
+ "compilerOptions": {
3
+ "strict": true,
4
+ "noEmit": true,
5
+ "baseUrl": "../src",
6
+ "moduleResolution": "node",
7
+ "esModuleInterop": true,
8
+ "allowSyntheticDefaultImports": true,
9
+ "paths": {
10
+ "@hyper-fetch/graphql": ["../src"]
11
+ }
12
+ }
13
+ }
package/jest.config.ts ADDED
@@ -0,0 +1,6 @@
1
+ import type { Config } from "@jest/types";
2
+
3
+ import { getJestConfig } from "../../jest.config";
4
+
5
+ const config: Config.InitialOptions = getJestConfig();
6
+ export default config;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyper-fetch/graphql",
3
- "version": "7.5.2",
3
+ "version": "7.5.3",
4
4
  "description": "Simple graphql adapter",
5
5
  "author": "Maciej Pyrc <maciekpyrc@gmail.com>, Kacper Skawina <kacper.skawina@gmail.com>",
6
6
  "homepage": "https://hyperfetch.bettertyped.com/",
package/project.json ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "name": "graphql",
3
+ "$schema": "../../node_modules/nx/schemas/project-schema.json"
4
+ }