import React from 'react'
import { FormState, DebouncedFieldState } from 'formstate-x'
import { observer } from 'mobx-react'
import { create, act } from 'react-test-renderer'

import { Form, FormItem, useFormstateX } from '.'
import { PasswordInput, TextInput } from './Input'

const defaultDelay = 10 // ms

function delay() {
  return new Promise<void>(resolve => {
    setTimeout(() => resolve(), defaultDelay)
  })
}

function mockEvent(target?: any) {
  return {
    target,
    preventDefault: () => undefined
  }
}

describe('react-icecream-form', () => {
  it('should work well', async () => {
    function createState(email = '') {
      return new FormState({
        email: new DebouncedFieldState(email, defaultDelay).withValidator(
          v => (/.+@.+/.test(v) ? null : 'Invalid email')
        ),
        password: new DebouncedFieldState('', defaultDelay).withValidator(
          v => v.length < 6 && 'Too short'
        )
      })
    }

    let submitted: any
    const submit = jest.fn(v => { submitted = v })

    interface MyFormProps {
      email?: string
    }

    const MyForm = observer(function MyForm({ email }: MyFormProps) {
      const state = useFormstateX(createState, [email])
      return (
        <Form state={state} onSubmit={submit}>
          <FormItem label="邮箱" >
            <TextInput state={state.$.email} />
          </FormItem>
          <FormItem label="密码">
            <PasswordInput state={state.$.password} />
          </FormItem>
        </Form>
      )
    })

    const rendered = create(<MyForm />)
    const form = () => rendered.root.findByType('form')
    const inputs = () => rendered.root.findAllByType('input')
    const emailInput = () => inputs()[0]
    const passwordInput = () => inputs()[1]

    expect(rendered.toJSON()).toMatchSnapshot('initialized')

    await act(async () => {
      emailInput().props.onChange(mockEvent({ value: 'foo' }))
      await delay()
    })

    expect(rendered.toJSON()).toMatchSnapshot('invalid email')

    await act(async () => {
      passwordInput().props.onChange(mockEvent({ value: 'bar' }))
      await delay()
    })

    expect(rendered.toJSON()).toMatchSnapshot('invalid email & password')

    act(() => {
      rendered.update(<MyForm email="icecream@form.com" />)
    })

    expect(rendered.toJSON()).toMatchSnapshot('initialized with email')

    await act(async () => {
      form().props.onSubmit(mockEvent())
      await delay()
    })

    expect(rendered.toJSON()).toMatchSnapshot('submit with invalid password')
    expect(submit).not.toHaveBeenCalled()

    await act(async () => {
      passwordInput().props.onChange(mockEvent({ value: '123456' }))
      await delay()
    })

    expect(rendered.toJSON()).toMatchSnapshot('no error')

    await act(async () => {
      form().props.onSubmit(mockEvent())
      await delay()
    })

    expect(rendered.toJSON()).toMatchSnapshot('submit with no error')
    expect(submit).toBeCalledTimes(1)
    expect(submitted).toEqual({
      email: 'icecream@form.com',
      password: '123456'
    })
  })
})

