Skip to content
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
46 changes: 26 additions & 20 deletions packages/form-core/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,11 +135,13 @@ export function deleteBy(obj: any, _path: any) {
return doDelete(obj)
}

const reFindNumbers0 = /^(\d*)$/gm
const reFindNumbers1 = /\.(\d*)\./gm
const reFindNumbers2 = /^(\d*)\./gm
const reFindNumbers3 = /\.(\d*$)/gm
const reFindMultiplePeriods = /\.{2,}/gm
const reLineOfOnlyDigits = /^(\d+)$/gm
// the second dot must be in a lookahead or the engine
// will skip subsequent numbers (like foo.0.1.)
const reDigitsBetweenDots = /\.(\d+)(?=\.)/gm
const reStartWithDigitThenDot = /^(\d+)\./gm
const reDotWithDigitsToEnd = /\.(\d+$)/gm
const reMultipleDots = /\.{2,}/gm

const intPrefix = '__int__'
const intReplace = `${intPrefix}$1`
Expand All @@ -156,21 +158,25 @@ export function makePathArray(str: string | Array<string | number>) {
throw new Error('Path must be a string.')
}

return str
.replace(/\[/g, '.')
.replace(/\]/g, '')
.replace(reFindNumbers0, intReplace)
.replace(reFindNumbers1, `.${intReplace}.`)
.replace(reFindNumbers2, `${intReplace}.`)
.replace(reFindNumbers3, `.${intReplace}`)
.replace(reFindMultiplePeriods, '.')
.split('.')
.map((d) => {
if (d.indexOf(intPrefix) === 0) {
return parseInt(d.substring(intPrefix.length), 10)
}
return d
})
return (
str
// Leading `[` may lead to wrong parsing down the line
// (Example: '[0][1]' should be '0.1', not '.0.1')
.replace(/(^\[)|]/gm, '')
.replace(/\[/g, '.')
.replace(reLineOfOnlyDigits, intReplace)
.replace(reDigitsBetweenDots, `.${intReplace}.`)
.replace(reStartWithDigitThenDot, `${intReplace}.`)
.replace(reDotWithDigitsToEnd, `.${intReplace}`)
.replace(reMultipleDots, '.')
.split('.')
.map((d) => {
if (d.indexOf(intPrefix) === 0) {
return parseInt(d.substring(intPrefix.length), 10)
}
return d
})
)
}

/**
Expand Down
96 changes: 80 additions & 16 deletions packages/form-core/tests/utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,43 @@ describe('setBy', () => {
kids: [...structure.kids, { name: 'John' }],
})
})

it('should preserve arrays when setting them within other arrays', () => {
const table: { field: { value: number }[][] } = {
field: [
[
{
value: 0,
},
{
value: 1,
},
],
[
{
value: 2,
},
],
],
}

const newTable = setBy(table, 'field[0][1].value', 2)
expect(newTable.field).toStrictEqual([
[
{
value: 0,
},
{
value: 2,
},
],
[
{
value: 2,
},
],
])
})
})

describe('deleteBy', () => {
Expand Down Expand Up @@ -138,27 +175,54 @@ describe('deleteBy', () => {
})

describe('makePathArray', () => {
it('should convert dot notation to array', () => {
expect(makePathArray('name')).toEqual(['name'])
expect(makePathArray('mother.name')).toEqual(['mother', 'name'])
expect(makePathArray('kids[0].name')).toEqual(['kids', 0, 'name'])
expect(makePathArray('kids[0].name[1]')).toEqual(['kids', 0, 'name', 1])
expect(makePathArray('kids[0].name[1].age')).toEqual([
'kids',
0,
'name',
1,
'age',
])
expect(makePathArray('kids[0].name[1].age[2]')).toEqual([
'kids',
it('should convert chained property access', () => {
expect(makePathArray('a')).toEqual(['a'])
expect(makePathArray('a.b')).toEqual(['a', 'b'])
expect(makePathArray('foo.bar.baz')).toEqual(['foo', 'bar', 'baz'])
})

it('should convert property access followed by array indeces', () => {
expect(makePathArray('a[0]')).toEqual(['a', 0])
expect(makePathArray('foo[1]')).toEqual(['foo', 1])
expect(makePathArray('a.b[2]')).toEqual(['a', 'b', 2])
})

it('should convert chained array indeces', () => {
expect(makePathArray('a[0][1]')).toEqual(['a', 0, 1])
expect(makePathArray('foo[3][4][5]')).toEqual(['foo', 3, 4, 5])
})

it('should convert array indeces followed by property access', () => {
expect(makePathArray('a[0].b')).toEqual(['a', 0, 'b'])
expect(makePathArray('foo[1].bar')).toEqual(['foo', 1, 'bar'])
expect(makePathArray('[2].bar')).toEqual([2, 'bar'])
expect(makePathArray('[1][5].baz')).toEqual([1, 5, 'baz'])
})

it('should convert mixed chains of access', () => {
expect(makePathArray('a.b[0].c[1].d')).toEqual(['a', 'b', 0, 'c', 1, 'd'])
expect(makePathArray('x[0].y[1].z')).toEqual(['x', 0, 'y', 1, 'z'])
})

it('should handle deeply nested paths', () => {
expect(makePathArray('a.b[0][1].c.d[2][3].e')).toEqual([
'a',
'b',
0,
'name',
1,
'age',
'c',
'd',
2,
3,
'e',
])
})

it('should convert paths starting with multiple array indeces', () => {
expect(makePathArray('[0][1]')).toEqual([0, 1])
expect(makePathArray('[2][3].a')).toEqual([2, 3, 'a'])
expect(makePathArray('[4][5][6].b[7]')).toEqual([4, 5, 6, 'b', 7])
})
})

describe('determineFormLevelErrorSourceAndValue', () => {
Expand Down