chore: clone logic refactor preparations & typehinting
This commit is contained in:
parent
320df96b96
commit
b1c2ee9056
@ -22,31 +22,19 @@ import {
|
||||
Violation,
|
||||
} from '@/validation/validator'
|
||||
|
||||
import {
|
||||
FormularioFieldContext,
|
||||
FormularioFieldModelGetConverter as ModelGetConverter,
|
||||
FormularioFieldModelSetConverter as ModelSetConverter,
|
||||
Empty,
|
||||
} from '@/types'
|
||||
|
||||
const VALIDATION_BEHAVIOR = {
|
||||
DEMAND: 'demand',
|
||||
LIVE: 'live',
|
||||
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 })
|
||||
export default class FormularioField extends Vue {
|
||||
@Inject({ default: '' }) __Formulario_path!: string
|
||||
|
42
src/types.ts
42
src/types.ts
@ -8,3 +8,45 @@ export interface FormularioFieldInterface {
|
||||
runValidation(): Promise<Violation[]>;
|
||||
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 { RecordLike, Scalar, isScalar } from '@/utils/types'
|
||||
import { RecordLike, Scalar, isScalar } from '@/types'
|
||||
|
||||
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
|
||||
* case of needing to unbind reactive watchers.
|
||||
|
@ -1,6 +1,5 @@
|
||||
export { default as clone } from './clone'
|
||||
export { default as has } from './has'
|
||||
export { isScalar } from './types'
|
||||
export { default as merge } from './merge'
|
||||
export { default as regexForFormat } from './regexForFormat'
|
||||
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', () => {
|
||||
it('Basic objects stay the same', () => {
|
||||
test('Basic objects stay the same', () => {
|
||||
const obj = { a: 123, b: 'hello' }
|
||||
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' } }
|
||||
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' }
|
||||
expect(clone({ a: 123, b: c }).b === c).toBe(false)
|
||||
})
|
||||
|
||||
it('Retains array structures inside of a pojo', () => {
|
||||
const obj = { a: 'abcd', d: ['first', 'second'] }
|
||||
test('Retains array structures inside of a pojo', () => {
|
||||
const obj = { a: 'abc', d: ['first', 'second'] }
|
||||
expect(Array.isArray(clone(obj).d)).toBe(true)
|
||||
})
|
||||
|
||||
it('Removes references inside array structures', () => {
|
||||
const obj = { a: 'abcd', d: ['first', { foo: 'bar' }] }
|
||||
test('Removes references inside array structures', () => {
|
||||
const obj = { a: 'abc', d: ['first', { foo: 'bar' }] }
|
||||
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…
Reference in New Issue
Block a user