import { shallow } from 'enzyme';
import React from 'react';

import { Site } from '../../../tests/storyshare';
import { SignInForm } from './SignInForm';

describe('SignInForm component', () => {
  const onSignIn = jest.fn();
  const site = new Site();

  site.login = jest.fn().mockReturnValue({});

  let wrapper;

  beforeEach(() => {
    site.login.mockClear();
    onSignIn.mockClear();
    wrapper = shallow(<SignInForm onSignIn={ onSignIn } site={ site } />);
  });

  const getEmail = () => wrapper.find('Input[name="email"]');

  const typeEmail = (value) =>
    getEmail().simulate('change', null, { name: 'email', value });

  const getPassword = () => wrapper.find('Input[name="password"]');

  const typePassword = (value) =>
    getPassword().simulate('change', null, { name: 'password', value });

  const fillInForm = () => {
    typeEmail('test@email');
    typePassword('test password');
  };

  it('renders', () => {
    expect(wrapper).toBeDefined();
  });

  it('consists of Form component', () => {
    wrapper.find('Form');
  });

  it('has an email input with correct placeholder and type', () => {
    expect(getEmail()).toExist();
    expect(getEmail()).toHaveProp('placeholder', 'Enter your email address');
    expect(getEmail()).toHaveProp('type', 'email');
  });

  it('allows an email address to be entered', () => {
    typeEmail('test@email');
    expect(getEmail()).toHaveProp('value', 'test@email');
  });

  it('has a password input with correct placeholder and type', () => {
    expect(getPassword()).toExist();
    expect(getPassword()).toHaveProp('placeholder', 'Enter a password');
    expect(getPassword()).toHaveProp('type', 'password');
  });

  it('allows a password to be entered', () => {
    typePassword('test password');
    expect(getPassword()).toHaveProp('value', 'test password');
  });

  it('has submit button within form', () => {
    expect(wrapper.find('FormButton')).toExist();
    expect(wrapper.find('FormButton')).toHaveProp('type', 'submit');
  });

  it('starts out with the submit button disabled', () => {
    expect(wrapper.find('FormButton')).toHaveProp('disabled', true);
  });

  it('enables the button when the form is filled in', () => {
    fillInForm();
    expect(wrapper.find('FormButton')).toHaveProp('disabled', false);
  });

  it('disables the button when the email is missing', () => {
    fillInForm();
    typeEmail('');
    expect(wrapper.find('FormButton')).toHaveProp('disabled', true);
    typeEmail(' ');
    expect(wrapper.find('FormButton')).toHaveProp('disabled', true);
  });

  it('disables the button when the password is missing', () => {
    fillInForm();
    typePassword('');
    expect(wrapper.find('FormButton')).toHaveProp('disabled', true);
  });

  it('calls site login method', async () => {
    fillInForm();
    const request = {
      promise: jest.fn().mockReturnThis(),
      setParams: jest.fn().mockReturnThis(),
      track: jest.fn().mockReturnThis(),
      create: jest.fn().mockReturnValue(false),
    };

    jest.spyOn(site, 'req').mockReturnValue(request);

    site.req.mockReturnValue(request);

    // This instance method is called directly as it runs asynchronously
    await wrapper.instance().handleSubmit();

    expect(site.login).toHaveBeenCalled();
    expect(site.login).toHaveBeenCalledWith({
      email: 'test@email',
      password: 'test password',
    });
    expect(onSignIn).toHaveBeenCalled();
  });

  it('displays an error message if an invalid email address was supplied',
    async () => {
      fillInForm();
      typeEmail('notvalid');

      // This instance method is called directly as it runs asynchronously
      await wrapper.instance().handleSubmit();
      wrapper.update();
      expect(wrapper.find('Message')).toExist();
      expect(wrapper.find('Message')).toHaveProp(
        'content',
        'Supplied email address is not valid',
      );
    });

  it('displays an error message if credentials incorrect', async () => {
    fillInForm();
    site.login.mockImplementation(
      () => { throw new Error('Invalid credentials'); },
    );

    // This instance method is called directly as it runs asynchronously
    await wrapper.instance().handleSubmit();

    wrapper.update();

    expect(wrapper.find('Message')).toExist();
    expect(wrapper.find('Message')).toHaveProp(
      'content',
      'Incorrect credentials',
    );

    expect(onSignIn).not.toHaveBeenCalled();
  });

  it('trims the email address before logging in', async () => {
    fillInForm();
    typeEmail('  test@email  ');

    // This instance method is called directly as it runs asynchronously
    await wrapper.instance().handleSubmit();

    expect(site.login).toHaveBeenCalled();
    expect(site.login).toHaveBeenCalledWith({
      email: 'test@email',
      password: 'test password',
    });
  });
});
