<?php
/**
 * CakePHP(tm) Tests <https://book.cakephp.org/view/1196/Testing>
 * Copyright 2005-2011, Cake Software Foundation, Inc. (https://cakefoundation.org)
 *
 * Licensed under The MIT License
 * Redistributions of files must retain the above copyright notice
 *
 * @copyright     Copyright 2005-2011, Cake Software Foundation, Inc. (https://cakefoundation.org)
 * @link          https://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests
 * @since         1.2.0
 * @license       https://opensource.org/licenses/mit-license.php MIT License
 */
namespace Cake\Test\TestCase\Controller;

use Cake\Controller\ComponentRegistry;
use Cake\Controller\Component\CookieComponent;
use Cake\Controller\Controller;
use Cake\Event\EventManager;
use Cake\TestSuite\TestCase;
use TestApp\Controller\ComponentTestController;
use TestApp\Controller\Component\AppleComponent;
use TestApp\Controller\Component\BananaComponent;
use TestApp\Controller\Component\ConfiguredComponent;
use TestApp\Controller\Component\OrangeComponent;
use TestApp\Controller\Component\SomethingWithCookieComponent;

/**
 * ComponentTest class
 */
class ComponentTest extends TestCase
{

    /**
     * setUp method
     *
     * @return void
     */
    public function setUp()
    {
        parent::setUp();
        static::setAppNamespace();
    }

    /**
     * test accessing inner components.
     *
     * @return void
     */
    public function testInnerComponentConstruction()
    {
        $Collection = new ComponentRegistry();
        $Component = new AppleComponent($Collection);

        $this->assertInstanceOf(OrangeComponent::class, $Component->Orange, 'class is wrong');
    }

    /**
     * test component loading
     *
     * @return void
     */
    public function testNestedComponentLoading()
    {
        $Collection = new ComponentRegistry();
        $Apple = new AppleComponent($Collection);

        $this->assertInstanceOf(OrangeComponent::class, $Apple->Orange, 'class is wrong');
        $this->assertInstanceOf(BananaComponent::class, $Apple->Orange->Banana, 'class is wrong');
        $this->assertEmpty($Apple->Session);
        $this->assertEmpty($Apple->Orange->Session);
    }

    /**
     * test that component components are not enabled in the collection.
     *
     * @return void
     */
    public function testInnerComponentsAreNotEnabled()
    {
        $mock = $this->getMockBuilder(EventManager::class)->getMock();
        $controller = new Controller();
        $controller->setEventManager($mock);

        $mock->expects($this->once())
            ->method('on')
            ->with($this->isInstanceOf(AppleComponent::class));

        $Collection = new ComponentRegistry($controller);
        $Apple = $Collection->load('Apple');

        $this->assertInstanceOf(OrangeComponent::class, $Apple->Orange, 'class is wrong');
    }

    /**
     * test a component being used more than once.
     *
     * @return void
     */
    public function testMultipleComponentInitialize()
    {
        $Collection = new ComponentRegistry();
        $Banana = $Collection->load('Banana');
        $Orange = $Collection->load('Orange');

        $this->assertSame($Banana, $Orange->Banana, 'Should be references');
        $Banana->testField = 'OrangeField';

        $this->assertSame($Banana->testField, $Orange->Banana->testField, 'References are broken');
    }

    /**
     * Test a duplicate component being loaded more than once with same and differing configurations.
     *
     * @return void
     */
    public function testDuplicateComponentInitialize()
    {
        $this->expectException(\RuntimeException::class);
        $this->expectExceptionMessage('The "Banana" alias has already been loaded with the following config:');
        $Collection = new ComponentRegistry();
        $Collection->load('Banana', ['property' => ['closure' => function () {
        }]]);
        $Collection->load('Banana', ['property' => ['closure' => function () {
        }]]);

        $this->assertInstanceOf(BananaComponent::class, $Collection->Banana, 'class is wrong');

        $Collection->load('Banana', ['property' => ['differs']]);
    }

    /**
     * Test mutually referencing components.
     *
     * @return void
     */
    public function testSomethingReferencingCookieComponent()
    {
        $Controller = new ComponentTestController();
        $Controller->loadComponent('SomethingWithCookie');
        $Controller->startupProcess();

        $this->assertInstanceOf(SomethingWithCookieComponent::class, $Controller->SomethingWithCookie);
        $this->assertInstanceOf(CookieComponent::class, $Controller->SomethingWithCookie->Cookie);
    }

    /**
     * Tests __debugInfo
     *
     * @return void
     */
    public function testDebugInfo()
    {
        $Collection = new ComponentRegistry();
        $Component = new AppleComponent($Collection);

        $expected = [
            'components' => [
                'Orange'
            ],
            'implementedEvents' => [
                'Controller.startup' => 'startup'
            ],
            '_config' => []
        ];
        $result = $Component->__debugInfo();
        $this->assertEquals($expected, $result);
    }

    /**
     * Tests null return for unknown magic properties.
     *
     * @return void
     */
    public function testMagicReturnsNull()
    {
        $Component = new AppleComponent(new ComponentRegistry());
        $this->assertNull($Component->ShouldBeNull);
    }

    /**
     * Tests config via constructor
     *
     * @return void
     */
    public function testConfigViaConstructor()
    {
        $Component = new ConfiguredComponent(new ComponentRegistry(), ['chicken' => 'soup']);
        $this->assertEquals(['chicken' => 'soup'], $Component->configCopy);
        $this->assertEquals(['chicken' => 'soup'], $Component->getConfig());
    }

    /**
     * Lazy load a component without events.
     *
     * @return void
     */
    public function testLazyLoading()
    {
        $Component = new ConfiguredComponent(new ComponentRegistry(), [], ['Apple', 'Banana', 'Orange']);
        $this->assertInstanceOf(AppleComponent::class, $Component->Apple, 'class is wrong');
        $this->assertInstanceOf(OrangeComponent::class, $Component->Orange, 'class is wrong');
        $this->assertInstanceOf(BananaComponent::class, $Component->Banana, 'class is wrong');
    }

    /**
     * Lazy load a component that does not exist.
     *
     * @return void
     */
    public function testLazyLoadingDoesNotExists()
    {
        $this->expectException(\Cake\Controller\Exception\MissingComponentException::class);
        $this->expectExceptionMessage('Component class YouHaveNoBananasComponent could not be found.');
        $Component = new ConfiguredComponent(new ComponentRegistry(), [], ['YouHaveNoBananas']);
        $bananas = $Component->YouHaveNoBananas;
    }

    /**
     * Lazy loaded components can have config options
     *
     * @return void
     */
    public function testConfiguringInnerComponent()
    {
        $Component = new ConfiguredComponent(new ComponentRegistry(), [], ['Configured' => ['foo' => 'bar']]);
        $this->assertInstanceOf(ConfiguredComponent::class, $Component->Configured, 'class is wrong');
        $this->assertNotSame($Component, $Component->Configured, 'Component instance was reused');
        $this->assertEquals(['foo' => 'bar', 'enabled' => false], $Component->Configured->getConfig());
    }

    /**
     * Test enabling events for lazy loaded components
     *
     * @return void
     */
    public function testEventsInnerComponent()
    {
        $eventManager = $this->getMockBuilder(EventManager::class)->getMock();
        $eventManager->expects($this->once())
            ->method('on')
            ->with($this->isInstanceOf(AppleComponent::class));

        $controller = new Controller();
        $controller->setEventManager($eventManager);

        $Collection = new ComponentRegistry($controller);

        $Component = new ConfiguredComponent($Collection, [], ['Apple' => ['enabled' => true]]);
        $this->assertInstanceOf(AppleComponent::class, $Component->Apple, 'class is wrong');
    }

    /**
     * Disabled events do not register for event listeners.
     *
     * @return void
     */
    public function testNoEventsInnerComponent()
    {
        $eventManager = $this->getMockBuilder(EventManager::class)->getMock();
        $eventManager->expects($this->never())->method('on');

        $controller = new Controller();
        $controller->setEventManager($eventManager);

        $Collection = new ComponentRegistry($controller);

        $Component = new ConfiguredComponent($Collection, [], ['Apple' => ['enabled' => false]]);
        $this->assertInstanceOf(AppleComponent::class, $Component->Apple, 'class is wrong');
    }
}
