Commit ead152da authored by David Suissa's avatar David Suissa
Browse files

Issue #3550652 by dydave: Automated tests: Simplified 'admin_toolbar_search'...

Issue #3550652 by dydave: Automated tests: Simplified 'admin_toolbar_search' FunctionalJavascript tests.
parent 46c14bb9
Loading
Loading
Loading
Loading
Loading
+73 −32
Original line number Diff line number Diff line
@@ -5,7 +5,7 @@ declare(strict_types=1);
namespace Drupal\Tests\admin_toolbar_search\FunctionalJavascript;

/**
 * Test the functionality of admin toolbar search.
 * Test the JS autocomplete search feature of the module.
 *
 * @group admin_toolbar
 * @group admin_toolbar_search
@@ -13,46 +13,87 @@ namespace Drupal\Tests\admin_toolbar_search\FunctionalJavascript;
class AdminToolbarSearchTest extends AdminToolbarSearchTestBase {

  /**
   * Tests search functionality without admin_toolbar_tools enabled.
   * Test the standalone search functionality, *without* 'admin_toolbar_tools'.
   *
   * Login as an admin user, wait for the search to load in the toolbar and
   * check several autocomplete suggestions: submit several search queries and
   * check the suggestions contain the expected results.
   *
   * @return void
   *   Nothing to return.
   */
  public function testToolbarSearch() {
    $search_field_tab = '#admin-toolbar-search-field-tab';
    $search_field_input = '#admin-toolbar-search-field-input';

    $this->drupalLogin($this->userWithAccess);
  public function testAdminToolbarSearch() {
    /** @var \Drupal\FunctionalJavascriptTests\JSWebAssert $assert_session */
    $assert_session = $this->assertSession();
    $assert_session->responseContains('admin_toolbar_search.css');
    $assert_session->responseContains('admin_toolbar_search.js');
    $assert_session->waitForElementVisible('css', $search_field_tab);
    $assert_session->waitForElementVisible('css', $search_field_input);

    $this->assertSuggestionContains('perform', 'admin/config/development/performance');
    $this->assertSuggestionContains('develop', 'admin/config/development/maintenance');
    $this->assertSuggestionContains('types', 'admin/structure/types');

    // Check several autocomplete suggestions, such as 'perform' or 'types'.
    // The permissions: 'access administration pages' and 'administer site
    // configuration', are required to test these routes.
    $search_queries = [
      // Check system routes.
      [
        // The 'query' is the text submitted in the autocomplete search field.
        'query' => 'basic',
        // The 'expected' is the string that should be contained in the HTML
        // returned in the expected suggestions (links, HTML markup, etc...).
        'expected' => 'admin/config/system/site-information',
        // A single result should be displayed.
        'expected_count' => 1,
      ],
      [
        'query' => 'perform',
        'expected' => 'admin/config/development/performance',
        // A single result should be displayed.
        'expected_count' => 1,
      ],
      [
        'query' => 'develop',
        'expected' => 'admin/config/development/maintenance',
        // Five results should be displayed.
        'expected_count' => (version_compare(\Drupal::VERSION, '10', '<')) ? 4 : 5,
      ],
      [
        'query' => 'file',
        'expected' => '/admin/config/media/file-system',
        // A single result should be displayed.
        'expected_count' => 1,
      ],
      [
        'query' => 'menus',
        // The 'node' module is required to test this link: Content types.
        'expected' => 'admin/structure/menu',
        // A single result should be displayed.
        'expected_count' => 1,
      ],
      // Check the links defined by the 'Admin Toolbar' module.
      [
        // cSpell:ignore toolb
        'query' => 'admin toolb',
        'expected' => 'admin/config/user-interface/admin-toolbar',
        // Two results should be displayed: admin toolbar and search settings.
        'expected_count' => 2,
      ],
      [
        'query' => 'admin toolbar sear',
        'expected' => 'admin/config/user-interface/admin-toolbar-search',
        // A single result should be displayed.
        'expected_count' => 1,
      ],
    ];

    // Execute all search queries and compare with the expected results.
    foreach ($search_queries as $search_query) {
      $this->assertSuggestionsContain($search_query['query'], $search_query['expected'], $search_query['expected_count']);
      // Check the expected url is found in the toolbar menu which is the reason
      // why it is displayed in the search suggestions.
      $this->assertAdminToolbarMenuLinkExists($search_query['expected']);
    }

  /**
   * Tests a user without the search permission can't use search.
   *
   * @return void
   *   Nothing to return.
   */
  public function testNoAccess() {
    $search_tab = '#admin-toolbar-search-tab';
    $search_toolbar_item = '#toolbar-item-administration-search';
    $search_tray = '#toolbar-item-administration-search-tray';
    // Test that the route 'admin_toolbar.search' returns an empty array, but
    // not an error, when module 'admin_toolbar_tools' is disabled.
    $this->drupalGet('admin/admin-toolbar-search');
    $assert_session->responseContains('[]');

    $this->drupalLogin($this->noAccessUser);
    $assert_session = $this->assertSession();
    $assert_session->responseNotContains('admin_toolbar_search.css');
    $assert_session->responseNotContains('admin_toolbar_search.js');
    $assert_session->elementNotExists('css', $search_tab);
    $assert_session->elementNotExists('css', $search_toolbar_item);
    $assert_session->elementNotExists('css', $search_tray);
  }

}
+143 −128
Original line number Diff line number Diff line
@@ -5,16 +5,31 @@ declare(strict_types=1);
namespace Drupal\Tests\admin_toolbar_search\FunctionalJavascript;

use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
use Drupal\system\Entity\Menu;
use Drupal\Tests\admin_toolbar\Traits\AdminToolbarHelperTestTrait;
use Drupal\admin_toolbar_search\Constants\AdminToolbarSearchConstants;

/**
 * Base class for testing the functionality of admin toolbar search.
 * Base class for testing the functionality of Admin Toolbar Search.
 *
 * @see \Drupal\Tests\admin_toolbar_search\FunctionalJavascript\AdminToolbarSearchTest
 *
 * @group admin_toolbar
 * @group admin_toolbar_search
 */
abstract class AdminToolbarSearchTestBase extends WebDriverTestBase {

  use AdminToolbarHelperTestTrait;

  /**
   * A user with access to the Admin Toolbar Search.
   *
   * An admin user with the permissions to access the site's admin backend form
   * pages and the 'use admin toolbar search' permission defined by the module.
   *
   * @var \Drupal\user\UserInterface
   */
  public $adminUser;

  /**
   * {@inheritdoc}
   */
@@ -26,185 +41,185 @@ abstract class AdminToolbarSearchTestBase extends WebDriverTestBase {
  protected static $modules = [
    'admin_toolbar',
    'admin_toolbar_search',
    'node',
    'media',
    'field_ui',
    // Required to test route: 'Menus'.
    'menu_ui',
    'block',
  ];

  /**
   * A user with the 'Use Admin Toolbar search' permission.
   *
   * @var \Drupal\user\UserInterface
   */
  protected $userWithAccess;

  /**
   * A test user without the 'Use Admin Toolbar search' permission.
   *
   * @var \Drupal\user\UserInterface
   */
  protected $noAccessUser;

  /**
   * {@inheritdoc}
   */
  public function setUp(): void {
    parent::setUp();

    $baby_names = [
      'ada' => 'Ada',
      'amara' => 'Amara',
      'amelia' => 'Amelia',
      'arabella' => 'Arabella',
      'asher' => 'Asher',
      'astrid' => 'Astrid',
      'atticus' => 'Atticus',
      'aurora' => 'Aurora',
      'ava' => 'Ava',
      'cora' => 'Cora',
      'eleanor' => 'Eleanor',
      'eloise' => 'Eloise',
      'felix' => 'Felix',
      'freya' => 'Freya',
      'genevieve' => 'Genevieve',
      'isla' => 'Isla',
      'jasper' => 'Jasper',
      'luna' => 'Luna',
      'maeve' => 'Maeve',
      'milo' => 'Milo',
      'nora' => 'Nora',
      'olivia' => 'Olivia',
      'ophelia' => 'Ophelia',
      'posie' => 'Posie',
      'rose' => 'Rose',
      'silas' => 'Silas',
      'soren' => 'Soren',
    ];

    foreach ($baby_names as $id => $label) {
      $menu = Menu::create([
        'id' => $id,
        'label' => $label,
      ]);
      $menu->save();
    }

    $this->drupalPlaceBlock('local_tasks_block');
    /* Setup users for the tests. */

    // Access to the toolbar and site's configuration admin pages, so basic
    // system config routes could be tested in the search autocomplete.
    $permissions = [
      'access toolbar',
      'administer menu',
      // Access to the menu links under '/admin/config/'.
      'access administration pages',
      'administer site configuration',
      'administer content types',
      // Permission needed to test links provided by the 'menu_ui' module.
      'administer menu',
      // This permission is needed to test the search autocomplete.
      'use admin toolbar search',
    ];
    $this->noAccessUser = $this->drupalCreateUser($permissions);
    $permissions[] = 'use admin toolbar search';
    $this->userWithAccess = $this->drupalCreateUser($permissions);

    // Create an admin user with access to the admin toolbar search.
    $this->adminUser = $this->drupalCreateUser($permissions);

    // Login with an admin user with access to the search in the toolbar.
    $this->drupalLogin($this->adminUser);

    // Wait for the necessary CSS and JS elements to load to test suggestions.
    $this->waitForAdminToolbarSearchVisible();
  }

  /**
   * Assert that the search suggestions contain a given string with given input.
   *
   * @param string $search
   *   The string to search for.
   * @param string $contains
   *   Some HTML that is expected to be within the suggestions element.
   * Helper function to wait for Admin Toolbar Search HTML IDs to be visible.
   *
   * @return void
   *   Nothing to return.
   */
  protected function assertSuggestionContains($search, $contains) {
    $this->resetSearch();
    $page = $this->getSession()->getPage();
    $page->fillField('admin-toolbar-search-field-input', $search);
    $this->getSession()->getDriver()->keyDown('//input[@id="admin-toolbar-search-field-input"]', ' ');
    $page->waitFor(3, function () use ($page) {
      return ($page->find('css', 'ul.ui-autocomplete')->isVisible() === TRUE);
    });
    $suggestions_markup = $page->find('css', 'ul.ui-autocomplete')->getHtml();
    $this->assertStringContainsString($contains, $suggestions_markup);
  protected function waitForAdminToolbarSearchVisible() {
    /** @var \Drupal\FunctionalJavascriptTests\JSWebAssert $assert_session */
    $assert_session = $this->assertSession();
    // Wait for the necessary CSS and JS elements to load to test suggestions.
    $assert_session->waitForElementVisible('css', '#' . AdminToolbarSearchConstants::ADMIN_TOOLBAR_SEARCH_HTML_IDS['search_tab']);
    $assert_session->waitForElementVisible('css', '#' . AdminToolbarSearchConstants::ADMIN_TOOLBAR_SEARCH_HTML_IDS['search_toolbar_item']);
    $assert_session->waitForElementVisible('css', '#' . AdminToolbarSearchConstants::ADMIN_TOOLBAR_SEARCH_HTML_IDS['search_tray']);
    $assert_session->waitForElementVisible('css', '#' . AdminToolbarSearchConstants::ADMIN_TOOLBAR_SEARCH_HTML_IDS['search_field_input']);
  }

  /**
   * Assert that the search suggestions does not contain a given string.
   * Assert the search suggestions for a given query contain expected string.
   *
   * Assert that the search suggestions does not contain a given string with a
   * given input.
   *
   * @param string $search
   *   The string to search for.
   * @param string $contains
   *   Some HTML that is not expected to be within the suggestions element.
   * @param string $query
   *   The string for which to search: the 'query' is the text submitted in the
   *   autocomplete search field.
   * @param string $expected_url
   *   The 'expected' is the string that should be contained in the HTML
   *   returned in the expected suggestions (links, HTML markup, etc...).
   * @param int $expected_count
   *   (optional) An integer to check the number of expected suggestions.
   * @param bool $expected_contains
   *   (optional) A flag indicating whether the expected url should be matched
   *   exactly (strict, by default) with an end of string operator ('$=') or
   *   loosely (contains) with a contains xpath expression.
   *
   * @return void
   *   Nothing to return.
   */
  protected function assertSuggestionNotContains($search, $contains) {
    $this->resetSearch();
    $page = $this->getSession()->getPage();
    $page->fillField('admin-toolbar-search-field-input', $search);
    $this->getSession()->getDriver()->keyDown('//input[@id="admin-toolbar-search-field-input"]', ' ');
    $page->waitFor(3, function () use ($page) {
      return ($page->find('css', 'ul.ui-autocomplete')->isVisible() === TRUE);
    });
    if ($page->find('css', 'ul.ui-autocomplete')->isVisible() === FALSE) {
      return;
    }
    else {
      $suggestions_markup = $page->find('css', 'ul.ui-autocomplete')->getHtml();
      $this->assertStringNotContainsString($contains, $suggestions_markup);
    }
  protected function assertSuggestionsContain($query, $expected_url, int $expected_count = 0, bool $expected_contains = FALSE) {
    $this->assetSuggestionsContainHelper($query, $expected_url, TRUE, $expected_count, $expected_contains);
  }

  /**
   * Search for an empty string to clear out the autocomplete suggestions.
   * Assert the search suggestions for a given query do not contain a string.
   *
   * @param string $query
   *   The string for which to search: the 'query' is the text submitted in the
   *   autocomplete search field.
   * @param string $expected_url
   *   The 'expected' is the string that should not be contained in the HTML
   *   returned in the expected suggestions (links, HTML markup, etc...).
   *
   * @return void
   *   Nothing to return.
   */
  protected function resetSearch() {
    $page = $this->getSession()->getPage();
    // Empty out the suggestions.
    $page->fillField('admin-toolbar-search-field-input', '');
    $this->getSession()->getDriver()->keyDown('//input[@id="admin-toolbar-search-field-input"]', ' ');
    $page->waitFor(3, function () use ($page) {
      return ($page->find('css', 'ul.ui-autocomplete')->isVisible() === FALSE);
    });
  protected function assertSuggestionsNotContain($query, $expected_url) {
    // By default, the last parameter is 'FALSE' to assert not contains.
    // Note: A search query can return results which could not include the
    // expected url.
    $this->assetSuggestionsContainHelper($query, $expected_url);
  }

  /**
   * Checks that there is a link with the specified url in the admin toolbar.
   * Helper function to assert search suggestions contain a string or not.
   *
   * @param string $url
   *   The url to assert exists in the admin menu.
   * @param string $query
   *   The string for which to search: the 'query' is the text submitted in the
   *   autocomplete search field.
   * @param string $expected_url
   *   The 'expected' is the string that should be contained in the HTML
   *   returned in the expected suggestions (links, HTML markup, etc...).
   * @param bool $assert_contains
   *   Set to 'FALSE', by default, to assert suggestions are *not* contained,
   *   or set to 'TRUE' to assert suggestions are contained with an exact match.
   * @param int $expected_count
   *   (optional) An integer to check the number of expected suggestions.
   * @param bool $expected_contains
   *   (optional) A flag indicating whether the expected url should be matched
   *   exactly (strict, by default) with an end of string operator ('$=') or
   *   loosely (contains) with a contains xpath expression.
   *
   * @return void
   *   Nothing to return.
   *
   * @throws \Behat\Mink\Exception\ElementNotFoundException
   * @throws \Behat\Mink\Exception\ExpectationException
   */
  protected function assertMenuHasHref($url) {
    $this->assertSession()
      ->elementExists('xpath', '//div[@id="toolbar-item-administration-tray"]//a[contains(@href, "' . $url . '")]');
  protected function assetSuggestionsContainHelper(string $query, string $expected_url, bool $assert_contains = FALSE, int $expected_count = 0, bool $expected_contains = FALSE) {
    /** @var \Behat\Mink\Session $test_session */
    $test_session = $this->getSession();

    // Reset the search field to ensure no previous search results are visible.
    $this->resetSearch();
    $page = $test_session->getPage();

    // Fill in the search input field with the query and trigger a search.
    $suggestions = $page->find('css', 'ul.ui-autocomplete');
    $page->fillField(AdminToolbarSearchConstants::ADMIN_TOOLBAR_SEARCH_HTML_IDS['search_field_input'], $query);
    // Add a space character by pressing the key down to trigger the search.
    $test_session->getDriver()->keyDown('//input[@id="' . AdminToolbarSearchConstants::ADMIN_TOOLBAR_SEARCH_HTML_IDS['search_field_input'] . '"]', ' ');

    // Wait for the suggestions to be visible and continue.
    $page->waitFor(3, function () use ($suggestions) {
      // If the search is empty, the list of suggestions should not be visible.
      return $suggestions->isVisible() === TRUE;
    });

    // Assert contains exact match, otherwise, assert not contains.
    if ($assert_contains && !$expected_contains) {
      // A CSS selector has to be used here, since xpath 1.0 does not support
      // 'ends-with'. Use the ends with CSS selector '$=' to allow absolute URLs
      // to be used as well and ensure the expected URL is an exact match.
      $exact_match_count = count($suggestions->findAll('css', 'a[href$="' . $expected_url . '"]'));
    }
    else {
      // Try to be more flexible by searching links containing the expected
      // value that should not be found in the results.
      $exact_match_count = count($suggestions->findAll('xpath', '//a[contains(@href, "' . $expected_url . '")]'));
    }
    // By default: assert contains, otherwise, assert not contains.
    $this->assertEquals($assert_contains, ($exact_match_count > 0));

    // Assert the number of expected suggestions.
    if ($expected_count > 0) {
      // Count the number of 'a' tags in the suggestions list.
      $suggestions_count = count($suggestions->findAll('xpath', '//a'));
      // Compare the number of suggestions with the expected count.
      $this->assertEquals($expected_count, $suggestions_count);
    }

  }

  /**
   * Checks that there is no link with the specified url in the admin toolbar.
   *
   * @param string $url
   *   The url to assert exists in the admin menu.
   * Search for an empty string to clear out the autocomplete suggestions.
   *
   * @return void
   *   Nothing to return.
   *
   * @throws \Behat\Mink\Exception\ExpectationException
   */
  protected function assertMenuDoesNotHaveHref($url) {
    $this->assertSession()
      ->elementNotExists('xpath', '//div[@id="toolbar-item-administration-tray"]//a[contains(@href, "' . $url . '")]');
  protected function resetSearch() {
    $page = $this->getSession()->getPage();
    // Empty out the suggestions.
    $page->fillField(AdminToolbarSearchConstants::ADMIN_TOOLBAR_SEARCH_HTML_IDS['search_field_input'], '');
    // Add a space character by pressing the key down to trigger the search.
    $this->getSession()->getDriver()->keyDown('//input[@id="' . AdminToolbarSearchConstants::ADMIN_TOOLBAR_SEARCH_HTML_IDS['search_field_input'] . '"]', ' ');
    $page->waitFor(3, function ($element) {
      // If the search is empty, the list of suggestions should not be visible.
      return ($element->find('css', 'ul.ui-autocomplete')->isVisible() === FALSE);
    });
  }

}
+0 −222

File deleted.

Preview size limit exceeded, changes collapsed.

+131 −0
Original line number Diff line number Diff line
<?php

declare(strict_types=1);

namespace Drupal\Tests\admin_toolbar_tools\FunctionalJavascript;

use Drupal\Tests\admin_toolbar_search\FunctionalJavascript\AdminToolbarSearchTestBase;

/**
 * Test the search integration of Admin Toolbar Tools with Admin Toolbar Search.
 *
 * @group admin_toolbar
 * @group admin_toolbar_tools
 *
 * @see admin_toolbar_search/js/admin_toolbar_search.js
 */
class AdminToolbarToolsSearchTest extends AdminToolbarSearchTestBase {

  /**
   * {@inheritdoc}
   */
  protected static $modules = [
    // Test admin_toolbar_search.
    'admin_toolbar_search',
    // This test is focused on the integration with Admin Toolbar Tools.
    'admin_toolbar_tools',
    // The following modules are required to test the 'ExtraLinks' feature.
    'menu_ui',
  ];

  /**
   * {@inheritdoc}
   */
  public function setUp(): void {
    // Call the parent setUp() to create the admin user and log in.
    parent::setUp();

    // Set 'max_bundle_number' to a value lower than the number of un-deletable
    // default core menus (5) to do a simple test/check.
    $max_bundle_number = 2;
    $this->config('admin_toolbar_tools.settings')
      ->set('max_bundle_number', $max_bundle_number)
      ->save();

    // Rebuild menu items: Admin Toolbar Tools requires a cache rebuild to
    // update the toolbar menu with the new config setting. See the submitForm()
    // method in AdminToolbarToolsSettingsForm.
    drupal_flush_all_caches();

    // Refresh the page to load the new config setting so the 'All menus' link
    // is available in the toolbar menu and search suggestions.
    $this->drupalGet('admin');
  }

  /**
   * Test the search functionality *with* 'admin_toolbar_tools' enabled.
   *
   * Test the JS code of the 'admin_toolbar_search' module that integrates with
   * 'admin_toolbar_tools' to load extra links via AJAX and include them in the
   * search suggestions.
   *
   * Test the integration with Admin Toolbar Tools:
   * - Ensure the extra links are loaded properly with an AJAX request to
   *   module's controller, triggered only once, when the search input field is
   *   focused for the first time.
   * - Ensure the search suggestions include the expected menu extra links
   *   collected with the 'SearchLinks' class.
   * - Check the search currently filters the links by label and url.
   *
   * @return void
   *   Nothing to return.
   */
  public function testAdminToolbarToolsSearch() {
    // Test the extra links AJAX request is working as expected for a simple
    // query with 'menu_ui' links. Use the 'account' menu links for testing
    // since it should always be present and the last of the default links.
    $test_menu_id = 'account';

    // Check several autocomplete suggestions, such as 'add', 'edit' or 'all'.
    // The permissions: 'access administration pages' and 'administer menu', are
    // required to test these routes.
    $search_queries = [
      // Check 'menu_ui' links, under 'Structure' > 'Menus'.
      [
        // Test the search suggestions for the 'account' menu links returns a
        // single link since the word 'edit' is found in filtered labels or urls
        // of the items loaded by SearchLinks.
        'query' => 'edit ' . $test_menu_id,
        'expected' => 'admin/structure/menu/manage/' . $test_menu_id,
        // A single result should be displayed.
        'expected_count' => 1,
      ],
      [
        // Test the 'add' link has a single suggestion and does not appear in
        // the toolbar menu links, since it was loaded via AJAX through
        // 'SearchLinks'.
        'query' => 'add menu ' . $test_menu_id,
        'expected' => 'admin/structure/menu/manage/' . $test_menu_id . '/add',
        // A single result should be displayed.
        'expected_count' => 1,
      ],
      [
        // Test the 'All menus' link only has a single suggestion, which means
        // the search is filtering on the link label and url.
        'query' => 'all menus',
        'expected' => 'admin/structure/menu',
        // A single result should be displayed.
        'expected_count' => 1,
      ],
    ];

    // Execute all search queries and compare with the expected results.
    foreach ($search_queries as $search_query) {
      $this->assertSuggestionsContain(
        $search_query['query'],
        $search_query['expected'],
        $search_query['expected_count']
      );
    }

    // Test the 'add' link has a single suggestion and does *not* appear in the
    // toolbar menu links, since it was loaded via AJAX through 'SearchLinks'.
    $this->assertAdminToolbarMenuLinkNotExists($test_menu_id . '/add');

    // Additional test to check query strings are not picked up in search
    // results. Expecting no result for this query.
    $this->assertSuggestionsNotContain('token', 'logout');

  }

}