Skip to content

Commit

Permalink
debug activate logic of field state (#12)
Browse files Browse the repository at this point in the history
* debug activate & update cases

* v1.0.7

* optimize validate logic to avoid duplicate _validate call

* typo: comment for delay & sync value
  • Loading branch information
nighca committed Dec 13, 2019
1 parent a1ac6b4 commit 9a05a63
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 150 deletions.
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "formstate-x",
"version": "1.0.6",
"version": "1.0.7",
"description": "Extended alternative for formstate",
"repository": {
"type": "git",
Expand Down
73 changes: 40 additions & 33 deletions src/fieldState.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@ import FieldState from './fieldState'
// })

const defaultDelay = 10
const stableDelay = defaultDelay * 3 // [onChange debounce] + [async validate] + [buffer]

function delay<T>(value?: T, delay: number = defaultDelay + 10): Promise<T> {
return new Promise(
resolve => setTimeout(() => resolve(value), delay)
)
async function delay(millisecond: number = stableDelay) {
await new Promise(resolve => setTimeout(() => resolve(), millisecond))
}

async function delayValue<T>(value: T, millisecond: number = defaultDelay) {
await delay(millisecond)
return value
}

function createFieldState<T>(initialValue: T) {
Expand Down Expand Up @@ -44,7 +48,7 @@ describe('FieldState', () => {
expect(state.value).toBe(initialValue)
expect(state.$).toBe(initialValue)

await when(() => state.validated)
await delay()
expect(state._value).toBe(value)
expect(state.value).toBe(value)
expect(state.$).toBe(value)
Expand All @@ -57,7 +61,7 @@ describe('FieldState', () => {
expect(state.value).toBe(value)
expect(state.$).toBe(value)

await when(() => state.validated)
await delay()
expect(state._value).toBe(newValue)
expect(state.value).toBe(newValue)
expect(state.$).toBe(newValue)
Expand Down Expand Up @@ -90,8 +94,17 @@ describe('FieldState', () => {
expect(state.value).toBe(value)
expect(state.dirty).toBe(true)

// set 不应该使 field 激活
expect(state.validating).toBe(false)
expect(state.validated).toBe(false)
expect(state.hasError).toBe(false)
await delay()
expect(state.validating).toBe(false)
expect(state.validated).toBe(false)
expect(state.hasError).toBe(false)

state.validate()
await when(() => state.validated)
await delay()
expect(state._value).toBe(value)
expect(state.value).toBe(value)
expect(state.dirty).toBe(true)
Expand All @@ -104,7 +117,7 @@ describe('FieldState', () => {
const state = createFieldState(initialValue)

state.onChange('123')
await when(() => state.validated)
await delay()
state.reset()

expect(state._value).toBe(initialValue)
Expand Down Expand Up @@ -138,15 +151,9 @@ describe('FieldState validation', () => {
const state = createFieldState('xxx').validators(val => !val && 'empty')
state.onChange('')

await when(() => state.validating)
expect(state.validating).toBe(true)
expect(state.validated).toBe(false)

await when(() => state.validated)
expect(state.validating).toBe(false)
await delay()
expect(state.validated).toBe(true)
expect(state.hasError).toBe(true)
expect(state.error).toBe('empty')

state.onChange('123')
state.onChange('123456')
Expand Down Expand Up @@ -178,7 +185,7 @@ describe('FieldState validation', () => {
const state = createFieldState('').validators(val => !val && 'empty')
state.validate()

await when(() => state.validated)
await delay()
expect(state.validating).toBe(false)
expect(state.validated).toBe(true)
expect(state.hasError).toBe(true)
Expand All @@ -191,7 +198,7 @@ describe('FieldState validation', () => {
const initialValue = ''
const state = createFieldState(initialValue).validators(val => !val && 'empty')
state.validate()
await when(() => state.validated)
await delay()

state.reset()
expect(state._value).toBe(initialValue)
Expand All @@ -211,23 +218,23 @@ describe('FieldState validation', () => {
)
state.validate()

await when(() => state.validated)
await delay()
expect(state.validating).toBe(false)
expect(state.validated).toBe(true)
expect(state.hasError).toBe(true)
expect(state.error).toBe('empty')

state.onChange('123456')

await when(() => state.validated)
await delay()
expect(state.validating).toBe(false)
expect(state.validated).toBe(true)
expect(state.hasError).toBe(true)
expect(state.error).toBe('too long')

state.onChange('123')

await when(() => state.validated)
await delay()
expect(state.validating).toBe(false)
expect(state.validated).toBe(true)
expect(state.hasError).toBe(false)
Expand All @@ -238,15 +245,15 @@ describe('FieldState validation', () => {

it('should work well with async validator', async () => {
const state = createFieldState('').validators(
val => !val && delay('empty')
val => delayValue(!val && 'empty')
)
state.validate()

await when(() => state.validating)
expect(state.validating).toBe(true)
expect(state.validated).toBe(false)

await when(() => state.validated)
await delay()
expect(state.validating).toBe(false)
expect(state.validated).toBe(true)
expect(state.hasError).toBe(true)
Expand All @@ -257,26 +264,26 @@ describe('FieldState validation', () => {

it('should work well with mixed sync and async validator', async () => {
const state = createFieldState('').validators(
val => !val && delay('empty'),
val => delayValue(!val && 'empty'),
val => val.length > 5 && 'too long'
)
state.validate()

await when(() => state.validated)
await delay()
expect(state.hasError).toBe(true)
expect(state.error).toBe('empty')

state.onChange('123456')

await when(() => state.validated)
await delay()
expect(state.hasError).toBe(true)
expect(state.error).toBe('too long')

state.onChange('123')

await when(() => state.validated)
expect(state.hasError).toBe(false)
await delay()
expect(state.error).toBeUndefined()
expect(state.hasError).toBe(false)

state.dispose()
})
Expand All @@ -288,22 +295,22 @@ describe('FieldState validation', () => {
)

state.onChange('123')
await when(() => state.validated)
await delay()
expect(state.hasError).toBe(true)
expect(state.error).toBe('same')

runInAction(() => target.value = '123456')
await when(() => state.validated)
await delay()
expect(state.hasError).toBe(false)
expect(state.error).toBeUndefined()

runInAction(() => target.value = '123')
await when(() => state.validated)
await delay()
expect(state.hasError).toBe(true)
expect(state.error).toBe('same')

state.onChange('123456')
await when(() => state.validated)
await delay()
expect(state.hasError).toBe(false)
expect(state.error).toBeUndefined()

Expand All @@ -316,12 +323,12 @@ describe('FieldState validation', () => {
)
state.onChange('123456')

await when(() => state.validated)
await delay()
expect(state.hasError).toBe(false)
expect(state.error).toBeUndefined()

state.validators(val => val.length > 5 && 'too long')
await when(() => state.validated)
await delay()
expect(state.hasError).toBe(true)
expect(state.error).toBe('too long')

Expand Down
49 changes: 22 additions & 27 deletions src/fieldState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,6 @@ export default class FieldState<TValue> extends Disposable implements Composible
*/
@observable.ref $: TValue

/**
* Apply `_value` to `value`.
*/
@action private applyValue() {
this._activated = true
this.value = this._value
}

/**
* The validate status.
*/
Expand Down Expand Up @@ -105,29 +97,18 @@ export default class FieldState<TValue> extends Disposable implements Composible
return this
}

/**
* Set `_value`.
*/
@action private setValue(value: TValue) {
this._value = value
this._validateStatus = ValidateStatus.NotValidated
}

/**
* Set `_value` on change event.
*/
@action onChange(value: TValue) {
if (value !== this._value) {
this.setValue(value)
}
this._value = value
}

/**
* Set `value` (& `_value`) synchronously.
*/
@action set(value: TValue) {
this.setValue(value)
this.value = this._value // 不使用 applyValue,避免 activate
this.value = this._value = value
}

/**
Expand Down Expand Up @@ -171,11 +152,19 @@ export default class FieldState<TValue> extends Disposable implements Composible
* Fire a validation behavior.
*/
async validate() {
runInAction('activate-when-validate', () => {
const validation = this.validation

runInAction('activate-and-sync-_value-when-validate', () => {
this._activated = true
// 若有用户交互产生的变更(因 debounce)尚未同步,同步之,确保本次 validate 结果是相对稳定的
this.value = this._value
})

this.applyValue()
// 若 `validation` 未发生变更,意味着未发生新的校验行为
// 若上边操作未触发自动的校验行为,强制调用之
if (this.validation === validation) {
this._validate()
}

// Compatible with formstate
await when(
Expand Down Expand Up @@ -241,14 +230,20 @@ export default class FieldState<TValue> extends Disposable implements Composible

this.reset()

// debounced auto sync: this._value -> this.value
// debounced reaction to `_value` change
this.addDisposer(reaction(
() => this._value,
() => this.applyValue(),
{ delay, name: 'applyValue-when-value-change' }
() => {
if (this.value !== this._value) {
this.value = this._value
this._validateStatus = ValidateStatus.NotValidated
this._activated = true
}
},
{ delay, name: 'reaction-when-_value-change' }
))

// auto sync while validated: this.value -> this.$
// auto sync when validate ok: this.value -> this.$
this.addDisposer(reaction(
() => this.validated && !this.hasError,
validateOk => validateOk && (this.$ = this.value),
Expand Down
Loading

0 comments on commit 9a05a63

Please sign in to comment.