Better UX for backend errors (fix #64)
This commit is contained in:
parent
ef50c635c3
commit
50dbd4ee52
|
|
@ -1,7 +1,7 @@
|
|||
import Vue from 'vue'
|
||||
import { emit } from './event'
|
||||
import { queryStringify } from './utils'
|
||||
import { showAjaxError } from './notify'
|
||||
import { showAjaxError, showModal } from './notify'
|
||||
|
||||
class HTTPError extends Error {
|
||||
response: Response
|
||||
|
|
@ -30,26 +30,27 @@ export async function walkFetch(request: Request): Promise<any> {
|
|||
|
||||
try {
|
||||
const response = await fetch(request)
|
||||
const body = response.headers.get('Content-Type') === 'application/json'
|
||||
? await response.json()
|
||||
: await response.text()
|
||||
if (response.ok) {
|
||||
return response.headers.get('Content-Type') === 'application/json'
|
||||
? response.json()
|
||||
: response.text()
|
||||
return body
|
||||
}
|
||||
|
||||
// Process validation errors from Laravel.
|
||||
if (response.status === 422) {
|
||||
const { errors }: {
|
||||
message: string,
|
||||
errors: { [field: string]: string[] }
|
||||
} = await response.json()
|
||||
const { errors }: { message: string, errors: { [field: string]: string[] } } = body
|
||||
return {
|
||||
code: 1,
|
||||
message: Object.keys(errors).map(field => errors[field][0])[0],
|
||||
}
|
||||
} else if (response.status === 403) {
|
||||
showModal(body.message, undefined, 'warning')
|
||||
return
|
||||
}
|
||||
|
||||
const res = response.clone()
|
||||
throw new HTTPError(await response.text(), res)
|
||||
throw new HTTPError(body.message || body, res)
|
||||
} catch (error) {
|
||||
emit('fetchError', error)
|
||||
showAjaxError(error)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import Vue from 'vue'
|
||||
import * as net from '@/scripts/net'
|
||||
import { on } from '@/scripts/event'
|
||||
import { showAjaxError } from '@/scripts/notify'
|
||||
import { showAjaxError, showModal } from '@/scripts/notify'
|
||||
|
||||
jest.mock('@/scripts/notify')
|
||||
|
||||
|
|
@ -77,9 +77,16 @@ test('low level fetch', async () => {
|
|||
.mockRejectedValueOnce(new Error('network'))
|
||||
.mockResolvedValueOnce({
|
||||
ok: false,
|
||||
headers: new Map(),
|
||||
text: () => Promise.resolve('404'),
|
||||
clone: () => ({}),
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
ok: false,
|
||||
json: () => Promise.resolve({ message: 'error' }),
|
||||
headers: new Map([['Content-Type', 'application/json']]),
|
||||
clone: () => ({}),
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json,
|
||||
|
|
@ -106,21 +113,35 @@ test('low level fetch', async () => {
|
|||
expect(stub.mock.calls[1][0]).toHaveProperty('message', '404')
|
||||
expect(stub.mock.calls[1][0]).toHaveProperty('response')
|
||||
|
||||
await net.walkFetch(request as Request)
|
||||
expect(showAjaxError.mock.calls[2][0]).toBeInstanceOf(Error)
|
||||
expect(stub.mock.calls[2][0]).toHaveProperty('message', 'error')
|
||||
expect(stub.mock.calls[2][0]).toHaveProperty('response')
|
||||
|
||||
await net.walkFetch(request as Request)
|
||||
expect(json).toBeCalled()
|
||||
|
||||
expect(await net.walkFetch(request as Request)).toBe('text')
|
||||
})
|
||||
|
||||
test('process Laravel validation errors', async () => {
|
||||
window.fetch = jest.fn().mockResolvedValue({
|
||||
status: 422,
|
||||
json() {
|
||||
return Promise.resolve({
|
||||
errors: { name: ['required'] },
|
||||
})
|
||||
},
|
||||
})
|
||||
test('process backend errors', async () => {
|
||||
window.fetch = jest.fn()
|
||||
.mockResolvedValueOnce({
|
||||
status: 422,
|
||||
headers: new Map([['Content-Type', 'application/json']]),
|
||||
json() {
|
||||
return Promise.resolve({
|
||||
errors: { name: ['required'] },
|
||||
})
|
||||
},
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
status: 403,
|
||||
headers: new Map([['Content-Type', 'application/json']]),
|
||||
json() {
|
||||
return Promise.resolve({ message: 'forbidden' })
|
||||
},
|
||||
})
|
||||
|
||||
const result: {
|
||||
code: number,
|
||||
|
|
@ -128,6 +149,9 @@ test('process Laravel validation errors', async () => {
|
|||
} = await net.walkFetch({ headers: new Headers() } as Request)
|
||||
expect(result.code).toBe(1)
|
||||
expect(result.message).toBe('required')
|
||||
|
||||
await net.walkFetch({ headers: new Headers() } as Request)
|
||||
expect(showModal).toBeCalledWith('forbidden', undefined, 'warning')
|
||||
})
|
||||
|
||||
test('inject to Vue instance', () => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user