Skip to content

Commit 1210fb0

Browse files
KonoMaxiMaximilian Konowski
and
Maximilian Konowski
authored
Test Suite written in Jest (#33)
Co-authored-by: Maximilian Konowski <m.konowski@itemsnet.de>
1 parent 7179454 commit 1210fb0

File tree

8 files changed

+2850
-151
lines changed

8 files changed

+2850
-151
lines changed

.babelrc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"env": {
3+
"test": {
4+
"plugins": ["@babel/plugin-transform-modules-commonjs"]
5+
}
6+
}
7+
}

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
11
node_modules/
2-
dist/
2+
dist/
3+
coverage/
4+
5+
.vscode

__tests__/fetch_request.js

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
/**
2+
* @jest-environment jsdom
3+
*/
4+
import 'isomorphic-fetch'
5+
import { FetchRequest } from '../src/fetch_request'
6+
import { FetchResponse } from '../src/fetch_response'
7+
8+
jest.mock('../src/lib/utils', () => {
9+
const originalModule = jest.requireActual('../src/lib/utils')
10+
return {
11+
__esModule: true,
12+
...originalModule,
13+
getCookie: jest.fn().mockReturnValue('mock-csrf-token'),
14+
metaContent: jest.fn()
15+
}
16+
})
17+
18+
describe('perform', () => {
19+
test('request is performed with 200', async () => {
20+
const mockResponse = new Response("success!", { status: 200 })
21+
window.fetch = jest.fn().mockResolvedValue(mockResponse)
22+
23+
const testRequest = new FetchRequest("get", "localhost")
24+
const testResponse = await testRequest.perform()
25+
26+
expect(window.fetch).toHaveBeenCalledTimes(1)
27+
expect(window.fetch).toHaveBeenCalledWith("localhost", testRequest.fetchOptions)
28+
expect(testResponse).toStrictEqual(new FetchResponse(mockResponse))
29+
})
30+
31+
test('request is performed with 401', async () => {
32+
const mockResponse = new Response(undefined, { status: 401, headers: {'WWW-Authenticate': 'https://localhost/login'}})
33+
window.fetch = jest.fn().mockResolvedValue(mockResponse)
34+
35+
delete window.location
36+
window.location = new URL('https://www.example.com')
37+
expect(window.location.href).toBe('https://www.example.com/')
38+
39+
const testRequest = new FetchRequest("get", "https://localhost")
40+
expect(testRequest.perform()).rejects.toBe('https://localhost/login')
41+
42+
testRequest.perform().catch(() => {
43+
expect(window.location.href).toBe('https://localhost/login')
44+
})
45+
})
46+
47+
test('turbo stream request automatically calls renderTurboStream', async () => {
48+
const mockResponse = new Response('', { status: 200, headers: { 'Content-Type': 'text/vnd.turbo-stream.html' }})
49+
window.fetch = jest.fn().mockResolvedValue(mockResponse)
50+
jest.spyOn(FetchResponse.prototype, "ok", "get").mockReturnValue(true)
51+
jest.spyOn(FetchResponse.prototype, "isTurboStream", "get").mockReturnValue(true)
52+
const renderSpy = jest.spyOn(FetchResponse.prototype, "renderTurboStream").mockImplementation()
53+
54+
const testRequest = new FetchRequest("get", "localhost")
55+
await testRequest.perform()
56+
57+
expect(renderSpy).toHaveBeenCalledTimes(1)
58+
})
59+
})
60+
61+
test('treat method name case-insensitive', async () => {
62+
const methodNames = [ "gEt", "GeT", "get", "GET"]
63+
for (const methodName of methodNames) {
64+
const testRequest = new FetchRequest(methodName, "localhost")
65+
expect(testRequest.fetchOptions.method).toBe("GET")
66+
}
67+
})
68+
69+
describe('header handling', () => {
70+
const defaultHeaders = {
71+
'X-Requested-With': 'XMLHttpRequest',
72+
'X-CSRF-Token': 'mock-csrf-token',
73+
'Accept': 'text/html, application/xhtml+xml'
74+
}
75+
describe('responseKind', () => {
76+
test('none', async () => {
77+
const defaultRequest = new FetchRequest("get", "localhost")
78+
expect(defaultRequest.fetchOptions.headers)
79+
.toStrictEqual(defaultHeaders)
80+
})
81+
test('html', async () => {
82+
const htmlRequest = new FetchRequest("get", "localhost", { responseKind: 'html' })
83+
expect(htmlRequest.fetchOptions.headers)
84+
.toStrictEqual(defaultHeaders)
85+
})
86+
test('json', async () => {
87+
const jsonRequest = new FetchRequest("get", "localhost", { responseKind: 'json' })
88+
expect(jsonRequest.fetchOptions.headers)
89+
.toStrictEqual({...defaultHeaders, 'Accept' : 'application/json'})
90+
})
91+
test('turbo-stream', async () => {
92+
const turboRequest = new FetchRequest("get", "localhost", { responseKind: 'turbo-stream' })
93+
expect(turboRequest.fetchOptions.headers)
94+
.toStrictEqual({...defaultHeaders, 'Accept' : 'text/vnd.turbo-stream.html, text/html, application/xhtml+xml'})
95+
})
96+
test('invalid', async () => {
97+
const invalidResponseKindRequest = new FetchRequest("get", "localhost", { responseKind: 'exotic' })
98+
expect(invalidResponseKindRequest.fetchOptions.headers)
99+
.toStrictEqual({...defaultHeaders, 'Accept' : '*/*'})
100+
})
101+
})
102+
103+
describe('contentType', () => {
104+
test('is added to headers', () => {
105+
const customRequest = new FetchRequest("get", "localhost/test.json", { contentType: 'any/thing' })
106+
expect(customRequest.fetchOptions.headers)
107+
.toStrictEqual({ ...defaultHeaders, "Content-Type": 'any/thing'})
108+
})
109+
test('is not set by formData', () => {
110+
const formData = new FormData()
111+
formData.append("this", "value")
112+
const formDataRequest = new FetchRequest("get", "localhost", { body: formData })
113+
expect(formDataRequest.fetchOptions.headers)
114+
.toStrictEqual(defaultHeaders)
115+
})
116+
test('is set by file body', () => {
117+
const file = new File(["contenxt"], "file.txt", { type: "text/plain" })
118+
const fileRequest = new FetchRequest("get", "localhost", { body: file })
119+
expect(fileRequest.fetchOptions.headers)
120+
.toStrictEqual({ ...defaultHeaders, "Content-Type": "text/plain"})
121+
})
122+
test('is set by json body', () => {
123+
const jsonRequest = new FetchRequest("get", "localhost", { body: { some: "json"} })
124+
expect(jsonRequest.fetchOptions.headers)
125+
.toStrictEqual({ ...defaultHeaders, "Content-Type": "application/json"})
126+
})
127+
})
128+
129+
test('additional headers are appended', () => {
130+
const request = new FetchRequest("get", "localhost", { contentType: "application/json", headers: { custom: "Header" } })
131+
expect(request.fetchOptions.headers)
132+
.toStrictEqual({ ...defaultHeaders, custom: "Header", "Content-Type": "application/json"})
133+
request.addHeader("test", "header")
134+
expect(request.fetchOptions.headers)
135+
.toStrictEqual({ ...defaultHeaders, custom: "Header", "Content-Type": "application/json", "test": "header"})
136+
})
137+
138+
test('headers win over contentType', () => {
139+
const request = new FetchRequest("get", "localhost", { contentType: "application/json", headers: { "Content-Type": "this/overwrites" } })
140+
expect(request.fetchOptions.headers)
141+
.toStrictEqual({ ...defaultHeaders, "Content-Type": "this/overwrites"})
142+
})
143+
144+
test('serializes JSON to String', () => {
145+
const jsonBody = { some: "json" }
146+
let request
147+
request = new FetchRequest("get", "localhost", { body: jsonBody, contentType: "application/json" })
148+
expect(request.fetchOptions.body).toBe(JSON.stringify(jsonBody))
149+
150+
request = new FetchRequest("get", "localhost", { body: jsonBody })
151+
expect(request.fetchOptions.body).toBe(JSON.stringify(jsonBody))
152+
})
153+
154+
test('not serializes JSON with explicit different contentType', () => {
155+
const jsonBody = { some: "json" }
156+
const request = new FetchRequest("get", "localhost", { body: jsonBody, contentType: "not/json" })
157+
expect(request.fetchOptions.body).toBe(jsonBody)
158+
})
159+
160+
test('set redirect', () => {
161+
let request
162+
const redirectTypes = [ "follow", "error", "manual" ]
163+
for (const redirect of redirectTypes) {
164+
request = new FetchRequest("get", "localhost", { redirect })
165+
expect(request.fetchOptions.redirect).toBe(redirect)
166+
}
167+
168+
request = new FetchRequest("get", "localhost")
169+
expect(request.fetchOptions.redirect).toBe("follow")
170+
171+
// maybe check for valid values and default to follow?
172+
// https://developer.mozilla.org/en-US/docs/Web/API/Request/redirect
173+
request = new FetchRequest("get", "localhost", { redirect: "nonsense"})
174+
expect(request.fetchOptions.redirect).toBe("nonsense")
175+
})
176+
177+
test('sets signal', () => {
178+
let request
179+
request = new FetchRequest("get", "localhost")
180+
expect(request.fetchOptions.signal).toBe(undefined)
181+
182+
request = new FetchRequest("get", "localhost", { signal: "signal"})
183+
expect(request.fetchOptions.signal).toBe("signal")
184+
})
185+
186+
test('has fixed credentials setting which cannot be changed', () => {
187+
let request
188+
request = new FetchRequest("get", "localhost")
189+
expect(request.fetchOptions.credentials).toBe('same-origin')
190+
191+
// has no effect
192+
request = new FetchRequest("get", "localhost", { credentials: "omit"})
193+
expect(request.fetchOptions.credentials).toBe('same-origin')
194+
})
195+
})
196+
197+
describe('query params are parsed', () => {
198+
test('anchors are rejected', () => {
199+
const testRequest = new FetchRequest("post", "localhost/test?a=1&b=2#anchor", { query: { c: 3 } })
200+
expect(testRequest.url).toBe("localhost/test?a=1&b=2&c=3")
201+
// const brokenRequest = new FetchRequest("post", "localhost/test#anchor", { query: { a: 1, b: 2, c: 3 } })
202+
// expect(brokenRequest.url).toBe("localhost/test?a=1&b=2&c=3")
203+
})
204+
test('url and options are merged', () => {
205+
const urlAndOptionRequest = new FetchRequest("post", "localhost/test?a=1&b=2", { query: { c: 3 } })
206+
expect(urlAndOptionRequest.url).toBe("localhost/test?a=1&b=2&c=3")
207+
})
208+
test('only url', () => {
209+
const urlRequest = new FetchRequest("post", "localhost/test?a=1&b=2")
210+
expect(urlRequest.url).toBe("localhost/test?a=1&b=2")
211+
})
212+
test('only options', () => {
213+
const optionRequest = new FetchRequest("post", "localhost/test", { query: { c: 3 } })
214+
expect(optionRequest.url).toBe("localhost/test?c=3")
215+
})
216+
test('options accept formData', () => {
217+
const formData = new FormData()
218+
formData.append("a", 1)
219+
220+
const urlAndOptionRequest = new FetchRequest("post", "localhost/test", { query: formData })
221+
expect(urlAndOptionRequest.url).toBe("localhost/test?a=1")
222+
})
223+
test('options accept urlSearchParams', () => {
224+
const urlSearchParams = new URLSearchParams()
225+
urlSearchParams.append("a", 1)
226+
227+
const urlAndOptionRequest = new FetchRequest("post", "localhost/test", { query: urlSearchParams })
228+
expect(urlAndOptionRequest.url).toBe("localhost/test?a=1")
229+
})
230+
test('handles empty query', () => {
231+
const emptyQueryRequest = new FetchRequest("get", "localhost/test")
232+
expect(emptyQueryRequest.url).toBe("localhost/test")
233+
})
234+
})

0 commit comments

Comments
 (0)