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

import PasswordStrength, { validators } from './PasswordStrength';

describe('PasswordStrength component', () => {
  const onValidate = jest.fn();

  let wrapper;

  beforeEach(() => {
    wrapper = shallow(
      <PasswordStrength
        password="fail"
        onValidate={ onValidate }
      />
    );
  });

  const listItems = () => wrapper.find('ListItem');

  const testFail = (item) => {
    expect(item).toHaveProp('style', { color: 'red' });
    expect(item.find('ListIcon')).toHaveProp('name', 'x');
  };

  const testPass = (item) => {
    expect(item).toHaveProp('style', { color: 'green' });
    expect(item.find('ListIcon')).toHaveProp('name', 'check');
  };

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

  it('starts out rendering 3 list items that are failing', () => {
    expect(listItems().length).toBe(3);
    listItems().forEach((ListItem) => {
      testFail(ListItem);
    });
  });

  it('renders 3 list with 3 conditions by default', () => {
    expect(wrapper.find('List')).toExist();
    expect(listItems().length).toBe(3);
    expect(listItems().at(0).find('ListContent').children().text())
      .toBe('Minimum 8 characters');
    expect(listItems().at(1).find('ListContent').children().text())
      .toBe('Minimum 1 number');
    expect(listItems().at(2).find('ListContent').children().text())
      .toBe('Contains a capital letter');
  });

  it('renders 3 list item with red color if password is empty ', () => {
    wrapper.setProps({ password: '' });

    expect(listItems().length).toBe(3);
    listItems().forEach((ListItem) => {
      testFail(ListItem);
    });
  });

  it('renders 2 list items if capital is false', () => {
    wrapper.setProps({ capital: false });

    expect(wrapper.find('ListItem').length).toBe(2);
  });

  it('renders 3 list item with color green if they all pass the test', () => {
    wrapper.setProps({ password: 'Asdfghjkl1' });

    expect(listItems().length).toBe(3);
    listItems().forEach((ListItem) => {
      testPass(ListItem);
    });
  });

  it('renders a 4th list item if special charater prop is true', () => {
    wrapper.setProps({ specialCharacters: true });

    expect(listItems().length).toBe(4);
    listItems().forEach((ListItem) => {
      testFail(ListItem);
    });
  });

  it('passes one test if capitals is true and capital letter exist', () => {
    wrapper.setProps({ capital: true, password: 'Asdfg' });

    testFail(listItems().at(0));
    testFail(listItems().at(1));
    testPass(listItems().at(2));
  });

  it('passes one test if number of numbers in password is minNumbers', () => {
    wrapper.setProps({ minNumbers: 3, password: 'afg123' });

    testFail(listItems().at(0));
    testPass(listItems().at(1));
    testFail(listItems().at(2));
  });

  it('renders "numbers" in content if minNumbers is more than 1', () => {
    wrapper.setProps({ minNumbers: 3, password: 'afg123' });

    testPass(listItems().at(1));
    expect(listItems().at(1).find('ListContent').children().text())
      .toBe('Minimum 3 numbers');
  });

  it('passes one test for password characters length is minCharacters', () => {
    wrapper.setProps({ minCharacters: 9, password: 'asdfghjkl' });

    testPass(listItems().at(0));
    testFail(listItems().at(1));
    testFail(listItems().at(2));
  });

  it('passes one test for special characters length if true and exists', () => {
    wrapper.setProps({ specialCharacters: true, password: 'asjkl.' });

    testFail(listItems().at(0));
    testFail(listItems().at(1));
    testFail(listItems().at(2));
    testPass(listItems().at(3));
  });

  it('passes down extra props to list', () => {
    wrapper.setProps({ extraProp: 'testProp' });

    expect(wrapper.find('List')).toHaveProp('extraProp', 'testProp');
  });

  it('avoids passing internal props through to ListItem', () => {
    expect(listItems().length).toBe(3);
    listItems().forEach((ListItem) => {
      expect(ListItem).not.toHaveProp('capital');
      expect(ListItem).not.toHaveProp('minCharacters');
      expect(ListItem).not.toHaveProp('minNumbers');
      expect(ListItem).not.toHaveProp('onValidate');
      expect(ListItem).not.toHaveProp('password');
      expect(ListItem).not.toHaveProp('specialCharacters');
    });
  });

  it('calls onValidate on mount', () => {
    onValidate.mockClear();
    shallow(<PasswordStrength password="" onValidate={ onValidate } />);
    expect(onValidate).toHaveBeenCalled();
  });

  it('calls onValidate with false if the password fails on mount', () => {
    onValidate.mockClear();
    shallow(<PasswordStrength password="fail" onValidate={ onValidate } />);
    expect(onValidate).toHaveBeenCalledWith(false);
  });

  it('calls onValidate with true if the password passes on mount', () => {
    onValidate.mockClear();
    shallow(
      <PasswordStrength password="Asdfghjk1" onValidate={ onValidate } />
    );
    expect(onValidate).toHaveBeenCalledWith(true);
  });

  it('calls onValidate with true if checks are disabled on mount', () => {
    onValidate.mockClear();
    shallow(
      <PasswordStrength
        capital={ false }
        minCharacters={ 0 }
        minNumbers={ 0 }
        onValidate={ onValidate }
        password="any"
        specialCharacters={ false }
      />
    );
    expect(onValidate).toHaveBeenCalled();
    expect(onValidate).toHaveBeenCalledWith(true);
  });

  it('copes if onValidate is not provided on mount', () => {
    shallow(<PasswordStrength password="goo" />);
    // Test succeeds if no errors
  });

  it('calls onValidate with true when a password changes to passing', () => {
    onValidate.mockClear();
    wrapper.setProps({ password: 'Asdfghjk1' });
    expect(onValidate).toHaveBeenCalled();
    expect(onValidate).toHaveBeenCalledWith(true);
  });

  it('calls onValidate with false when a password changes to failing', () => {
    wrapper.setProps({ password: 'Asdfghjk1' });
    onValidate.mockClear();

    wrapper.setProps({ password: 'fail' });

    expect(onValidate).toHaveBeenCalled();
    expect(onValidate).toHaveBeenCalledWith(false);
  });

  it('avoids calling onValidate when the password is the same', () => {
    onValidate.mockClear();
    wrapper.setProps({ password: 'fail' });
    expect(onValidate).not.toHaveBeenCalled();
  });

  it('avoids calling onValidate when the password is still failing', () => {
    onValidate.mockClear();
    wrapper.setProps({ password: 'failing' });
    expect(onValidate).not.toHaveBeenCalled();
  });

  it('avoids calling onValidate when the password is still passing', () => {
    wrapper.setProps({ password: 'Asdfghjk1' });
    onValidate.mockClear();

    wrapper.setProps({ password: 'Asdfghjk12' });

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

  it('calls onValidate with true if checks are disabled', () => {
    onValidate.mockClear();
    wrapper.setProps({
      capital: false,
      minCharacters: 0,
      minNumbers: 0,
      specialCharacters: false,
    });
    expect(onValidate).toHaveBeenCalled();
    expect(onValidate).toHaveBeenCalledWith(true);
  });

  it('copes if onValidate is not provided on update', () => {
    wrapper.setProps({ onValidate: undefined });
    // Test succeeds if no errors
  });
});

const testValidator = (
  validator,
  value,
  expectedName,
  enabledValues,
  disabledValues,
  passTestCases,
  failTestCases
) => {
  describe(`${validator} validator`, () => {
    const { isEnabled, name, validate } = validators[validator];

    it('returns expected name', () => {
      expect(name(value)).toEqual(expectedName);
    });

    it('knows when it should be enabled', () => {
      forEach((enabledValue) => {
        expect(isEnabled(enabledValue)).toBe(true);
      }, enabledValues);
    });

    it('knows when it should be disabled', () => {
      forEach((disabledValue) => {
        expect(isEnabled(disabledValue)).toBe(false);
      }, disabledValues);
    });

    it('validates true if password passed validate test', () => {
      forEach((testCase) => {
        expect(validate(testCase, value)).toBe(true);
      }, passTestCases);
    });

    it('validates false if password fails validate test', () => {
      forEach((testCase) => {
        expect(validate(testCase, value)).toBe(false);
      }, failTestCases);
    });
  });
};

testValidator(
  'minCharacters',
  7,
  'Minimum 7 characters',
  [1, 10],
  [0, -3, false],
  ['1234567', '12345678'],
  ['1', '123456']
);

testValidator(
  'minNumbers',
  2,
  'Minimum 2 numbers',
  [1, 10],
  [0, -3, false],
  ['abc12', 'abc123'],
  ['one1', 'none']
);

testValidator(
  'capital',
  true,
  'Contains a capital letter',
  [true],
  [false],
  ['Abcde', 'ABCDE'],
  ['hjhjh', 'jhjh898']
);

testValidator(
  'specialCharacters',
  true,
  'Must contain one of the special characters .!@#$%^&*()_+-=',
  [true],
  [false],
  ['abc..', 'abc!='],
  ['adcde', 'abcde898']
);
