'use strict';

const hooks = require('../hooks');

describe('Hooks Module', () => {
  describe('Internals', () => {
    describe('createHook', () => {
      test(`It's possible to create a hook that has all the needed methods`, () => {
        const hook = hooks.internals.createHook();

        expect(hook).toHaveProperty('handlers', expect.any(Array));
        expect(hook).toHaveProperty('register', expect.any(Function));
        expect(hook).toHaveProperty('delete', expect.any(Function));
        expect(hook).toHaveProperty('call', expect.any(Function));
      });

      test('Call is not implemented by default', async () => {
        const hook = hooks.internals.createHook();
        const doCall = () => hook.call('foo');

        expect(doCall).toThrowError('Method not implemented');
      });
    });
  });

  describe('Hooks', () => {
    describe('Async Series Hook', () => {
      test('Can create an async series hook (from factory)', () => {
        const hook = hooks.createAsyncSeriesHook();

        expect(hook).toHaveProperty('register', expect.any(Function));
        expect(hook).toHaveProperty('call', expect.any(Function));
      });

      test('Running a hook without any handler does nothing', async () => {
        const ctx = { foo: 'bar' };
        const hook = hooks.createAsyncSeriesHook();

        await hook.call(ctx);

        expect(ctx).toMatchObject({ foo: 'bar' });
      });

      test('Running a hook with an handler can mutate the context', async () => {
        const ctx = { foo: 'bar' };
        const handler = jest.fn(context => {
          context.foo = 'foo';
        });

        const hook = hooks.createAsyncSeriesHook();

        hook.register(handler);

        await hook.call(ctx);

        expect(handler).toHaveBeenCalled();
        expect(ctx).toHaveProperty('foo', 'foo');
      });

      test('Running a hook with multiple handlers can mutate the hook in order', async () => {
        const ctx = { foo: [] };
        const handlers = [
          jest.fn(context => context.foo.push('foo')),
          jest.fn(context => context.foo.push('bar')),
        ];
        const hook = hooks.createAsyncSeriesHook();

        handlers.forEach(handler => hook.register(handler));

        await hook.call(ctx);

        handlers.forEach(handler => expect(handler).toHaveBeenCalled());
        expect(ctx).toHaveProperty('foo', ['foo', 'bar']);
      });
    });

    describe('Async Series Waterfall Hook', () => {
      test('Can create an async series waterfall hook (from factory)', () => {
        const hook = hooks.createAsyncSeriesWaterfallHook();

        expect(hook).toHaveProperty('register', expect.any(Function));
        expect(hook).toHaveProperty('call', expect.any(Function));
      });

      test('Running a hook without any handler returns the initial param (from hook.call)', async () => {
        const param = 'foo';
        const hook = hooks.createAsyncSeriesWaterfallHook();

        const result = await hook.call(param);

        expect(result).toEqual(param);
      });

      test('Running a hook with an handler can update the final value', async () => {
        const param = 'foo';
        const handler = jest.fn(param => [param, 'bar']);

        const hook = hooks.createAsyncSeriesWaterfallHook();

        hook.register(handler);

        const result = await hook.call(param);

        expect(handler).toHaveBeenCalled();
        expect(result).toEqual(['foo', 'bar']);
      });

      test(`Running a hook with multiple handlers means every handler will receive the previous one's result`, async () => {
        const param = 'foo';
        const handlers = [jest.fn(param => `${param}.bar`), jest.fn(param => `${param}.foobar`)];
        const hook = hooks.createAsyncSeriesWaterfallHook();

        handlers.forEach(handler => hook.register(handler));

        const result = await hook.call(param);

        handlers.forEach(handler => expect(handler).toHaveBeenCalled());
        expect(result).toEqual('foo.bar.foobar');
      });
    });

    describe('Async Parallel Hook', () => {
      test('Can create an async parallel hook (from factory)', () => {
        const hook = hooks.createAsyncParallelHook();

        expect(hook).toHaveProperty('register', expect.any(Function));
        expect(hook).toHaveProperty('call', expect.any(Function));
      });

      test('Running a hook without any handler returns an empty array (from hook.call)', async () => {
        const param = 'foo';
        const hook = hooks.createAsyncParallelHook();

        const result = await hook.call(param);

        expect(result).toEqual([]);
      });

      test('Running a hook with an handler returns its value', async () => {
        const param = 'test';
        const handler = jest.fn(param => `${param}.bar`);

        const hook = hooks.createAsyncParallelHook();

        hook.register(handler);

        const result = await hook.call(param);

        expect(handler).toHaveBeenCalled();
        expect(result).toEqual(['test.bar']);
      });

      test(`Running a hook with multiple handlers return every result as an array`, async () => {
        const param = 'test';
        const handlers = [jest.fn(param => `${param}.foo`), jest.fn(param => `${param}.bar`)];
        const hook = hooks.createAsyncParallelHook();

        handlers.forEach(handler => hook.register(handler));

        const result = await hook.call(param);

        handlers.forEach(handler => expect(handler).toHaveBeenCalled());
        expect(result).toEqual(['test.foo', 'test.bar']);
      });
    });
  });
});
