Skip to content

Commit

Permalink
fix: improve SubmissionError detection
Browse files Browse the repository at this point in the history
Now event after obfuscation, or importing SubmissionForm
from a different context, `isSubmissionError` will return true
only for `SubmissionError`

Closes #4641
  • Loading branch information
iamandrewluca committed Apr 10, 2020
1 parent 638dc44 commit e705b7c
Show file tree
Hide file tree
Showing 9 changed files with 160 additions and 458 deletions.
4 changes: 2 additions & 2 deletions .size-limit.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
[
{
"path": "lib/index.js",
"limit": "29 kb"
"limit": "30 kb"
},
{
"path": "es/index.js",
"limit": "26 kb"
"limit": "30 kb"
},
{
"path": "dist/redux-form.js",
Expand Down
11 changes: 9 additions & 2 deletions src/SubmissionError.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
// @flow
import ExtendableError from 'es6-error'

class SubmissionError extends ExtendableError {
const __FLAG__ = '@@redux-form/submission-error-flag'

export class SubmissionError extends ExtendableError {
/** @private */
static __FLAG__ = __FLAG__

constructor(errors: Object) {
super('Submit Validation Failed')
this.errors = errors
}
}

export default SubmissionError
export function isSubmissionError(error: any): boolean {
return (error && error.constructor && error.constructor.__FLAG__ === __FLAG__) === true
}
29 changes: 8 additions & 21 deletions src/__tests__/Form.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import plainExpectations from '../structure/plain/__tests__/expectations'
import immutable from '../structure/immutable'
import immutableExpectations from '../structure/immutable/__tests__/expectations'

import SubmissionError from '../SubmissionError'
import { SubmissionError } from '../SubmissionError'
import actions from '../actions'

const {
Expand All @@ -26,8 +26,7 @@ const {
updateSyncErrors
} = actions

const propsAtNthRender = (componentSpy, callNumber) =>
componentSpy.mock.calls[callNumber][0]
const propsAtNthRender = (componentSpy, callNumber) => componentSpy.mock.calls[callNumber][0]

const describeForm = (name, structure, combineReducers, setup) => {
const reduxForm = createReduxForm(structure)
Expand Down Expand Up @@ -273,14 +272,10 @@ const describeForm = (name, structure, combineReducers, setup) => {
expect(logger.mock.calls[callIndex++][1]).toEqual(clearSubmit('testForm'))

// check that touch action was dispatched
expect(logger.mock.calls[callIndex++][1]).toEqual(
touch('testForm', 'foo')
)
expect(logger.mock.calls[callIndex++][1]).toEqual(touch('testForm', 'foo'))

// check that setSubmitFailed action was dispatched
expect(logger.mock.calls[callIndex++][1]).toEqual(
setSubmitFailed('testForm', 'foo')
)
expect(logger.mock.calls[callIndex++][1]).toEqual(setSubmitFailed('testForm', 'foo'))

// form rerendered twice, once with submit trigger, and then after submit failure
expect(formRender).toHaveBeenCalledTimes(4)
Expand All @@ -296,9 +291,7 @@ const describeForm = (name, structure, combineReducers, setup) => {
)

// check that updateSyncErrors action was dispatched
expect(logger.mock.calls[callIndex++][1]).toEqual(
updateSyncErrors('testForm', {})
)
expect(logger.mock.calls[callIndex++][1]).toEqual(updateSyncErrors('testForm', {}))

// rerendered once to flip dirty flag, and again to flip invalid flag
expect(formRender).toHaveBeenCalledTimes(6)
Expand All @@ -318,14 +311,10 @@ const describeForm = (name, structure, combineReducers, setup) => {
expect(logger.mock.calls[callIndex++][1]).toEqual(clearSubmit('testForm'))

// check that touch action was dispatched
expect(logger.mock.calls[callIndex++][1]).toEqual(
touch('testForm', 'foo')
)
expect(logger.mock.calls[callIndex++][1]).toEqual(touch('testForm', 'foo'))

// check that submit succeeded action was dispatched
expect(logger.mock.calls[callIndex++][1]).toEqual(
setSubmitSucceeded('testForm')
)
expect(logger.mock.calls[callIndex++][1]).toEqual(setSubmitSucceeded('testForm'))

// check no additional actions dispatched
expect(logger).toHaveBeenCalledTimes(callIndex)
Expand All @@ -339,9 +328,7 @@ const describeForm = (name, structure, combineReducers, setup) => {
})
}

describeForm('Form.plain', plain, plainCombineReducers, () =>
expect.extend(plainExpectations)
)
describeForm('Form.plain', plain, plainCombineReducers, () => expect.extend(plainExpectations))
describeForm('Form.immutable', immutable, immutableCombineReducers, () =>
expect.extend(immutableExpectations)
)
19 changes: 19 additions & 0 deletions src/__tests__/SubmissionError.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// @flow

import ExtendableError from 'es6-error'
import { isSubmissionError, SubmissionError } from '../SubmissionError'

class FakeSubmissionError {
static __FLAG__ = '@@redux-form/submission-error-flag'
}

describe('isSubmissionError', () => {
it('should return `true` only when argument is instance `SubmissionError`', () => {
expect(isSubmissionError(new SubmissionError({}))).toBe(true)
expect(isSubmissionError(new FakeSubmissionError())).toBe(true)
expect(isSubmissionError(new Error())).toBe(false)
expect(isSubmissionError(new ExtendableError())).toBe(false)
expect(isSubmissionError({})).toBe(false)
expect(isSubmissionError()).toBe(false)
})
})
74 changes: 19 additions & 55 deletions src/__tests__/handleSubmit.spec.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import handleSubmit from '../handleSubmit'
import SubmissionError from '../SubmissionError'
import { SubmissionError } from '../SubmissionError'
import { noop } from 'lodash'

describe('handleSubmit', () => {
Expand Down Expand Up @@ -52,10 +52,7 @@ describe('handleSubmit', () => {
values
}

const result = handleSubmit(submit, props, false, asyncValidate, [
'foo',
'baz'
])
const result = handleSubmit(submit, props, false, asyncValidate, ['foo', 'baz'])

expect(asyncValidate).not.toHaveBeenCalled()
expect(submit).not.toHaveBeenCalled()
Expand Down Expand Up @@ -87,9 +84,7 @@ describe('handleSubmit', () => {
values
}

expect(
handleSubmit(submit, props, true, asyncValidate, ['foo', 'baz'])
).toBe(69)
expect(handleSubmit(submit, props, true, asyncValidate, ['foo', 'baz'])).toBe(69)

expect(submit).toHaveBeenCalledWith(values, dispatch, props)
expect(startSubmit).not.toHaveBeenCalled()
Expand All @@ -108,9 +103,7 @@ describe('handleSubmit', () => {
const touch = jest.fn()
const setSubmitFailed = jest.fn()
const setSubmitSucceeded = jest.fn()
const asyncValidate = jest
.fn()
.mockImplementation(() => Promise.resolve(values))
const asyncValidate = jest.fn().mockImplementation(() => Promise.resolve(values))
const props = {
dispatch,
startSubmit,
Expand Down Expand Up @@ -147,9 +140,7 @@ describe('handleSubmit', () => {
const touch = jest.fn()
const setSubmitFailed = jest.fn()
const setSubmitSucceeded = jest.fn()
const asyncValidate = jest
.fn()
.mockImplementation(() => Promise.resolve(values))
const asyncValidate = jest.fn().mockImplementation(() => Promise.resolve(values))
const props = {
dispatch,
onSubmitFail,
Expand Down Expand Up @@ -192,9 +183,7 @@ describe('handleSubmit', () => {
const setSubmitFailed = jest.fn()
const setSubmitSucceeded = jest.fn()
const asyncErrors = { foo: 'async error' }
const asyncValidate = jest
.fn()
.mockImplementation(() => Promise.reject(asyncErrors))
const asyncValidate = jest.fn().mockImplementation(() => Promise.reject(asyncErrors))
const props = {
dispatch,
startSubmit,
Expand Down Expand Up @@ -241,10 +230,7 @@ describe('handleSubmit', () => {
values
}

return handleSubmit(submit, props, true, asyncValidate, [
'foo',
'baz'
]).then(result => {
return handleSubmit(submit, props, true, asyncValidate, ['foo', 'baz']).then(result => {
expect(result).toBe(69)
expect(asyncValidate).toHaveBeenCalledWith()
expect(submit).toHaveBeenCalledWith(values, dispatch, props)
Expand Down Expand Up @@ -276,10 +262,7 @@ describe('handleSubmit', () => {
values
}

return handleSubmit(submit, props, true, asyncValidate, [
'foo',
'baz'
]).then(result => {
return handleSubmit(submit, props, true, asyncValidate, ['foo', 'baz']).then(result => {
expect(result).toBe(69)
expect(asyncValidate).toHaveBeenCalledWith()
expect(submit).toHaveBeenCalledWith(values, dispatch, props)
Expand All @@ -296,9 +279,7 @@ describe('handleSubmit', () => {
const submitErrors = { foo: 'submit error' }
const submit = jest
.fn()
.mockImplementation(() =>
Promise.reject(new SubmissionError(submitErrors))
)
.mockImplementation(() => Promise.reject(new SubmissionError(submitErrors)))
const dispatch = noop
const startSubmit = jest.fn()
const stopSubmit = jest.fn()
Expand All @@ -316,10 +297,7 @@ describe('handleSubmit', () => {
values
}

return handleSubmit(submit, props, true, asyncValidate, [
'foo',
'baz'
]).then(error => {
return handleSubmit(submit, props, true, asyncValidate, ['foo', 'baz']).then(error => {
expect(error).toBe(submitErrors)
expect(asyncValidate).toHaveBeenCalledWith()
expect(submit).toHaveBeenCalledWith(values, dispatch, props)
Expand All @@ -334,9 +312,7 @@ describe('handleSubmit', () => {
it('should not set errors if rejected value not a SubmissionError', () => {
const values = { foo: 'bar', baz: 42 }
const submitErrors = { foo: 'submit error' }
const submit = jest
.fn()
.mockImplementation(() => Promise.reject(submitErrors))
const submit = jest.fn().mockImplementation(() => Promise.reject(submitErrors))
const dispatch = noop
const startSubmit = jest.fn()
const stopSubmit = jest.fn()
Expand Down Expand Up @@ -377,9 +353,7 @@ describe('handleSubmit', () => {
const submitErrors = { foo: 'submit error' }
const submit = jest
.fn()
.mockImplementation(() =>
Promise.reject(new SubmissionError(submitErrors))
)
.mockImplementation(() => Promise.reject(new SubmissionError(submitErrors)))
const dispatch = noop
const startSubmit = jest.fn()
const stopSubmit = jest.fn()
Expand All @@ -397,10 +371,7 @@ describe('handleSubmit', () => {
values
}

return handleSubmit(submit, props, true, asyncValidate, [
'foo',
'baz'
]).then(error => {
return handleSubmit(submit, props, true, asyncValidate, ['foo', 'baz']).then(error => {
expect(error).toBe(submitErrors)
expect(asyncValidate).toHaveBeenCalledWith()
expect(submit).toHaveBeenCalledWith(values, dispatch, props)
Expand Down Expand Up @@ -456,19 +427,17 @@ describe('handleSubmit', () => {
values
}

expect(() =>
handleSubmit(submit, props, true, asyncValidate, ['foo', 'baz'])
).toThrow('spline reticulation failed')
expect(() => handleSubmit(submit, props, true, asyncValidate, ['foo', 'baz'])).toThrow(
'spline reticulation failed'
)
expect(submit).toHaveBeenCalled()
})

it('should not swallow async errors', () => {
const values = { foo: 'bar', baz: 42 }
const submit = jest
.fn()
.mockImplementation(() =>
Promise.reject(new Error('spline reticulation failed'))
)
.mockImplementation(() => Promise.reject(new Error('spline reticulation failed')))
const startSubmit = jest.fn()
const stopSubmit = jest.fn()
const touch = jest.fn()
Expand Down Expand Up @@ -518,10 +487,7 @@ describe('handleSubmit', () => {
values
}

const result = handleSubmit(submit, props, false, asyncValidate, [
'foo',
'baz'
])
const result = handleSubmit(submit, props, false, asyncValidate, ['foo', 'baz'])

expect(asyncValidate).not.toHaveBeenCalled()
expect(submit).not.toHaveBeenCalled()
Expand Down Expand Up @@ -557,9 +523,7 @@ describe('handleSubmit', () => {
values
}

expect(
handleSubmit(submit, props, true, asyncValidate, ['foo', 'baz'])
).toBe(mockAction)
expect(handleSubmit(submit, props, true, asyncValidate, ['foo', 'baz'])).toBe(mockAction)

expect(submit).toHaveBeenCalledWith(values, dispatch, props)
expect(dispatch).toHaveBeenCalledWith(mockAction)
Expand Down
Loading

0 comments on commit e705b7c

Please sign in to comment.