chore: Form state management refactor preparations
This commit is contained in:
parent
343bdb16cb
commit
9868d99c19
93
src/utils/access.ts
Normal file
93
src/utils/access.ts
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
import has from './has'
|
||||||
|
import { isRecordLike, isScalar } from '@/types'
|
||||||
|
|
||||||
|
const extractIntOrNaN = (value: string): number => {
|
||||||
|
const numeric = parseInt(value)
|
||||||
|
|
||||||
|
return numeric.toString() === value ? numeric : NaN
|
||||||
|
}
|
||||||
|
|
||||||
|
const extractPath = (field: string): string[] => {
|
||||||
|
const path = [] as string[]
|
||||||
|
|
||||||
|
field.split('.').forEach(key => {
|
||||||
|
if (/(.*)\[(\d+)]$/.test(key)) {
|
||||||
|
path.push(...key.substr(0, key.length - 1).split('[').filter(k => k.length))
|
||||||
|
} else {
|
||||||
|
path.push(key)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
const unsetInRecord = (record: Record<string, unknown>, prop: string): Record<string, unknown> => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
const { [prop]: _, ...copy } = record
|
||||||
|
|
||||||
|
return copy
|
||||||
|
}
|
||||||
|
|
||||||
|
export function get (state: unknown, fieldOrPath: string|string[]): unknown {
|
||||||
|
const path = typeof fieldOrPath === 'string' ? extractPath(fieldOrPath) : fieldOrPath
|
||||||
|
|
||||||
|
if (isScalar(state) || path.length === 0) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const key = path.shift() as string
|
||||||
|
const index = extractIntOrNaN(key)
|
||||||
|
|
||||||
|
if (!isNaN(index)) {
|
||||||
|
if (Array.isArray(state) && index >= 0 && index < state.length) {
|
||||||
|
return path.length === 0 ? state[index] : get(state[index], path)
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
if (has(state as Record<string, unknown>, key)) {
|
||||||
|
const values = state as Record<string, unknown>
|
||||||
|
|
||||||
|
return path.length === 0 ? values[key] : get(values[key], path)
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export function unset (state: unknown, fieldOrPath: string|string[]): unknown {
|
||||||
|
if (!isRecordLike(state)) {
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
const path = typeof fieldOrPath === 'string' ? extractPath(fieldOrPath) : fieldOrPath
|
||||||
|
|
||||||
|
if (path.length === 0) {
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
const key = path.shift() as string
|
||||||
|
const index = extractIntOrNaN(key)
|
||||||
|
|
||||||
|
if (!isNaN(index) && Array.isArray(state) && index >= 0 && index < state.length) {
|
||||||
|
const values = (state as unknown[]).slice()
|
||||||
|
|
||||||
|
if (path.length === 0) {
|
||||||
|
values.splice(index, 1)
|
||||||
|
} else {
|
||||||
|
values[index] = unset(values[index], path)
|
||||||
|
}
|
||||||
|
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
|
||||||
|
if (has(state as Record<string, unknown>, key)) {
|
||||||
|
const values = state as Record<string, unknown>
|
||||||
|
|
||||||
|
return path.length === 0
|
||||||
|
? unsetInRecord(values, key)
|
||||||
|
: { ...values, [key]: unset(values[key], path) }
|
||||||
|
}
|
||||||
|
|
||||||
|
return state
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
export { default as clone } from './clone'
|
export { default as clone } from './clone'
|
||||||
export { default as has } from './has'
|
export { default as has } from './has'
|
||||||
export { default as merge } from './merge'
|
export { default as merge } from './merge'
|
||||||
|
export { get, unset } from './access'
|
||||||
export { default as regexForFormat } from './regexForFormat'
|
export { default as regexForFormat } from './regexForFormat'
|
||||||
export { default as shallowEquals } from './shallowEquals'
|
export { default as shallowEquals } from './shallowEquals'
|
||||||
export { default as snakeToCamel } from './snakeToCamel'
|
export { default as snakeToCamel } from './snakeToCamel'
|
||||||
|
72
test/unit/utils/access.test.js
Normal file
72
test/unit/utils/access.test.js
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import { get, unset } from '@/utils/access'
|
||||||
|
|
||||||
|
class Sample {
|
||||||
|
constructor() {
|
||||||
|
this.fieldA = 'fieldA'
|
||||||
|
this.fieldB = 'fieldB'
|
||||||
|
}
|
||||||
|
|
||||||
|
doSomething () {}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('access', () => {
|
||||||
|
describe('get', () => {
|
||||||
|
test.each([
|
||||||
|
[{ a: { b: { c: 1 } } }, 'a', { b: { c: 1 } }],
|
||||||
|
[{ a: { b: { c: 1 } }, d: 1 }, 'a', { b: { c: 1 } }],
|
||||||
|
[{ a: { b: { c: 1 } } }, 'a.b.c', 1],
|
||||||
|
[{ a: { b: [1] } }, 'a.b[0]', 1],
|
||||||
|
[{ a: { b: [1, 2, 3] } }, 'a.b[0]', 1],
|
||||||
|
[{ a: { b: [1, 2, 3] } }, 'a.b[1]', 2],
|
||||||
|
[{ a: { b: [1, 2, 3] } }, 'a.b[2]', 3],
|
||||||
|
[{ a: { b: [1, 2, 3] } }, 'a.b[3]', undefined],
|
||||||
|
[{ a: { b: [{ c: 1 }, 2, 3] } }, 'a.b[0].c', 1],
|
||||||
|
[{ a: { b: [{ c: 1 }, 2, 3] } }, 'a.b[1].c', undefined],
|
||||||
|
[[{ c: 1 }, 2, 3], '[0].c', 1],
|
||||||
|
[[{ c: 2 }, 2, 3], '[0].c', 2],
|
||||||
|
[new Sample(), 'fieldA', 'fieldA'],
|
||||||
|
])('gets by path', (record, path, expected) => {
|
||||||
|
expect(get(record, path)).toEqual(expected)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('unset', () => {
|
||||||
|
test.each([
|
||||||
|
[{ a: { b: { c: 1 } } }, 'a', {}],
|
||||||
|
[{ a: { b: { c: 1 } }, d: 1 }, 'a', { d: 1 }],
|
||||||
|
[{ a: { b: { c: 1 } } }, 'a.b.c', { a: { b: {} } }],
|
||||||
|
[{ a: { b: [1] } }, 'a.b[0]', { a: { b: [] } }],
|
||||||
|
[{ a: { b: [1, 2, 3] } }, 'a.b[0]', { a: { b: [2, 3] } }],
|
||||||
|
[{ a: { b: [1, 2, 3] } }, 'a.b[1]', { a: { b: [1, 3] } }],
|
||||||
|
[{ a: { b: [1, 2, 3] } }, 'a.b[2]', { a: { b: [1, 2] } }],
|
||||||
|
[{ a: { b: [1, 2, 3] } }, 'a.b[3]', { a: { b: [1, 2, 3] } }],
|
||||||
|
[{ a: { b: [{ c: 1 }, 2, 3] } }, 'a.b[0].c', { a: { b: [{}, 2, 3] } }],
|
||||||
|
[{ a: { b: [{ c: 1 }, 2, 3] } }, 'a.b[1].c', { a: { b: [{ c: 1 }, 2, 3] } }],
|
||||||
|
[[{ c: 1 }, 2, 3], '[0].c', [{}, 2, 3]],
|
||||||
|
])('unsets by path', (record, path, expected) => {
|
||||||
|
const processed = unset(record, path)
|
||||||
|
|
||||||
|
expect(processed).toEqual(expected)
|
||||||
|
expect(processed === record).toBeFalsy()
|
||||||
|
})
|
||||||
|
|
||||||
|
test.each`
|
||||||
|
type | scalar
|
||||||
|
${'booleans'} | ${false}
|
||||||
|
${'numbers'} | ${123}
|
||||||
|
${'strings'} | ${'hello'}
|
||||||
|
${'symbols'} | ${Symbol(123)}
|
||||||
|
${'undefined'} | ${undefined}
|
||||||
|
${'null'} | ${null}
|
||||||
|
`('not unsets for $type', ({ scalar }) => {
|
||||||
|
expect(unset(scalar, 'key')).toStrictEqual(scalar)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('not unsets for class instance', () => {
|
||||||
|
const sample = new Sample()
|
||||||
|
const processed = unset(sample, 'fieldA')
|
||||||
|
|
||||||
|
expect(processed.fieldA).toStrictEqual('fieldA')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
Loading…
x
Reference in New Issue
Block a user