'use strict';

jest.autoMockOff();

const StateMachine = require('..');

describe('StateMachine', () => {
  it('starts in initial state', () => {
    const stateMachine = StateMachine
      .getBuilder()
      .initialState('State')
      .build();

    expect(stateMachine.getCurrentState()).toBe('State');
  });

  it('throws if configuration is undefined', () => {
    expect(() => new StateMachine())
      .toThrow('Configuration must be specified.');
  });

  it('throws if configuration is null', () => {
    expect(() => new StateMachine())
      .toThrow('Configuration must be specified.');
  });

  it('throws if configuration is not an object', () => {
    expect(() => new StateMachine(100))
      .toThrow('Configuration must be an object.');
  });

  it('throws if initial state is undefined', () => {
    expect(() => StateMachine.getBuilder().build())
      .toThrow('Initial state must be specified.');
  });

  it('transitions to next state', () => {
    const stateMachine = StateMachine
      .getBuilder()
      .initialState('State1').on('event').transition('State2')
      .build()
      .handle('event');

      expect(stateMachine.getCurrentState()).toBe('State2');
  });

  it('selects first transition for which condition is true', () => {
    const stateMachine = StateMachine
      .getBuilder()
      .initialState('State1')
        .on('event')
          .transition('State2').withCondition(() => false)
          .transition('State3').withCondition(() => true)
      .build()
      .handle('event');

    expect(stateMachine.getCurrentState()).toBe('State3');
  });

  it('calls handlers with correct parameters', () => {
    const mocks = getHandlerMocks();

    StateMachine
      .getBuilder()
      .onStateEnter(mocks.stateEnterHandler)
      .onStateExit(mocks.stateExitHandler)
      .onTransition(mocks.transitionHandler)
      .initialState('State1')
        .on('event').transition('State2').withAction(mocks.transitionAction)
        .onExit(mocks.exitAction)
      .state('State2')
        .onEnter(mocks.entryAction)
      .build()
      .handle('event');

    expect(mocks.stateExitHandler).toBeCalledWith('State1');
    expect(mocks.exitAction).toBeCalledWith('State1');
    expect(mocks.transitionHandler).toBeCalledWith('State1', 'State2');
    expect(mocks.transitionAction).toBeCalledWith('State1', 'State2');
    expect(mocks.stateEnterHandler).toBeCalledWith('State2');
    expect(mocks.entryAction).toBeCalledWith('State2');
  });

  it('calls handlers in correct order', () => {
    const calledHandlers = [];

    StateMachine
      .getBuilder()
      .onStateEnter(() => calledHandlers.push('StateMachine: stateEnter'))
      .onStateExit(() => calledHandlers.push('StateMachine: stateExit'))
      .onTransition(() => calledHandlers.push('StateMachine: transition'))
      .initialState('State1')
        .on('event')
          .transition('State2')
          .withAction(() => calledHandlers.push('Transition action'))
        .onExit(() => calledHandlers.push('State1 exit action'))
      .state('State2')
        .onEnter(() => calledHandlers.push('State2 entry action'))
      .build()
      .handle('event');

    expect(calledHandlers).toEqual([
      'StateMachine: stateExit',
      'State1 exit action',
      'StateMachine: transition',
      'Transition action',
      'StateMachine: stateEnter',
      'State2 entry action'
    ]);
  });

  it('calls all handlers for self-transition', () => {
    const mocks = getHandlerMocks();

    StateMachine
      .getBuilder()
      .onStateEnter(mocks.stateEnterHandler)
      .onStateExit(mocks.stateExitHandler)
      .onTransition(mocks.transitionHandler)
      .initialState('State')
        .onEnter(mocks.entryAction)
        .onExit(mocks.exitAction)
        .on('event').selfTransition().withAction(mocks.transitionAction)
      .build()
      .handle('event');

    expect(mocks.stateExitHandler).toBeCalledWith('State');
    expect(mocks.exitAction).toBeCalledWith('State');
    expect(mocks.transitionHandler).toBeCalledWith('State', 'State');
    expect(mocks.transitionAction).toBeCalledWith('State', 'State');
    expect(mocks.stateEnterHandler).toBeCalledWith('State');
    expect(mocks.entryAction).toBeCalledWith('State');
  });

  it('calls only transition handlers for internal transition', () => {
    const mocks = getHandlerMocks();

    StateMachine
      .getBuilder()
      .onStateEnter(mocks.stateEnterHandler)
      .onStateExit(mocks.stateExitHandler)
      .onTransition(mocks.transitionHandler)
      .initialState('State')
        .onEnter(mocks.entryAction)
        .onExit(mocks.exitAction)
        .on('event').internalTransition().withAction(mocks.transitionAction)
      .build()
      .handle('event');

    expect(mocks.stateExitHandler).not.toBeCalled();
    expect(mocks.exitAction).not.toBeCalled();
    expect(mocks.transitionHandler).toBeCalledWith('State', 'State');
    expect(mocks.transitionAction).toBeCalledWith('State', 'State');
    expect(mocks.stateEnterHandler).not.toBeCalled();
    expect(mocks.entryAction).not.toBeCalled();
  });

  describe('canHandle', () => {
    it('returns true when event can be handled', () => {
      const stateMachine = StateMachine
        .getBuilder()
        .initialState('State1').on('event').transition('State2')
        .build();

        expect(stateMachine.canHandle('event')).toBe(true);
    });

    it('returns false when event cannot be handled', () => {
      const stateMachine = StateMachine
        .getBuilder()
        .initialState('State')
        .build();

        expect(stateMachine.canHandle('event')).toBe(false);
    });
  });
});

function getHandlerMocks() {
  return {
    stateEnterHandler: jest.genMockFn(),
    stateExitHandler: jest.genMockFn(),
    transitionHandler: jest.genMockFn(),
    entryAction: jest.genMockFn(),
    exitAction: jest.genMockFn(),
    transitionAction: jest.genMockFn()
  };
}

@@ -109,31 +109,41 @@
 it('
-starts in initial state
+can be created using build method
 ', (
@@ -310,32 +310,332 @@
 State');%0A  %7D);%0A%0A
+  it('can be created by passing configuration to its constructor', () =%3E %7B%0A    const config = StateMachine%0A      .getBuilder()%0A      .initialState('State')%0A      .getConfiguration();%0A%0A    const stateMachine = new StateMachine(config);%0A    expect(stateMachine.getCurrentState()).toBe('State');%0A  %7D);%0A%0A
   it('throws if 

Add tests for state machine creation