Mirror: The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.
at main 8.5 kB view raw
1// @vitest-environment jsdom 2 3import { expect, describe, it } from 'vitest'; 4import { Kind } from '@0no-co/graphql.web'; 5import { makeOperation } from '../utils/operation'; 6import { queryOperation, mutationOperation } from '../test-utils'; 7import { makeFetchBody, makeFetchURL, makeFetchOptions } from './fetchOptions'; 8 9describe('makeFetchBody', () => { 10 it('creates a fetch body', () => { 11 const body = makeFetchBody(queryOperation); 12 expect(body).toMatchInlineSnapshot(` 13 { 14 "documentId": undefined, 15 "extensions": undefined, 16 "operationName": "getUser", 17 "query": "query getUser($name: String) { 18 user(name: $name) { 19 id 20 firstName 21 lastName 22 } 23 }", 24 "variables": { 25 "name": "Clara", 26 }, 27 } 28 `); 29 }); 30 31 it('omits the query property when APQ is set', () => { 32 const apqOperation = makeOperation(queryOperation.kind, queryOperation); 33 34 apqOperation.extensions = { 35 ...apqOperation.extensions, 36 persistedQuery: { 37 version: 1, 38 sha256Hash: '[test]', 39 }, 40 }; 41 42 expect(makeFetchBody(apqOperation).query).toBe(undefined); 43 44 apqOperation.extensions.persistedQuery!.miss = true; 45 expect(makeFetchBody(apqOperation).query).not.toBe(undefined); 46 }); 47 48 it('omits the query property when query is a persisted document', () => { 49 // A persisted documents is one that carries a `documentId` property and 50 // has no definitions 51 const persistedOperation = makeOperation(queryOperation.kind, { 52 ...queryOperation, 53 query: { 54 kind: Kind.DOCUMENT, 55 definitions: [], 56 documentId: 'TestDocumentId', 57 }, 58 }); 59 60 expect(makeFetchBody(persistedOperation).query).toBe(undefined); 61 expect(makeFetchBody(persistedOperation).documentId).toBe('TestDocumentId'); 62 }); 63}); 64 65describe('makeFetchURL', () => { 66 it('returns the URL by default', () => { 67 const body = makeFetchBody(queryOperation); 68 expect(makeFetchURL(queryOperation, body)).toBe( 69 'http://localhost:3000/graphql' 70 ); 71 }); 72 73 it('returns the URL by default when only a path is provided', () => { 74 const operation = makeOperation(queryOperation.kind, queryOperation, { 75 url: '/graphql', 76 }); 77 const body = makeFetchBody(operation); 78 expect(makeFetchURL(operation, body)).toBe('/graphql'); 79 }); 80 81 it('returns a query parameter URL when GET is preferred', () => { 82 const operation = makeOperation(queryOperation.kind, queryOperation, { 83 ...queryOperation.context, 84 preferGetMethod: true, 85 }); 86 87 const body = makeFetchBody(operation); 88 expect(makeFetchURL(operation, body)).toMatchInlineSnapshot( 89 '"http://localhost:3000/graphql?query=query+getUser%28%24name%3A+String%29+%7B%0A++user%28name%3A+%24name%29+%7B%0A++++id%0A++++firstName%0A++++lastName%0A++%7D%0A%7D&operationName=getUser&variables=%7B%22name%22%3A%22Clara%22%7D"' 90 ); 91 }); 92 93 it('returns a query parameter URL when GET is preferred and only a path is provided', () => { 94 const operation = makeOperation(queryOperation.kind, queryOperation, { 95 url: '/graphql', 96 preferGetMethod: true, 97 }); 98 99 const body = makeFetchBody(operation); 100 expect(makeFetchURL(operation, body)).toMatchInlineSnapshot( 101 '"/graphql?query=query+getUser%28%24name%3A+String%29+%7B%0A++user%28name%3A+%24name%29+%7B%0A++++id%0A++++firstName%0A++++lastName%0A++%7D%0A%7D&operationName=getUser&variables=%7B%22name%22%3A%22Clara%22%7D"' 102 ); 103 }); 104 105 it('returns the URL without query parameters when it exceeds given length', () => { 106 const operation = makeOperation(queryOperation.kind, queryOperation, { 107 ...queryOperation.context, 108 preferGetMethod: true, 109 }); 110 111 operation.variables = { 112 ...operation.variables, 113 test: 'x'.repeat(2048), 114 }; 115 116 const body = makeFetchBody(operation); 117 expect(makeFetchURL(operation, body)).toBe('http://localhost:3000/graphql'); 118 // Resets the `preferGetMethod` field 119 expect(operation.context.preferGetMethod).toBe(false); 120 }); 121 122 it('returns the URL without query parameters for mutations', () => { 123 const operation = makeOperation(mutationOperation.kind, mutationOperation, { 124 ...mutationOperation.context, 125 preferGetMethod: true, 126 }); 127 128 const body = makeFetchBody(operation); 129 expect(makeFetchURL(operation, body)).toBe('http://localhost:3000/graphql'); 130 }); 131}); 132 133describe('makeFetchOptions', () => { 134 it('creates a JSON request by default', () => { 135 const body = makeFetchBody(queryOperation); 136 expect(makeFetchOptions(queryOperation, body)).toMatchInlineSnapshot(` 137 { 138 "body": "{"operationName":"getUser","query":"query getUser($name: String) {\\n user(name: $name) {\\n id\\n firstName\\n lastName\\n }\\n}","variables":{"name":"Clara"}}", 139 "headers": { 140 "accept": "application/graphql-response+json, application/graphql+json, application/json, text/event-stream, multipart/mixed", 141 "content-type": "application/json", 142 }, 143 "method": "POST", 144 } 145 `); 146 }); 147 148 it('handles the Headers object', () => { 149 const headers = new Headers(); 150 headers.append('x-test', 'true'); 151 const operation = makeOperation(queryOperation.kind, queryOperation, { 152 ...queryOperation.context, 153 fetchOptions: { 154 headers, 155 }, 156 }); 157 const body = makeFetchBody(operation); 158 159 expect(makeFetchOptions(operation, body)).toMatchInlineSnapshot(` 160 { 161 "body": "{"operationName":"getUser","query":"query getUser($name: String) {\\n user(name: $name) {\\n id\\n firstName\\n lastName\\n }\\n}","variables":{"name":"Clara"}}", 162 "headers": { 163 "accept": "application/graphql-response+json, application/graphql+json, application/json, text/event-stream, multipart/mixed", 164 "content-type": "application/json", 165 "x-test": "true", 166 }, 167 "method": "POST", 168 } 169 `); 170 }); 171 172 it('handles an Array headers init', () => { 173 const operation = makeOperation(queryOperation.kind, queryOperation, { 174 ...queryOperation.context, 175 fetchOptions: { 176 headers: [['x-test', 'true']], 177 }, 178 }); 179 const body = makeFetchBody(operation); 180 181 expect(makeFetchOptions(operation, body)).toMatchInlineSnapshot(` 182 { 183 "body": "{"operationName":"getUser","query":"query getUser($name: String) {\\n user(name: $name) {\\n id\\n firstName\\n lastName\\n }\\n}","variables":{"name":"Clara"}}", 184 "headers": { 185 "accept": "application/graphql-response+json, application/graphql+json, application/json, text/event-stream, multipart/mixed", 186 "content-type": "application/json", 187 "x-test": "true", 188 }, 189 "method": "POST", 190 } 191 `); 192 }); 193 194 it('creates a GET request when preferred for query operations', () => { 195 const operation = makeOperation(queryOperation.kind, queryOperation, { 196 ...queryOperation.context, 197 preferGetMethod: 'force', 198 }); 199 200 const body = makeFetchBody(operation); 201 expect(makeFetchOptions(operation, body)).toMatchInlineSnapshot(` 202 { 203 "body": undefined, 204 "headers": { 205 "accept": "application/graphql-response+json, application/graphql+json, application/json, text/event-stream, multipart/mixed", 206 }, 207 "method": "GET", 208 } 209 `); 210 }); 211 212 it('creates a POST multipart request when a file is detected', () => { 213 const operation = makeOperation(mutationOperation.kind, mutationOperation); 214 operation.variables = { 215 ...operation.variables, 216 file: new Blob(), 217 }; 218 219 const body = makeFetchBody(operation); 220 const options = makeFetchOptions(operation, body); 221 expect(options).toMatchInlineSnapshot(` 222 { 223 "body": FormData {}, 224 "headers": { 225 "accept": "application/graphql-response+json, application/graphql+json, application/json, text/event-stream, multipart/mixed", 226 }, 227 "method": "POST", 228 } 229 `); 230 231 expect(options.body).toBeInstanceOf(FormData); 232 const form = options.body as FormData; 233 234 expect(JSON.parse(form.get('operations') as string)).toEqual({ 235 ...body, 236 variables: { 237 ...body.variables, 238 file: null, 239 }, 240 }); 241 242 expect(form.get('map')).toMatchInlineSnapshot(`"{"0":["variables.file"]}"`); 243 expect(form.get('0')).toBeInstanceOf(Blob); 244 }); 245});