Skip to content

Commit

Permalink
Escape regex tokens within field names (#38)
Browse files Browse the repository at this point in the history
* Escape regex tokens within field names

* Update utils.js

* fixing smells one report at a time...

* Updated regex with one from MDN

* Fixed failing test
  • Loading branch information
PerfectPixel authored and erikras committed Nov 20, 2019
1 parent 7ee1ef8 commit 9f8f74f
Show file tree
Hide file tree
Showing 9 changed files with 410 additions and 4 deletions.
3 changes: 2 additions & 1 deletion src/insert.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// @flow
import type { MutableState, Mutator, Tools } from 'final-form'
import moveFieldState from './moveFieldState'
import { escapeRegexTokens } from './utils'

const insert: Mutator<any> = (
[name, index, value]: any[],
Expand All @@ -16,7 +17,7 @@ const insert: Mutator<any> = (
const backup = { ...state.fields }

// now we have increment any higher indexes
const pattern = new RegExp(`^${name}\\[(\\d+)\\](.*)`)
const pattern = new RegExp(`^${escapeRegexTokens(name)}\\[(\\d+)\\](.*)`)

// we need to increment high indices first so
// lower indices won't overlap
Expand Down
80 changes: 80 additions & 0 deletions src/insert.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -162,4 +162,84 @@ describe('insert', () => {
}
})
})

it('should increment other field data from the specified index (nested arrays)', () => {
const array = ['a', 'b', 'c', 'd']
// implementation of changeValue taken directly from Final Form
const changeValue = (state, name, mutate) => {
const before = getIn(state.formState.values, name)
const after = mutate(before)
state.formState.values = setIn(state.formState.values, name, after) || {}
}
const resetFieldState = name => {
state.fields[name].touched = false
}
const state = {
formState: {
values: {
foo: [array]
}
},
fields: {
'foo[0][0]': {
name: 'foo[0][0]',
touched: true,
error: 'A Error'
},
'foo[0][1]': {
name: 'foo[0][1]',
touched: true,
error: 'B Error'
},
'foo[0][2]': {
name: 'foo[0][2]',
touched: true,
error: 'C Error'
},
'foo[0][3]': {
name: 'foo[0][3]',
touched: false,
error: 'D Error'
}
}
}
const returnValue = insert(['foo[0]', 1, 'NEWVALUE'], state, {
changeValue,
resetFieldState
})
expect(returnValue).toBeUndefined()
expect(state.formState.values.foo).not.toBe(array) // copied
expect(state).toEqual({
formState: {
values: {
foo: [['a', 'NEWVALUE', 'b', 'c', 'd']]
}
},
fields: {
'foo[0][0]': {
name: 'foo[0][0]',
touched: true,
error: 'A Error'
},
'foo[0][2]': {
name: 'foo[0][2]',
touched: true,
error: 'B Error',
lastFieldState: undefined
},
'foo[0][3]': {
name: 'foo[0][3]',
touched: true,
error: 'C Error',
lastFieldState: undefined
},
'foo[0][4]': {
name: 'foo[0][4]',
touched: false,
error: 'D Error',
lastFieldState: undefined
}
}
})
})
})
5 changes: 4 additions & 1 deletion src/pop.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// @flow
import type { MutableState, Mutator, Tools } from 'final-form'
import { escapeRegexTokens } from './utils'

const pop: Mutator<any> = (
[name]: any[],
Expand All @@ -21,7 +22,9 @@ const pop: Mutator<any> = (

// now we have to remove any subfields for our index,
if (removedIndex !== undefined) {
const pattern = new RegExp(`^${name}\\[${removedIndex}].*`)
const pattern = new RegExp(
`^${escapeRegexTokens(name)}\\[${removedIndex}].*`
)
Object.keys(state.fields).forEach(key => {
if (pattern.test(key)) {
delete state.fields[key]
Expand Down
77 changes: 77 additions & 0 deletions src/pop.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -195,4 +195,81 @@ describe('pop', () => {
}
})
})

it('should pop value off the end of array and return it (nested arrays)', () => {
const array = ['a', 'b', 'c', 'd']
// implementation of changeValue taken directly from Final Form
const changeValue = (state, name, mutate) => {
const before = getIn(state.formState.values, name)
const after = mutate(before)
state.formState.values = setIn(state.formState.values, name, after) || {}
}
const state = {
formState: {
values: {
foo: [array],
anotherField: 42
}
},
fields: {
'foo[0][0]': {
name: 'foo[0][0]',
touched: true,
error: 'A Error'
},
'foo[0][1]': {
name: 'foo[0][1]',
touched: false,
error: 'B Error'
},
'foo[0][2]': {
name: 'foo[0][2]',
touched: true,
error: 'C Error'
},
'foo[0][3]': {
name: 'foo[0][3]',
touched: false,
error: 'D Error'
},
anotherField: {
name: 'anotherField',
touched: false
}
}
}
const returnValue = pop(['foo[0]'], state, { changeValue })
expect(returnValue).toBe('d')
expect(Array.isArray(state.formState.values.foo)).toBe(true)
expect(state.formState.values.foo).not.toBe(array) // copied
expect(state).toEqual({
formState: {
values: {
foo: [['a', 'b', 'c']],
anotherField: 42
}
},
fields: {
'foo[0][0]': {
name: 'foo[0][0]',
touched: true,
error: 'A Error'
},
'foo[0][1]': {
name: 'foo[0][1]',
touched: false,
error: 'B Error'
},
'foo[0][2]': {
name: 'foo[0][2]',
touched: true,
error: 'C Error'
},
anotherField: {
name: 'anotherField',
touched: false
}
}
})
})
})
3 changes: 2 additions & 1 deletion src/remove.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// @flow
import type { MutableState, Mutator, Tools } from 'final-form'
import moveFieldState from './moveFieldState'
import { escapeRegexTokens } from './utils'

const remove: Mutator<any> = (
[name, index]: any[],
Expand All @@ -17,7 +18,7 @@ const remove: Mutator<any> = (

// now we have to remove any subfields for our index,
// and decrement all higher indexes.
const pattern = new RegExp(`^${name}\\[(\\d+)\\](.*)`)
const pattern = new RegExp(`^${escapeRegexTokens(name)}\\[(\\d+)\\](.*)`)
const backup = { ...state, fields: { ...state.fields } }
Object.keys(state.fields).forEach(key => {
const tokens = pattern.exec(key)
Expand Down
112 changes: 112 additions & 0 deletions src/remove.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,118 @@ describe('remove', () => {
}
})
})


it('should remove value from the specified index, and return it (nested arrays)', () => {
const array = ['a', 'b', 'c', 'd']
// implementation of changeValue taken directly from Final Form
const changeValue = (state, name, mutate) => {
const before = getIn(state.formState.values, name)
const after = mutate(before)
state.formState.values = setIn(state.formState.values, name, after) || {}
}
function blur0() {}
function change0() {}
function focus0() {}
function blur1() {}
function change1() {}
function focus1() {}
function blur2() {}
function change2() {}
function focus2() {}
function blur3() {}
function change3() {}
function focus3() {}
const state = {
formState: {
values: {
foo: [array],
anotherField: 42
}
},
fields: {
'foo[0][0]': {
name: 'foo[0][0]',
blur: blur0,
change: change0,
focus: focus0,
touched: true,
error: 'A Error'
},
'foo[0][1]': {
name: 'foo[0][1]',
blur: blur1,
change: change1,
focus: focus1,
touched: false,
error: 'B Error'
},
'foo[0][2]': {
name: 'foo[0][2]',
blur: blur2,
change: change2,
focus: focus2,
touched: true,
error: 'C Error'
},
'foo[0][3]': {
name: 'foo[0][3]',
blur: blur3,
change: change3,
focus: focus3,
touched: false,
error: 'D Error'
},
anotherField: {
name: 'anotherField',
touched: false
}
}
}
const returnValue = remove(['foo[0]', 1], state, { changeValue })
expect(returnValue).toBe('b')
expect(state.formState.values.foo).not.toBe(array) // copied
expect(state).toEqual({
formState: {
values: {
foo: [['a', 'c', 'd']],
anotherField: 42
}
},
fields: {
'foo[0][0]': {
name: 'foo[0][0]',
blur: blur0,
change: change0,
focus: focus0,
touched: true,
error: 'A Error'
},
'foo[0][1]': {
name: 'foo[0][1]',
blur: blur1,
change: change1,
focus: focus1,
touched: true,
error: 'C Error',
lastFieldState: undefined
},
'foo[0][2]': {
name: 'foo[0][2]',
blur: blur2,
change: change2,
focus: focus2,
touched: false,
error: 'D Error',
lastFieldState: undefined
},
anotherField: {
name: 'anotherField',
touched: false
}
}
})
})

it('should remove value from the specified index, and handle new fields', () => {
const array = ['a', { key: 'val' }]
Expand Down
3 changes: 2 additions & 1 deletion src/removeBatch.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// @flow
import type { MutableState, Mutator, Tools } from 'final-form'
import moveFieldState from './moveFieldState'
import { escapeRegexTokens } from './utils'

const countBelow = (array, value) =>
array.reduce((count, item) => (item < value ? count + 1 : count), 0)
Expand Down Expand Up @@ -38,7 +39,7 @@ const removeBatch: Mutator<any> = (

// now we have to remove any subfields for our indexes,
// and decrement all higher indexes.
const pattern = new RegExp(`^${name}\\[(\\d+)\\](.*)`)
const pattern = new RegExp(`^${escapeRegexTokens(name)}\\[(\\d+)\\](.*)`)
const newState = { ...state, fields: {} }
Object.keys(state.fields).forEach(key => {
const tokens = pattern.exec(key)
Expand Down
Loading

0 comments on commit 9f8f74f

Please sign in to comment.