/**
 * Internal dependencies
 */
import { emitEvent, emitEventWithAbort } from '../emitters';

describe( 'Testing emitters', () => {
	let observerMocks = {};
	let observerA;
	let observerB;
	let observerPromiseWithResolvedValue;
	beforeEach( () => {
		observerA = jest.fn().mockReturnValue( true );
		observerB = jest.fn().mockReturnValue( true );
		observerPromiseWithResolvedValue = jest.fn().mockResolvedValue( 10 );
		observerMocks = new Map( [
			[ 'observerA', { priority: 10, callback: observerA } ],
			[ 'observerB', { priority: 10, callback: observerB } ],
			[
				'observerReturnValue',
				{ priority: 10, callback: jest.fn().mockReturnValue( 10 ) },
			],
			[
				'observerPromiseWithReject',
				{
					priority: 10,
					callback: jest.fn().mockRejectedValue( 'an error' ),
				},
			],
			[
				'observerPromiseWithResolvedValue',
				{ priority: 10, callback: observerPromiseWithResolvedValue },
			],
		] );
	} );
	describe( 'Testing emitEvent()', () => {
		it( 'invokes all observers', async () => {
			const observers = { test: observerMocks };
			const response = await emitEvent( observers, 'test', 'foo' );
			expect( console ).toHaveErroredWith( 'an error' );
			expect( observerA ).toHaveBeenCalledTimes( 1 );
			expect( observerB ).toHaveBeenCalledWith( 'foo' );
			expect( response ).toBe( true );
		} );
	} );
	describe( 'Testing emitEventWithAbort()', () => {
		it( 'does not abort on any return value other than an object with a type property', async () => {
			observerMocks.delete( 'observerPromiseWithReject' );
			const observers = { test: observerMocks };
			const response = await emitEventWithAbort(
				observers,
				'test',
				'foo'
			);
			expect( console ).not.toHaveErrored();
			expect( observerB ).toHaveBeenCalledTimes( 1 );
			expect( observerPromiseWithResolvedValue ).toHaveBeenCalled();
			expect( response ).toBe( true );
		} );
		it( 'Aborts on a return value with an object that has a type property', async () => {
			const validObjectResponse = jest
				.fn()
				.mockReturnValue( { type: 'success' } );
			observerMocks.set( 'observerValidObject', {
				priority: 5,
				callback: validObjectResponse,
			} );
			observerMocks.delete( 'observerPromiseWithReject' );
			const observers = { test: observerMocks };
			const response = await emitEventWithAbort(
				observers,
				'test',
				'foo'
			);
			expect( console ).not.toHaveErrored();
			expect( validObjectResponse ).toHaveBeenCalledTimes( 1 );
			expect( observerPromiseWithResolvedValue ).not.toHaveBeenCalled();
			expect( response ).toEqual( { type: 'success' } );
		} );
		it( 'throws an error on an object returned from observer without a type property', async () => {
			const failingObjectResponse = jest.fn().mockReturnValue( {} );
			observerMocks.set( 'observerInvalidObject', {
				priority: 5,
				callback: failingObjectResponse,
			} );
			const observers = { test: observerMocks };
			const response = await emitEventWithAbort(
				observers,
				'test',
				'foo'
			);
			expect( console ).toHaveErrored();
			expect( failingObjectResponse ).toHaveBeenCalledTimes( 1 );
			expect( observerPromiseWithResolvedValue ).not.toHaveBeenCalled();
			expect( response ).toEqual( { type: 'error' } );
		} );
	} );
	describe( 'Test Priority', () => {
		it( 'executes observers in expected order by priority', async () => {
			const a = jest.fn();
			const b = jest.fn().mockReturnValue( { type: 'error' } );
			const observers = {
				test: new Map( [
					[ 'observerA', { priority: 200, callback: a } ],
					[ 'observerB', { priority: 10, callback: b } ],
				] ),
			};
			await emitEventWithAbort( observers, 'test', 'foo' );
			expect( console ).not.toHaveErrored();
			expect( b ).toHaveBeenCalledTimes( 1 );
			expect( a ).not.toHaveBeenCalled();
		} );
	} );
} );
