@operato/graphql 10.29.23 → 10.29.36
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/CHANGELOG.md +17 -0
- package/coverage/lcov-report/index.html +546 -21
- package/coverage/lcov.info +42783 -366
- package/dist/src/graphql-env.js +4 -1
- package/dist/src/graphql-env.js.map +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/upload-progress.d.ts +24 -0
- package/dist/src/upload-progress.js +71 -0
- package/dist/src/upload-progress.js.map +1 -0
- package/dist/test/upload-progress.test.d.ts +1 -0
- package/dist/test/upload-progress.test.js +130 -0
- package/dist/test/upload-progress.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -4
- package/test/upload-progress.test.ts +156 -0
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@operato/graphql",
|
|
3
3
|
"description": "Webcomponent graphql-client following open-wc recommendations",
|
|
4
4
|
"author": "heartyoh@hatiolab.com",
|
|
5
|
-
"version": "10.29.
|
|
5
|
+
"version": "10.29.36",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "dist/src/index.js",
|
|
8
8
|
"module": "dist/src/index.js",
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"./gql-context.js": "./dist/src/gql-context.js",
|
|
24
24
|
"./graphql-client.js": "./dist/src/graphql-client.js",
|
|
25
25
|
"./graphql-env.js": "./dist/src/graphql-env.js",
|
|
26
|
+
"./upload-progress.js": "./dist/src/upload-progress.js",
|
|
26
27
|
"./json-to-graphql-query.js": "./dist/src/json-to-graphql-query.js",
|
|
27
28
|
"./ox-input-graphql": "./dist/src/input/ox-input-graphql"
|
|
28
29
|
},
|
|
@@ -59,8 +60,8 @@
|
|
|
59
60
|
},
|
|
60
61
|
"dependencies": {
|
|
61
62
|
"@apollo/client": "^3.6.9",
|
|
62
|
-
"@operato/i18n": "^10.29.
|
|
63
|
-
"@operato/utils": "^10.29.
|
|
63
|
+
"@operato/i18n": "^10.29.27",
|
|
64
|
+
"@operato/utils": "^10.29.36",
|
|
64
65
|
"apollo-upload-client": "^18.0.0",
|
|
65
66
|
"graphql-tag": "^2.12.6",
|
|
66
67
|
"graphql-ws": "^5.14.0",
|
|
@@ -124,5 +125,5 @@
|
|
|
124
125
|
"prettier --write"
|
|
125
126
|
]
|
|
126
127
|
},
|
|
127
|
-
"gitHead": "
|
|
128
|
+
"gitHead": "90c8bc8593e5d68745aeddd45752fab83c50cc16"
|
|
128
129
|
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { expect } from '@open-wc/testing'
|
|
2
|
+
import gql from 'graphql-tag'
|
|
3
|
+
|
|
4
|
+
import { execute } from '@apollo/client/core/index.js'
|
|
5
|
+
|
|
6
|
+
import { getClientLink } from '../src/graphql-env.js'
|
|
7
|
+
import { uploadContext, uploadFetch } from '../src/upload-progress.js'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Upload progress.
|
|
11
|
+
*
|
|
12
|
+
* ## Why this is tested
|
|
13
|
+
*
|
|
14
|
+
* Only XHR can tell how much of a body went out, so an upload that asks for progress leaves `fetch`.
|
|
15
|
+
* What must hold:
|
|
16
|
+
*
|
|
17
|
+
* ```
|
|
18
|
+
* unchanged a request that does not ask for progress — every query, every plain upload — still
|
|
19
|
+
* goes through fetch with the same arguments; no XHR is made
|
|
20
|
+
* progress an upload that asks gets fractions up to 1, and the same Response fetch would give
|
|
21
|
+
* credentials the session cookie still goes (credentials: include → withCredentials)
|
|
22
|
+
* abort the link's abort stops the XHR and fails like an aborted fetch
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
class FakeXHR {
|
|
27
|
+
static made: FakeXHR[] = []
|
|
28
|
+
|
|
29
|
+
method = ''
|
|
30
|
+
url = ''
|
|
31
|
+
withCredentials = false
|
|
32
|
+
headers: Record<string, string> = {}
|
|
33
|
+
body: any
|
|
34
|
+
aborted = false
|
|
35
|
+
status = 200
|
|
36
|
+
statusText = 'OK'
|
|
37
|
+
responseText = '{"data":{"attach":{"id":"1"}}}'
|
|
38
|
+
upload: { onprogress?: (e: any) => void } = {}
|
|
39
|
+
onload?: () => void
|
|
40
|
+
onerror?: () => void
|
|
41
|
+
onabort?: () => void
|
|
42
|
+
|
|
43
|
+
constructor() {
|
|
44
|
+
FakeXHR.made.push(this)
|
|
45
|
+
}
|
|
46
|
+
open(method: string, url: string) {
|
|
47
|
+
this.method = method
|
|
48
|
+
this.url = url
|
|
49
|
+
}
|
|
50
|
+
setRequestHeader(name: string, value: string) {
|
|
51
|
+
this.headers[name] = value
|
|
52
|
+
}
|
|
53
|
+
getAllResponseHeaders() {
|
|
54
|
+
return 'content-type: application/json\r\nx-one: 1\r\n'
|
|
55
|
+
}
|
|
56
|
+
send(body: any) {
|
|
57
|
+
this.body = body
|
|
58
|
+
setTimeout(() => {
|
|
59
|
+
this.upload.onprogress?.({ lengthComputable: true, loaded: 50, total: 100 })
|
|
60
|
+
this.onload?.()
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
abort() {
|
|
64
|
+
this.aborted = true
|
|
65
|
+
this.onabort?.()
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
describe('upload progress', () => {
|
|
70
|
+
const realXHR = window.XMLHttpRequest
|
|
71
|
+
const realFetch = window.fetch
|
|
72
|
+
let fetched: any[]
|
|
73
|
+
|
|
74
|
+
beforeEach(() => {
|
|
75
|
+
FakeXHR.made = []
|
|
76
|
+
fetched = []
|
|
77
|
+
;(window as any).XMLHttpRequest = FakeXHR
|
|
78
|
+
window.fetch = (async (...args: any[]) => {
|
|
79
|
+
fetched.push(args)
|
|
80
|
+
return new Response('{"data":{"one":1}}', { status: 200, headers: { 'content-type': 'application/json' } })
|
|
81
|
+
}) as any
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
afterEach(() => {
|
|
85
|
+
window.XMLHttpRequest = realXHR
|
|
86
|
+
window.fetch = realFetch
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('leaves a request without progress to fetch, as it was', async () => {
|
|
90
|
+
const init = { method: 'POST', body: 'x', credentials: 'include' as RequestCredentials }
|
|
91
|
+
await uploadFetch('/graphql', init)
|
|
92
|
+
|
|
93
|
+
expect(FakeXHR.made.length).to.equal(0)
|
|
94
|
+
expect(fetched.length).to.equal(1)
|
|
95
|
+
expect(fetched[0][0]).to.equal('/graphql')
|
|
96
|
+
expect(fetched[0][1]).to.deep.equal(init)
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it('reports progress and answers with the response fetch would give', async () => {
|
|
100
|
+
const fractions: number[] = []
|
|
101
|
+
const response = await uploadFetch('/graphql', {
|
|
102
|
+
method: 'POST',
|
|
103
|
+
credentials: 'include',
|
|
104
|
+
headers: { 'Apollo-Require-Preflight': 'true' },
|
|
105
|
+
body: 'form',
|
|
106
|
+
onUploadProgress: (fraction: number) => fractions.push(fraction)
|
|
107
|
+
} as any)
|
|
108
|
+
|
|
109
|
+
const xhr = FakeXHR.made[0]
|
|
110
|
+
expect(xhr.withCredentials, 'the session cookie stays home').to.equal(true)
|
|
111
|
+
expect(xhr.headers['apollo-require-preflight']).to.equal('true')
|
|
112
|
+
expect(xhr.body).to.equal('form')
|
|
113
|
+
expect(fractions).to.deep.equal([0.5, 1])
|
|
114
|
+
|
|
115
|
+
expect(response.status).to.equal(200)
|
|
116
|
+
expect(response.headers.get('x-one')).to.equal('1')
|
|
117
|
+
expect(await response.json()).to.deep.equal({ data: { attach: { id: '1' } } })
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it('stops the upload when aborted', async () => {
|
|
121
|
+
const controller = new AbortController()
|
|
122
|
+
const sent = uploadFetch('/graphql', { method: 'POST', body: 'form', signal: controller.signal, onUploadProgress: () => {} } as any)
|
|
123
|
+
|
|
124
|
+
controller.abort()
|
|
125
|
+
|
|
126
|
+
const error = await sent.then(
|
|
127
|
+
() => null,
|
|
128
|
+
e => e
|
|
129
|
+
)
|
|
130
|
+
expect(FakeXHR.made[0].aborted).to.equal(true)
|
|
131
|
+
expect(error?.name).to.equal('AbortError')
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
it('sends a query through fetch and an upload with progress through XHR', async () => {
|
|
135
|
+
const run = (operation: any) =>
|
|
136
|
+
new Promise((resolve, reject) => execute(getClientLink(), operation).subscribe({ next: resolve, error: reject }))
|
|
137
|
+
|
|
138
|
+
await run({ query: gql`query { one }` })
|
|
139
|
+
expect(fetched.length, 'a query left fetch').to.equal(1)
|
|
140
|
+
expect(FakeXHR.made.length, 'a query went through XHR').to.equal(0)
|
|
141
|
+
|
|
142
|
+
const fractions: number[] = []
|
|
143
|
+
const file = new File(['abc'], 'a.pdf', { type: 'application/pdf' })
|
|
144
|
+
const result: any = await run({
|
|
145
|
+
query: gql`mutation ($file: Upload!) { attach(file: $file) { id } }`,
|
|
146
|
+
variables: { file },
|
|
147
|
+
context: uploadContext(fraction => fractions.push(fraction))
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
expect(fetched.length, 'the upload went through fetch').to.equal(1)
|
|
151
|
+
expect(FakeXHR.made.length).to.equal(1)
|
|
152
|
+
expect(FakeXHR.made[0].body).to.be.instanceOf(FormData)
|
|
153
|
+
expect(fractions).to.deep.equal([0.5, 1])
|
|
154
|
+
expect(result.data.attach.id).to.equal('1')
|
|
155
|
+
})
|
|
156
|
+
})
|