import { shallow } from 'enzyme';
import React from 'react';
import { Form } from 'semantic-ui-react';

import Context from './Context';
import { ResetResponseForm } from './ResetResponseForm';

describe('ResetResponseForm component', () => {
  const onResetResponse = jest.fn();

  const request = {
    setParams: jest.fn().mockReturnThis(),
    promise: jest.fn().mockReturnThis(),
    create: jest.fn().mockResolvedValue(),
  };

  const site = {
    req: jest.fn().mockReturnValue(request),
  };

  let wrapper;

  beforeEach(() => {
    request.create.mockResolvedValue();

    wrapper = shallow(
      <ResetResponseForm
        onResetResponse={ onResetResponse }
        site={ site }
        token="test-token"
      >
        Content
      </ResetResponseForm>
    );
  });

  const getProvider = () => wrapper.find(Context.Provider);

  const getContext = (field) => getProvider().prop('value')[field];

  const typePassword = (value = 'p@55w0rd') =>
    getContext('handleChange')(null, { name: 'password', value });

  /**
   * Submits the form
   * We call the instance method directly so we can await the Promise
   * @returns {Promise<void>} resolves when complete
   */
  const doSubmit = () => wrapper.instance().handleSubmit();

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

  it('renders a Provider to pass the context', () => {
    expect(getProvider()).toExist();
    expect(getProvider()).toHaveProp('value');
  });

  it('starts out without any error state', () => {
    expect(getContext('error')).toBeDefined();
    expect(getContext('error')).toBeNull();
  });

  it('exposes a method to receive changes from fields', () => {
    expect(getContext('handleChange')).toBeDefined();
    expect(typeof getContext('handleChange')).toBe('function');
  });

  it('allows a password to be entered', () => {
    typePassword();
    expect(getContext('password')).toBe('p@55w0rd');
  });

  it('exposes a method to receive password validation information', () => {
    expect(getContext('handlePasswordValidation')).toBeDefined();
    expect(typeof getContext('handlePasswordValidation')).toBe('function');
  });

  it('exposes a method to check whether the form is complete', () => {
    expect(getContext('isFormValid')).toBeDefined();
    expect(typeof getContext('isFormValid')).toBe('function');
  });

  it('starts out incomplete', () => {
    expect(getContext('isFormValid')()).toBe(false);
  });

  it('is complete when a password is entered', () => {
    typePassword();
    expect(getContext('isFormValid')()).toBe(true);
  });

  it('is incomplete if the password is invalid', () => {
    typePassword();
    getContext('handlePasswordValidation')(false);
    expect(getContext('isFormValid')()).toBe(false);
  });

  it('can be marked incomplete', () => {
    typePassword();
    wrapper.setProps({ invalid: true });
    expect(getContext('isFormValid')()).toBe(false);
  });

  it('starts out with an empty password', () => {
    expect(getContext('password')).toBeDefined();
    expect(getContext('password')).toBe('');
  });

  it('renders a Form', () => {
    expect(wrapper.find('Form')).toExist();
  });

  it('passes props down to the Form', () => {
    wrapper.setProps({ size: 'small' });
    expect(wrapper.find('Form').children()).toHaveText('Content');
    expect(wrapper.find('Form')).toHaveProp('size', 'small');
  });

  it('avoids passing internal props to the Form', () => {
    wrapper.setProps({ invalid: true });
    const form = wrapper.find('Form');
    expect(form).not.toHaveProp('identityProvider');
    expect(form).not.toHaveProp('invalid');
    expect(form).not.toHaveProp('onResetResponse');
    expect(form).not.toHaveProp('site');
    expect(form).not.toHaveProp('token');
  });

  it('copies propTypes from the Form', () => {
    // Check a representative prop
    expect(ResetResponseForm.propTypes.size).toBe(Form.propTypes.size);
  });

  it('starts out without the Form error flag', () => {
    expect(wrapper.find('Form')).toHaveProp('error', false);
  });

  it('adds the error flag when requested to by a prop', () => {
    wrapper.setProps({ error: true });
    expect(wrapper.find('Form')).toHaveProp('error', true);
  });

  it('starts out without the Form loader', () => {
    expect(wrapper.find('Form')).toHaveProp('loading', false);
  });

  it('adds the loader when requested to by a prop', () => {
    wrapper.setProps({ loading: true });
    expect(wrapper.find('Form')).toHaveProp('loading', true);
  });

  it('listens to form submit events', () => {
    // We call the instance method directly in tests so need to check it's
    // bound to the correct event
    expect(wrapper.find('Form'))
      .toHaveProp('onSubmit', wrapper.instance().handleSubmit);
  });

  it('requests a password reset on submission', async () => {
    site.req.mockClear();
    request.setParams.mockClear();
    request.promise.mockClear();
    request.create.mockClear();
    typePassword();

    await doSubmit();

    expect(site.req).toHaveBeenCalled();
    expect(site.req)
      .toHaveBeenCalledWith('identityprovider/email/resetresponse');

    expect(request.setParams).toHaveBeenCalled();
    expect(request.setParams).toHaveBeenCalledWith({
      password: 'p@55w0rd',
      token: 'test-token',
    });
    expect(request.promise).toHaveBeenCalled();
    expect(request.create).toHaveBeenCalled();
  });

  it('does nothing if the form is marked incomplete', () => {
    site.req.mockClear();
    typePassword();
    wrapper.setProps({ invalid: true });

    doSubmit();

    expect(site.req).not.toHaveBeenCalled();
  });

  it('adds a loader to the form when submitting', () => {
    typePassword();
    doSubmit();
    expect(wrapper.find('Form')).toHaveProp('loading', true);
  });

  it('removes the loader when submission is completed', async () => {
    typePassword();
    await doSubmit();
    expect(wrapper.find('Form')).toHaveProp('loading', false);
  });

  it('does nothing if the form is incomplete', () => {
    site.req.mockClear();
    doSubmit();
    expect(site.req).not.toHaveBeenCalled();
  });

  it('allows the IDP to be customised', async () => {
    site.req.mockClear();
    wrapper.setProps({ identityProvider: 'test-idp' });
    typePassword();

    await doSubmit();

    expect(site.req)
      .toHaveBeenCalledWith('identityprovider/test-idp/resetresponse');
  });

  it('calls the event handler when submission is completed', async () => {
    onResetResponse.mockClear();
    typePassword();

    await doSubmit();

    expect(onResetResponse).toHaveBeenCalled();
  });

  it('copes if there is no reset response event handler', async () => {
    wrapper.setProps({ onResetResponse: undefined });
    typePassword();

    await doSubmit();

    // Test succeeds if no errors
  });

  it('adds an error state if an error occurs', async () => {
    request.create.mockRejectedValue();
    typePassword();

    await doSubmit();

    expect(getContext('error')).toBe('error');
  });

  it('adds the error flag if an error occurs', async () => {
    request.create.mockRejectedValue();
    typePassword();

    await doSubmit();

    expect(wrapper.find('Form')).toHaveProp('error', true);
  });

  it('removes the loader if an error occurs', async () => {
    request.create.mockRejectedValue();
    typePassword();

    await doSubmit();

    expect(wrapper.find('Form')).toHaveProp('loading', false);
  });
});
