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

import PasswordStrength from '../PasswordStrength';
import { Site } from '../../../tests/storyshare';
import { SignUpForm } from './SignUpForm';

describe('SignUpForm component', () => {
  let wrapper;
  const onSignUp = jest.fn();
  const site = new Site();

  site.login = jest.fn();

  beforeEach(() => {
    site.login.mockClear();
    onSignUp.mockClear();
    wrapper = shallow(
      <SignUpForm
        onSignUp={ onSignUp }
        privacyPolicyLink="privacy-policy-link"
        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 getFirst = () => wrapper.find('Input[name="firstName"]');

  const typeFirst = (value) =>
    getFirst().simulate('change', null, { name: 'firstName', value });

  const getLast = () => wrapper.find('Input[name="lastName"]');

  const typeLast = (value) =>
    getLast().simulate('change', null, { name: 'lastName', value });

  const getPrivacy = () => wrapper.find('FormCheckbox');

  const setPrivacy = (checked = true) =>
    getPrivacy().simulate('change', null, { checked });

  const fillInForm = () => {
    typeEmail('test@email');
    typePassword('a2Cdefgh');
    typeFirst('test first');
    typeLast('test last');
    setPrivacy();
    wrapper.find(PasswordStrength).simulate('validate', true);
  };

  const makeRequest = () => ({
    promise: jest.fn().mockReturnThis(),
    setParams: jest.fn().mockReturnThis(),
    track: jest.fn().mockReturnThis(),
    create: jest.fn().mockReturnValue(false),
  });

  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('renders a password strength indicator', () => {
    expect(wrapper.find(PasswordStrength)).toExist();
  });

  it('has a firstname input with correct placeholder', () => {
    expect(getFirst()).toExist();
    expect(getFirst()).toHaveProp('placeholder', 'Enter your first name');
  });

  it('allows a first name to be entered', () => {
    typeFirst('test first');
    expect(getFirst()).toHaveProp('value', 'test first');
  });

  it('has a lastname input with correct placeholder', () => {
    expect(getLast()).toExist();
    expect(getLast()).toHaveProp('placeholder', 'Enter your last name');
  });

  it('allows a last name to be entered', () => {
    typeLast('test last');
    expect(getLast()).toHaveProp('value', 'test last');
  });

  it('renders a checkbox for privacy policy agreement', () => {
    expect(getPrivacy()).toExist();
  });

  it('has a link to the privacy policy', () => {
    const label = shallow(getPrivacy().prop('label'));
    const link = label.find('a');
    expect(link).toExist();
    expect(link.children()).toHaveText('privacy policy');
    expect(link).toHaveProp('href', 'privacy-policy-link');
  });

  it('starts out with the checkbox unticked', () => {
    expect(getPrivacy()).toHaveProp('checked', false);
  });

  it('allows the checkbox to be toggled', () => {
    setPrivacy();
    expect(getPrivacy()).toHaveProp('checked', true);
    setPrivacy(false);
    expect(getPrivacy()).toHaveProp('checked', false);
  });

  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('uses the PasswordStrength to check password', () => {
    expect(wrapper.find(PasswordStrength)).toExist();
  });

  it('disables the button when the password is invalid', () => {
    fillInForm();
    wrapper.find(PasswordStrength).simulate('validate', false);
    expect(wrapper.find('FormButton')).toHaveProp('disabled', true);
  });

  it('enables button if password valid', () => {
    fillInForm();
    wrapper.find(PasswordStrength).simulate('validate', true);
    expect(wrapper.find('FormButton')).toHaveProp('disabled', false);
  });

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

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

  it('disables the button when the privacy checkbox is not ticked', () => {
    fillInForm();
    setPrivacy(false);
    expect(wrapper.find('FormButton')).toHaveProp('disabled', true);
  });

  it('calls site registration method', async () => {
    const request = makeRequest();
    request.create.mockReturnValueOnce({ token: 'success' });

    fillInForm();

    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.req).toHaveBeenCalledTimes(2);
    expect(site.req.mock.calls)
      .toEqual([['identityprovider/email/register'], ['users/me']]);

    expect(site.login).toHaveBeenCalled();
    expect(site.login).toHaveBeenCalledWith({ jwt: 'success' });

    expect(onSignUp).toHaveBeenCalled();
  });

  it('displays an error on site registration failure', async () => {
    const request = makeRequest();
    fillInForm();

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

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

    expect(site.req).toHaveBeenCalled();
    expect(site.login).not.toHaveBeenCalled();
    expect(onSignUp).not.toHaveBeenCalled();

    wrapper.update();
    expect(wrapper.find('Message')).toExist();
    expect(wrapper.find('Message')).toHaveProp(
      'content',
      'An error occurred during registration, please try again',
    );
  });

  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',
      );

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

  it('registers with firstname/lastname if supplied', async () => {
    const request = makeRequest();
    site.req.mockReturnValueOnce(request);
    fillInForm();

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

    expect(request.setParams).toHaveBeenCalledWith({
      email: 'test@email',
      first_name: 'test first',
      last_name: 'test last',
      password: 'a2Cdefgh',
    });
  });

  it('trims fields before submitting', async () => {
    const request = makeRequest();
    site.req.mockReturnValueOnce(request);
    fillInForm();
    typeEmail('  test@email  ');
    typePassword('  a2Cdefgh  ');
    typeFirst('  test first  ');
    typeLast('  test last  ');

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

    expect(request.setParams).toHaveBeenCalledWith({
      email: 'test@email',
      first_name: 'test first',
      last_name: 'test last',
      password: '  a2Cdefgh  ', // password should be passed unchanged
    });
  });
});
