text: const {createForm} = require('../build');
  const yup = require('yup');
  const Chance = require('chance');
  
  const chance = new Chance();
  
  function nonEmpty(array) {
    return array.filter((str) => str !== '');
  }
  
  function subscribeOnce(observable) {
    return new Promise((resolve) => {
      observable.subscribe(resolve)(); // immediately invoke to unsubscribe
    });
  }
  
  describe('createForm', () => {
    let instance;
    let initialValues = {
      name: chance.name(),
      email: chance.email(),
      country: chance.country(),
    };
    let validationSchema = yup.object().shape({
      name: yup.string().required(),
      email: yup.string().email().required(),
      country: yup.string().required(),
    });
    let onSubmit = jest.fn();
  
    function getInstance(options = {}) {
      return createForm({
        initialValues: options.initialValues || initialValues,
        validationSchema: options.validationSchema || validationSchema,
        onSubmit: options.onSubmit || onSubmit,
      });
    }
  
    beforeEach(() => {
      instance = getInstance();
    });
  
    describe('config', () => {
      it('requires initialValues to be provided and not to be empty', () => {
        const consoleWarn = jest.spyOn(console, 'warn').mockImplementation();
        const initialValues = {};
        const config = {initialValues, onSubmit: jest.fn()};
  
        createForm(config);
        expect(consoleWarn).toBeCalledWith(
          'createForm requires initialValues to be a non empty object or array, provided {}',
        );
      });
    });
  
    describe('$form', () => {
      it('returns an observable with a subscribe method', () => {
        expect(instance.form.subscribe).toBeDefined();
      });
  
      it('contains the current values which are accessed by subscription', () => {
        subscribeOnce(instance.form).then((values) => {
          expect(values.name).toBe(initialValues.name);
          expect(values.email).toBe(initialValues.email);
          expect(values.country).toBe(initialValues.country);
        });
      });
    });
  
    describe('$errors', () => {
      it('returns an observable with a subscribe method', () => {
        expect(instance.errors.subscribe).toBeDefined();
      });
  
      it('contains the current values which are accessed by subscription', () => {
        subscribeOnce(instance.errors).then((errors) => {
          expect(errors.name).toBe('');
          expect(errors.email).toBe('');
          expect(errors.country).toBe('');
        });
      });
    });
  
    describe('$touched', () => {
      it('returns an observable with a subscribe method', () => {
        expect(instance.errors.subscribe).toBeDefined();
      });
  
      it('contains the current values which are accessed by subscription', () => {
        subscribeOnce(instance.touched).then((touched) => {
          expect(touched.name).toBe(false);
          expect(touched.email).toBe(false);
          expect(touched.country).toBe(false);
        });
      });
    });
  
    describe('$isValid', () => {
      it('returns an observable with a subscribe method', () => {
        expect(instance.isValid.subscribe).toBeDefined();
      });
  
      it('returns false if form is invalid', () => {
        instance = getInstance({
          name: '',
          email: '',
          country: '',
        });
        subscribeOnce(instance.isValid).then((isValid) =>
          expect(isValid).toBe(false),
        );
      });
  
      it('returns false if some fields are untouched', async () => {
        const touched = await subscribeOnce(instance.touched);
        const someUntouched = Object.values(touched).some((val) => val === false);
        expect(someUntouched).toBe(true);
  
        const isValid = await subscribeOnce(instance.isValid);
        expect(isValid).toBe(false);
      });
  
      it('returns true if form is valid and all fields touched', () => {
        instance.touched.set({
          name: true,
          email: true,
          country: true,
        });
        subscribeOnce(instance.isValid).then((isValid) =>
          expect(isValid).toBe(true),
        );
      });
    });
  
    describe('handleReset', () => {
      it('resets form to initial state', () => {
        instance.form.set({name: 'foo'});
        subscribeOnce(instance.form).then((values) =>
          expect(values.name).toBe('foo'),
        );
  
        instance.handleReset();
        subscribeOnce(instance.form).then((form) =>
          expect(form.name).toBe(form.name),
        );
      });
  
      it('resets errors to initial state', () => {
        instance.errors.set({name: 'name is required'});
        subscribeOnce(instance.errors).then((errors) =>
          expect(errors.name).toBe('name is required'),
        );
  
        instance.handleReset();
        subscribeOnce(instance.errors).then((errors) =>
          expect(errors.name).toBe(''),
        );
      });
  
      it('resets touched to initial state', () => {
        instance.touched.set({name: true});
        subscribeOnce(instance.touched).then((touched) =>
          expect(touched.name).toBe(true),
        );
  
        instance.handleReset();
        subscribeOnce(instance.touched).then((touched) =>
          expect(touched.name).toBe(false),
        );
      });
    });
  
    describe('handleChange', () => {
      it('updates the form when connected to change handler of input', async (done) => {
        const email = chance.email();
        const event = {
          target: {
            name: 'email',
            value: email,
          },
        };
  
        await new Promise((resolve) => {
          subscribeOnce(instance.form).then((form) => {
            expect(form.email).toBe(initialValues.email);
  
            resolve();
          });
        });
  
        instance
          .handleChange(event)
          .then(() => subscribeOnce(instance.form))
          .then((form) => expect(form.email).toBe(email))
          .then(done);
      });
  
      it('uses checked value for checkbox inputs', (done) => {
        instance = getInstance({
          initialValues: {
            terms: false,
          },
          validationSchema: yup.object().shape({
            terms: yup.bool().oneOf([true]),
          }),
        });
        const event = {
          target: {
            name: 'terms',
            getAttribute: (type) => 'checkbox',
            checked: true,
          },
        };
        instance
          .handleChange(event)
          .then(() => subscribeOnce(instance.form))
          .then((form) => expect(form.terms).toBe(true))
          .then(done);
      });
  
      it('runs field validation when validateSchema is provided', (done) => {
        const invalid = 'invalid.email';
        const event = {
          target: {
            name: 'email',
            value: invalid,
          },
        };
  
        instance
          .handleChange(event)
          .then(() => subscribeOnce(instance.errors))
          .then((errors) =>
            expect(errors.email).toBe('email must be a valid email'),
          )
          .then(done);
      });
  
      it('runs field validation when validateFn is provided', (done) => {
        const invalid = 'invalid.email';
        const event = {
          target: {
            name: 'email',
            value: invalid,
          },
        };
        const instance = createForm({
          initialValues: {
            email: '',
          },
          validate: (values) => {
            let errs = {};
            if (values.email === 'invalid.email') {
              errs.email = 'this email is invalid';
            }
            return errs;
          },
          onSubmit: (values) => console.log(values),
        });
        instance
          .handleChange(event)
          .then(() => subscribeOnce(instance.errors))
          .then((errors) => expect(errors.email).toBe('this email is invalid'))
          .then(done);
      });
  
      it('does not throw when no validationSchema or validateFn provided', () => {
        const event = {
          target: {
            name: 'email',
            value: 'foo',
          },
        };
        const instance = createForm({
          initialValues: {email: ''},
          onSubmit: console.log.bind(console),
        });
  
        expect(() => instance.handleChange(event)).not.toThrow();
      });
  
      it('assigns empty string to field if validateFn returns undefined', (done) => {
        const value = 'email@email.com';
        const event = {
          target: {
            name: 'email',
            value,
          },
        };
        const instance = createForm({
          initialValues: {
            email: '',
          },
          validate: (values) => undefined,
          onSubmit: (values) => console.log(values),
        });
  
        instance
          .handleChange(event)
          .then(() => subscribeOnce(instance.errors))
          .then((errors) => expect(errors.email).toBe(''))
          .then(done);
      });
    });
  
    describe('handleSubmit', () => {
      it('validates form on submit when validationSchema is provided', (done) => {
        instance = getInstance({
          initialValues: {
            name: '',
            email: '',
            country: '',
          },
        });
  
        subscribeOnce(instance.errors).then((errors) => {
          const errorValues = nonEmpty(Object.values(errors));
          expect(errorValues.length).toBe(0);
        });
  
        instance
          .handleSubmit()
          .then(() => subscribeOnce(instance.errors))
          .then((errors) => nonEmpty(Object.values(errors)))
          .then((errors) => expect(errors.length).toBe(3))
          .then(done);
      });
  
      it('calls onSubmit when form is valid', (done) => {
        expect(onSubmit).not.toBeCalled();
        instance.handleSubmit().then(expect(onSubmit).toBeCalled).then(done);
      });
  
      it('does not call onSubmit when form is invalid', (done) => {
        const onSubmit = jest.fn();
        // create invalid form
        instance = getInstance({
          initialValues: {name: ''},
          onSubmit,
        });
        instance.handleSubmit().then(expect(onSubmit).not.toBeCalled).then(done);
      });
  
      it('calls onSubmit with formValues, $form and $error', async () => {
        const onSubmit = jest.fn();
        instance = getInstance({onSubmit});
  
        await instance.handleSubmit();
        const [formValue, $form, $errors] = onSubmit.mock.calls[0]; // onSubmit callback args
  
        expect(formValue.name).toBe(initialValues.name);
        expect(formValue.email).toBe(initialValues.email);
        expect(formValue.country).toBe(initialValues.country);
  
        subscribeOnce($form).then((form) => {
          expect(form.name).toBe(initialValues.name);
          expect(form.email).toBe(initialValues.email);
          expect(form.country).toBe(initialValues.country);
        });
  
        subscribeOnce($errors).then((errors) => {
          expect(errors.name).toBe('');
          expect(errors.email).toBe('');
          expect(errors.country).toBe('');
        });
      });
    });
  
    describe('validateField', () => {
      it('validate a field only by name', (done) => {
        instance = getInstance({
          initialValues: {
            name: '',
            email: '',
            country: '',
          },
        });
  
        subscribeOnce(instance.errors).then((errors) => {
          const errorValues = nonEmpty(Object.values(errors));
          expect(errorValues.length).toBe(0);
        });
  
        instance
          .validateField('email')
          .then(() => subscribeOnce(instance.errors))
          .then((errors) => nonEmpty(Object.values(errors)))
          .then((errors) => expect(errors.length).toBe(1))
          .then(done);
      });
    });
    describe('updateValidateField', () => {
      it('update and validate a single field', (done) => {
        instance = getInstance({
          initialValues: {
            name: '',
            email: '',
            country: '',
          },
        });
  
        instance.errors.set({name: 'name is required'});
  
        subscribeOnce(instance.errors).then((errors) => {
          const errorValues = nonEmpty(Object.values(errors));
          expect(errorValues.length).toBe(1);
        });
  
        instance
          .updateValidateField('name', 'name')
          .then(() => subscribeOnce(instance.errors))
          .then((errors) => nonEmpty(Object.values(errors)))
          .then((errors) => expect(errors.length).toBe(0))
          .then(done);
      });
    });
  
    describe('when a validation depends on another field: using when', () => {
      beforeEach(() => {
        validationSchema = yup.object().shape({
          wantsSomething: yup.boolean(),
          what: yup.string().when('wantsSomething', {
            is: true,
            then: yup.string().required(),
          }),
        });
      });
  
      it('when a is true, b is required', (done) => {
        instance = getInstance({
          initialValues: {
            wantsSomething: true,
          },
        });
  
        subscribeOnce(instance.errors).then((errors) => {
          const errorValues = nonEmpty(Object.values(errors));
          expect(errorValues.length).toBe(0);
        });
  
        instance
          .handleSubmit()
          .then(() => subscribeOnce(instance.errors))
          .then((errors) => {
            const errorValues = nonEmpty(Object.values(errors));
            expect(errorValues.length).toBe(1);
            expect(errors.what).toBe('what is a required field');
          })
          .then(done);
      });
      it('when a is false, b is not required', (done) => {
        instance = getInstance({
          initialValues: {
            wantsSomething: false,
          },
        });
  
        subscribeOnce(instance.errors).then((errors) => {
          const errorValues = nonEmpty(Object.values(errors));
          expect(errorValues.length).toBe(0);
        });
  
        instance
          .handleSubmit()
          .then(() => subscribeOnce(instance.errors))
          .then((errors) => {
            const errorValues = nonEmpty(Object.values(errors));
            expect(errorValues.length).toBe(0);
          })
          .then(done);
      });
    });
  
    describe('when a validation depends on another field: using ref', () => {
      beforeEach(() => {
        validationSchema = yup.object().shape({
          password: yup.string().required(),
          passwordConfirmation: yup
            .string()
            .oneOf([yup.ref('password'), null], "Passwords don't match!"),
        });
      });
  
      it("is invalid when passwords don't match", (done) => {
        instance = getInstance({
          initialValues: {
            password: 'a',
            passwordConfirmation: 'b',
          },
        });
  
        subscribeOnce(instance.errors).then((errors) => {
          const errorValues = nonEmpty(Object.values(errors));
          expect(errorValues.length).toBe(0);
        });
  
        instance
          .handleSubmit()
          .then(() => subscribeOnce(instance.errors))
          .then((errors) => {
            const errorValues = nonEmpty(Object.values(errors));
            expect(errorValues.length).toBe(1);
            expect(errors.passwordConfirmation).toBe("Passwords don't match!");
          })
          .then(done);
      });
  
      it('is valid when passwords match', (done) => {
        instance = getInstance({
          initialValues: {
            password: 'a',
            passwordConfirmation: 'a',
          },
        });
  
        subscribeOnce(instance.errors).then((errors) => {
          const errorValues = nonEmpty(Object.values(errors));
          expect(errorValues.length).toBe(0);
        });
  
        instance
          .handleSubmit()
          .then(() => subscribeOnce(instance.errors))
          .then((errors) => {
            const errorValues = nonEmpty(Object.values(errors));
            expect(errorValues.length).toBe(0);
          })
          .then(done);
      });
    });
  });
  