Skip to content

Test Suite written in Jest #33

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Sep 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"env": {
"test": {
"plugins": ["@babel/plugin-transform-modules-commonjs"]
}
}
}
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
node_modules/
dist/
dist/
coverage/

.vscode
234 changes: 234 additions & 0 deletions __tests__/fetch_request.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
/**
* @jest-environment jsdom
*/
import 'isomorphic-fetch'
import { FetchRequest } from '../src/fetch_request'
import { FetchResponse } from '../src/fetch_response'

jest.mock('../src/lib/utils', () => {
const originalModule = jest.requireActual('../src/lib/utils')
return {
__esModule: true,
...originalModule,
getCookie: jest.fn().mockReturnValue('mock-csrf-token'),
metaContent: jest.fn()
}
})

describe('perform', () => {
test('request is performed with 200', async () => {
const mockResponse = new Response("success!", { status: 200 })
window.fetch = jest.fn().mockResolvedValue(mockResponse)

const testRequest = new FetchRequest("get", "localhost")
const testResponse = await testRequest.perform()

expect(window.fetch).toHaveBeenCalledTimes(1)
expect(window.fetch).toHaveBeenCalledWith("localhost", testRequest.fetchOptions)
expect(testResponse).toStrictEqual(new FetchResponse(mockResponse))
})

test('request is performed with 401', async () => {
const mockResponse = new Response(undefined, { status: 401, headers: {'WWW-Authenticate': 'https://localhost/login'}})
window.fetch = jest.fn().mockResolvedValue(mockResponse)

delete window.location
window.location = new URL('https://www.example.com')
expect(window.location.href).toBe('https://www.example.com/')

const testRequest = new FetchRequest("get", "https://localhost")
expect(testRequest.perform()).rejects.toBe('https://localhost/login')

testRequest.perform().catch(() => {
expect(window.location.href).toBe('https://localhost/login')
})
})

test('turbo stream request automatically calls renderTurboStream', async () => {
const mockResponse = new Response('', { status: 200, headers: { 'Content-Type': 'text/vnd.turbo-stream.html' }})
window.fetch = jest.fn().mockResolvedValue(mockResponse)
jest.spyOn(FetchResponse.prototype, "ok", "get").mockReturnValue(true)
jest.spyOn(FetchResponse.prototype, "isTurboStream", "get").mockReturnValue(true)
const renderSpy = jest.spyOn(FetchResponse.prototype, "renderTurboStream").mockImplementation()

const testRequest = new FetchRequest("get", "localhost")
await testRequest.perform()

expect(renderSpy).toHaveBeenCalledTimes(1)
})
})

test('treat method name case-insensitive', async () => {
const methodNames = [ "gEt", "GeT", "get", "GET"]
for (const methodName of methodNames) {
const testRequest = new FetchRequest(methodName, "localhost")
expect(testRequest.fetchOptions.method).toBe("GET")
}
})

describe('header handling', () => {
const defaultHeaders = {
'X-Requested-With': 'XMLHttpRequest',
'X-CSRF-Token': 'mock-csrf-token',
'Accept': 'text/html, application/xhtml+xml'
}
describe('responseKind', () => {
test('none', async () => {
const defaultRequest = new FetchRequest("get", "localhost")
expect(defaultRequest.fetchOptions.headers)
.toStrictEqual(defaultHeaders)
})
test('html', async () => {
const htmlRequest = new FetchRequest("get", "localhost", { responseKind: 'html' })
expect(htmlRequest.fetchOptions.headers)
.toStrictEqual(defaultHeaders)
})
test('json', async () => {
const jsonRequest = new FetchRequest("get", "localhost", { responseKind: 'json' })
expect(jsonRequest.fetchOptions.headers)
.toStrictEqual({...defaultHeaders, 'Accept' : 'application/json'})
})
test('turbo-stream', async () => {
const turboRequest = new FetchRequest("get", "localhost", { responseKind: 'turbo-stream' })
expect(turboRequest.fetchOptions.headers)
.toStrictEqual({...defaultHeaders, 'Accept' : 'text/vnd.turbo-stream.html, text/html, application/xhtml+xml'})
})
test('invalid', async () => {
const invalidResponseKindRequest = new FetchRequest("get", "localhost", { responseKind: 'exotic' })
expect(invalidResponseKindRequest.fetchOptions.headers)
.toStrictEqual({...defaultHeaders, 'Accept' : '*/*'})
})
})

describe('contentType', () => {
test('is added to headers', () => {
const customRequest = new FetchRequest("get", "localhost/test.json", { contentType: 'any/thing' })
expect(customRequest.fetchOptions.headers)
.toStrictEqual({ ...defaultHeaders, "Content-Type": 'any/thing'})
})
test('is not set by formData', () => {
const formData = new FormData()
formData.append("this", "value")
const formDataRequest = new FetchRequest("get", "localhost", { body: formData })
expect(formDataRequest.fetchOptions.headers)
.toStrictEqual(defaultHeaders)
})
test('is set by file body', () => {
const file = new File(["contenxt"], "file.txt", { type: "text/plain" })
const fileRequest = new FetchRequest("get", "localhost", { body: file })
expect(fileRequest.fetchOptions.headers)
.toStrictEqual({ ...defaultHeaders, "Content-Type": "text/plain"})
})
test('is set by json body', () => {
const jsonRequest = new FetchRequest("get", "localhost", { body: { some: "json"} })
expect(jsonRequest.fetchOptions.headers)
.toStrictEqual({ ...defaultHeaders, "Content-Type": "application/json"})
})
})

test('additional headers are appended', () => {
const request = new FetchRequest("get", "localhost", { contentType: "application/json", headers: { custom: "Header" } })
expect(request.fetchOptions.headers)
.toStrictEqual({ ...defaultHeaders, custom: "Header", "Content-Type": "application/json"})
request.addHeader("test", "header")
expect(request.fetchOptions.headers)
.toStrictEqual({ ...defaultHeaders, custom: "Header", "Content-Type": "application/json", "test": "header"})
})

test('headers win over contentType', () => {
const request = new FetchRequest("get", "localhost", { contentType: "application/json", headers: { "Content-Type": "this/overwrites" } })
expect(request.fetchOptions.headers)
.toStrictEqual({ ...defaultHeaders, "Content-Type": "this/overwrites"})
})

test('serializes JSON to String', () => {
const jsonBody = { some: "json" }
let request
request = new FetchRequest("get", "localhost", { body: jsonBody, contentType: "application/json" })
expect(request.fetchOptions.body).toBe(JSON.stringify(jsonBody))

request = new FetchRequest("get", "localhost", { body: jsonBody })
expect(request.fetchOptions.body).toBe(JSON.stringify(jsonBody))
})

test('not serializes JSON with explicit different contentType', () => {
const jsonBody = { some: "json" }
const request = new FetchRequest("get", "localhost", { body: jsonBody, contentType: "not/json" })
expect(request.fetchOptions.body).toBe(jsonBody)
})

test('set redirect', () => {
let request
const redirectTypes = [ "follow", "error", "manual" ]
for (const redirect of redirectTypes) {
request = new FetchRequest("get", "localhost", { redirect })
expect(request.fetchOptions.redirect).toBe(redirect)
}

request = new FetchRequest("get", "localhost")
expect(request.fetchOptions.redirect).toBe("follow")

// maybe check for valid values and default to follow?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't the default being checked in the prior check? About checking valid values, I don't think that's necessary, since Fetch API will raise an error if the user provides an invalid value. At least I think that we would be testing Fetch API behavior and not Request.JS behavior. Having a test that proves that it's possible to override the default value provided by request.js is enough, like the test bellow (even if that isn't a valid value)

// https://developer.mozilla.org/en-US/docs/Web/API/Request/redirect
request = new FetchRequest("get", "localhost", { redirect: "nonsense"})
expect(request.fetchOptions.redirect).toBe("nonsense")
})

test('sets signal', () => {
let request
request = new FetchRequest("get", "localhost")
expect(request.fetchOptions.signal).toBe(undefined)

request = new FetchRequest("get", "localhost", { signal: "signal"})
expect(request.fetchOptions.signal).toBe("signal")
})

test('has fixed credentials setting which cannot be changed', () => {
let request
request = new FetchRequest("get", "localhost")
expect(request.fetchOptions.credentials).toBe('same-origin')

// has no effect
request = new FetchRequest("get", "localhost", { credentials: "omit"})
expect(request.fetchOptions.credentials).toBe('same-origin')
})
})

describe('query params are parsed', () => {
test('anchors are rejected', () => {
const testRequest = new FetchRequest("post", "localhost/test?a=1&b=2#anchor", { query: { c: 3 } })
expect(testRequest.url).toBe("localhost/test?a=1&b=2&c=3")
// const brokenRequest = new FetchRequest("post", "localhost/test#anchor", { query: { a: 1, b: 2, c: 3 } })
// expect(brokenRequest.url).toBe("localhost/test?a=1&b=2&c=3")
})
test('url and options are merged', () => {
const urlAndOptionRequest = new FetchRequest("post", "localhost/test?a=1&b=2", { query: { c: 3 } })
expect(urlAndOptionRequest.url).toBe("localhost/test?a=1&b=2&c=3")
})
test('only url', () => {
const urlRequest = new FetchRequest("post", "localhost/test?a=1&b=2")
expect(urlRequest.url).toBe("localhost/test?a=1&b=2")
})
test('only options', () => {
const optionRequest = new FetchRequest("post", "localhost/test", { query: { c: 3 } })
expect(optionRequest.url).toBe("localhost/test?c=3")
})
test('options accept formData', () => {
const formData = new FormData()
formData.append("a", 1)

const urlAndOptionRequest = new FetchRequest("post", "localhost/test", { query: formData })
expect(urlAndOptionRequest.url).toBe("localhost/test?a=1")
})
test('options accept urlSearchParams', () => {
const urlSearchParams = new URLSearchParams()
urlSearchParams.append("a", 1)

const urlAndOptionRequest = new FetchRequest("post", "localhost/test", { query: urlSearchParams })
expect(urlAndOptionRequest.url).toBe("localhost/test?a=1")
})
test('handles empty query', () => {
const emptyQueryRequest = new FetchRequest("get", "localhost/test")
expect(emptyQueryRequest.url).toBe("localhost/test")
})
})
Loading