ViewAjaxControllerTest.php

Same filename and directory in other branches
  1. 9 core/modules/views/tests/src/Unit/Controller/ViewAjaxControllerTest.php
  2. 8.9.x core/modules/views/tests/src/Unit/Controller/ViewAjaxControllerTest.php
  3. 11.x core/modules/views/tests/src/Unit/Controller/ViewAjaxControllerTest.php

Namespace

Drupal\Tests\views\Unit\Controller

File

core/modules/views/tests/src/Unit/Controller/ViewAjaxControllerTest.php

View source
<?php

declare (strict_types=1);
namespace Drupal\Tests\views\Unit\Controller;

use Drupal\Core\Render\RenderContext;
use Drupal\Core\Render\Renderer;
use Drupal\Core\Utility\CallableResolver;
use Drupal\Tests\UnitTestCase;
use Drupal\views\Ajax\ViewAjaxResponse;
use Drupal\views\Controller\ViewAjaxController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

/**
 * @coversDefaultClass \Drupal\views\Controller\ViewAjaxController
 * @group views
 */
class ViewAjaxControllerTest extends UnitTestCase {
    const USE_AJAX = TRUE;
    const USE_NO_AJAX = FALSE;
    
    /**
     * The mocked view entity storage.
     *
     * @var \Drupal\Core\Entity\EntityStorageInterface|\PHPUnit\Framework\MockObject\MockObject
     */
    protected $viewStorage;
    
    /**
     * The mocked executable factory.
     *
     * @var \Drupal\views\ViewExecutableFactory|\PHPUnit\Framework\MockObject\MockObject
     */
    protected $executableFactory;
    
    /**
     * The tested views ajax controller.
     *
     * @var \Drupal\views\Controller\ViewAjaxController
     */
    protected $viewAjaxController;
    
    /**
     * The mocked current path.
     *
     * @var \Drupal\Core\Path\CurrentPathStack|\PHPUnit\Framework\MockObject\MockObject
     */
    protected $currentPath;
    
    /**
     * The redirect destination.
     *
     * @var \Drupal\Core\Routing\RedirectDestinationInterface|\PHPUnit\Framework\MockObject\MockObject
     */
    protected $redirectDestination;
    
    /**
     * The renderer.
     *
     * @var \Drupal\Core\Render\RendererInterface|\PHPUnit\Framework\MockObject\MockObject
     */
    protected $renderer;
    
    /**
     * {@inheritdoc}
     */
    protected function setUp() : void {
        parent::setUp();
        $this->viewStorage = $this->createMock('Drupal\\Core\\Entity\\EntityStorageInterface');
        $this->executableFactory = $this->getMockBuilder('Drupal\\views\\ViewExecutableFactory')
            ->disableOriginalConstructor()
            ->getMock();
        $this->renderer = $this->createMock('\\Drupal\\Core\\Render\\RendererInterface');
        $this->renderer
            ->expects($this->any())
            ->method('renderRoot')
            ->willReturnCallback(function (array &$elements) {
            $elements['#attached'] = [];
            return $elements['#markup'] ?? '';
        });
        $this->renderer
            ->expects($this->any())
            ->method('executeInRenderContext')
            ->willReturnCallback(function (RenderContext $context, callable $callable) {
            return $callable();
        });
        $this->currentPath = $this->getMockBuilder('Drupal\\Core\\Path\\CurrentPathStack')
            ->disableOriginalConstructor()
            ->getMock();
        $this->redirectDestination = $this->createMock('\\Drupal\\Core\\Routing\\RedirectDestinationInterface');
        $this->viewAjaxController = new ViewAjaxController($this->viewStorage, $this->executableFactory, $this->renderer, $this->currentPath, $this->redirectDestination);
        $element_info_manager = $this->createMock('\\Drupal\\Core\\Render\\ElementInfoManagerInterface');
        $element_info_manager->expects($this->any())
            ->method('getInfo')
            ->with('status_messages')
            ->willReturn([]);
        $request_stack = new RequestStack();
        $request_stack->push(new Request());
        $this->renderer = new Renderer($this->createMock(CallableResolver::class), $this->createMock('\\Drupal\\Core\\Theme\\ThemeManagerInterface'), $element_info_manager, $this->createMock('\\Drupal\\Core\\Render\\PlaceholderGeneratorInterface'), $this->createMock('\\Drupal\\Core\\Render\\RenderCacheInterface'), $request_stack, [
            'required_cache_contexts' => [
                'languages:language_interface',
                'theme',
            ],
        ]);
        $container = new ContainerBuilder();
        $container->set('renderer', $this->renderer);
        \Drupal::setContainer($container);
    }
    
    /**
     * Tests missing view_name and view_display_id.
     */
    public function testMissingViewName() {
        $request = new Request();
        $this->expectException(NotFoundHttpException::class);
        $this->viewAjaxController
            ->ajaxView($request);
    }
    
    /**
     * Tests non-existent view with view_name and view_display_id.
     */
    public function testMissingView() {
        $request = new Request();
        $request->request
            ->set('view_name', 'test_view');
        $request->request
            ->set('view_display_id', 'page_1');
        $this->viewStorage
            ->expects($this->once())
            ->method('load')
            ->with('test_view')
            ->willReturn(FALSE);
        $this->expectException(NotFoundHttpException::class);
        $this->viewAjaxController
            ->ajaxView($request);
    }
    
    /**
     * Tests a view without having access to it.
     */
    public function testAccessDeniedView() {
        $request = new Request();
        $request->request
            ->set('view_name', 'test_view');
        $request->request
            ->set('view_display_id', 'page_1');
        $view = $this->getMockBuilder('Drupal\\views\\Entity\\View')
            ->disableOriginalConstructor()
            ->getMock();
        $this->viewStorage
            ->expects($this->once())
            ->method('load')
            ->with('test_view')
            ->willReturn($view);
        $executable = $this->getMockBuilder('Drupal\\views\\ViewExecutable')
            ->disableOriginalConstructor()
            ->getMock();
        $executable->expects($this->once())
            ->method('access')
            ->willReturn(FALSE);
        $this->executableFactory
            ->expects($this->once())
            ->method('get')
            ->with($view)
            ->willReturn($executable);
        $this->expectException(AccessDeniedHttpException::class);
        $this->viewAjaxController
            ->ajaxView($request);
    }
    
    /**
     * Tests a valid view without arguments pagers etc.
     */
    public function testAjaxView() {
        $request = new Request();
        $request->query
            ->set('view_name', 'test_view');
        $request->query
            ->set('view_display_id', 'page_1');
        $request->query
            ->set('view_path', '/test-page');
        $request->query
            ->set('_wrapper_format', 'ajax');
        $request->query
            ->set('ajax_page_state', 'drupal.settings[]');
        $request->query
            ->set('type', 'article');
        [
            $view,
            $executable,
        ] = $this->setupValidMocks();
        $this->redirectDestination
            ->expects($this->atLeastOnce())
            ->method('set')
            ->with('/test-page?type=article');
        $this->currentPath
            ->expects($this->once())
            ->method('setPath')
            ->with('/test-page', $request);
        $response = $this->viewAjaxController
            ->ajaxView($request);
        $this->assertTrue($response instanceof ViewAjaxResponse);
        $this->assertSame($response->getView(), $executable);
        $this->assertViewResultCommand($response);
    }
    
    /**
     * Tests a valid view with a view_path with no slash.
     */
    public function testAjaxViewViewPathNoSlash() {
        $request = new Request();
        $request->query
            ->set('view_name', 'test_view');
        $request->query
            ->set('view_display_id', 'page_1');
        $request->query
            ->set('view_path', 'test-page');
        $request->query
            ->set('_wrapper_format', 'ajax');
        $request->query
            ->set('ajax_page_state', 'drupal.settings[]');
        $request->query
            ->set('type', 'article');
        [
            $view,
            $executable,
        ] = $this->setupValidMocks();
        $this->redirectDestination
            ->expects($this->atLeastOnce())
            ->method('set')
            ->with('/test-page?type=article');
        $this->currentPath
            ->expects($this->once())
            ->method('setPath')
            ->with('/test-page');
        $response = $this->viewAjaxController
            ->ajaxView($request);
        $this->assertInstanceOf(ViewAjaxResponse::class, $response);
        $this->assertSame($response->getView(), $executable);
        $this->assertViewResultCommand($response);
    }
    
    /**
     * Tests a valid view without ajax enabled.
     */
    public function testAjaxViewWithoutAjax() {
        $request = new Request();
        $request->request
            ->set('view_name', 'test_view');
        $request->request
            ->set('view_display_id', 'page_1');
        $request->request
            ->set('view_path', '/test-page');
        $request->request
            ->set('_wrapper_format', 'ajax');
        $request->request
            ->set('ajax_page_state', 'drupal.settings[]');
        $request->request
            ->set('type', 'article');
        $this->setupValidMocks(static::USE_NO_AJAX);
        $this->expectException(AccessDeniedHttpException::class);
        $this->viewAjaxController
            ->ajaxView($request);
    }
    
    /**
     * Tests a valid view with arguments.
     */
    public function testAjaxViewWithArguments() {
        $request = new Request();
        $request->request
            ->set('view_name', 'test_view');
        $request->request
            ->set('view_display_id', 'page_1');
        $request->request
            ->set('view_args', 'arg1/arg2');
        [
            $view,
            $executable,
        ] = $this->setupValidMocks();
        $executable->expects($this->once())
            ->method('preview')
            ->with('page_1', [
            'arg1',
            'arg2',
        ]);
        $response = $this->viewAjaxController
            ->ajaxView($request);
        $this->assertInstanceOf(ViewAjaxResponse::class, $response);
        $this->assertViewResultCommand($response);
    }
    
    /**
     * Tests a valid view with arguments.
     */
    public function testAjaxViewWithEmptyArguments() {
        $request = new Request();
        $request->request
            ->set('view_name', 'test_view');
        $request->request
            ->set('view_display_id', 'page_1');
        // Simulate a request that has a second, empty argument.
        $request->request
            ->set('view_args', 'arg1/');
        [
            $view,
            $executable,
        ] = $this->setupValidMocks();
        $executable->expects($this->once())
            ->method('preview')
            ->with('page_1', $this->identicalTo([
            'arg1',
            NULL,
        ]));
        $response = $this->viewAjaxController
            ->ajaxView($request);
        $this->assertInstanceOf(ViewAjaxResponse::class, $response);
        $this->assertViewResultCommand($response);
    }
    
    /**
     * Tests a valid view with arguments.
     */
    public function testAjaxViewWithHtmlEntityArguments() {
        $request = new Request();
        $request->request
            ->set('view_name', 'test_view');
        $request->request
            ->set('view_display_id', 'page_1');
        $request->request
            ->set('view_args', 'arg1 &amp; arg2/arg3');
        [
            $view,
            $executable,
        ] = $this->setupValidMocks();
        $executable->expects($this->once())
            ->method('preview')
            ->with('page_1', [
            'arg1 & arg2',
            'arg3',
        ]);
        $response = $this->viewAjaxController
            ->ajaxView($request);
        $this->assertInstanceOf(ViewAjaxResponse::class, $response);
        $this->assertViewResultCommand($response);
    }
    
    /**
     * Tests a valid view with a pager.
     */
    public function testAjaxViewWithPager() {
        $request = new Request();
        $request->request
            ->set('view_name', 'test_view');
        $request->request
            ->set('view_display_id', 'page_1');
        $dom_id = $this->randomMachineName(20);
        $request->request
            ->set('view_dom_id', $dom_id);
        $request->request
            ->set('pager_element', '0');
        [
            $view,
            $executable,
        ] = $this->setupValidMocks();
        $display_handler = $this->getMockBuilder('Drupal\\views\\Plugin\\views\\display\\DisplayPluginBase')
            ->disableOriginalConstructor()
            ->getMock();
        $display_handler->expects($this->once())
            ->method('setOption')
            ->with($this->equalTo('pager_element'));
        $display_collection = $this->getMockBuilder('Drupal\\views\\DisplayPluginCollection')
            ->disableOriginalConstructor()
            ->getMock();
        $display_collection->expects($this->any())
            ->method('get')
            ->with('page_1')
            ->willReturn($display_handler);
        $executable->displayHandlers = $display_collection;
        $response = $this->viewAjaxController
            ->ajaxView($request);
        $this->assertInstanceOf(ViewAjaxResponse::class, $response);
        $commands = $this->getCommands($response);
        $this->assertEquals('scrollTop', $commands[0]['command']);
        $this->assertEquals('.js-view-dom-id-' . $dom_id, $commands[0]['selector']);
        $this->assertViewResultCommand($response, 1);
    }
    
    /**
     * Sets up a bunch of valid mocks like the view entity and executable.
     *
     * @param bool $use_ajax
     *   Whether the 'use_ajax' option is set on the view display. Defaults to
     *   using ajax (TRUE).
     *
     * @return array
     *   A pair of view storage entity and executable.
     */
    protected function setupValidMocks($use_ajax = self::USE_AJAX) {
        $view = $this->getMockBuilder('Drupal\\views\\Entity\\View')
            ->disableOriginalConstructor()
            ->getMock();
        $this->viewStorage
            ->expects($this->once())
            ->method('load')
            ->with('test_view')
            ->willReturn($view);
        $executable = $this->getMockBuilder('Drupal\\views\\ViewExecutable')
            ->disableOriginalConstructor()
            ->getMock();
        $executable->expects($this->once())
            ->method('access')
            ->willReturn(TRUE);
        $executable->expects($this->any())
            ->method('setDisplay')
            ->willReturn(TRUE);
        $executable->expects($this->atMost(1))
            ->method('preview')
            ->willReturn([
            '#markup' => 'View result',
        ]);
        $this->executableFactory
            ->expects($this->once())
            ->method('get')
            ->with($view)
            ->willReturn($executable);
        $display_handler = $this->getMockBuilder('Drupal\\views\\Plugin\\views\\display\\DisplayPluginBase')
            ->disableOriginalConstructor()
            ->getMock();
        // Ensure that the pager element is not set.
        $display_handler->expects($this->never())
            ->method('setOption');
        $display_handler->expects($this->any())
            ->method('ajaxEnabled')
            ->willReturn($use_ajax);
        $display_collection = $this->getMockBuilder('Drupal\\views\\DisplayPluginCollection')
            ->disableOriginalConstructor()
            ->getMock();
        $display_collection->expects($this->any())
            ->method('get')
            ->with('page_1')
            ->willReturn($display_handler);
        $executable->display_handler = $display_handler;
        $executable->displayHandlers = $display_collection;
        return [
            $view,
            $executable,
        ];
    }
    
    /**
     * Gets the commands entry from the response object.
     *
     * @param \Drupal\views\Ajax\ViewAjaxResponse $response
     *   The views ajax response object.
     *
     * @return mixed
     *   Returns the commands.
     */
    protected function getCommands(ViewAjaxResponse $response) {
        $reflection_property = new \ReflectionProperty('Drupal\\views\\Ajax\\ViewAjaxResponse', 'commands');
        $commands = $reflection_property->getValue($response);
        return $commands;
    }
    
    /**
     * Ensures that the main view content command is added.
     *
     * @param \Drupal\views\Ajax\ViewAjaxResponse $response
     *   The response object.
     * @param int $position
     *   The position where the view content command is expected.
     *
     * @internal
     */
    protected function assertViewResultCommand(ViewAjaxResponse $response, int $position = 0) : void {
        $commands = $this->getCommands($response);
        $this->assertEquals('insert', $commands[$position]['command']);
        $this->assertEquals('View result', $commands[$position]['data']);
    }

}

Classes

Title Deprecated Summary
ViewAjaxControllerTest @coversDefaultClass \Drupal\views\Controller\ViewAjaxController @group views

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.