chore: clone logic refactor preparations & typehinting
This commit is contained in:
parent
320df96b96
commit
b1c2ee9056
@ -22,31 +22,19 @@ import {
|
|||||||
Violation,
|
Violation,
|
||||||
} from '@/validation/validator'
|
} from '@/validation/validator'
|
||||||
|
|
||||||
|
import {
|
||||||
|
FormularioFieldContext,
|
||||||
|
FormularioFieldModelGetConverter as ModelGetConverter,
|
||||||
|
FormularioFieldModelSetConverter as ModelSetConverter,
|
||||||
|
Empty,
|
||||||
|
} from '@/types'
|
||||||
|
|
||||||
const VALIDATION_BEHAVIOR = {
|
const VALIDATION_BEHAVIOR = {
|
||||||
DEMAND: 'demand',
|
DEMAND: 'demand',
|
||||||
LIVE: 'live',
|
LIVE: 'live',
|
||||||
SUBMIT: 'submit',
|
SUBMIT: 'submit',
|
||||||
}
|
}
|
||||||
|
|
||||||
type FormularioFieldContext<U> = {
|
|
||||||
model: U;
|
|
||||||
name: string;
|
|
||||||
runValidation(): Promise<Violation[]>;
|
|
||||||
violations: Violation[];
|
|
||||||
errors: string[];
|
|
||||||
allErrors: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ModelGetConverter {
|
|
||||||
<U, T>(value: U|Empty): U|T|Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ModelSetConverter {
|
|
||||||
<T, U>(curr: U|T, prev: U|Empty): U|T;
|
|
||||||
}
|
|
||||||
|
|
||||||
type Empty = null | undefined
|
|
||||||
|
|
||||||
@Component({ name: 'FormularioField', inheritAttrs: false })
|
@Component({ name: 'FormularioField', inheritAttrs: false })
|
||||||
export default class FormularioField extends Vue {
|
export default class FormularioField extends Vue {
|
||||||
@Inject({ default: '' }) __Formulario_path!: string
|
@Inject({ default: '' }) __Formulario_path!: string
|
||||||
|
42
src/types.ts
42
src/types.ts
@ -8,3 +8,45 @@ export interface FormularioFieldInterface {
|
|||||||
runValidation(): Promise<Violation[]>;
|
runValidation(): Promise<Violation[]>;
|
||||||
resetValidation(): void;
|
resetValidation(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type FormularioFieldContext<T> = {
|
||||||
|
model: T;
|
||||||
|
name: string;
|
||||||
|
runValidation(): Promise<Violation[]>;
|
||||||
|
violations: Violation[];
|
||||||
|
errors: string[];
|
||||||
|
allErrors: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FormularioFieldModelGetConverter {
|
||||||
|
<U, T>(value: U|Empty): U|T|Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FormularioFieldModelSetConverter {
|
||||||
|
<T, U>(curr: U|T, prev: U|Empty): U|T;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Empty = undefined | null
|
||||||
|
|
||||||
|
export type RecordKey = string | number
|
||||||
|
export type RecordLike<T> = T[] | Record<RecordKey, T>
|
||||||
|
|
||||||
|
export type Scalar = boolean | number | string | symbol | Empty
|
||||||
|
|
||||||
|
export function isRecordLike (value: unknown): boolean {
|
||||||
|
return typeof value === 'object' && value !== null && ['Array', 'Object'].includes(value.constructor.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isScalar (value: unknown): boolean {
|
||||||
|
switch (typeof value) {
|
||||||
|
case 'bigint':
|
||||||
|
case 'boolean':
|
||||||
|
case 'number':
|
||||||
|
case 'string':
|
||||||
|
case 'symbol':
|
||||||
|
case 'undefined':
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return value === null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
import has from '@/utils/has'
|
import has from '@/utils/has'
|
||||||
import { RecordLike, Scalar, isScalar } from '@/utils/types'
|
import { RecordLike, Scalar, isScalar } from '@/types'
|
||||||
|
|
||||||
type Cloneable = Scalar|Date|RecordLike<unknown>
|
type Cloneable = Scalar|Date|RecordLike<unknown>
|
||||||
|
|
||||||
|
export const cloneInstance = <T>(original: T): T => {
|
||||||
|
return Object.assign(Object.create(Object.getPrototypeOf(original)), original)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A simple (somewhat non-comprehensive) clone function, valid for our use
|
* A simple (somewhat non-comprehensive) clone function, valid for our use
|
||||||
* case of needing to unbind reactive watchers.
|
* case of needing to unbind reactive watchers.
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
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 { isScalar } from './types'
|
|
||||||
export { default as merge } from './merge'
|
export { default as merge } from './merge'
|
||||||
export { default as regexForFormat } from './regexForFormat'
|
export { default as regexForFormat } from './regexForFormat'
|
||||||
export { default as shallowEquals } from './shallowEquals'
|
export { default as shallowEquals } from './shallowEquals'
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
export type Empty = undefined | null
|
|
||||||
|
|
||||||
export type RecordKey = string | number
|
|
||||||
export type RecordLike<T> = T[] | Record<RecordKey, T>
|
|
||||||
|
|
||||||
export type Scalar = boolean | number | string | symbol | Empty
|
|
||||||
|
|
||||||
export function isScalar (value: unknown): boolean {
|
|
||||||
switch (typeof value) {
|
|
||||||
case 'boolean':
|
|
||||||
case 'number':
|
|
||||||
case 'string':
|
|
||||||
case 'symbol':
|
|
||||||
case 'undefined':
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return value === null
|
|
||||||
}
|
|
||||||
}
|
|
49
test/unit/types.test.js
Normal file
49
test/unit/types.test.js
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import {
|
||||||
|
isRecordLike,
|
||||||
|
isScalar,
|
||||||
|
} from '@/types'
|
||||||
|
|
||||||
|
describe('types', () => {
|
||||||
|
const scalars = [
|
||||||
|
['booleans', false],
|
||||||
|
['numbers', 123],
|
||||||
|
['strings', 'hello'],
|
||||||
|
['symbols', Symbol(123)],
|
||||||
|
['undefined', undefined],
|
||||||
|
['null', null],
|
||||||
|
]
|
||||||
|
|
||||||
|
const records = [
|
||||||
|
[{}],
|
||||||
|
[{ a: 'a', b: ['b'] }],
|
||||||
|
[[]],
|
||||||
|
[['b', 'c']],
|
||||||
|
]
|
||||||
|
|
||||||
|
describe('isRecordLike', () => {
|
||||||
|
test.each(records)('passes on records', record => {
|
||||||
|
expect(isRecordLike(record)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test.each(scalars)('fails on $type', (type, scalar) => {
|
||||||
|
expect(isRecordLike(scalar)).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
test.each([
|
||||||
|
['class instance', new class {} ()],
|
||||||
|
['builtin Date instance', new Date()],
|
||||||
|
])('fails on $type', (type, instance) => {
|
||||||
|
expect(isRecordLike(instance)).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('isScalar', () => {
|
||||||
|
test.each(scalars)('passes on $type', (type, scalar) => {
|
||||||
|
expect(isScalar(scalar)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test.each(records)('fails on records & arrays', record => {
|
||||||
|
expect(isScalar(record)).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
@ -1,28 +1,61 @@
|
|||||||
import clone from '@/utils/clone'
|
import clone, { cloneInstance } from '@/utils/clone'
|
||||||
|
|
||||||
describe('clone', () => {
|
describe('clone', () => {
|
||||||
it('Basic objects stay the same', () => {
|
test('Basic objects stay the same', () => {
|
||||||
const obj = { a: 123, b: 'hello' }
|
const obj = { a: 123, b: 'hello' }
|
||||||
expect(clone(obj)).toEqual(obj)
|
expect(clone(obj)).toEqual(obj)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Basic nested objects stay the same', () => {
|
test('Basic nested objects stay the same', () => {
|
||||||
const obj = { a: 123, b: { c: 'hello-world' } }
|
const obj = { a: 123, b: { c: 'hello-world' } }
|
||||||
expect(clone(obj)).toEqual(obj)
|
expect(clone(obj)).toEqual(obj)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Simple pojo reference types are re-created', () => {
|
test('Simple pojo reference types are re-created', () => {
|
||||||
const c = { c: 'hello-world' }
|
const c = { c: 'hello-world' }
|
||||||
expect(clone({ a: 123, b: c }).b === c).toBe(false)
|
expect(clone({ a: 123, b: c }).b === c).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Retains array structures inside of a pojo', () => {
|
test('Retains array structures inside of a pojo', () => {
|
||||||
const obj = { a: 'abcd', d: ['first', 'second'] }
|
const obj = { a: 'abc', d: ['first', 'second'] }
|
||||||
expect(Array.isArray(clone(obj).d)).toBe(true)
|
expect(Array.isArray(clone(obj).d)).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Removes references inside array structures', () => {
|
test('Removes references inside array structures', () => {
|
||||||
const obj = { a: 'abcd', d: ['first', { foo: 'bar' }] }
|
const obj = { a: 'abc', d: ['first', { foo: 'bar' }] }
|
||||||
expect(clone(obj).d[1] === obj.d[1]).toBe(false)
|
expect(clone(obj).d[1] === obj.d[1]).toBe(false)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('cloneInstance', () => {
|
||||||
|
test('creates a copy of a class instance', () => {
|
||||||
|
class Sample {
|
||||||
|
constructor() {
|
||||||
|
this.fieldA = 'fieldA'
|
||||||
|
this.fieldB = 'fieldB'
|
||||||
|
}
|
||||||
|
|
||||||
|
doSomething () {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const sample = new Sample()
|
||||||
|
const copy = cloneInstance(sample)
|
||||||
|
|
||||||
|
expect(sample === copy).toBeFalsy()
|
||||||
|
|
||||||
|
expect(copy).toBeInstanceOf(Sample)
|
||||||
|
expect(copy.fieldA).toEqual('fieldA')
|
||||||
|
expect(copy.fieldB).toEqual('fieldB')
|
||||||
|
expect(copy.doSomething).toBeTruthy()
|
||||||
|
expect(copy.doSomething).not.toThrow()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('creates a broken copy of builtins', () => {
|
||||||
|
const sample = new Date()
|
||||||
|
const copy = cloneInstance(sample)
|
||||||
|
|
||||||
|
expect(sample === copy).toBeFalsy()
|
||||||
|
expect(copy).toBeInstanceOf(Date)
|
||||||
|
expect(() => copy.toISOString()).toThrow()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
import { isScalar } from '@/utils/types'
|
|
||||||
|
|
||||||
describe('isScalar', () => {
|
|
||||||
const expectIsScalar = value => expect(isScalar(value)).toBe(true)
|
|
||||||
|
|
||||||
test('passes on booleans', () => expectIsScalar(false))
|
|
||||||
test('passes on numbers', () => expectIsScalar(123))
|
|
||||||
test('passes on strings', () => expectIsScalar('hello'))
|
|
||||||
test('passes on symbols', () => expectIsScalar(Symbol(123)))
|
|
||||||
test('passes on undefined', () => expectIsScalar(undefined))
|
|
||||||
test('passes on null', () => expectIsScalar(null))
|
|
||||||
|
|
||||||
test('fails on pojo', () => expect(isScalar({})).toBe(false))
|
|
||||||
})
|
|
Loading…
x
Reference in New Issue
Block a user