Chris@0: drupalCreateUser(['administer search', 'search content', 'use advanced search', 'access content', 'access site reports', 'administer site configuration']); Chris@0: $this->drupalLogin($user); Chris@0: Chris@0: // Set up the search plugin. Chris@0: $this->plugin = $this->container->get('plugin.manager.search')->createInstance('node_search'); Chris@0: Chris@0: // Check indexing counts before adding any nodes. Chris@0: $this->assertIndexCounts(0, 0, 'before adding nodes'); Chris@0: $this->assertDatabaseCounts(0, 0, 'before adding nodes'); Chris@0: Chris@0: // Add two new languages. Chris@0: ConfigurableLanguage::createFromLangcode('hu')->save(); Chris@0: ConfigurableLanguage::createFromLangcode('sv')->save(); Chris@0: Chris@0: // Make the body field translatable. The title is already translatable by Chris@0: // definition. The parent class has already created the article and page Chris@0: // content types. Chris@0: $field_storage = FieldStorageConfig::loadByName('node', 'body'); Chris@0: $field_storage->setTranslatable(TRUE); Chris@0: $field_storage->save(); Chris@0: Chris@0: // Create a few page nodes with multilingual body values. Chris@0: $default_format = filter_default_format(); Chris@0: $nodes = [ Chris@0: [ Chris@0: 'title' => 'First node en', Chris@0: 'type' => 'page', Chris@0: 'body' => [['value' => $this->randomMachineName(32), 'format' => $default_format]], Chris@0: 'langcode' => 'en', Chris@0: ], Chris@0: [ Chris@0: 'title' => 'Second node this is the English title', Chris@0: 'type' => 'page', Chris@0: 'body' => [['value' => $this->randomMachineName(32), 'format' => $default_format]], Chris@0: 'langcode' => 'en', Chris@0: ], Chris@0: [ Chris@0: 'title' => 'Third node en', Chris@0: 'type' => 'page', Chris@0: 'body' => [['value' => $this->randomMachineName(32), 'format' => $default_format]], Chris@0: 'langcode' => 'en', Chris@0: ], Chris@0: // After the third node, we don't care what the settings are. But we Chris@0: // need to have at least 5 to make sure the throttling is working Chris@0: // correctly. So, let's make 8 total. Chris@0: [], Chris@0: [], Chris@0: [], Chris@0: [], Chris@0: [], Chris@0: ]; Chris@0: $this->searchableNodes = []; Chris@0: foreach ($nodes as $setting) { Chris@0: $this->searchableNodes[] = $this->drupalCreateNode($setting); Chris@0: } Chris@0: Chris@0: // Add a single translation to the second node. Chris@0: $translation = $this->searchableNodes[1]->addTranslation('hu', ['title' => 'Second node hu']); Chris@0: $translation->body->value = $this->randomMachineName(32); Chris@0: $this->searchableNodes[1]->save(); Chris@0: Chris@0: // Add two translations to the third node. Chris@0: $translation = $this->searchableNodes[2]->addTranslation('hu', ['title' => 'Third node this is the Hungarian title']); Chris@0: $translation->body->value = $this->randomMachineName(32); Chris@0: $translation = $this->searchableNodes[2]->addTranslation('sv', ['title' => 'Third node sv']); Chris@0: $translation->body->value = $this->randomMachineName(32); Chris@0: $this->searchableNodes[2]->save(); Chris@0: Chris@0: // Verify that we have 8 nodes left to do. Chris@0: $this->assertIndexCounts(8, 8, 'before updating the search index'); Chris@0: $this->assertDatabaseCounts(0, 0, 'before updating the search index'); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Tests the indexing throttle and search results with multilingual nodes. Chris@0: */ Chris@0: public function testMultilingualSearch() { Chris@0: // Index only 2 nodes per cron run. We cannot do this setting in the UI, Chris@0: // because it doesn't go this low. Chris@0: $this->config('search.settings')->set('index.cron_limit', 2)->save(); Chris@0: // Get a new search plugin, to make sure it has this setting. Chris@0: $this->plugin = $this->container->get('plugin.manager.search')->createInstance('node_search'); Chris@0: Chris@0: // Update the index. This does the initial processing. Chris@0: $this->plugin->updateIndex(); Chris@0: // Run the shutdown function. Testing is a unique case where indexing Chris@0: // and searching has to happen in the same request, so running the shutdown Chris@0: // function manually is needed to finish the indexing process. Chris@0: search_update_totals(); Chris@0: $this->assertIndexCounts(6, 8, 'after updating partially'); Chris@0: $this->assertDatabaseCounts(2, 0, 'after updating partially'); Chris@0: Chris@0: // Now index the rest of the nodes. Chris@0: // Make sure index throttle is high enough, via the UI. Chris@0: $this->drupalPostForm('admin/config/search/pages', ['cron_limit' => 20], t('Save configuration')); Chris@0: $this->assertEqual(20, $this->config('search.settings')->get('index.cron_limit', 100), 'Config setting was saved correctly'); Chris@0: // Get a new search plugin, to make sure it has this setting. Chris@0: $this->plugin = $this->container->get('plugin.manager.search')->createInstance('node_search'); Chris@0: Chris@0: $this->plugin->updateIndex(); Chris@0: search_update_totals(); Chris@0: $this->assertIndexCounts(0, 8, 'after updating fully'); Chris@0: $this->assertDatabaseCounts(8, 0, 'after updating fully'); Chris@0: Chris@0: // Click the reindex button on the admin page, verify counts, and reindex. Chris@0: $this->drupalPostForm('admin/config/search/pages', [], t('Re-index site')); Chris@0: $this->drupalPostForm(NULL, [], t('Re-index site')); Chris@0: $this->assertIndexCounts(8, 8, 'after reindex'); Chris@0: $this->assertDatabaseCounts(8, 0, 'after reindex'); Chris@0: $this->plugin->updateIndex(); Chris@0: search_update_totals(); Chris@0: Chris@0: // Test search results. Chris@0: Chris@0: // This should find two results for the second and third node. Chris@0: $this->plugin->setSearch('English OR Hungarian', [], []); Chris@0: $search_result = $this->plugin->execute(); Chris@0: $this->assertEqual(count($search_result), 2, 'Found two results.'); Chris@0: // Nodes are saved directly after each other and have the same created time Chris@0: // so testing for the order is not possible. Chris@0: $results = [$search_result[0]['title'], $search_result[1]['title']]; Chris@0: $this->assertTrue(in_array('Third node this is the Hungarian title', $results), 'The search finds the correct Hungarian title.'); Chris@0: $this->assertTrue(in_array('Second node this is the English title', $results), 'The search finds the correct English title.'); Chris@0: Chris@0: // Now filter for Hungarian results only. Chris@0: $this->plugin->setSearch('English OR Hungarian', ['f' => ['language:hu']], []); Chris@0: $search_result = $this->plugin->execute(); Chris@0: Chris@0: $this->assertEqual(count($search_result), 1, 'The search found only one result'); Chris@0: $this->assertEqual($search_result[0]['title'], 'Third node this is the Hungarian title', 'The search finds the correct Hungarian title.'); Chris@0: Chris@0: // Test for search with common key word across multiple languages. Chris@0: $this->plugin->setSearch('node', [], []); Chris@0: $search_result = $this->plugin->execute(); Chris@0: Chris@0: $this->assertEqual(count($search_result), 6, 'The search found total six results'); Chris@0: Chris@0: // Test with language filters and common key word. Chris@0: $this->plugin->setSearch('node', ['f' => ['language:hu']], []); Chris@0: $search_result = $this->plugin->execute(); Chris@0: Chris@0: $this->assertEqual(count($search_result), 2, 'The search found 2 results'); Chris@0: Chris@0: // Test to check for the language of result items. Chris@0: foreach ($search_result as $result) { Chris@0: $this->assertEqual($result['langcode'], 'hu', 'The search found the correct Hungarian result'); Chris@0: } Chris@0: Chris@0: // Mark one of the nodes for reindexing, using the API function, and Chris@0: // verify indexing status. Chris@0: search_mark_for_reindex('node_search', $this->searchableNodes[0]->id()); Chris@0: $this->assertIndexCounts(1, 8, 'after marking one node to reindex via API function'); Chris@0: Chris@0: // Update the index and verify the totals again. Chris@0: $this->plugin = $this->container->get('plugin.manager.search')->createInstance('node_search'); Chris@0: $this->plugin->updateIndex(); Chris@0: search_update_totals(); Chris@0: $this->assertIndexCounts(0, 8, 'after indexing again'); Chris@0: Chris@0: // Mark one node for reindexing by saving it, and verify indexing status. Chris@0: $this->searchableNodes[1]->save(); Chris@0: $this->assertIndexCounts(1, 8, 'after marking one node to reindex via save'); Chris@0: Chris@0: // The request time is always the same throughout test runs. Update the Chris@0: // request time to a previous time, to simulate it having been marked Chris@0: // previously. Chris@0: $current = REQUEST_TIME; Chris@0: $old = $current - 10; Chris@0: db_update('search_dataset') Chris@0: ->fields(['reindex' => $old]) Chris@0: ->condition('reindex', $current, '>=') Chris@0: ->execute(); Chris@0: Chris@0: // Save the node again. Verify that the request time on it is not updated. Chris@0: $this->searchableNodes[1]->save(); Chris@0: $result = db_select('search_dataset', 'd') Chris@0: ->fields('d', ['reindex']) Chris@0: ->condition('type', 'node_search') Chris@0: ->condition('sid', $this->searchableNodes[1]->id()) Chris@0: ->execute() Chris@0: ->fetchField(); Chris@0: $this->assertEqual($result, $old, 'Reindex time was not updated if node was already marked'); Chris@0: Chris@0: // Add a bogus entry to the search index table using a different search Chris@0: // type. This will not appear in the index status, because it is not Chris@0: // managed by a plugin. Chris@0: search_index('foo', $this->searchableNodes[0]->id(), 'en', 'some text'); Chris@0: $this->assertIndexCounts(1, 8, 'after adding a different index item'); Chris@0: Chris@0: // Mark just this "foo" index for reindexing. Chris@0: search_mark_for_reindex('foo'); Chris@0: $this->assertIndexCounts(1, 8, 'after reindexing the other search type'); Chris@0: Chris@0: // Mark everything for reindexing. Chris@0: search_mark_for_reindex(); Chris@0: $this->assertIndexCounts(8, 8, 'after reindexing everything'); Chris@0: Chris@0: // Clear one item from the index, but with wrong language. Chris@0: $this->assertDatabaseCounts(8, 1, 'before clear'); Chris@0: search_index_clear('node_search', $this->searchableNodes[0]->id(), 'hu'); Chris@0: $this->assertDatabaseCounts(8, 1, 'after clear with wrong language'); Chris@0: // Clear using correct language. Chris@0: search_index_clear('node_search', $this->searchableNodes[0]->id(), 'en'); Chris@0: $this->assertDatabaseCounts(7, 1, 'after clear with right language'); Chris@0: // Don't specify language. Chris@0: search_index_clear('node_search', $this->searchableNodes[1]->id()); Chris@0: $this->assertDatabaseCounts(6, 1, 'unspecified language clear'); Chris@0: // Clear everything in 'foo'. Chris@0: search_index_clear('foo'); Chris@0: $this->assertDatabaseCounts(6, 0, 'other index clear'); Chris@0: // Clear everything. Chris@0: search_index_clear(); Chris@0: $this->assertDatabaseCounts(0, 0, 'complete clear'); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Verifies the indexing status counts. Chris@0: * Chris@0: * @param int $remaining Chris@0: * Count of remaining items to verify. Chris@0: * @param int $total Chris@0: * Count of total items to verify. Chris@0: * @param string $message Chris@0: * Message to use, something like "after updating the search index". Chris@0: */ Chris@0: protected function assertIndexCounts($remaining, $total, $message) { Chris@0: // Check status via plugin method call. Chris@0: $status = $this->plugin->indexStatus(); Chris@0: $this->assertEqual($status['remaining'], $remaining, 'Remaining items ' . $message . ' is ' . $remaining); Chris@0: $this->assertEqual($status['total'], $total, 'Total items ' . $message . ' is ' . $total); Chris@0: Chris@0: // Check text in progress section of Search settings page. Note that this Chris@0: // test avoids using Chris@0: // \Drupal\Core\StringTranslation\TranslationInterface::formatPlural(), so Chris@0: // it tests for fragments of text. Chris@0: $indexed = $total - $remaining; Chris@0: $percent = ($total > 0) ? floor(100 * $indexed / $total) : 100; Chris@0: $this->drupalGet('admin/config/search/pages'); Chris@0: $this->assertText($percent . '% of the site has been indexed.', 'Progress percent text at top of Search settings page is correct at: ' . $message); Chris@0: $this->assertText($remaining . ' item', 'Remaining text at top of Search settings page is correct at: ' . $message); Chris@0: Chris@0: // Check text in pages section of Search settings page. Chris@0: $this->assertText($indexed . ' of ' . $total . ' indexed', 'Progress text in pages section of Search settings page is correct at: ' . $message); Chris@0: Chris@0: // Check text on status report page. Chris@0: $this->drupalGet('admin/reports/status'); Chris@0: $this->assertText('Search index progress', 'Search status section header is present on status report page'); Chris@0: $this->assertText($percent . '%', 'Correct percentage is shown on status report page at: ' . $message); Chris@0: $this->assertText('(' . $remaining . ' remaining)', 'Correct remaining value is shown on status report page at: ' . $message); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Checks actual database counts of items in the search index. Chris@0: * Chris@0: * @param int $count_node Chris@0: * Count of node items to assert. Chris@0: * @param int $count_foo Chris@0: * Count of "foo" items to assert. Chris@0: * @param string $message Chris@0: * Message suffix to use. Chris@0: */ Chris@0: protected function assertDatabaseCounts($count_node, $count_foo, $message) { Chris@0: // Count number of distinct nodes by ID. Chris@0: $results = db_select('search_dataset', 'i') Chris@0: ->fields('i', ['sid']) Chris@0: ->condition('type', 'node_search') Chris@0: ->groupBy('sid') Chris@0: ->execute() Chris@0: ->fetchCol(); Chris@0: $this->assertEqual($count_node, count($results), 'Node count was ' . $count_node . ' for ' . $message); Chris@0: Chris@0: // Count number of "foo" records. Chris@0: $results = db_select('search_dataset', 'i') Chris@0: ->fields('i', ['sid']) Chris@0: ->condition('type', 'foo') Chris@0: ->execute() Chris@0: ->fetchCol(); Chris@0: $this->assertEqual($count_foo, count($results), 'Foo count was ' . $count_foo . ' for ' . $message); Chris@0: Chris@0: } Chris@0: Chris@0: }