class IntegrationTest in Search API 8
Same name in this branch
- 8 tests/src/Functional/IntegrationTest.php \Drupal\Tests\search_api\Functional\IntegrationTest
- 8 modules/search_api_db/tests/src/FunctionalJavascript/IntegrationTest.php \Drupal\Tests\search_api_db\FunctionalJavascript\IntegrationTest
- 8 modules/search_api_db/search_api_db_defaults/tests/src/Functional/IntegrationTest.php \Drupal\Tests\search_api_db_defaults\Functional\IntegrationTest
Tests the overall functionality of the Search API framework and admin UI.
@group search_api
Hierarchy
- class \Drupal\Tests\BrowserTestBase extends \PHPUnit\Framework\TestCase uses FunctionalTestSetupTrait, TestSetupTrait, AssertLegacyTrait, BlockCreationTrait, ConfigTestTrait, ContentTypeCreationTrait, NodeCreationTrait, PhpunitCompatibilityTrait, RandomGeneratorTrait, TestRequirementsTrait, UiHelperTrait, UserCreationTrait, XdebugRequestTrait
- class \Drupal\Tests\search_api\Functional\SearchApiBrowserTestBase
- class \Drupal\Tests\search_api\Functional\IntegrationTest uses PluginTestTrait, PostRequestIndexingTrait
- class \Drupal\Tests\search_api\Functional\SearchApiBrowserTestBase
Expanded class hierarchy of IntegrationTest
File
- tests/
src/ Functional/ IntegrationTest.php, line 27
Namespace
Drupal\Tests\search_api\FunctionalView source
class IntegrationTest extends SearchApiBrowserTestBase {
use PluginTestTrait;
use PostRequestIndexingTrait;
/**
* An admin user used for this test.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $adminUser2;
/**
* The ID of the backend plugin used for the test server.
*
* @var string
*/
protected $serverBackend = 'search_api_test';
/**
* The ID of the search server used for this test.
*
* @var string
*/
protected $serverId;
/**
* A storage instance for indexes.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $indexStorage;
/**
* {@inheritdoc}
*/
public static $modules = [
'node',
'search_api',
'search_api_test',
'search_api_test_no_ui',
'field_ui',
'link',
'image',
];
/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp();
$this->indexStorage = \Drupal::entityTypeManager()
->getStorage('search_api_index');
$permissions = [
'administer search_api',
'access administration pages',
'administer nodes',
'bypass node access',
'administer content types',
'administer node fields',
];
$this->adminUser = $this
->drupalCreateUser($permissions);
$this->adminUser2 = $this
->drupalCreateUser($permissions);
$this
->drupalLogin($this->adminUser);
}
/**
* Tests various operations via the Search API's admin UI.
*/
public function testFramework() {
$this
->createServer();
$this
->createServerDuplicate();
$this
->checkServerAvailability();
$this
->createIndex();
$this
->createIndexDuplicate();
$this
->editServer();
$this
->editIndex();
$this
->checkUserIndexCreation();
$this
->checkContentEntityTracking();
$this
->enableAllProcessors();
$this
->checkFieldLabels();
$this
->addFieldsToIndex();
$this
->checkDataTypesTable();
$this
->removeFieldsFromIndex();
$this
->checkReferenceFieldsNonBaseFields();
$this
->configureFilter();
$this
->configureFilterPage();
$this
->checkProcessorChanges();
$this
->changeProcessorFieldBoost();
$this
->setReadOnly();
$this
->disableEnableIndex();
$this
->changeIndexDatasource();
$this
->changeIndexServer();
$this
->checkIndexing();
$this
->checkIndexActions();
$this
->deleteServer();
}
/**
* Tests what happens when an index has an integer as id/label.
*
* This needs to be in a separate test because we want to test the content
* tracking behavior as well as the fields / processors editing and adding
* without messing with the other index. This test also makes sure that the
* server also has an integer as id/label.
*/
public function testIntegerIndex() {
Server::create([
'id' => 456,
'name' => 789,
'description' => 'WebTest server description',
'backend' => $this->serverBackend,
'backend_config' => [],
])
->save();
$this
->drupalCreateNode([
'type' => 'article',
]);
$this
->drupalCreateNode([
'type' => 'article',
]);
$this
->drupalGet('admin/config/search/search-api/add-index');
$this->indexId = 123;
$edit = [
'name' => $this->indexId,
'id' => $this->indexId,
'status' => 1,
'description' => 'test Index:: 123~',
'server' => 456,
'datasources[entity:node]' => TRUE,
];
$this
->submitForm($edit, 'Save');
$this
->assertSession()
->statusCodeEquals(200);
$this
->assertSession()
->pageTextContains('Please configure the used datasources.');
$this
->submitForm([], 'Save');
$this
->checkForMetaRefresh();
$this
->assertSession()
->statusCodeEquals(200);
$this
->assertSession()
->pageTextContains('The index was successfully saved.');
$this
->assertEquals(2, $this
->countTrackedItems());
$this
->enableAllProcessors();
$this
->checkFieldLabels();
$this
->addFieldsToIndex();
$this
->addFieldsWithDependenciesToIndex();
$this
->removeFieldsDependencies();
$this
->removeFieldsFromIndex();
$this
->checkUnsavedChanges();
$this
->configureFilter();
$this
->configureFilterPage();
$this
->checkProcessorChanges();
$this
->changeProcessorFieldBoost();
$this
->setReadOnly();
$this
->disableEnableIndex();
$this
->changeIndexDatasource();
$this
->changeIndexServer();
$this
->checkIndexing();
$this
->checkIndexActions();
}
/**
* Tests creating a search server via the UI.
*
* @param string $server_id
* The ID of the server to create.
*/
protected function createServer($server_id = '_test_server') {
$this->serverId = $server_id;
$server_name = 'Search API &{}<>! Server';
$server_description = 'A >server< used for testing &.';
$settings_path = 'admin/config/search/search-api/add-server';
$this
->drupalGet($settings_path);
$this
->assertSession()
->statusCodeEquals(200);
$this
->assertSession()
->pageTextNotContains('No UI backend');
$edit = [
'name' => '',
'status' => 1,
'description' => 'A server used for testing.',
'backend' => $this->serverBackend,
];
$this
->submitForm($edit, 'Save');
$this
->assertSession()
->pageTextContains(new FormattableMarkup('@name field is required.', [
'@name' => 'Server name',
]));
$edit = [
'name' => $server_name,
'status' => 1,
'description' => $server_description,
'backend' => $this->serverBackend,
];
$this
->submitForm($edit, 'Save');
$this
->assertSession()
->pageTextContains(new FormattableMarkup('@name field is required.', [
'@name' => 'Machine-readable name',
]));
$edit += [
'id' => $this->serverId,
];
$this
->configureBackendAndSave($edit);
$this
->assertSession()
->pageTextContains('The server was successfully saved.');
$this
->assertSession()
->addressEquals('admin/config/search/search-api/server/' . $this->serverId);
$this
->assertHtmlEscaped($server_name);
$this
->assertHtmlEscaped($server_description);
$this
->drupalGet('admin/config/search/search-api');
$this
->assertHtmlEscaped($server_name);
$this
->assertHtmlEscaped($server_description);
}
/**
* Lets derived backend integration tests fill their server create form.
*
* @param array $edit
* The common server form values so far.
*/
protected function configureBackendAndSave(array $edit) {
// Nothing to configure here for the test backend.
$this
->submitForm($edit, 'Save');
}
/**
* Tests creating a search server with an existing machine name.
*/
protected function createServerDuplicate() {
$server_add_page = 'admin/config/search/search-api/add-server';
$this
->drupalGet($server_add_page);
$edit = [
'name' => $this->serverId,
'id' => $this->serverId,
'backend' => $this->serverBackend,
];
// Try to submit an server with a duplicate machine name.
$this
->submitForm($edit, 'Save');
$this
->assertSession()
->pageTextContains('The machine-readable name is already in use. It must be unique.');
}
/**
* Tests creating a search index via the UI.
*/
protected function createIndex() {
$settings_path = 'admin/config/search/search-api/add-index';
$this->indexId = 'test_index';
$index_description = 'An >index< used for &! tęsting.';
$index_name = 'Search >API< test &!^* index';
$index_datasource = 'entity:node';
$this
->drupalGet($settings_path);
$this
->assertSession()
->statusCodeEquals(200);
$this
->assertSession()
->pageTextNotContains('No UI datasource');
$this
->assertSession()
->pageTextNotContains('No UI tracker');
// Make sure plugin labels are only escaped when necessary.
$this
->assertHtmlEscaped('"Test" tracker');
$this
->assertHtmlEscaped('"String label" test tracker');
$this
->assertHtmlEscaped('"Test" datasource');
// Make sure datasource and tracker plugin descriptions are displayed.
$dummy_index = Index::create();
foreach ([
'createDatasourcePlugins',
'createTrackerPlugins',
] as $method) {
/** @var \Drupal\search_api\Plugin\IndexPluginInterface[] $plugins */
$plugins = \Drupal::getContainer()
->get('search_api.plugin_helper')
->{$method}($dummy_index);
foreach ($plugins as $plugin) {
if ($plugin
->isHidden()) {
continue;
}
$description = Utility::escapeHtml($plugin
->getDescription());
$this
->assertSession()
->responseContains($description);
}
}
// Test form validation (required fields).
$edit = [
'status' => 1,
'description' => $index_description,
];
$this
->submitForm($edit, 'Save');
$this
->assertSession()
->pageTextContains('Index name field is required.');
$this
->assertSession()
->pageTextContains('Machine-readable name field is required.');
$this
->assertSession()
->pageTextContains('Datasources field is required.');
$edit = [
'name' => $index_name,
'id' => $this->indexId,
'status' => 1,
'description' => $index_description,
'server' => $this->serverId,
'datasources[' . $index_datasource . ']' => TRUE,
];
$this
->submitForm($edit, 'Save');
$this
->assertSession()
->pageTextContains('Please configure the used datasources.');
$this
->submitForm([], 'Save');
$this
->checkForMetaRefresh();
$this
->assertSession()
->pageTextContains('The index was successfully saved.');
$this
->assertSession()
->addressEquals($this
->getIndexPath());
$this
->assertHtmlEscaped($index_name);
$this
->drupalGet($this
->getIndexPath('edit'));
$this
->assertHtmlEscaped($index_name);
$index = $this
->getIndex(TRUE);
$this
->assertInstanceOf(IndexInterface::class, $index, 'Index was correctly created.');
$this
->assertEquals($edit['name'], $index
->label(), 'Name correctly inserted.');
$this
->assertEquals($edit['id'], $index
->id(), 'Index ID correctly inserted.');
$this
->assertTrue($index
->status(), 'Index status correctly inserted.');
$this
->assertEquals($edit['description'], $index
->getDescription(), 'Index ID correctly inserted.');
$this
->assertEquals($edit['server'], $index
->getServerId(), 'Index server ID correctly inserted.');
$this
->assertEquals($index_datasource, $index
->getDatasourceIds()[0], 'Index datasource id correctly inserted.');
// Test the "Save and add fields" button.
$index2_id = 'test_index2';
$edit['id'] = $index2_id;
unset($edit['server']);
$this
->drupalGet($settings_path);
$this
->submitForm($edit, 'Save and add fields');
$this
->assertSession()
->pageTextContains('Please configure the used datasources.');
$this
->submitForm([], 'Save and add fields');
$this
->assertSession()
->pageTextContains('The index was successfully saved.');
$this->indexStorage
->resetCache([
$index2_id,
]);
$index = $this->indexStorage
->load($index2_id);
$this
->assertSession()
->addressEquals($index
->toUrl('add-fields'));
$this
->drupalGet('admin/config/search/search-api');
$this
->assertHtmlEscaped($index_name);
$this
->assertHtmlEscaped($index_description);
}
/**
* Tests creating a search index with an existing machine name.
*/
protected function createIndexDuplicate() {
$index_add_page = 'admin/config/search/search-api/add-index';
$this
->drupalGet($index_add_page);
$edit = [
'name' => $this->indexId,
'id' => $this->indexId,
'server' => $this->serverId,
'datasources[entity:node]' => TRUE,
];
// Try to submit an index with a duplicate machine name.
$this
->submitForm($edit, 'Save');
$this
->assertSession()
->pageTextContains('The machine-readable name is already in use. It must be unique.');
// Try to submit an index with a duplicate machine name after form
// rebuilding via datasource submit.
$this
->submitForm($edit, 'datasources_configure');
$this
->submitForm($edit, 'Save');
$this
->assertSession()
->pageTextContains('The machine-readable name is already in use. It must be unique.');
// Try to submit an index with a duplicate machine name after form
// rebuilding via datasource submit using AJAX.
$this
->submitForm($edit, 'datasources_configure');
$this
->submitForm($edit, 'Save');
$this
->assertSession()
->pageTextContains('The machine-readable name is already in use. It must be unique.');
}
/**
* Tests whether editing a server works correctly.
*/
protected function editServer() {
$path = 'admin/config/search/search-api/server/' . $this->serverId . '/edit';
$this
->drupalGet($path);
// Check if it's possible to change the machine name.
$elements = $this
->xpath('//form[@id="search-api-server-edit-form"]/div[contains(@class, "form-item-id")]/input[@disabled]');
$this
->assertEquals(1, count($elements), 'Machine name cannot be changed.');
$tracked_items_before = $this
->countTrackedItems();
$edit = [
'name' => 'Test server',
];
$this
->submitForm($edit, 'Save');
/** @var \Drupal\search_api\IndexInterface $index */
$index = $this->indexStorage
->load($this->indexId);
$remaining = $index
->getTrackerInstance()
->getRemainingItemsCount();
$this
->assertEquals(0, $remaining, 'Index was not scheduled for re-indexing when saving its server.');
$this
->setReturnValue('backend', 'postUpdate', TRUE);
$this
->drupalGet($path);
$this
->submitForm($edit, 'Save');
$tracked_items = $this
->countTrackedItems();
$remaining = $index
->getTrackerInstance()
->getRemainingItemsCount();
$this
->assertEquals($tracked_items, $remaining, 'Backend could trigger re-indexing upon save.');
$this
->assertEquals($tracked_items_before, $tracked_items, 'Items are still tracked after re-indexing was triggered.');
}
/**
* Tests editing a search index via the UI.
*/
protected function editIndex() {
$tracked_items = $this
->countTrackedItems();
$edit_path = 'admin/config/search/search-api/index/' . $this->indexId . '/edit';
$this
->drupalGet($edit_path);
// Check if it's possible to change the machine name.
$elements = $this
->xpath('//form[@id="search-api-index-edit-form"]/div[contains(@class, "form-item-id")]/input[@disabled]');
$this
->assertEquals(1, count($elements), 'Machine name cannot be changed.');
// Test the AJAX functionality for configuring the tracker.
$edit = [
'tracker' => 'search_api_test',
];
$this
->submitForm($edit, 'tracker_configure');
$edit['tracker_config[foo]'] = 'foobar';
$this
->submitForm($edit, 'Save');
$this
->checkForMetaRefresh();
$this
->assertSession()
->statusCodeEquals(200);
$this
->assertSession()
->pageTextContains('The index was successfully saved.');
// Verify that everything was changed correctly.
$index = $this
->getIndex(TRUE);
$tracker = $index
->getTrackerInstance();
$this
->assertInstanceOf(TestTracker::class, $tracker, get_class($tracker));
$this
->assertInstanceOf(TestTracker::class, $tracker, 'Tracker was successfully switched.');
$configuration = [
'foo' => 'foobar',
'dependencies' => [],
];
$this
->assertEquals($configuration, $tracker
->getConfiguration(), 'Tracker config was successfully saved.');
$this
->assertEquals($tracked_items, $this
->countTrackedItems(), 'Items are still correctly tracked.');
// Revert back to the default tracker for the rest of the test.
$this
->drupalGet($edit_path);
$edit = [
'tracker' => 'default',
];
$this
->submitForm($edit, 'tracker_configure');
$edit['tracker_config[indexing_order]'] = 'fifo';
$this
->submitForm($edit, 'Save');
$this
->checkForMetaRefresh();
$this
->assertSession()
->statusCodeEquals(200);
$this
->assertSession()
->pageTextContains('The index was successfully saved.');
$index = $this
->getIndex(TRUE);
$tracker = $index
->getTrackerInstance();
$this
->assertInstanceOf(Basic::class, $tracker, 'Tracker was successfully switched.');
}
/**
* Tests that an entity without bundles can be used as a datasource.
*/
protected function checkUserIndexCreation() {
$edit = [
'name' => 'IndexName',
'id' => 'user_index',
'datasources[entity:user]' => TRUE,
];
$this
->drupalGet('admin/config/search/search-api/add-index');
$this
->submitForm($edit, 'Save');
$this
->assertSession()
->statusCodeEquals(200);
$this
->assertSession()
->pageTextContains('Please configure the used datasources.');
$this
->submitForm([], 'Save');
$this
->assertSession()
->statusCodeEquals(200);
$this
->assertSession()
->pageTextContains('The index was successfully saved.');
$this
->assertSession()
->pageTextContains($edit['name']);
}
/**
* Tests the server availability.
*/
protected function checkServerAvailability() {
$this
->drupalGet('admin/config/search/search-api/server/' . $this->serverId . '/edit');
$this
->drupalGet('admin/config/search/search-api');
$this
->assertSession()
->statusCodeEquals(200);
$this
->assertSession()
->responseContains('Enabled');
$this
->setReturnValue('backend', 'isAvailable', FALSE);
$this
->drupalGet('admin/config/search/search-api');
$this
->assertSession()
->statusCodeEquals(200);
$this
->assertSession()
->responseContains('Unavailable');
$this
->setReturnValue('backend', 'isAvailable', TRUE);
}
/**
* Tests whether the tracking information is properly maintained.
*
* Will especially test the bundle option of the content entity datasource.
*/
protected function checkContentEntityTracking() {
// Initially there should be no tracked items, because there are no nodes.
$tracked_items = $this
->countTrackedItems();
$this
->assertEquals(0, $tracked_items, 'No items are tracked yet.');
// Add two articles and two pages (one of them "invisible" to Search API).
$article1 = $this
->drupalCreateNode([
'type' => 'article',
]);
$this
->drupalCreateNode([
'type' => 'article',
]);
$this
->drupalCreateNode([
'type' => 'page',
]);
$page2 = Node::create([
'body' => [
[
'value' => $this
->randomMachineName(32),
'format' => filter_default_format(),
],
],
'title' => $this
->randomMachineName(8),
'type' => 'page',
'uid' => \Drupal::currentUser()
->id(),
]);
$page2->search_api_skip_tracking = TRUE;
$page2
->save();
// The 3 new nodes without "search_api_skip_tracking" property set should
// have been added to the tracking table immediately.
$tracked_items = $this
->countTrackedItems();
$this
->assertEquals(3, $tracked_items, 'Three items are tracked.');
$this
->getCalledMethods('backend');
$page2
->delete();
$methods = $this
->getCalledMethods('backend');
$this
->assertEquals([], $methods, 'Tracking of a delete operation could successfully be prevented.');
// Test disabling the index.
$settings_path = $this
->getIndexPath('edit');
$this
->drupalGet($settings_path);
$edit = [
'status' => FALSE,
'datasource_configs[entity:node][bundles][default]' => 0,
'datasource_configs[entity:node][bundles][selected][article]' => FALSE,
'datasource_configs[entity:node][bundles][selected][page]' => FALSE,
];
$this
->submitForm($edit, 'Save');
$this
->assertSession()
->pageTextContains('The index was successfully saved.');
$tracked_items = $this
->countTrackedItems();
$this
->assertEquals(0, $tracked_items, 'No items are tracked.');
// Test re-enabling the index.
$this
->drupalGet($settings_path);
$edit = [
'status' => TRUE,
'datasource_configs[entity:node][bundles][default]' => 0,
'datasource_configs[entity:node][bundles][selected][article]' => TRUE,
'datasource_configs[entity:node][bundles][selected][page]' => TRUE,
];
$this
->submitForm($edit, 'Save');
$this
->checkForMetaRefresh();
$this
->assertSession()
->pageTextContains('The index was successfully saved.');
$tracked_items = $this
->countTrackedItems();
$this
->assertEquals(3, $tracked_items, 'Three items are tracked.');
// Uncheck "default" and don't select any bundles. This should remove all
// items from the tracking table.
$edit = [
'status' => TRUE,
'datasource_configs[entity:node][bundles][default]' => 0,
'datasource_configs[entity:node][bundles][selected][article]' => FALSE,
'datasource_configs[entity:node][bundles][selected][page]' => FALSE,
];
$this
->drupalGet($settings_path);
$this
->submitForm($edit, 'Save');
$this
->checkForMetaRefresh();
$this
->assertSession()
->pageTextContains('The index was successfully saved.');
$tracked_items = $this
->countTrackedItems();
$this
->assertEquals(0, $tracked_items, 'No items are tracked.');
// Leave "default" unchecked and select the "article" bundle. This should
// re-add the two articles to the tracking table.
$edit = [
'status' => TRUE,
'datasource_configs[entity:node][bundles][default]' => 0,
'datasource_configs[entity:node][bundles][selected][article]' => TRUE,
'datasource_configs[entity:node][bundles][selected][page]' => FALSE,
];
$this
->drupalGet($settings_path);
$this
->submitForm($edit, 'Save');
$this
->checkForMetaRefresh();
$this
->assertSession()
->pageTextContains('The index was successfully saved.');
$tracked_items = $this
->countTrackedItems();
$this
->assertEquals(2, $tracked_items, 'Two items are tracked.');
// Leave "default" unchecked and select only the "page" bundle. This should
// result in only the page being present in the tracking table.
$edit = [
'status' => TRUE,
'datasource_configs[entity:node][bundles][default]' => 0,
'datasource_configs[entity:node][bundles][selected][article]' => FALSE,
'datasource_configs[entity:node][bundles][selected][page]' => TRUE,
];
$this
->drupalGet($settings_path);
$this
->submitForm($edit, 'Save');
$this
->checkForMetaRefresh();
$this
->assertSession()
->pageTextContains('The index was successfully saved.');
$tracked_items = $this
->countTrackedItems();
$this
->assertEquals(1, $tracked_items, 'One item is tracked.');
// Check "default" again and select the "article" bundle. This shouldn't
// change the tracking table, which should still only contain the page.
$edit = [
'status' => TRUE,
'datasource_configs[entity:node][bundles][default]' => 1,
'datasource_configs[entity:node][bundles][selected][article]' => TRUE,
'datasource_configs[entity:node][bundles][selected][page]' => FALSE,
];
$this
->drupalGet($settings_path);
$this
->submitForm($edit, 'Save');
$this
->checkForMetaRefresh();
$this
->assertSession()
->pageTextContains('The index was successfully saved.');
$tracked_items = $this
->countTrackedItems();
$this
->assertEquals(1, $tracked_items, 'One item is tracked.');
// Leave "default" checked but now select only the "page" bundle. This
// should result in only the articles being tracked.
$edit = [
'status' => TRUE,
'datasource_configs[entity:node][bundles][default]' => 1,
'datasource_configs[entity:node][bundles][selected][article]' => FALSE,
'datasource_configs[entity:node][bundles][selected][page]' => TRUE,
];
$this
->drupalGet($settings_path);
$this
->submitForm($edit, 'Save');
$this
->checkForMetaRefresh();
$this
->assertSession()
->pageTextContains('The index was successfully saved.');
$tracked_items = $this
->countTrackedItems();
$this
->assertEquals(2, $tracked_items, 'Two items are tracked.');
// Index items, then check whether updating an article is handled correctly.
$this
->triggerPostRequestIndexing();
$this
->getCalledMethods('backend');
$article1
->save();
$methods = $this
->getCalledMethods('backend');
$this
->assertEquals([], $methods, 'No items were indexed right away (before end of page request).');
$this
->triggerPostRequestIndexing();
$methods = $this
->getCalledMethods('backend');
$this
->assertEquals([
'indexItems',
], $methods, 'Update successfully tracked.');
$article1->search_api_skip_tracking = TRUE;
$article1
->save();
$methods = $this
->getCalledMethods('backend');
$this
->assertEquals([], $methods, 'Tracking of entity update successfully prevented.');
unset($article1->search_api_skip_tracking);
// Delete an article. That should remove it from the item table.
$article1
->delete();
$tracked_items = $this
->countTrackedItems();
$this
->assertEquals(1, $tracked_items, 'One item is tracked.');
}
/**
* Counts the number of tracked items in the test index.
*
* @return int
* The number of tracked items in the test index.
*/
protected function countTrackedItems() {
return $this
->getIndex()
->getTrackerInstance()
->getTotalItemsCount();
}
/**
* Counts the number of unindexed items in the test index.
*
* @return int
* The number of unindexed items in the test index.
*/
protected function countRemainingItems() {
return $this
->getIndex()
->getTrackerInstance()
->getRemainingItemsCount();
}
/**
* Counts the number of items indexed on the server for the test index.
*
* @return int
* The number of items indexed on the server for the test index.
*/
protected function countItemsOnServer() {
$key = 'search_api_test.backend.indexed.' . $this->indexId;
return count(\Drupal::state()
->get($key, []));
}
/**
* Enables all processors.
*/
public function enableAllProcessors() {
$this
->drupalGet($this
->getIndexPath('processors'));
$edit = [
'status[content_access]' => 1,
'status[entity_status]' => 1,
'status[highlight]' => 1,
'status[html_filter]' => 1,
'status[ignorecase]' => 1,
'status[ignore_character]' => 1,
'status[stopwords]' => 1,
'status[tokenizer]' => 1,
'status[transliteration]' => 1,
];
$this
->submitForm($edit, 'Save');
$this
->assertSession()
->pageTextContains('The indexing workflow was successfully edited.');
}
/**
* Tests that field labels are always properly escaped.
*/
protected function checkFieldLabels() {
$content_type_name = '&%@Content()_=';
// Add a new content type with funky chars.
$edit = [
'name' => $content_type_name,
'type' => '_content_',
];
$this
->drupalGet('admin/structure/types/add');
$this
->assertSession()
->statusCodeEquals(200);
$this
->submitForm($edit, 'Save and manage fields');
// Make sure this worked.
$entity_bundle_info = $this->container
->get('entity_type.bundle.info');
$entity_bundle_info
->clearCachedBundles();
$bundles = $entity_bundle_info
->getBundleInfo('node');
$this
->assertArrayHasKey('_content_', $bundles);
// Add a field to that content type with funky chars.
$field_name = '^6%{[*>.<"field';
FieldStorageConfig::create([
'field_name' => 'field__field_',
'type' => 'string',
'entity_type' => 'node',
])
->save();
FieldConfig::create([
'field_name' => 'field__field_',
'entity_type' => 'node',
'bundle' => '_content_',
'label' => $field_name,
])
->save();
$url_options['query']['datasource'] = 'entity:node';
$this
->drupalGet($this
->getIndexPath('fields/add/nojs'), $url_options);
$this
->assertHtmlEscaped($field_name);
$this
->assertSession()
->responseContains('(<code>field__field_</code>)');
$this
->addField('entity:node', 'field__field_', $field_name);
$this
->drupalGet($this
->getIndexPath('fields'));
$this
->assertHtmlEscaped($field_name);
// Also check data type labels/descriptions.
$this
->assertHtmlEscaped('"Test" data type');
$this
->assertSession()
->responseContains('Dummy <em>data type</em> implementation');
$this
->submitForm([], 'Save changes');
$edit = [
'datasource_configs[entity:node][bundles][default]' => 1,
];
$this
->drupalGet($this
->getIndexPath('edit'));
$this
->assertHtmlEscaped($content_type_name);
$this
->submitForm($edit, 'Save');
$this
->assertSession()
->pageTextContains('The index was successfully saved.');
$this
->addField(NULL, 'rendered_item', 'Rendered HTML output');
$this
->assertHtmlEscaped($content_type_name);
$this
->submitForm([], 'Save');
$this
->assertSession()
->pageTextContains(' The field configuration was successfully saved.');
$this
->addField(NULL, 'aggregated_field', 'Aggregated field');
$this
->assertHtmlEscaped($field_name);
$this
->submitForm([
'fields[entity:node/field__field_]' => TRUE,
], 'Save');
$this
->assertSession()
->pageTextContains(' The field configuration was successfully saved.');
}
/**
* Tests whether adding fields to the index works correctly.
*/
protected function addFieldsToIndex() {
// Make sure that hidden properties are not displayed.
$url_options['query']['datasource'] = '';
$this
->drupalGet($this
->getIndexPath('fields/add/nojs'), $url_options);
$this
->assertSession()
->pageTextNotContains('Node access information');
$fields = [
'nid' => 'ID',
'title' => 'Title',
'body' => 'Body',
'revision_log' => 'Revision log message',
'uid:entity:name' => 'Authored by » User » Name',
];
foreach ($fields as $property_path => $label) {
$this
->addField('entity:node', $property_path, $label);
}
$this
->assertSession()
->pageTextNotContains('No UI data type');
$index = $this
->getIndex(TRUE);
$fields = $index
->getFields();
$this
->assertArrayNotHasKey('nid', $fields, 'Field changes have not been persisted.');
$this
->drupalGet($this
->getIndexPath('fields'));
$this
->submitForm([], 'Save changes');
$this
->assertSession()
->pageTextContains('The changes were successfully saved.');
$index = $this
->getIndex(TRUE);
$fields = $index
->getFields();
$this
->assertArrayHasKey('nid', $fields, 'nid field is indexed.');
// Ensure that we aren't offered to index properties of the "Content type"
// property.
$path = $this
->getIndexPath('fields/add/nojs');
$url_options = [
'query' => [
'datasource' => 'entity:node',
],
];
$this
->drupalGet($path, $url_options);
$this
->assertSession()
->responseNotContains('property_path=type');
// The "Content access" processor correctly marked fields as locked.
$this
->assertArrayHasKey('uid', $fields, 'uid field is indexed.');
$this
->assertTrue($fields['uid']
->isIndexedLocked(), 'uid field is locked.');
$this
->assertTrue($fields['uid']
->isTypeLocked(), 'uid field is type-locked.');
$this
->assertEquals('integer', $fields['uid']
->getType(), 'uid field has type integer.');
$this
->assertArrayHasKey('status', $fields, 'status field is indexed.');
$this
->assertTrue($fields['status']
->isIndexedLocked(), 'status field is locked.');
$this
->assertTrue($fields['status']
->isTypeLocked(), 'status field is type-locked.');
$this
->assertEquals('boolean', $fields['status']
->getType(), 'status field has type boolean.');
// Check that a 'parent_data_type.data_type' Search API field type => data
// type mapping relationship works.
$this
->assertArrayHasKey('body', $fields, 'body field is indexed.');
$this
->assertEquals('text', $fields['body']
->getType(), 'Complex field mapping relationship works.');
// Test renaming of fields.
$edit = [
'fields[title][title]' => 'new_title',
'fields[title][id]' => 'new_id',
'fields[title][type]' => 'text',
'fields[title][boost]' => Utility::formatBoostFactor(21),
'fields[revision_log][type]' => 'search_api_test',
];
$this
->drupalGet($this
->getIndexPath('fields'));
$this
->submitForm($edit, 'Save changes');
$this
->assertSession()
->pageTextContains('The changes were successfully saved.');
$index = $this
->getIndex(TRUE);
$fields = $index
->getFields();
$this
->assertArrayHasKey('new_id', $fields, 'title field is indexed.');
$this
->assertEquals($edit['fields[title][title]'], $fields['new_id']
->getLabel(), 'title field title is saved.');
$this
->assertEquals($edit['fields[title][id]'], $fields['new_id']
->getFieldIdentifier(), 'title field id value is saved.');
$this
->assertEquals($edit['fields[title][type]'], $fields['new_id']
->getType(), 'title field type is text.');
$this
->assertEquals($edit['fields[title][boost]'], $fields['new_id']
->getBoost(), 'title field boost value is 21.');
$this
->assertArrayHasKey('revision_log', $fields, 'revision_log field is indexed.');
$this
->assertEquals($edit['fields[revision_log][type]'], $fields['revision_log']
->getType(), 'revision_log field type is search_api_test.');
// Reset field values to original.
$edit = [
'fields[new_id][title]' => 'Title',
'fields[new_id][id]' => 'title',
];
$this
->drupalGet($this
->getIndexPath('fields'));
$this
->submitForm($edit, 'Save changes');
$this
->assertSession()
->pageTextContains('The changes were successfully saved.');
// Make sure that property paths are correctly displayed.
$this
->assertSession()
->pageTextContains('uid:entity:name');
// Verify that custom boost values set directly in the config won't be
// overwritten when saving the "Fields" form in the UI.
$index = $this
->getIndex(TRUE);
$index
->getField('title')
->setBoost(4.0);
$index
->save();
$this
->drupalGet($this
->getIndexPath('fields'));
$this
->submitForm([], 'Save changes');
$this
->assertSession()
->pageTextContains('The changes were successfully saved.');
$index = $this
->getIndex(TRUE);
$this
->assertEquals(4.0, $index
->getField('title')
->getBoost());
}
/**
* Tests if the data types table is available and contains correct values.
*/
protected function checkDataTypesTable() {
$this
->drupalGet($this
->getIndexPath('fields'));
$rows = $this
->xpath('//*[@id="search-api-data-types-table"]//table/tbody/tr');
$this
->assertIsArray($rows);
$this
->assertNotEmpty($rows);
/** @var \Behat\Mink\Element\NodeElement $row */
foreach ($rows as $row) {
$columns = $row
->findAll('xpath', '/td');
$label = $columns[0]
->getText();
$icon = basename($columns[2]
->find('xpath', '/img')
->getAttribute('src'));
$fallback = $columns[3]
->getText();
// Make sure we display the right icon and fallback column.
if (strpos($label, 'Unsupported') === 0) {
$this
->assertEquals('error.svg', $icon, 'An error icon is shown for unsupported data types.');
$this
->assertNotEquals($fallback, '', 'The fallback data type label is not empty for unsupported data types.');
}
else {
$this
->assertEquals('check.svg', $icon, 'A check icon is shown for supported data types.');
$this
->assertEquals('', $fallback, 'The fallback data type label is empty for supported data types.');
}
}
}
/**
* Adds a field for a specific property to the index.
*
* @param string|null $datasource_id
* The property's datasource's ID, or NULL if it is a datasource-independent
* property.
* @param string $property_path
* The property path.
* @param string|null $label
* (optional) If given, the label to check for in the success message.
*/
protected function addField($datasource_id, $property_path, $label = NULL) {
$path = $this
->getIndexPath('fields/add/nojs');
$url_options = [
'query' => [
'datasource' => $datasource_id,
],
];
list($parent_path) = Utility::splitPropertyPath($property_path);
if ($parent_path) {
$url_options['query']['property_path'] = $parent_path;
}
if ($this
->getUrl() !== $this
->buildUrl($path, $url_options)) {
$this
->drupalGet($path, $url_options);
}
// Unfortunately it doesn't seem possible to specify the clicked button by
// anything other than label, so we have to pass it as extra POST data.
$combined_property_path = Utility::createCombinedId($datasource_id, $property_path);
$this
->assertSession()
->responseContains('name="' . $combined_property_path . '"');
$this
->submitForm([], $combined_property_path);
if ($label) {
$args['%label'] = $label;
$this
->assertSession()
->responseContains(new FormattableMarkup('Field %label was added to the index.', $args));
}
}
/**
* Tests field dependencies.
*/
protected function addFieldsWithDependenciesToIndex() {
// Add a new link field.
FieldStorageConfig::create([
'field_name' => 'field_link',
'type' => 'link',
'entity_type' => 'node',
])
->save();
FieldConfig::create([
'field_name' => 'field_link',
'entity_type' => 'node',
'bundle' => 'article',
'label' => 'Link',
])
->save();
// Add a new image field, for both articles and basic pages.
FieldStorageConfig::create([
'field_name' => 'field_image',
'type' => 'image',
'entity_type' => 'node',
])
->save();
FieldConfig::create([
'field_name' => 'field_image',
'entity_type' => 'node',
'bundle' => 'article',
'label' => 'Image',
])
->save();
FieldConfig::create([
'field_name' => 'field_image',
'entity_type' => 'node',
'bundle' => 'page',
'label' => 'Image',
])
->save();
$fields = [
'field_link' => 'Link',
'field_image' => 'Image',
];
foreach ($fields as $property_path => $label) {
$this
->addField('entity:node', $property_path, $label);
}
$this
->drupalGet($this
->getIndexPath('fields'));
$this
->submitForm([], 'Save changes');
// Check that index configuration is updated with dependencies.
$field_dependencies = (array) \Drupal::config('search_api.index.' . $this->indexId)
->get('dependencies.config');
$this
->assertTrue(in_array('field.storage.node.field_link', $field_dependencies), 'The link field has been added as a dependency of the index.');
$this
->assertTrue(in_array('field.storage.node.field_image', $field_dependencies), 'The image field has been added as a dependency of the index.');
}
/**
* Tests whether removing fields on which the index depends works correctly.
*/
protected function removeFieldsDependencies() {
// Remove a field and make sure that doing so does not remove the search
// index.
$this
->drupalGet('admin/structure/types/manage/article/fields/node.article.field_link/delete');
$this
->assertSession()
->pageTextNotContains('The listed configuration will be deleted.');
$this
->assertSession()
->pageTextContains('Search index');
$this
->submitForm([], 'Delete');
$this
->drupalGet('admin/structure/types/manage/article/fields/node.article.field_image/delete');
$this
->submitForm([], 'Delete');
$this
->assertNotNull($this
->getIndex(), 'Index was not deleted.');
$this
->drupalGet($this
->getIndexPath('fields'));
$this
->assertSession()
->statusCodeEquals(200);
$this
->assertSession()
->pageTextNotContains('field_link');
$this
->assertSession()
->fieldExists('fields[field_image][id]');
$this
->assertSession()
->fieldValueEquals('fields[field_image][id]', 'field_image');
$field_dependencies = \Drupal::config('search_api.index.' . $this->indexId)
->get('dependencies.config');
$this
->assertFalse(in_array('field.storage.node.field_link', (array) $field_dependencies), "The link field has been removed from the index's dependencies.");
$this
->assertTrue(in_array('field.storage.node.field_image', (array) $field_dependencies), "The image field has been removed from the index's dependencies.");
}
/**
* Tests whether removing fields from the index works correctly.
*/
protected function removeFieldsFromIndex() {
// Find the "Remove" link for the "body" field.
$links = $this
->xpath('//a[@data-drupal-selector=:id]', [
':id' => 'edit-fields-body-remove',
]);
$this
->assertNotEmpty($links, 'Found "Remove" link for body field');
$this
->assertIsArray($links);
$url_target = $this
->getAbsoluteUrl($links[0]
->getAttribute('href'));
$this
->drupalGet($url_target);
$this
->drupalGet($this
->getIndexPath('fields'));
$this
->submitForm([], 'Save changes');
$index = $this
->getIndex(TRUE);
$fields = $index
->getFields();
$this
->assertArrayNotHasKey('body', $fields);
}
/**
* Tests whether unsaved fields changes work correctly.
*/
protected function checkUnsavedChanges() {
$this
->addField('entity:node', 'changed', 'Changed');
$this
->drupalGet($this
->getIndexPath('fields'));
$this
->assertSession()
->pageTextContains('You have unsaved changes.');
// Log in a different admin user.
$this
->drupalLogin($this->adminUser2);
// Construct the message that should be displayed.
$username = [
'#theme' => 'username',
'#account' => $this->adminUser,
];
$args = [
'@user' => \Drupal::getContainer()
->get('renderer')
->renderPlain($username),
':url' => $this
->getIndex()
->toUrl('break-lock-form')
->toString(),
];
$message = (string) new FormattableMarkup('This index is being edited by user @user, and is therefore locked from editing by others. This lock is @age old. Click here to <a href=":url">break this lock</a>.', $args);
// Since we can't predict the age that will be shown, just check for
// everything else.
$message_parts = explode('@age', $message);
$this
->drupalGet($this
->getIndexPath('fields/add/nojs'));
$this
->assertSession()
->responseContains($message_parts[0]);
$this
->assertSession()
->responseContains($message_parts[1]);
$this
->assertSession()
->elementNotExists('xpath', '//input[not(@disabled)]');
$this
->drupalGet($this
->getIndexPath('fields/edit/rendered_item'));
$this
->assertSession()
->responseContains($message_parts[0]);
$this
->assertSession()
->responseContains($message_parts[1]);
$this
->assertSession()
->elementNotExists('xpath', '//input[not(@disabled)]');
$this
->drupalGet($this
->getIndexPath('fields'));
$this
->assertSession()
->responseContains($message_parts[0]);
$this
->assertSession()
->responseContains($message_parts[1]);
$this
->assertSession()
->elementNotExists('xpath', '//input[not(@disabled)]');
$this
->clickLink('break this lock');
$this
->assertSession()
->responseContains(new FormattableMarkup('By breaking this lock, any unsaved changes made by @user will be lost.', $args));
$this
->submitForm([], 'Break lock');
$this
->assertSession()
->pageTextContains('The lock has been broken. You may now edit this search index.');
// Make sure the field has not been added to the index.
$index = $this
->getIndex(TRUE);
$fields = $index
->getFields();
$this
->assertArrayNotHasKey('changed', $fields);
// Find the "Remove" link for the "title" field.
$links = $this
->xpath('//a[@data-drupal-selector=:id]', [
':id' => 'edit-fields-title-remove',
]);
$this
->assertNotEmpty($links, 'Found "Remove" link for title field');
$this
->assertIsArray($links);
$url_target = $this
->getAbsoluteUrl($links[0]
->getAttribute('href'));
$this
->drupalGet($url_target);
$this
->assertSession()
->pageTextContains('You have unsaved changes.');
$this
->submitForm([], 'Cancel');
$this
->assertArrayHasKey('title', $fields, 'The title field has not been removed from the index.');
}
/**
* Tests if non-base fields of referenced entities can be added.
*/
protected function checkReferenceFieldsNonBaseFields() {
// Add a new entity_reference field.
$field_label = 'reference_field';
FieldStorageConfig::create([
'field_name' => 'field__reference_field_',
'type' => 'entity_reference',
'entity_type' => 'node',
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
'settings' => [
'allowed_values' => [
[
'target_type' => 'node',
],
],
],
])
->save();
FieldConfig::create([
'field_name' => 'field__reference_field_',
'entity_type' => 'node',
'bundle' => 'article',
'label' => $field_label,
])
->save();
EntityFormDisplay::load('node.article.default')
->setComponent('field__reference_field_', [
'type' => 'entity_reference_autocomplete',
])
->save();
$node_label = $this
->getIndex()
->getDatasource('entity:node')
->label();
$field_label = "{$field_label} » {$node_label} » {$field_label}";
$this
->addField('entity:node', 'field__reference_field_:entity:field__reference_field_', $field_label);
$this
->drupalGet($this
->getIndexPath('fields'));
$this
->submitForm([], 'Save changes');
$this
->drupalGet('node/2/edit');
$edit = [
'field__reference_field_[0][target_id]' => 'Something (2)',
];
$this
->drupalGet('node/2/edit');
$this
->submitForm($edit, 'Save');
$indexed_values = \Drupal::state()
->get("search_api_test.backend.indexed.{$this->indexId}", []);
$this
->assertEquals([
2,
], $indexed_values['entity:node/2:en']['field__reference_field_'], 'Correct value indexed for nested non-base field.');
}
/**
* Tests that configuring a processor works.
*/
protected function configureFilter() {
$edit = [
'status[ignorecase]' => 1,
'processors[ignorecase][settings][fields][title]' => 'title',
'processors[ignorecase][settings][fields][field__field_]' => FALSE,
];
$this
->drupalGet($this
->getIndexPath('processors'));
$this
->submitForm($edit, 'Save');
$index = $this
->getIndex(TRUE);
try {
$configuration = $index
->getProcessor('ignorecase')
->getConfiguration();
unset($configuration['weights']);
$expected = [
'fields' => [
'title',
],
'all_fields' => FALSE,
];
$this
->assertEquals($expected, $configuration, 'Title field enabled for ignore case filter.');
} catch (SearchApiException $e) {
$this
->fail('"Ignore case" processor not enabled.');
}
$this
->assertSession()
->pageTextContains('The indexing workflow was successfully edited.');
}
/**
* Tests that the "no values changed" message on the "Processors" tab works.
*/
public function configureFilterPage() {
$this
->drupalGet($this
->getIndexPath('processors'));
$this
->submitForm([], 'Save');
$this
->assertSession()
->pageTextContains('No values were changed.');
}
/**
* Tests that changing or a processor doesn't always trigger reindexing.
*/
protected function checkProcessorChanges() {
$edit = [
'status[ignorecase]' => 1,
'processors[ignorecase][settings][fields][title]' => 'title',
];
// Enable just the ignore case processor, just to have a clean default state
// before testing.
$this
->drupalGet($this
->getIndexPath('processors'));
$this
->submitForm($edit, 'Save');
$this
->assertSession()
->statusCodeEquals(200);
$this
->submitForm($edit, 'Save');
$this
->assertSession()
->statusCodeEquals(200);
$this
->assertSession()
->pageTextContains('No values were changed.');
$this
->assertSession()
->pageTextNotContains('All content was scheduled for reindexing so the new settings can take effect.');
$edit['processors[ignorecase][settings][fields][title]'] = FALSE;
$this
->submitForm($edit, 'Save');
$this
->assertSession()
->statusCodeEquals(200);
$this
->assertSession()
->pageTextContains('All content was scheduled for reindexing so the new settings can take effect.');
$this
->assertSession()
->responseContains($this
->getIndex()
->toUrl('canonical')
->toString());
}
/**
* Tests that a field added by a processor can be changed.
*
* For most fields added by processors, such as the "URL field" processor,
* only be the "Indexed" checkbox should be locked, not type and boost. This
* method verifies this.
*/
protected function changeProcessorFieldBoost() {
// Add the URL field.
$this
->addField(NULL, 'search_api_url', 'URI');
// Change the boost of the field.
$fields_path = $this
->getIndexPath('fields');
$this
->drupalGet($fields_path);
$this
->submitForm([
'fields[url][boost]' => Utility::formatBoostFactor(8),
], 'Save changes');
$this
->assertSession()
->pageTextContains('The changes were successfully saved.');
$option_field = $this
->assertSession()
->optionExists('edit-fields-url-boost', Utility::formatBoostFactor(8));
$this
->assertTrue($option_field
->hasAttribute('selected'), 'Boost is correctly saved.');
// Change the type of the field.
$this
->drupalGet($fields_path);
$this
->submitForm([
'fields[url][type]' => 'text',
], 'Save changes');
$this
->assertSession()
->pageTextContains('The changes were successfully saved.');
$option_field = $this
->assertSession()
->optionExists('edit-fields-url-type', 'text');
$this
->assertTrue($option_field
->hasAttribute('selected'), 'Type is correctly saved.');
}
/**
* Sets an index to "read only" and checks if it reacts correctly.
*
* The expected behavior is that, when an index is set to "read only", it
* keeps tracking but won't index any items.
*/
protected function setReadOnly() {
$index = $this
->getIndex(TRUE);
$index
->reindex();
$index_path = $this
->getIndexPath();
$settings_path = $index_path . '/edit';
// Re-enable tracking of all bundles. After this there should be two
// unindexed items tracked by the index.
$edit = [
'status' => TRUE,
'read_only' => TRUE,
'datasource_configs[entity:node][bundles][default]' => 0,
'datasource_configs[entity:node][bundles][selected][article]' => TRUE,
'datasource_configs[entity:node][bundles][selected][page]' => TRUE,
];
$this
->drupalGet($settings_path);
$this
->submitForm($edit, 'Save');
$this
->checkForMetaRefresh();
$this
->assertSession()
->pageTextContains('The index was successfully saved.');
$index = $this
->getIndex(TRUE);
$remaining_before = $this
->countRemainingItems();
$this
->drupalGet($index_path);
$this
->assertSession()
->pageTextNotContains('Index now');
// Also try indexing via the API to make sure it is really not possible.
$indexed = $this
->indexItems();
$this
->assertEquals(0, $indexed, 'No items were indexed after setting the index to "read only".');
$remaining_after = $this
->countRemainingItems();
$this
->assertEquals($remaining_before, $remaining_after, 'No items were indexed after setting the index to "read only".');
// Disable "read only" and verify indexing now works again.
$edit = [
'read_only' => FALSE,
'datasource_configs[entity:node][bundles][default]' => 1,
'datasource_configs[entity:node][bundles][selected][article]' => FALSE,
'datasource_configs[entity:node][bundles][selected][page]' => FALSE,
];
$this
->drupalGet($settings_path);
$this
->submitForm($edit, 'Save');
$this
->checkForMetaRefresh();
$this
->assertSession()
->pageTextContains('The index was successfully saved.');
$this
->drupalGet($index_path);
$this
->submitForm([], 'Index now');
$this
->checkForMetaRefresh();
$remaining_after = $index
->getTrackerInstance()
->getRemainingItemsCount();
$this
->assertEquals(0, $remaining_after, 'Items were indexed after removing the "read only" flag.');
}
/**
* Disables and enables an index and checks if it reacts correctly.
*
* The expected behavior is that, when an index is disabled, all its items
* are removed from both the tracker and the server.
*
* When it is enabled again, the items are re-added to the tracker.
*/
protected function disableEnableIndex() {
// Disable the index and check that no items are tracked.
$settings_path = $this
->getIndexPath('edit');
$edit = [
'status' => FALSE,
];
$this
->drupalGet($settings_path);
$this
->submitForm($edit, 'Save');
$this
->assertSession()
->pageTextContains('The index was successfully saved.');
$tracked_items = $this
->countTrackedItems();
$this
->assertEquals(0, $tracked_items, 'No items are tracked after disabling the index.');
$tracked_items = \Drupal::database()
->select('search_api_item', 'i')
->countQuery()
->execute()
->fetchField();
$this
->assertEquals(0, $tracked_items, 'No items left in tracking table.');
// @todo Also try to verify whether the items got deleted from the server.
// Re-enable the index and check that the items are tracked again.
$edit = [
'status' => TRUE,
];
$this
->drupalGet($settings_path);
$this
->submitForm($edit, 'Save');
$this
->checkForMetaRefresh();
$this
->assertSession()
->pageTextContains('The index was successfully saved.');
$tracked_items = $this
->countTrackedItems();
$this
->assertEquals(2, $tracked_items, 'After enabling the index, 2 items are tracked.');
}
/**
* Changes the index's datasources and checks if it reacts correctly.
*
* The expected behavior is that, when an index's datasources are changed, the
* tracker should remove all items from the datasources it no longer needs to
* handle and add the new ones.
*/
protected function changeIndexDatasource() {
$index = $this
->getIndex(TRUE);
$index
->reindex();
$user_count = \Drupal::entityQuery('user')
->accessCheck(FALSE)
->count()
->execute();
$node_count = \Drupal::entityQuery('node')
->accessCheck(FALSE)
->count()
->execute();
// Enable indexing of users.
$settings_path = $this
->getIndexPath('edit');
$edit = [
'datasources[entity:user]' => TRUE,
'datasources[entity:node]' => TRUE,
];
$this
->drupalGet($settings_path);
$this
->submitForm($edit, 'Save');
$this
->assertSession()
->pageTextContains('Please configure the used datasources.');
$this
->submitForm([], 'Save');
$this
->checkForMetaRefresh();
$this
->assertSession()
->pageTextContains('The index was successfully saved.');
$tracked_items = $this
->countTrackedItems();
$this
->assertEquals($user_count + $node_count, $tracked_items, 'Correct number of items tracked after enabling the "User" datasource.');
// Disable indexing of users again.
$edit = [
'datasources[entity:user]' => FALSE,
'datasources[entity:node]' => TRUE,
];
$this
->drupalGet($settings_path);
$this
->submitForm($edit, 'Save');
$this
->assertSession()
->pageTextContains('The index was successfully saved.');
$this
->executeTasks();
$tracked_items = $this
->countTrackedItems();
$this
->assertEquals($node_count, $tracked_items, 'Correct number of items tracked after disabling the "User" datasource.');
}
/**
* Changes the index's server and checks if it reacts correctly.
*
* The expected behavior is that, when an index's server is changed, all of
* the index's items should be removed from the previous server and marked as
* "unindexed" in the tracker.
*/
protected function changeIndexServer() {
$node_count = \Drupal::entityQuery('node')
->accessCheck(FALSE)
->count()
->execute();
$this
->assertEquals($node_count, $this
->countTrackedItems(), 'All nodes are correctly tracked by the index.');
// Index all remaining items on the index.
$this
->indexItems();
$remaining_items = $this
->countRemainingItems();
$this
->assertEquals(0, $remaining_items, 'All items have been successfully indexed.');
// Create a second search server.
$this
->createServer('test_server_2');
// Change the index's server to the new one.
$settings_path = $this
->getIndexPath('edit');
$edit = [
'server' => $this->serverId,
];
$this
->drupalGet($settings_path);
$this
->submitForm($edit, 'Save');
$this
->assertSession()
->pageTextContains('The index was successfully saved.');
// After saving the new index, we should have called reindex.
$remaining_items = $this
->countRemainingItems();
$this
->assertEquals($node_count, $remaining_items, 'All items still need to be indexed.');
}
/**
* Tests whether indexing via the UI works correctly.
*/
protected function checkIndexing() {
$node = $this
->drupalCreateNode([
'type' => 'article',
]);
$this
->drupalCreateNode([
'type' => 'article',
]);
$this
->drupalCreateNode([
'type' => 'article',
]);
$this
->drupalCreateNode([
'type' => 'article',
]);
// Skip indexing for one node.
$key = 'search_api_test.backend.indexItems.skip';
\Drupal::state()
->set($key, [
'entity:node/' . $node
->id() . ':' . $node
->language()
->getId(),
]);
// Ensure all items need to be indexed.
$this
->getIndex()
->reindex();
$this
->drupalGet($this
->getIndexPath());
$this
->submitForm([], 'Index now');
$this
->assertSession()
->statusCodeEquals(200);
$this
->checkForMetaRefresh();
$count = \Drupal::entityQuery('node')
->accessCheck(FALSE)
->count()
->execute() - 1;
$this
->assertSession()
->pageTextContains("Successfully indexed {$count} items.");
$this
->assertSession()
->pageTextContains('1 item could not be indexed.');
$this
->assertSession()
->pageTextNotContains("Couldn't index items.");
$this
->assertSession()
->pageTextNotContains('An error occurred');
$this
->drupalGet($this
->getIndexPath());
$this
->submitForm([], 'Index now');
$this
->assertSession()
->statusCodeEquals(200);
$this
->checkForMetaRefresh();
$this
->assertSession()
->pageTextContains("Couldn't index items.");
$this
->assertSession()
->pageTextNotContains('An error occurred');
\Drupal::state()
->set($key, []);
$this
->setError('backend', 'indexItems');
$this
->drupalGet($this
->getIndexPath());
$this
->submitForm([], 'Index now');
$this
->assertSession()
->statusCodeEquals(200);
$this
->checkForMetaRefresh();
$this
->assertSession()
->pageTextContains("Couldn't index items.");
$this
->assertSession()
->pageTextNotContains('An error occurred');
$this
->setError('backend', 'indexItems', FALSE);
$this
->drupalGet($this
->getIndexPath());
$this
->submitForm([], 'Index now');
$this
->assertSession()
->statusCodeEquals(200);
$this
->checkForMetaRefresh();
$this
->assertSession()
->pageTextContains("Successfully indexed 1 item.");
$this
->assertSession()
->pageTextNotContains('could not be indexed.');
$this
->assertSession()
->pageTextNotContains("Couldn't index items.");
$this
->assertSession()
->pageTextNotContains('An error occurred');
}
/**
* Tests the various actions on the index status form.
*/
protected function checkIndexActions() {
$assert_session = $this
->assertSession();
$index = $this
->getIndex();
$tracker = $index
->getTrackerInstance();
$label = $index
->label();
$this
->indexItems();
// Manipulate the tracking information to make it slightly off (so
// rebuilding the tracker will be necessary).
$deleted = \Drupal::database()
->delete('search_api_item')
->condition('index_id', $index
->id())
->condition('item_id', Utility::createCombinedId('entity:node', '2:en'))
->execute();
$this
->assertEquals(1, $deleted);
$manipulated_items_count = \Drupal::entityQuery('node')
->accessCheck(FALSE)
->count()
->execute() - 1;
$this
->assertEquals($manipulated_items_count, $tracker
->getIndexedItemsCount());
$this
->assertEquals($manipulated_items_count, $tracker
->getTotalItemsCount());
$this
->assertEquals($manipulated_items_count + 1, $this
->countItemsOnServer());
$this
->drupalGet($this
->getIndexPath('reindex'));
$this
->submitForm([], 'Confirm');
$assert_session
->pageTextContains("The search index {$label} was successfully queued for reindexing.");
$this
->assertEquals(0, $tracker
->getIndexedItemsCount());
$this
->assertEquals($manipulated_items_count, $tracker
->getTotalItemsCount());
$this
->assertEquals($manipulated_items_count + 1, $this
->countItemsOnServer());
$this
->indexItems();
$this
->drupalGet($this
->getIndexPath('clear'));
$this
->submitForm([], 'Confirm');
$assert_session
->pageTextContains("All items were successfully deleted from search index {$label}.");
$this
->assertEquals(0, $tracker
->getIndexedItemsCount());
$this
->assertEquals($manipulated_items_count, $tracker
->getTotalItemsCount());
$this
->assertEquals(0, $this
->countItemsOnServer());
$this
->indexItems();
$this
->drupalGet($this
->getIndexPath('rebuild-tracker'));
$this
->submitForm([], 'Confirm');
$assert_session
->pageTextContains("The tracking information for search index {$label} will be rebuilt.");
$this
->assertEquals(0, $tracker
->getIndexedItemsCount());
$this
->assertEquals($manipulated_items_count + 1, $tracker
->getTotalItemsCount());
$this
->assertEquals($manipulated_items_count, $this
->countItemsOnServer());
$this
->indexItems();
}
/**
* Tests deleting a search server via the UI.
*/
protected function deleteServer() {
$server = Server::load($this->serverId);
// Load confirmation form.
$this
->drupalGet('admin/config/search/search-api/server/' . $this->serverId . '/delete');
$this
->assertSession()
->statusCodeEquals(200);
$this
->assertSession()
->responseContains(new FormattableMarkup('Are you sure you want to delete the search server %name?', [
'%name' => $server
->label(),
]));
$this
->assertSession()
->pageTextContains('Deleting a server will disable all its indexes and their searches.');
// Confirm deletion.
$this
->submitForm([], 'Delete');
$this
->assertSession()
->responseContains(new FormattableMarkup('The search server %name has been deleted.', [
'%name' => $server
->label(),
]));
$this
->assertNull(Server::load($this->serverId), 'Server could not be found anymore.');
$this
->assertSession()
->addressEquals('admin/config/search/search-api');
// Confirm that the index hasn't been deleted.
$this->indexStorage
->resetCache([
$this->indexId,
]);
/** @var \Drupal\search_api\IndexInterface $index */
$index = $this->indexStorage
->load($this->indexId);
$this
->assertInstanceOf(IndexInterface::class, $index, 'The index associated with the server was not deleted.');
$this
->assertFalse($index
->status(), 'The index associated with the server was disabled.');
$this
->assertNull($index
->getServerId(), 'The index was removed from the server.');
}
/**
* Retrieves test index.
*
* @param bool $reset
* (optional) If TRUE, reset the entity cache before loading.
*
* @return \Drupal\search_api\IndexInterface
* The test index.
*/
protected function getIndex($reset = FALSE) {
if ($reset) {
$this->indexStorage
->resetCache([
$this->indexId,
]);
}
return $this->indexStorage
->load($this->indexId);
}
/**
* Indexes all (unindexed) items on the specified index.
*
* @return int
* The number of successfully indexed items.
*/
protected function indexItems() {
/** @var \Drupal\search_api\IndexInterface $index */
$index = Index::load($this->indexId);
return $index
->indexItems();
}
/**
* Ensures that all occurrences of the string are properly escaped.
*
* This makes sure that the string is only mentioned in an escaped version and
* is never double escaped.
*
* @param string $string
* The raw string to check for.
*/
protected function assertHtmlEscaped($string) {
$this
->assertSession()
->responseContains(Html::escape($string));
$this
->assertSession()
->responseNotContains(Html::escape(Html::escape($string)));
$this
->assertSession()
->responseNotContains($string);
}
}
Members
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
AssertHelperTrait:: |
protected static | function | Casts MarkupInterface objects into strings. | |
AssertLegacyTrait:: |
protected | function | Deprecated Scheduled for removal in Drupal 10.0.0. Use self::assertTrue() instead. | |
AssertLegacyTrait:: |
protected | function | Asserts whether an expected cache tag was present in the last response. | |
AssertLegacyTrait:: |
protected | function | Asserts that the element with the given CSS selector is not present. | |
AssertLegacyTrait:: |
protected | function | Asserts that the element with the given CSS selector is present. | |
AssertLegacyTrait:: |
protected | function | Deprecated Scheduled for removal in Drupal 10.0.0. Use self::assertEquals() instead. | |
AssertLegacyTrait:: |
protected | function | Passes if the raw text IS found escaped on the loaded page, fail otherwise. | |
AssertLegacyTrait:: |
protected | function | Asserts that a field exists with the given name or ID. | |
AssertLegacyTrait:: |
protected | function | Asserts that a field exists with the given ID and value. | |
AssertLegacyTrait:: |
protected | function | Asserts that a field exists with the given name and value. | |
AssertLegacyTrait:: |
protected | function | Asserts that a field exists in the current page by the given XPath. | |
AssertLegacyTrait:: |
protected | function | Asserts that a checkbox field in the current page is checked. | |
AssertLegacyTrait:: |
protected | function | Asserts that a field exists in the current page with a given Xpath result. | |
AssertLegacyTrait:: |
protected | function | Checks that current response header equals value. | |
AssertLegacyTrait:: |
protected | function | Deprecated Scheduled for removal in Drupal 10.0.0. Use self::assertSame() instead. | |
AssertLegacyTrait:: |
protected | function | Deprecated Scheduled for removal in Drupal 10.0.0. Use self::assertEquals() instead. | |
AssertLegacyTrait:: |
protected | function | Passes if a link with the specified label is found. | |
AssertLegacyTrait:: |
protected | function | Passes if a link containing a given href (part) is found. | |
AssertLegacyTrait:: |
protected | function | Asserts whether an expected cache tag was absent in the last response. | |
AssertLegacyTrait:: |
protected | function | Passes if the raw text is not found escaped on the loaded page. | |
AssertLegacyTrait:: |
protected | function | Asserts that a field does NOT exist with the given name or ID. | |
AssertLegacyTrait:: |
protected | function | Asserts that a field does not exist with the given ID and value. | |
AssertLegacyTrait:: |
protected | function | Asserts that a field does not exist with the given name and value. | |
AssertLegacyTrait:: |
protected | function | Asserts that a field does not exist or its value does not match, by XPath. | |
AssertLegacyTrait:: |
protected | function | Asserts that a checkbox field in the current page is not checked. | |
AssertLegacyTrait:: |
protected | function | Passes if a link with the specified label is not found. | |
AssertLegacyTrait:: |
protected | function | Passes if a link containing a given href (part) is not found. | |
AssertLegacyTrait:: |
protected | function | Asserts that a select option does NOT exist in the current page. | |
AssertLegacyTrait:: |
protected | function | Triggers a pass if the Perl regex pattern is not found in the raw content. | |
AssertLegacyTrait:: |
protected | function | Passes if the raw text IS not found on the loaded page, fail otherwise. | 1 |
AssertLegacyTrait:: |
protected | function | Deprecated Scheduled for removal in Drupal 10.0.0. Use self::assertNotEquals() instead. | |
AssertLegacyTrait:: |
protected | function | Passes if the page (with HTML stripped) does not contains the text. | 1 |
AssertLegacyTrait:: |
protected | function | Deprecated Scheduled for removal in Drupal 10.0.0. Use self::assertNotSame() instead. | |
AssertLegacyTrait:: |
protected | function | Passes if the text is found MORE THAN ONCE on the text version of the page. | |
AssertLegacyTrait:: |
protected | function | Asserts that a select option in the current page exists. | |
AssertLegacyTrait:: |
protected | function | Asserts that a select option with the visible text exists. | |
AssertLegacyTrait:: |
protected | function | Asserts that a select option in the current page is checked. | |
AssertLegacyTrait:: |
protected | function | Triggers a pass if the Perl regex pattern is found in the raw content. | |
AssertLegacyTrait:: |
protected | function | Passes if the raw text IS found on the loaded page, fail otherwise. | 1 |
AssertLegacyTrait:: |
protected | function | Asserts the page responds with the specified response code. | 1 |
AssertLegacyTrait:: |
protected | function | Passes if the page (with HTML stripped) contains the text. | 1 |
AssertLegacyTrait:: |
protected | function | Helper for assertText and assertNoText. | |
AssertLegacyTrait:: |
protected | function | Pass if the page title is the given string. | |
AssertLegacyTrait:: |
protected | function | Passes if the text is found ONLY ONCE on the text version of the page. | |
AssertLegacyTrait:: |
protected | function | Passes if the internal browser's URL matches the given path. | |
AssertLegacyTrait:: |
protected | function | Builds an XPath query. | |
AssertLegacyTrait:: |
protected | function | Helper: Constructs an XPath for the given set of attributes and value. | |
AssertLegacyTrait:: |
protected | function | Get all option elements, including nested options, in a select. | |
AssertLegacyTrait:: |
protected | function | Gets the current raw content. | |
AssertLegacyTrait:: |
protected | function | Deprecated Scheduled for removal in Drupal 10.0.0. Use self::assertTrue() instead. | |
AssertLegacyTrait:: |
protected | function | ||
BlockCreationTrait:: |
protected | function | Creates a block instance based on default settings. Aliased as: drupalPlaceBlock | |
BrowserHtmlDebugTrait:: |
protected | property | The Base URI to use for links to the output files. | |
BrowserHtmlDebugTrait:: |
protected | property | Class name for HTML output logging. | |
BrowserHtmlDebugTrait:: |
protected | property | Counter for HTML output logging. | |
BrowserHtmlDebugTrait:: |
protected | property | Counter storage for HTML output logging. | |
BrowserHtmlDebugTrait:: |
protected | property | Directory name for HTML output logging. | |
BrowserHtmlDebugTrait:: |
protected | property | HTML output output enabled. | |
BrowserHtmlDebugTrait:: |
protected | property | The file name to write the list of URLs to. | |
BrowserHtmlDebugTrait:: |
protected | property | HTML output test ID. | |
BrowserHtmlDebugTrait:: |
protected | function | Formats HTTP headers as string for HTML output logging. | |
BrowserHtmlDebugTrait:: |
protected | function | Returns headers in HTML output format. | 1 |
BrowserHtmlDebugTrait:: |
protected | function | Logs a HTML output message in a text file. | |
BrowserHtmlDebugTrait:: |
protected | function | Creates the directory to store browser output. | |
BrowserTestBase:: |
protected | property | The base URL. | |
BrowserTestBase:: |
protected | property | The config importer that can be used in a test. | |
BrowserTestBase:: |
protected | property | An array of custom translations suitable for drupal_rewrite_settings(). | |
BrowserTestBase:: |
protected | property | The database prefix of this test run. | |
BrowserTestBase:: |
protected | property | Mink session manager. | |
BrowserTestBase:: |
protected | property | ||
BrowserTestBase:: |
protected | property | 1 | |
BrowserTestBase:: |
protected | property | The original container. | |
BrowserTestBase:: |
protected | property | The original array of shutdown function callbacks. | |
BrowserTestBase:: |
protected | property | ||
BrowserTestBase:: |
protected | property | The profile to install as a basis for testing. | 39 |
BrowserTestBase:: |
protected | property | The app root. | |
BrowserTestBase:: |
protected | property | Browser tests are run in separate processes to prevent collisions between code that may be loaded by tests. | |
BrowserTestBase:: |
protected | property | Time limit in seconds for the test. | |
BrowserTestBase:: |
protected | property | The translation file directory for the test environment. | |
BrowserTestBase:: |
protected | function | Clean up the Simpletest environment. | |
BrowserTestBase:: |
protected | function | Configuration accessor for tests. Returns non-overridden configuration. | |
BrowserTestBase:: |
protected | function | Translates a CSS expression to its XPath equivalent. | |
BrowserTestBase:: |
protected | function | Gets the value of an HTTP response header. | |
BrowserTestBase:: |
protected | function | Returns all response headers. | |
BrowserTestBase:: |
public static | function | Ensures test files are deletable. | |
BrowserTestBase:: |
protected | function | Gets an instance of the default Mink driver. | |
BrowserTestBase:: |
protected | function | Gets the JavaScript drupalSettings variable for the currently-loaded page. | 1 |
BrowserTestBase:: |
protected | function | Obtain the HTTP client for the system under test. | |
BrowserTestBase:: |
protected | function | Get the Mink driver args from an environment variable, if it is set. Can be overridden in a derived class so it is possible to use a different value for a subset of tests, e.g. the JavaScript tests. | 1 |
BrowserTestBase:: |
protected | function | Helper function to get the options of select field. | |
BrowserTestBase:: |
protected | function |
Provides a Guzzle middleware handler to log every response received. Overrides BrowserHtmlDebugTrait:: |
|
BrowserTestBase:: |
public | function | Returns Mink session. | |
BrowserTestBase:: |
protected | function | Get session cookies from current session. | |
BrowserTestBase:: |
protected | function |
Retrieves the current calling line in the class under test. Overrides BrowserHtmlDebugTrait:: |
|
BrowserTestBase:: |
protected | function | Visits the front page when initializing Mink. | 3 |
BrowserTestBase:: |
protected | function | Initializes Mink sessions. | 1 |
BrowserTestBase:: |
public | function | Installs Drupal into the Simpletest site. | 1 |
BrowserTestBase:: |
protected | function | Registers additional Mink sessions. | |
BrowserTestBase:: |
protected | function | 3 | |
BrowserTestBase:: |
protected | function | Transforms a nested array into a flat array suitable for drupalPostForm(). | |
BrowserTestBase:: |
protected | function | Performs an xpath search on the contents of the internal browser. | |
BrowserTestBase:: |
public | function | 1 | |
BrowserTestBase:: |
public | function | Prevents serializing any properties. | |
ConfigTestTrait:: |
protected | function | Returns a ConfigImporter object to import test configuration. | |
ConfigTestTrait:: |
protected | function | Copies configuration objects from source storage to target storage. | |
ContentTypeCreationTrait:: |
protected | function | Creates a custom content type based on default settings. Aliased as: drupalCreateContentType | 1 |
FunctionalTestSetupTrait:: |
protected | property | The flag to set 'apcu_ensure_unique_prefix' setting. | 1 |
FunctionalTestSetupTrait:: |
protected | property | The class loader to use for installation and initialization of setup. | |
FunctionalTestSetupTrait:: |
protected | property | The config directories used in this test. | |
FunctionalTestSetupTrait:: |
protected | property | The "#1" admin user. | |
FunctionalTestSetupTrait:: |
protected | function | Execute the non-interactive installer. | 1 |
FunctionalTestSetupTrait:: |
protected | function | Returns all supported database driver installer objects. | |
FunctionalTestSetupTrait:: |
protected | function | Initializes the kernel after installation. | |
FunctionalTestSetupTrait:: |
protected | function | Initialize settings created during install. | |
FunctionalTestSetupTrait:: |
protected | function | Initializes user 1 for the site to be installed. | |
FunctionalTestSetupTrait:: |
protected | function | Installs the default theme defined by `static::$defaultTheme` when needed. | |
FunctionalTestSetupTrait:: |
protected | function | Install modules defined by `static::$modules`. | 1 |
FunctionalTestSetupTrait:: |
protected | function | Returns the parameters that will be used when Simpletest installs Drupal. | 9 |
FunctionalTestSetupTrait:: |
protected | function | Prepares the current environment for running the test. | 23 |
FunctionalTestSetupTrait:: |
protected | function | Creates a mock request and sets it on the generator. | |
FunctionalTestSetupTrait:: |
protected | function | Prepares site settings and services before installation. | 2 |
FunctionalTestSetupTrait:: |
protected | function | Resets and rebuilds the environment after setup. | |
FunctionalTestSetupTrait:: |
protected | function | Rebuilds \Drupal::getContainer(). | |
FunctionalTestSetupTrait:: |
protected | function | Resets all data structures after having enabled new modules. | |
FunctionalTestSetupTrait:: |
protected | function | Changes parameters in the services.yml file. | |
FunctionalTestSetupTrait:: |
protected | function | Sets up the base URL based upon the environment variable. | |
FunctionalTestSetupTrait:: |
protected | function | Rewrites the settings.php file of the test site. | |
IntegrationTest:: |
protected | property | An admin user used for this test. | |
IntegrationTest:: |
protected | property | A storage instance for indexes. | |
IntegrationTest:: |
public static | property |
Modules to enable for this test. Overrides SearchApiBrowserTestBase:: |
|
IntegrationTest:: |
protected | property | The ID of the backend plugin used for the test server. | |
IntegrationTest:: |
protected | property | The ID of the search server used for this test. | |
IntegrationTest:: |
protected | function | Adds a field for a specific property to the index. | |
IntegrationTest:: |
protected | function | Tests whether adding fields to the index works correctly. | |
IntegrationTest:: |
protected | function | Tests field dependencies. | |
IntegrationTest:: |
protected | function | Ensures that all occurrences of the string are properly escaped. | |
IntegrationTest:: |
protected | function | Changes the index's datasources and checks if it reacts correctly. | |
IntegrationTest:: |
protected | function | Changes the index's server and checks if it reacts correctly. | |
IntegrationTest:: |
protected | function | Tests that a field added by a processor can be changed. | |
IntegrationTest:: |
protected | function | Tests whether the tracking information is properly maintained. | |
IntegrationTest:: |
protected | function | Tests if the data types table is available and contains correct values. | |
IntegrationTest:: |
protected | function | Tests that field labels are always properly escaped. | |
IntegrationTest:: |
protected | function | Tests the various actions on the index status form. | |
IntegrationTest:: |
protected | function | Tests whether indexing via the UI works correctly. | |
IntegrationTest:: |
protected | function | Tests that changing or a processor doesn't always trigger reindexing. | |
IntegrationTest:: |
protected | function | Tests if non-base fields of referenced entities can be added. | |
IntegrationTest:: |
protected | function | Tests the server availability. | |
IntegrationTest:: |
protected | function | Tests whether unsaved fields changes work correctly. | |
IntegrationTest:: |
protected | function | Tests that an entity without bundles can be used as a datasource. | |
IntegrationTest:: |
protected | function | Lets derived backend integration tests fill their server create form. | |
IntegrationTest:: |
protected | function | Tests that configuring a processor works. | |
IntegrationTest:: |
public | function | Tests that the "no values changed" message on the "Processors" tab works. | |
IntegrationTest:: |
protected | function | Counts the number of items indexed on the server for the test index. | |
IntegrationTest:: |
protected | function | Counts the number of unindexed items in the test index. | |
IntegrationTest:: |
protected | function | Counts the number of tracked items in the test index. | |
IntegrationTest:: |
protected | function | Tests creating a search index via the UI. | |
IntegrationTest:: |
protected | function | Tests creating a search index with an existing machine name. | |
IntegrationTest:: |
protected | function | Tests creating a search server via the UI. | |
IntegrationTest:: |
protected | function | Tests creating a search server with an existing machine name. | |
IntegrationTest:: |
protected | function | Tests deleting a search server via the UI. | |
IntegrationTest:: |
protected | function | Disables and enables an index and checks if it reacts correctly. | |
IntegrationTest:: |
protected | function | Tests editing a search index via the UI. | |
IntegrationTest:: |
protected | function | Tests whether editing a server works correctly. | |
IntegrationTest:: |
public | function | Enables all processors. | |
IntegrationTest:: |
protected | function | Retrieves test index. | |
IntegrationTest:: |
protected | function | Indexes all (unindexed) items on the specified index. | |
IntegrationTest:: |
protected | function | Tests whether removing fields on which the index depends works correctly. | |
IntegrationTest:: |
protected | function | Tests whether removing fields from the index works correctly. | |
IntegrationTest:: |
protected | function | Sets an index to "read only" and checks if it reacts correctly. | |
IntegrationTest:: |
public | function |
Overrides SearchApiBrowserTestBase:: |
|
IntegrationTest:: |
public | function | Tests various operations via the Search API's admin UI. | |
IntegrationTest:: |
public | function | Tests what happens when an index has an integer as id/label. | |
NodeCreationTrait:: |
protected | function | Creates a node based on default settings. Aliased as: drupalCreateNode | |
NodeCreationTrait:: |
public | function | Get a node from the database based on its title. Aliased as: drupalGetNodeByTitle | |
PhpunitCompatibilityTrait:: |
public | function | Returns a mock object for the specified class using the available method. | |
PhpunitCompatibilityTrait:: |
public | function | Compatibility layer for PHPUnit 6 to support PHPUnit 4 code. | |
PluginTestTrait:: |
protected | function | Retrieves the methods called on a given plugin. | |
PluginTestTrait:: |
protected | function | Retrieves the arguments of a certain method called on the given plugin. | |
PluginTestTrait:: |
protected | function | Sets an exception to be thrown on calls to the given method. | |
PluginTestTrait:: |
protected | function | Overrides a method for a certain plugin. | |
PluginTestTrait:: |
protected | function | Sets the return value for a certain method on a test plugin. | |
PostRequestIndexingTrait:: |
protected | function | Triggers any post-request indexing operations that were registered. | |
RandomGeneratorTrait:: |
protected | property | The random generator. | |
RandomGeneratorTrait:: |
protected | function | Gets the random generator for the utility methods. | |
RandomGeneratorTrait:: |
protected | function | Generates a unique random string containing letters and numbers. | 1 |
RandomGeneratorTrait:: |
public | function | Generates a random PHP object. | |
RandomGeneratorTrait:: |
public | function | Generates a pseudo-random string of ASCII characters of codes 32 to 126. | |
RandomGeneratorTrait:: |
public | function | Callback for random string validation. | |
RefreshVariablesTrait:: |
protected | function | Refreshes in-memory configuration and state information. | 3 |
SearchApiBrowserTestBase:: |
protected static | property | Set this to TRUE to include "item" and "article" bundles for test entities. | 2 |
SearchApiBrowserTestBase:: |
protected | property | An admin user used for this test. | |
SearchApiBrowserTestBase:: |
protected | property | The permissions of the admin user. | |
SearchApiBrowserTestBase:: |
protected | property | The anonymous user used for this test. | |
SearchApiBrowserTestBase:: |
protected | property |
The theme to install as the default for testing. Overrides BrowserTestBase:: |
|
SearchApiBrowserTestBase:: |
protected | property | The ID of the search index used for this test. | |
SearchApiBrowserTestBase:: |
protected | property |
The number of meta refresh redirects to follow, or NULL if unlimited. Overrides UiHelperTrait:: |
|
SearchApiBrowserTestBase:: |
protected | property |
The number of meta refresh redirects followed during ::drupalGet(). Overrides UiHelperTrait:: |
|
SearchApiBrowserTestBase:: |
protected | property | A user without Search API admin permission. | |
SearchApiBrowserTestBase:: |
protected | property | The URL generator. | |
SearchApiBrowserTestBase:: |
protected | function | Executes all pending Search API tasks. | |
SearchApiBrowserTestBase:: |
protected | function | Returns the system path for the test index. | |
SearchApiBrowserTestBase:: |
public | function | Creates or loads an index. | |
SearchApiBrowserTestBase:: |
public | function | Creates or loads a server. | |
SearchApiBrowserTestBase:: |
protected | function |
Initialize various configurations post-installation. Overrides FunctionalTestSetupTrait:: |
|
SessionTestTrait:: |
protected | property | The name of the session cookie. | |
SessionTestTrait:: |
protected | function | Generates a session cookie name. | |
SessionTestTrait:: |
protected | function | Returns the session name in use on the child site. | |
StorageCopyTrait:: |
protected static | function | Copy the configuration from one storage to another and remove stale items. | |
TestRequirementsTrait:: |
private | function | Checks missing module requirements. | |
TestRequirementsTrait:: |
protected | function | Check module requirements for the Drupal use case. | 1 |
TestRequirementsTrait:: |
protected static | function | Returns the Drupal root directory. | |
TestSetupTrait:: |
protected static | property | An array of config object names that are excluded from schema checking. | |
TestSetupTrait:: |
protected | property | The dependency injection container used in the test. | |
TestSetupTrait:: |
protected | property | The DrupalKernel instance used in the test. | |
TestSetupTrait:: |
protected | property | The site directory of the original parent site. | |
TestSetupTrait:: |
protected | property | The private file directory for the test environment. | |
TestSetupTrait:: |
protected | property | The public file directory for the test environment. | |
TestSetupTrait:: |
protected | property | The site directory of this test run. | |
TestSetupTrait:: |
protected | property | Set to TRUE to strict check all configuration saved. | 2 |
TestSetupTrait:: |
protected | property | The temporary file directory for the test environment. | |
TestSetupTrait:: |
protected | property | The test run ID. | |
TestSetupTrait:: |
protected | function | Changes the database connection to the prefixed one. | |
TestSetupTrait:: |
protected | function | Gets the config schema exclusions for this test. | |
TestSetupTrait:: |
public static | function | Returns the database connection to the site running Simpletest. | |
TestSetupTrait:: |
protected | function | Generates a database prefix for running tests. | 2 |
UiHelperTrait:: |
protected | property | The current user logged in using the Mink controlled browser. | |
UiHelperTrait:: |
public | function | Returns WebAssert object. | 1 |
UiHelperTrait:: |
protected | function | Builds an a absolute URL from a system path or a URL object. | |
UiHelperTrait:: |
protected | function | Checks for meta refresh tag and if found call drupalGet() recursively. | |
UiHelperTrait:: |
protected | function | Clicks the element with the given CSS selector. | |
UiHelperTrait:: |
protected | function | Follows a link by complete name. | |
UiHelperTrait:: |
protected | function | Searches elements using a CSS selector in the raw content. | |
UiHelperTrait:: |
protected | function | Retrieves a Drupal path or an absolute path. | 3 |
UiHelperTrait:: |
protected | function | Logs in a user using the Mink controlled browser. | |
UiHelperTrait:: |
protected | function | Logs a user out of the Mink controlled browser and confirms. | |
UiHelperTrait:: |
protected | function | Executes a form submission. | |
UiHelperTrait:: |
protected | function | Returns whether a given user account is logged in. | |
UiHelperTrait:: |
protected | function | Takes a path and returns an absolute path. | |
UiHelperTrait:: |
protected | function | Retrieves the plain-text content from the current page. | |
UiHelperTrait:: |
protected | function | Get the current URL from the browser. | |
UiHelperTrait:: |
protected | function | Prepare for a request to testing site. | 1 |
UiHelperTrait:: |
protected | function | Fills and submits a form. | |
UserCreationTrait:: |
protected | function | Checks whether a given list of permission names is valid. | |
UserCreationTrait:: |
protected | function | Creates an administrative role. | |
UserCreationTrait:: |
protected | function | Creates a role with specified permissions. Aliased as: drupalCreateRole | |
UserCreationTrait:: |
protected | function | Create a user with a given set of permissions. Aliased as: drupalCreateUser | |
UserCreationTrait:: |
protected | function | Grant permissions to a user role. | |
UserCreationTrait:: |
protected | function | Switch the current logged in user. | |
UserCreationTrait:: |
protected | function | Creates a random user account and sets it as current user. | |
XdebugRequestTrait:: |
protected | function | Adds xdebug cookies, from request setup. |