1085 lines
38 KiB
Plaintext
1085 lines
38 KiB
Plaintext
<?php
|
|
// $Id$
|
|
|
|
// The search index can contain different types of content. Typically the type is 'node'.
|
|
// Here we test with _test_ and _test2_ as the type.
|
|
define('SEARCH_TYPE', '_test_');
|
|
define('SEARCH_TYPE_2', '_test2_');
|
|
define('SEARCH_TYPE_JPN', '_test3_');
|
|
|
|
class SearchMatchTestCase extends DrupalWebTestCase {
|
|
public static function getInfo() {
|
|
return array(
|
|
'name' => 'Search engine queries',
|
|
'description' => 'Indexes content and queries it.',
|
|
'group' => 'Search',
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Implementation setUp().
|
|
*/
|
|
function setUp() {
|
|
parent::setUp('search');
|
|
}
|
|
|
|
/**
|
|
* Test search indexing.
|
|
*/
|
|
function testMatching() {
|
|
$this->_setup();
|
|
$this->_testQueries();
|
|
}
|
|
|
|
/**
|
|
* Set up a small index of items to test against.
|
|
*/
|
|
function _setup() {
|
|
variable_set('minimum_word_size', 3);
|
|
|
|
for ($i = 1; $i <= 7; ++$i) {
|
|
search_index($i, SEARCH_TYPE, $this->getText($i));
|
|
}
|
|
for ($i = 1; $i <= 5; ++$i) {
|
|
search_index($i + 7, SEARCH_TYPE_2, $this->getText2($i));
|
|
}
|
|
// No getText builder function for Japanese text; just a simple array.
|
|
foreach (array(
|
|
13 => '以呂波耳・ほへとち。リヌルヲ。',
|
|
14 => 'ドルーパルが大好きよ!',
|
|
15 => 'コーヒーとケーキ',
|
|
) as $i => $jpn) {
|
|
search_index($i, SEARCH_TYPE_JPN, $jpn);
|
|
}
|
|
search_update_totals();
|
|
}
|
|
|
|
/**
|
|
* _test_: Helper method for generating snippets of content.
|
|
*
|
|
* Generated items to test against:
|
|
* 1 ipsum
|
|
* 2 dolore sit
|
|
* 3 sit am ut
|
|
* 4 am ut enim am
|
|
* 5 ut enim am minim veniam
|
|
* 6 enim am minim veniam es cillum
|
|
* 7 am minim veniam es cillum dolore eu
|
|
*/
|
|
function getText($n) {
|
|
$words = explode(' ', "Ipsum dolore sit am. Ut enim am minim veniam. Es cillum dolore eu.");
|
|
return implode(' ', array_slice($words, $n - 1, $n));
|
|
}
|
|
|
|
/**
|
|
* _test2_: Helper method for generating snippets of content.
|
|
*
|
|
* Generated items to test against:
|
|
* 8 dear
|
|
* 9 king philip
|
|
* 10 philip came over
|
|
* 11 came over from germany
|
|
* 12 over from germany swimming
|
|
*/
|
|
function getText2($n) {
|
|
$words = explode(' ', "Dear King Philip came over from Germany swimming.");
|
|
return implode(' ', array_slice($words, $n - 1, $n));
|
|
}
|
|
|
|
/**
|
|
* Run predefine queries looking for indexed terms.
|
|
*/
|
|
function _testQueries() {
|
|
/*
|
|
Note: OR queries that include short words in OR groups are only accepted
|
|
if the ORed terms are ANDed with at least one long word in the rest of the query.
|
|
|
|
e.g. enim dolore OR ut = enim (dolore OR ut) = (enim dolor) OR (enim ut) -> good
|
|
e.g. dolore OR ut = (dolore) OR (ut) -> bad
|
|
|
|
This is a design limitation to avoid full table scans.
|
|
*/
|
|
$queries = array(
|
|
// Simple AND queries.
|
|
'ipsum' => array(1),
|
|
'enim' => array(4, 5, 6),
|
|
'xxxxx' => array(),
|
|
'enim minim' => array(5, 6),
|
|
'enim xxxxx' => array(),
|
|
'dolore eu' => array(7),
|
|
'dolore xx' => array(),
|
|
'ut minim' => array(5),
|
|
'xx minim' => array(),
|
|
'enim veniam am minim ut' => array(5),
|
|
// Simple OR queries.
|
|
'dolore OR ipsum' => array(1, 2, 7),
|
|
'dolore OR xxxxx' => array(2, 7),
|
|
'dolore OR ipsum OR enim' => array(1, 2, 4, 5, 6, 7),
|
|
'ipsum OR dolore sit OR cillum' => array(2, 7),
|
|
'minim dolore OR ipsum' => array(7),
|
|
'dolore OR ipsum veniam' => array(7),
|
|
'minim dolore OR ipsum OR enim' => array(5, 6, 7),
|
|
'dolore xx OR yy' => array(),
|
|
'xxxxx dolore OR ipsum' => array(),
|
|
// Negative queries.
|
|
'dolore -sit' => array(7),
|
|
'dolore -eu' => array(2),
|
|
'dolore -xxxxx' => array(2, 7),
|
|
'dolore -xx' => array(2, 7),
|
|
// Phrase queries.
|
|
'"dolore sit"' => array(2),
|
|
'"sit dolore"' => array(),
|
|
'"am minim veniam es"' => array(6, 7),
|
|
'"minim am veniam es"' => array(),
|
|
// Mixed queries.
|
|
'"am minim veniam es" OR dolore' => array(2, 6, 7),
|
|
'"minim am veniam es" OR "dolore sit"' => array(2),
|
|
'"minim am veniam es" OR "sit dolore"' => array(),
|
|
'"am minim veniam es" -eu' => array(6),
|
|
'"am minim veniam" -"cillum dolore"' => array(5, 6),
|
|
'"am minim veniam" -"dolore cillum"' => array(5, 6, 7),
|
|
'xxxxx "minim am veniam es" OR dolore' => array(),
|
|
'xx "minim am veniam es" OR dolore' => array()
|
|
);
|
|
foreach ($queries as $query => $results) {
|
|
$result = db_select('search_index', 'i')
|
|
->extend('SearchQuery')
|
|
->searchExpression($query, SEARCH_TYPE)
|
|
->execute();
|
|
|
|
$set = $result ? $result->fetchAll() : array();
|
|
$this->_testQueryMatching($query, $set, $results);
|
|
$this->_testQueryScores($query, $set, $results);
|
|
}
|
|
|
|
// These queries are run against the second index type, SEARCH_TYPE_2.
|
|
$queries = array(
|
|
// Simple AND queries.
|
|
'ipsum' => array(),
|
|
'enim' => array(),
|
|
'enim minim' => array(),
|
|
'dear' => array(8),
|
|
'germany' => array(11, 12),
|
|
);
|
|
foreach ($queries as $query => $results) {
|
|
$result = db_select('search_index', 'i')
|
|
->extend('SearchQuery')
|
|
->searchExpression($query, SEARCH_TYPE_2)
|
|
->execute();
|
|
|
|
$set = $result ? $result->fetchAll() : array();
|
|
$this->_testQueryMatching($query, $set, $results);
|
|
$this->_testQueryScores($query, $set, $results);
|
|
}
|
|
|
|
// These queries are run against the third index type, SEARCH_TYPE_JPN.
|
|
$queries = array(
|
|
// Simple AND queries.
|
|
'呂波耳' => array(13),
|
|
'以呂波耳' => array(13),
|
|
'ほへと ヌルヲ' => array(13),
|
|
'とちリ' => array(),
|
|
'ドルーパル' => array(14),
|
|
'パルが大' => array(14),
|
|
'コーヒー' => array(15),
|
|
'ヒーキ' => array(),
|
|
);
|
|
foreach ($queries as $query => $results) {
|
|
$result = db_select('search_index', 'i')
|
|
->extend('SearchQuery')
|
|
->searchExpression($query, SEARCH_TYPE_JPN)
|
|
->execute();
|
|
|
|
$set = $result ? $result->fetchAll() : array();
|
|
$this->_testQueryMatching($query, $set, $results);
|
|
$this->_testQueryScores($query, $set, $results);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test the matching abilities of the engine.
|
|
*
|
|
* Verify if a query produces the correct results.
|
|
*/
|
|
function _testQueryMatching($query, $set, $results) {
|
|
// Get result IDs.
|
|
$found = array();
|
|
foreach ($set as $item) {
|
|
$found[] = $item->sid;
|
|
}
|
|
|
|
// Compare $results and $found.
|
|
sort($found);
|
|
sort($results);
|
|
$this->assertEqual($found, $results, "Query matching '$query'");
|
|
}
|
|
|
|
/**
|
|
* Test the scoring abilities of the engine.
|
|
*
|
|
* Verify if a query produces normalized, monotonous scores.
|
|
*/
|
|
function _testQueryScores($query, $set, $results) {
|
|
// Get result scores.
|
|
$scores = array();
|
|
foreach ($set as $item) {
|
|
$scores[] = $item->calculated_score;
|
|
}
|
|
|
|
// Check order.
|
|
$sorted = $scores;
|
|
sort($sorted);
|
|
$this->assertEqual($scores, array_reverse($sorted), "Query order '$query'");
|
|
|
|
// Check range.
|
|
$this->assertEqual(!count($scores) || (min($scores) > 0.0 && max($scores) <= 1.0001), TRUE, "Query scoring '$query'");
|
|
}
|
|
}
|
|
|
|
class SearchBikeShed extends DrupalWebTestCase {
|
|
protected $searching_user;
|
|
|
|
public static function getInfo() {
|
|
return array(
|
|
'name' => 'Bike shed',
|
|
'description' => 'Tests the bike shed text on the no results page.',
|
|
'group' => 'Search'
|
|
);
|
|
}
|
|
|
|
function setUp() {
|
|
parent::setUp('search');
|
|
|
|
// Create user.
|
|
$this->searching_user = $this->drupalCreateUser(array('search content'));
|
|
}
|
|
|
|
function testFailedSearch() {
|
|
$this->drupalLogin($this->searching_user);
|
|
$this->drupalGet('search/node');
|
|
$this->assertText(t('Enter your keywords'));
|
|
|
|
$edit = array();
|
|
$edit['keys'] = 'bike shed ' . $this->randomName();
|
|
$this->drupalPost('search/node', $edit, t('Search'));
|
|
$this->assertText(t('Consider loosening your query with OR. bike OR shed will often show more results than bike shed.'), t('Help text is displayed when search returns no results.'));
|
|
}
|
|
}
|
|
|
|
class SearchAdvancedSearchForm extends DrupalWebTestCase {
|
|
protected $node;
|
|
|
|
public static function getInfo() {
|
|
return array(
|
|
'name' => 'Advanced search form',
|
|
'description' => 'Indexes content and tests the advanced search form.',
|
|
'group' => 'Search',
|
|
);
|
|
}
|
|
|
|
function setUp() {
|
|
parent::setUp('search');
|
|
// Create and login user.
|
|
$test_user = $this->drupalCreateUser(array('access content', 'search content', 'use advanced search', 'administer nodes'));
|
|
$this->drupalLogin($test_user);
|
|
|
|
// Create initial node.
|
|
$node = $this->drupalCreateNode();
|
|
$this->node = $this->drupalCreateNode();
|
|
|
|
// First update the index. This does the initial processing.
|
|
node_update_index();
|
|
|
|
// Then, run the shutdown function. Testing is a unique case where indexing
|
|
// and searching has to happen in the same request, so running the shutdown
|
|
// function manually is needed to finish the indexing process.
|
|
search_update_totals();
|
|
}
|
|
|
|
/**
|
|
* Test using the search form with GET and POST queries.
|
|
* Test using the advanced search form to limit search to nodes of type "Basic page".
|
|
*/
|
|
function testNodeType() {
|
|
$this->assertTrue($this->node->type == 'page', t('Node type is Basic page.'));
|
|
|
|
// Assert that the dummy title doesn't equal the real title.
|
|
$dummy_title = 'Lorem ipsum';
|
|
$this->assertNotEqual($dummy_title, $this->node->title, t("Dummy title doens't equal node title"));
|
|
|
|
// Search for the dummy title with a GET query.
|
|
$this->drupalGet('search/node/' . $dummy_title);
|
|
$this->assertNoText($this->node->title, t('Basic page node is not found with dummy title.'));
|
|
|
|
// Search for the title of the node with a GET query.
|
|
$this->drupalGet('search/node/' . $this->node->title);
|
|
$this->assertText($this->node->title, t('Basic page node is found with GET query.'));
|
|
|
|
// Search for the title of the node with a POST query.
|
|
$edit = array('or' => $this->node->title);
|
|
$this->drupalPost('search/node', $edit, t('Advanced search'));
|
|
$this->assertText($this->node->title, t('Basic page node is found with POST query.'));
|
|
|
|
// Advanced search type option.
|
|
$this->drupalPost('search/node', array_merge($edit, array('type[page]' => 'page')), t('Advanced search'));
|
|
$this->assertText($this->node->title, t('Basic page node is found with POST query and type:page.'));
|
|
|
|
$this->drupalPost('search/node', array_merge($edit, array('type[article]' => 'article')), t('Advanced search'));
|
|
$this->assertText('bike shed', t('Article node is not found with POST query and type:article.'));
|
|
}
|
|
}
|
|
|
|
class SearchRankingTestCase extends DrupalWebTestCase {
|
|
public static function getInfo() {
|
|
return array(
|
|
'name' => 'Search engine ranking',
|
|
'description' => 'Indexes content and tests ranking factors.',
|
|
'group' => 'Search',
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Implementation setUp().
|
|
*/
|
|
function setUp() {
|
|
parent::setUp('search', 'statistics', 'comment');
|
|
}
|
|
|
|
function testRankings() {
|
|
// Login with sufficient privileges.
|
|
$this->drupalLogin($this->drupalCreateUser(array('post comments without approval', 'create page content')));
|
|
|
|
// Build a list of the rankings to test.
|
|
$node_ranks = array('sticky', 'promote', 'relevance', 'recent', 'comments', 'views');
|
|
|
|
// Create nodes for testing.
|
|
foreach ($node_ranks as $node_rank) {
|
|
$settings = array('type' => 'page', 'title' => array(LANGUAGE_NONE => array(array('value' => 'Drupal rocks'))), 'body' => array(LANGUAGE_NONE => array(array('value' => "Drupal's search rocks"))));
|
|
foreach (array(0, 1) as $num) {
|
|
if ($num == 1) {
|
|
switch ($node_rank) {
|
|
case 'sticky':
|
|
case 'promote':
|
|
$settings[$node_rank] = 1;
|
|
break;
|
|
case 'relevance':
|
|
$settings['body'][LANGUAGE_NONE][0]['value'] .= " really rocks";
|
|
break;
|
|
case 'recent':
|
|
$settings['created'] = REQUEST_TIME + 3600;
|
|
break;
|
|
case 'comments':
|
|
$settings['comment'] = 2;
|
|
break;
|
|
}
|
|
}
|
|
$nodes[$node_rank][$num] = $this->drupalCreateNode($settings);
|
|
}
|
|
}
|
|
|
|
// Update the search index.
|
|
module_invoke_all('update_index');
|
|
search_update_totals();
|
|
|
|
// Refresh variables after the treatment.
|
|
$this->refreshVariables();
|
|
|
|
// Add a comment to one of the nodes.
|
|
$edit = array();
|
|
$edit['subject'] = 'my comment title';
|
|
$edit['comment_body[' . LANGUAGE_NONE . '][0][value]'] = 'some random comment';
|
|
$this->drupalGet('comment/reply/' . $nodes['comments'][1]->nid);
|
|
$this->drupalPost(NULL, $edit, t('Preview'));
|
|
$this->drupalPost(NULL, $edit, t('Save'));
|
|
|
|
// Enable counting of statistics.
|
|
variable_set('statistics_count_content_views', 1);
|
|
|
|
// Then View one of the nodes a bunch of times.
|
|
for ($i = 0; $i < 5; $i ++) {
|
|
$this->drupalGet('node/' . $nodes['views'][1]->nid);
|
|
}
|
|
|
|
// Test each of the possible rankings.
|
|
foreach ($node_ranks as $node_rank) {
|
|
// Disable all relevancy rankings except the one we are testing.
|
|
foreach ($node_ranks as $var) {
|
|
variable_set('node_rank_' . $var, $var == $node_rank ? 10 : 0);
|
|
}
|
|
|
|
// Do the search and assert the results.
|
|
$set = node_search_execute('rocks');
|
|
$this->assertEqual($set[0]['node']->nid, $nodes[$node_rank][1]->nid, 'Search ranking "' . $node_rank . '" order.');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test rankings of HTML tags.
|
|
*/
|
|
function testHTMLRankings() {
|
|
// Login with sufficient privileges.
|
|
$this->drupalLogin($this->drupalCreateUser(array('create page content')));
|
|
|
|
// Test HTML tags with different weights.
|
|
$sorted_tags = array('h1', 'h2', 'h3', 'h4', 'a', 'h5', 'h6', 'notag');
|
|
$shuffled_tags = $sorted_tags;
|
|
|
|
// Shuffle tags to ensure HTML tags are ranked properly.
|
|
shuffle($shuffled_tags);
|
|
$settings = array(
|
|
'type' => 'page',
|
|
'title' => array(LANGUAGE_NONE => array(array('value' => 'Simple node'))),
|
|
);
|
|
foreach ($shuffled_tags as $tag) {
|
|
switch ($tag) {
|
|
case 'a':
|
|
$settings['body'] = array(LANGUAGE_NONE => array(array('value' => l('Drupal Rocks', 'node'), 'format' => 3)));
|
|
break;
|
|
case 'notag':
|
|
$settings['body'] = array(LANGUAGE_NONE => array(array('value' => 'Drupal Rocks')));
|
|
break;
|
|
default:
|
|
$settings['body'] = array(LANGUAGE_NONE => array(array('value' => "<$tag>Drupal Rocks</$tag>", 'format' => 3)));
|
|
break;
|
|
}
|
|
$nodes[$tag] = $this->drupalCreateNode($settings);
|
|
}
|
|
|
|
// Update the search index.
|
|
module_invoke_all('update_index');
|
|
search_update_totals();
|
|
|
|
// Refresh variables after the treatment.
|
|
$this->refreshVariables();
|
|
|
|
// Disable all other rankings.
|
|
$node_ranks = array('sticky', 'promote', 'recent', 'comments', 'views');
|
|
foreach ($node_ranks as $node_rank) {
|
|
variable_set('node_rank_' . $node_rank, 0);
|
|
}
|
|
$set = node_search_execute('rocks');
|
|
|
|
// Test the ranking of each tag.
|
|
foreach ($sorted_tags as $tag_rank => $tag) {
|
|
// Assert the results.
|
|
if ($tag == 'notag') {
|
|
$this->assertEqual($set[$tag_rank]['node']->nid, $nodes[$tag]->nid, 'Search tag ranking for plain text order.');
|
|
} else {
|
|
$this->assertEqual($set[$tag_rank]['node']->nid, $nodes[$tag]->nid, 'Search tag ranking for "<' . $sorted_tags[$tag_rank] . '>" order.');
|
|
}
|
|
}
|
|
|
|
// Test tags with the same weight against the sorted tags.
|
|
$unsorted_tags = array('u', 'b', 'i', 'strong', 'em');
|
|
foreach ($unsorted_tags as $tag) {
|
|
$settings['body'] = array(LANGUAGE_NONE => array(array('value' => "<$tag>Drupal Rocks</$tag>", 'format' => 3)));
|
|
$node = $this->drupalCreateNode($settings);
|
|
|
|
// Update the search index.
|
|
module_invoke_all('update_index');
|
|
search_update_totals();
|
|
|
|
// Refresh variables after the treatment.
|
|
$this->refreshVariables();
|
|
|
|
$set = node_search_execute('rocks');
|
|
|
|
// Ranking should always be second to last.
|
|
$set = array_slice($set, -2, 1);
|
|
|
|
// Assert the results.
|
|
$this->assertEqual($set[0]['node']->nid, $node->nid, 'Search tag ranking for "<' . $tag . '>" order.');
|
|
|
|
// Delete node so it doesn't show up in subsequent search results.
|
|
node_delete($node->nid);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Verifies that if we combine two rankings, search still works.
|
|
*
|
|
* See issue http://drupal.org/node/771596
|
|
*/
|
|
function testDoubleRankings() {
|
|
// Login with sufficient privileges.
|
|
$this->drupalLogin($this->drupalCreateUser(array('post comments without approval', 'create page content')));
|
|
|
|
// See testRankings() above - build a node that will rank high for sticky.
|
|
$settings = array(
|
|
'type' => 'page',
|
|
'title' => array(LANGUAGE_NONE => array(array('value' => 'Drupal rocks'))),
|
|
'body' => array(LANGUAGE_NONE => array(array('value' => "Drupal's search rocks"))),
|
|
'sticky' => 1,
|
|
);
|
|
|
|
$node = $this->drupalCreateNode($settings);
|
|
|
|
// Update the search index.
|
|
module_invoke_all('update_index');
|
|
search_update_totals();
|
|
|
|
// Refresh variables after the treatment.
|
|
$this->refreshVariables();
|
|
|
|
// Set up for ranking sticky and lots of comments; make sure others are
|
|
// disabled.
|
|
$node_ranks = array('sticky', 'promote', 'relevance', 'recent', 'comments', 'views');
|
|
foreach ($node_ranks as $var) {
|
|
$value = ($var == 'sticky' || $var == 'comments') ? 10 : 0;
|
|
variable_set('node_rank_' . $var, $value);
|
|
}
|
|
|
|
// Do the search and assert the results.
|
|
$set = node_search_execute('rocks');
|
|
$this->assertEqual($set[0]['node']->nid, $node->nid, 'Search double ranking order.');
|
|
}
|
|
}
|
|
|
|
class SearchBlockTestCase extends DrupalWebTestCase {
|
|
public static function getInfo() {
|
|
return array(
|
|
'name' => 'Block availability',
|
|
'description' => 'Check if the search form block is available.',
|
|
'group' => 'Search',
|
|
);
|
|
}
|
|
|
|
function setUp() {
|
|
parent::setUp('search');
|
|
|
|
// Create and login user
|
|
$admin_user = $this->drupalCreateUser(array('administer blocks', 'search content'));
|
|
$this->drupalLogin($admin_user);
|
|
}
|
|
|
|
function testSearchFormBlock() {
|
|
// Set block title to confirm that the interface is availble.
|
|
$this->drupalPost('admin/structure/block/manage/search/form/configure', array('title' => $this->randomName(8)), t('Save block'));
|
|
$this->assertText(t('The block configuration has been saved.'), t('Block configuration set.'));
|
|
|
|
// Set the block to a region to confirm block is availble.
|
|
$edit = array();
|
|
$edit['search_form[region]'] = 'footer';
|
|
$this->drupalPost('admin/structure/block', $edit, t('Save blocks'));
|
|
$this->assertText(t('The block settings have been updated.'), t('Block successfully move to footer region.'));
|
|
}
|
|
|
|
/**
|
|
* Test that the search block form works correctly.
|
|
*/
|
|
function testBlock() {
|
|
// Enable the block, and place it in the 'content' region so that it isn't
|
|
// hidden on 404 pages.
|
|
$edit = array('search_form[region]' => 'content');
|
|
$this->drupalPost('admin/structure/block', $edit, t('Save blocks'));
|
|
|
|
// Test a normal search via the block form, from the front page.
|
|
$terms = array('search_block_form' => 'test');
|
|
$this->drupalPost('node', $terms, t('Search'));
|
|
$this->assertText('Your search yielded no results');
|
|
|
|
// Test a search from the block on a 404 page.
|
|
$this->drupalGet('foo');
|
|
$this->assertResponse(404);
|
|
$this->drupalPost(NULL, $terms, t('Search'));
|
|
$this->assertResponse(200);
|
|
$this->assertText('Your search yielded no results');
|
|
|
|
// Test a search from the block when it doesn't appear on the search page.
|
|
$edit = array('pages' => 'search');
|
|
$this->drupalPost('admin/structure/block/manage/search/form/configure', $edit, t('Save block'));
|
|
$this->drupalPost('node', $terms, t('Search'));
|
|
$this->assertText('Your search yielded no results');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Tests that searching for a phrase gets the correct page count.
|
|
*/
|
|
class SearchExactTestCase extends DrupalWebTestCase {
|
|
public static function getInfo() {
|
|
return array(
|
|
'name' => 'Search engine phrase queries',
|
|
'description' => 'Tests that searching for a phrase gets the correct page count.',
|
|
'group' => 'Search',
|
|
);
|
|
}
|
|
|
|
function setUp() {
|
|
parent::setUp('search');
|
|
}
|
|
|
|
/**
|
|
* Tests that the correct number of pager links are found for both keywords and phrases.
|
|
*/
|
|
function testExactQuery() {
|
|
// Login with sufficient privileges.
|
|
$this->drupalLogin($this->drupalCreateUser(array('create page content', 'search content')));
|
|
|
|
$settings = array(
|
|
'type' => 'page',
|
|
'title' => 'Simple Node',
|
|
);
|
|
// Create nodes with exact phrase.
|
|
for ($i = 0; $i <= 17; $i++) {
|
|
$settings['body'] = array(LANGUAGE_NONE => array(array('value' => 'love pizza')));
|
|
$this->drupalCreateNode($settings);
|
|
}
|
|
// Create nodes containing keywords.
|
|
for ($i = 0; $i <= 17; $i++) {
|
|
$settings['body'] = array(LANGUAGE_NONE => array(array('value' => 'love cheesy pizza')));
|
|
$this->drupalCreateNode($settings);
|
|
}
|
|
|
|
// Update the search index.
|
|
module_invoke_all('update_index');
|
|
search_update_totals();
|
|
|
|
// Refresh variables after the treatment.
|
|
$this->refreshVariables();
|
|
|
|
// Test that the correct number of pager links are found for keyword search.
|
|
$edit = array('keys' => 'love pizza');
|
|
$this->drupalPost('search/node', $edit, t('Search'));
|
|
$this->assertLinkByHref('page=1', 0, '2nd page link is found for keyword search.');
|
|
$this->assertLinkByHref('page=2', 0, '3rd page link is found for keyword search.');
|
|
$this->assertLinkByHref('page=3', 0, '4th page link is found for keyword search.');
|
|
$this->assertNoLinkByHref('page=4', '5th page link is not found for keyword search.');
|
|
|
|
// Test that the correct number of pager links are found for exact phrase search.
|
|
$edit = array('keys' => '"love pizza"');
|
|
$this->drupalPost('search/node', $edit, t('Search'));
|
|
$this->assertLinkByHref('page=1', 0, '2nd page link is found for exact phrase search.');
|
|
$this->assertNoLinkByHref('page=2', '3rd page link is not found for exact phrase search.');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test integration searching comments.
|
|
*/
|
|
class SearchCommentTestCase extends DrupalWebTestCase {
|
|
protected $admin_user;
|
|
|
|
public static function getInfo() {
|
|
return array(
|
|
'name' => 'Comment Search tests',
|
|
'description' => 'Verify text formats and filters used elsewhere.',
|
|
'group' => 'Search',
|
|
);
|
|
}
|
|
|
|
function setUp() {
|
|
parent::setUp('comment', 'search');
|
|
|
|
// Create and log in an administrative user having access to the Full HTML
|
|
// text format.
|
|
$full_html_format = db_query_range('SELECT * FROM {filter_format} WHERE name = :name', 0, 1, array(':name' => 'Full HTML'))->fetchObject();
|
|
$this->admin_user = $this->drupalCreateUser(array(
|
|
'administer filters',
|
|
filter_permission_name($full_html_format),
|
|
'administer permissions',
|
|
'create page content',
|
|
'post comments without approval',
|
|
));
|
|
$this->drupalLogin($this->admin_user);
|
|
}
|
|
|
|
/**
|
|
* Verify that comments are rendered using proper format in search results.
|
|
*/
|
|
function testSearchResultsComment() {
|
|
$comment_body = 'Test comment body';
|
|
|
|
variable_set('comment_preview_article', DRUPAL_OPTIONAL);
|
|
// Enable check_plain() for 'Filtered HTML' text format.
|
|
$filtered_html_format_id = db_query_range('SELECT format FROM {filter_format} WHERE name = :name', 0, 1, array(':name' => 'Filtered HTML'))->fetchField();
|
|
$edit = array(
|
|
'filters[filter_html_escape][status]' => $filtered_html_format_id,
|
|
);
|
|
$this->drupalPost('admin/config/content/formats/1', $edit, t('Save configuration'));
|
|
// Allow anonymous users to search content.
|
|
$edit = array(
|
|
DRUPAL_ANONYMOUS_RID . '[search content]' => 1,
|
|
// @todo Comments are added to search index without checking first whether
|
|
// anonymous users are allowed to access comments.
|
|
DRUPAL_ANONYMOUS_RID . '[access comments]' => 1,
|
|
// @todo Without this permission, "Login or register to post comments" is
|
|
// added to the search index. Comment.module is not guilty; that text
|
|
// seems to be added via node links.
|
|
DRUPAL_ANONYMOUS_RID . '[post comments]' => 1,
|
|
);
|
|
$this->drupalPost('admin/people/permissions', $edit, t('Save permissions'));
|
|
|
|
// Create a node.
|
|
$node = $this->drupalCreateNode(array('type' => 'article'));
|
|
// Post a comment using 'Full HTML' text format.
|
|
$edit_comment = array();
|
|
$edit_comment['subject'] = 'Test comment subject';
|
|
$edit_comment['comment_body[' . LANGUAGE_NONE . '][0][value]'] = '<h1>' . $comment_body . '</h1>';
|
|
$full_html_format_id = db_query_range('SELECT format FROM {filter_format} WHERE name = :name', 0, 1, array(':name' => 'Full HTML'))->fetchField();
|
|
$edit_comment['comment_body[' . LANGUAGE_NONE . '][0][format]'] = $full_html_format_id;
|
|
$this->drupalPost('comment/reply/' . $node->nid, $edit_comment, t('Save'));
|
|
|
|
// Invoke search index update.
|
|
$this->drupalLogout();
|
|
$this->cronRun();
|
|
|
|
// Search for the comment subject.
|
|
$edit = array(
|
|
'search_block_form' => "'" . $edit_comment['subject'] . "'",
|
|
);
|
|
$this->drupalPost('', $edit, t('Search'));
|
|
$this->assertText($node->title, t('Node found in search results.'));
|
|
$this->assertText($edit_comment['subject'], t('Comment subject found in search results.'));
|
|
|
|
// Search for the comment body.
|
|
$edit = array(
|
|
'search_block_form' => "'" . $comment_body . "'",
|
|
);
|
|
$this->drupalPost('', $edit, t('Search'));
|
|
$this->assertText($node->title, t('Node found in search results.'));
|
|
|
|
// Verify that comment is rendered using proper format.
|
|
$this->assertText($comment_body, t('Comment body text found in search results.'));
|
|
$this->assertNoRaw(t('n/a'), t('HTML in comment body is not hidden.'));
|
|
$this->assertNoRaw(check_plain($edit_comment['comment_body[' . LANGUAGE_NONE . '][0][value]']), t('HTML in comment body is not escaped.'));
|
|
|
|
// Hide comments.
|
|
$this->drupalLogin($this->admin_user);
|
|
$node->comment = 0;
|
|
node_save($node);
|
|
|
|
// Invoke search index update.
|
|
$this->drupalLogout();
|
|
$this->cronRun();
|
|
|
|
// Search for $title.
|
|
$this->drupalPost('', $edit, t('Search'));
|
|
$this->assertNoText($comment_body, t('Comment body text not found in search results.'));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Tests that comment count display toggles properly on comment status of node
|
|
*
|
|
* Issue 537278
|
|
*
|
|
* - Nodes with comment status set to Open should always how comment counts
|
|
* - Nodes with comment status set to Closed should show comment counts
|
|
* only when there are comments
|
|
* - Nodes with comment status set to Hidden should never show comment counts
|
|
*/
|
|
class SearchCommentCountToggleTestCase extends DrupalWebTestCase {
|
|
protected $searching_user;
|
|
protected $searchable_nodes;
|
|
|
|
public static function getInfo() {
|
|
return array(
|
|
'name' => 'Comment count toggle',
|
|
'description' => 'Verify that comment count display toggles properly on comment status of node.',
|
|
'group' => 'Search',
|
|
);
|
|
}
|
|
|
|
function setUp() {
|
|
parent::setUp('search');
|
|
|
|
// Create searching user.
|
|
$this->searching_user = $this->drupalCreateUser(array('search content', 'access content', 'access comments', 'post comments without approval'));
|
|
|
|
// Create initial nodes.
|
|
$node_params = array('type' => 'article', 'body' => array(LANGUAGE_NONE => array(array('value' => 'SearchCommentToggleTestCase'))));
|
|
|
|
$this->searchable_nodes['1 comment'] = $this->drupalCreateNode($node_params);
|
|
$this->searchable_nodes['0 comments'] = $this->drupalCreateNode($node_params);
|
|
|
|
// Login with sufficient privileges.
|
|
$this->drupalLogin($this->searching_user);
|
|
|
|
// Create a comment array
|
|
$edit_comment = array();
|
|
$edit_comment['subject'] = $this->randomName();
|
|
$edit_comment['comment_body[' . LANGUAGE_NONE . '][0][value]'] = $this->randomName();
|
|
$filtered_html_format_id = db_query_range('SELECT format FROM {filter_format} WHERE name = :name', 0, 1, array(':name' => 'Filtered HTML'))->fetchField();
|
|
$edit_comment['comment_body[' . LANGUAGE_NONE . '][0][format]'] = $filtered_html_format_id;
|
|
|
|
// Post comment to the test node with comment
|
|
$this->drupalPost('comment/reply/' . $this->searchable_nodes['1 comment']->nid, $edit_comment, t('Save'));
|
|
|
|
// First update the index. This does the initial processing.
|
|
node_update_index();
|
|
|
|
// Then, run the shutdown function. Testing is a unique case where indexing
|
|
// and searching has to happen in the same request, so running the shutdown
|
|
// function manually is needed to finish the indexing process.
|
|
search_update_totals();
|
|
}
|
|
|
|
/**
|
|
* Verify that comment count display toggles properly on comment status of node
|
|
*/
|
|
function testSearchCommentCountToggle() {
|
|
// Search for the nodes by string in the node body.
|
|
$edit = array(
|
|
'search_block_form' => "'SearchCommentToggleTestCase'",
|
|
);
|
|
|
|
// Test comment count display for nodes with comment status set to Open
|
|
$this->drupalPost('', $edit, t('Search'));
|
|
$this->assertText(t('0 comments'), t('Empty comment count displays for nodes with comment status set to Open'));
|
|
$this->assertText(t('1 comment'), t('Non-empty comment count displays for nodes with comment status set to Open'));
|
|
|
|
// Test comment count display for nodes with comment status set to Closed
|
|
$this->searchable_nodes['0 comments']->comment = COMMENT_NODE_CLOSED;
|
|
node_save($this->searchable_nodes['0 comments']);
|
|
$this->searchable_nodes['1 comment']->comment = COMMENT_NODE_CLOSED;
|
|
node_save($this->searchable_nodes['1 comment']);
|
|
|
|
$this->drupalPost('', $edit, t('Search'));
|
|
$this->assertNoText(t('0 comments'), t('Empty comment count does not display for nodes with comment status set to Closed'));
|
|
$this->assertText(t('1 comment'), t('Non-empty comment count displays for nodes with comment status set to Closed'));
|
|
|
|
// Test comment count display for nodes with comment status set to Hidden
|
|
$this->searchable_nodes['0 comments']->comment = COMMENT_NODE_HIDDEN;
|
|
node_save($this->searchable_nodes['0 comments']);
|
|
$this->searchable_nodes['1 comment']->comment = COMMENT_NODE_HIDDEN;
|
|
node_save($this->searchable_nodes['1 comment']);
|
|
|
|
$this->drupalPost('', $edit, t('Search'));
|
|
$this->assertNoText(t('0 comments'), t('Empty comment count does not display for nodes with comment status set to Hidden'));
|
|
$this->assertNoText(t('1 comment'), t('Non-empty comment count does not display for nodes with comment status set to Hidden'));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test search_simplify() on every Unicode character.
|
|
*/
|
|
class SearchSimplifyTestCase extends DrupalWebTestCase {
|
|
public static function getInfo() {
|
|
return array(
|
|
'name' => 'Search simplify',
|
|
'description' => 'Check that simplification works as intended.',
|
|
'group' => 'Search',
|
|
);
|
|
}
|
|
|
|
function testSearchSimplify() {
|
|
$input = file_get_contents(DRUPAL_ROOT . '/modules/search/tests/UnicodeTest.txt');
|
|
$strings = explode(chr(10), $input);
|
|
foreach ($strings as $key => $string) {
|
|
$simplified = search_simplify($string);
|
|
if ($key % 2) {
|
|
$this->assertIdentical($simplified, ' ', "Line $key is excluded from the index");
|
|
}
|
|
else {
|
|
$this->assertTrue(drupal_strlen($simplified) >= drupal_strlen($string), "Nothing is removed on line $key.");
|
|
}
|
|
}
|
|
$string = '';
|
|
for ($i = 0; $i < 32; $i++) {
|
|
$string .= chr($i);
|
|
}
|
|
// Diff really does not like files starting with \0 so test it separately.
|
|
$this->assertIdentical(' ', search_simplify($string), t('Search simplify works for ASCII control characters.'));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test config page.
|
|
*/
|
|
class SearchConfigSettingsForm extends DrupalWebTestCase {
|
|
public static function getInfo() {
|
|
return array(
|
|
'name' => 'Config settings form',
|
|
'description' => 'Verify the search config settings form.',
|
|
'group' => 'Search',
|
|
);
|
|
}
|
|
|
|
function setUp() {
|
|
parent::setUp('search');
|
|
|
|
// Login as a user that can create and search content.
|
|
$this->drupalLogin($this->drupalCreateUser(array('search content', 'administer search', 'administer nodes', 'bypass node access')));
|
|
}
|
|
|
|
/**
|
|
* Verify the search settings form.
|
|
*/
|
|
function testSearchSettingsPage() {
|
|
// Add a single piece of content and index it.
|
|
$node = $this->drupalCreateNode();
|
|
// Link the node to itself to test that it's only indexed once.
|
|
$langcode = LANGUAGE_NONE;
|
|
$body_key = "body[$langcode][0][value]";
|
|
$edit[$body_key] = l($node->title, 'node/' . $node->nid);
|
|
$this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
|
|
|
|
node_update_index();
|
|
search_update_totals();
|
|
|
|
// Test that the settings form displays the correct count of items left to index.
|
|
$this->drupalGet('admin/config/search/settings');
|
|
$this->assertText(t('There are @count items left to index.', array('@count' => 0)));
|
|
|
|
// Test the re-index button.
|
|
$this->drupalPost('admin/config/search/settings', array(), t('Re-index site'));
|
|
$this->assertText(t('Are you sure you want to re-index the site'));
|
|
$this->drupalPost('admin/config/search/settings/reindex', array(), t('Re-index site'));
|
|
$this->assertText(t('The index will be rebuilt'));
|
|
$this->drupalGet('admin/config/search/settings');
|
|
$this->assertText(t('There is 1 item left to index.'));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test the CJK tokenizer.
|
|
*/
|
|
class SearchTokenizerTestCase extends DrupalWebTestCase {
|
|
public static function getInfo() {
|
|
return array(
|
|
'name' => 'CJK tokenizer',
|
|
'description' => 'Check that CJK tokenizer works as intended.',
|
|
'group' => 'Search',
|
|
);
|
|
}
|
|
|
|
function setUp() {
|
|
parent::setUp('search');
|
|
}
|
|
|
|
/**
|
|
* Verifies that strings of CJK characters are tokenized.
|
|
*
|
|
* The search_simplify() function does special things with numbers, symbols,
|
|
* and punctuation. So we only test that CJK characters that are not in these
|
|
* character classes are tokenized properly. See PREG_CLASS_CKJ for more
|
|
* information.
|
|
*/
|
|
function testTokenizer() {
|
|
// Set the minimum word size to 1 (to split all CJK characters) and make
|
|
// sure CJK tokenizing is turned on.
|
|
variable_set('minimum_word_size', 1);
|
|
variable_set('overlap_cjk', TRUE);
|
|
$this->refreshVariables();
|
|
|
|
// Create a string of CJK characters from various character ranges in
|
|
// the Unicode tables.
|
|
|
|
// Beginnings of the character ranges.
|
|
$starts = array(
|
|
'CJK unified' => 0x4e00,
|
|
'CJK Ext A' => 0x3400,
|
|
'CJK Compat' => 0xf900,
|
|
'Hangul Jamo' => 0x1100,
|
|
'Hangul Ext A' => 0xa960,
|
|
'Hangul Ext B' => 0xd7b0,
|
|
'Hangul Compat' => 0x3131,
|
|
'Half non-punct 1' => 0xff21,
|
|
'Half non-punct 2' => 0xff41,
|
|
'Half non-punct 3' => 0xff66,
|
|
'Hangul Syllables' => 0xac00,
|
|
'Hiragana' => 0x3040,
|
|
'Katakana' => 0x30a1,
|
|
'Katakana Ext' => 0x31f0,
|
|
'CJK Reserve 1' => 0x20000,
|
|
'CJK Reserve 2' => 0x30000,
|
|
'Bomofo' => 0x3100,
|
|
'Bomofo Ext' => 0x31a0,
|
|
'Lisu' => 0xa4d0,
|
|
'Yi' => 0xa000,
|
|
);
|
|
|
|
// Ends of the character ranges.
|
|
$ends = array(
|
|
'CJK unified' => 0x9fcf,
|
|
'CJK Ext A' => 0x4dbf,
|
|
'CJK Compat' => 0xfaff,
|
|
'Hangul Jamo' => 0x11ff,
|
|
'Hangul Ext A' => 0xa97f,
|
|
'Hangul Ext B' => 0xd7ff,
|
|
'Hangul Compat' => 0x318e,
|
|
'Half non-punct 1' => 0xff3a,
|
|
'Half non-punct 2' => 0xff5a,
|
|
'Half non-punct 3' => 0xffdc,
|
|
'Hangul Syllables' => 0xd7af,
|
|
'Hiragana' => 0x309f,
|
|
'Katakana' => 0x30ff,
|
|
'Katakana Ext' => 0x31ff,
|
|
'CJK Reserve 1' => 0x2fffd,
|
|
'CJK Reserve 2' => 0x3fffd,
|
|
'Bomofo' => 0x312f,
|
|
'Bomofo Ext' => 0x31b7,
|
|
'Lisu' => 0xa4fd,
|
|
'Yi' => 0xa48f,
|
|
);
|
|
|
|
// Generate characters consisting of starts, midpoints, and ends.
|
|
$chars = array();
|
|
$charcodes = array();
|
|
foreach ($starts as $key => $value) {
|
|
$charcodes[] = $starts[$key];
|
|
$chars[] = $this->code2utf($starts[$key]);
|
|
$mid = round(0.5 * ($starts[$key] + $ends[$key]));
|
|
$charcodes[] = $mid;
|
|
$chars[] = $this->code2utf($mid);
|
|
$charcodes[] = $ends[$key];
|
|
$chars[] = $this->code2utf($ends[$key]);
|
|
}
|
|
|
|
// Merge into a string and tokenize.
|
|
$string = implode('', $chars);
|
|
$out = trim(search_simplify($string));
|
|
$expected = drupal_strtolower(implode(' ', $chars));
|
|
|
|
// Verify that the output matches what we expect.
|
|
$this->assertEqual($out, $expected, 'CJK tokenizer worked on all supplied CJK characters');
|
|
}
|
|
|
|
/**
|
|
* Verifies that strings of non-CJK characters are not tokenized.
|
|
*
|
|
* This is just a sanity check - it verifies that strings of letters are
|
|
* not tokenized.
|
|
*/
|
|
function testNoTokenizer() {
|
|
// Set the minimum word size to 1 (to split all CJK characters) and make
|
|
// sure CJK tokenizing is turned on.
|
|
variable_set('minimum_word_size', 1);
|
|
variable_set('overlap_cjk', TRUE);
|
|
$this->refreshVariables();
|
|
|
|
$letters = 'abcdefghijklmnopqrstuvwxyz';
|
|
$out = trim(search_simplify($letters));
|
|
|
|
$this->assertEqual($letters, $out, 'Letters are not CJK tokenized');
|
|
}
|
|
|
|
/**
|
|
* Like PHP chr() function, but for unicode characters.
|
|
*
|
|
* chr() only works for ASCII characters up to character 255. This function
|
|
* converts a number to the corresponding unicode character. Adapted from
|
|
* functions supplied in comments on several functions on php.net.
|
|
*/
|
|
function code2utf($num) {
|
|
if ($num < 128) {
|
|
return chr($num);
|
|
}
|
|
|
|
if ($num < 2048) {
|
|
return chr(($num >> 6) + 192) . chr(($num & 63) + 128);
|
|
}
|
|
|
|
if ($num < 65536) {
|
|
return chr(($num >> 12) + 224) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128);
|
|
}
|
|
|
|
if ($num < 2097152) {
|
|
return chr(($num >> 18) + 240) . chr((($num >> 12) & 63) + 128) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128);
|
|
}
|
|
|
|
return '';
|
|
}
|
|
}
|