25 $dbType = $this->db->getType();
26 $dbSupported = ( $dbType ===
'mysql' )
27 || ( $dbType ===
'sqlite' && $this->db->getFulltextSearchModule() ==
'FTS3' );
29 if ( !$dbSupported ) {
30 $this->markTestSkipped(
"MySQL or SQLite with FTS3 only" );
33 $searchType = SearchEngineFactory::getSearchEngineClass( $this->db );
35 'wgSearchType' => $searchType,
36 'wgCapitalLinks' =>
true,
37 'wgCapitalLinkOverrides' => [
42 $this->search =
new $searchType( $this->db );
46 unset( $this->search );
60 'wgSearchType' =>
null,
61 'wgCapitalLinks' =>
true,
62 'wgCapitalLinkOverrides' => [
67 $this->
insertPage(
'Not_Main_Page',
'This is not a main page' );
70 'This is not a talk page to the main page, see [[smithee]]'
72 $this->
insertPage(
'Smithee',
'A smithee is one who smiths. See also [[Alan Smithee]]' );
73 $this->
insertPage(
'Talk:Smithee',
'This article sucks.' );
74 $this->
insertPage(
'Unrelated_page',
'Nothing in this page is about the S word.' );
75 $this->
insertPage(
'Another_page',
'This page also is unrelated.' );
84 $this->
insertPage(
'HalfNumbers',
'1234567890' );
85 $this->
insertPage(
'FullNumbers',
'1234567890' );
86 $this->
insertPage(
'DomainName',
'example.com' );
87 $this->
insertPage(
'DomainName',
'example.com' );
88 $this->
insertPage(
'Category:search is not Search',
'' );
89 $this->
insertPage(
'Category:Search is not search',
'' );
94 $this->markTestIncomplete( __CLASS__ .
" does no yet support non-wikitext content "
95 .
"in the main namespace" );
97 $this->assertTrue( is_object( $results ) );
100 foreach ( $results as $row ) {
101 $matches[] = $row->getTitle()->getPrefixedText();
104 # Search is not guaranteed to return results in a certain order;
105 # sort them numerically so we will compare simply that we received
106 # the expected matches.
114 [
'FullOneUp',
'FullTwoLow',
'HalfOneUp',
'HalfTwoLow' ],
115 $this->
fetchIds( $this->search->searchText(
'AZ' ) ),
116 "Search for normalized from Half-width Upper" );
118 [
'FullOneUp',
'FullTwoLow',
'HalfOneUp',
'HalfTwoLow' ],
119 $this->
fetchIds( $this->search->searchText(
'az' ) ),
120 "Search for normalized from Half-width Lower" );
122 [
'FullOneUp',
'FullTwoLow',
'HalfOneUp',
'HalfTwoLow' ],
123 $this->
fetchIds( $this->search->searchText(
'AZ' ) ),
124 "Search for normalized from Full-width Upper" );
126 [
'FullOneUp',
'FullTwoLow',
'HalfOneUp',
'HalfTwoLow' ],
127 $this->
fetchIds( $this->search->searchText(
'az' ) ),
128 "Search for normalized from Full-width Lower" );
134 $this->
fetchIds( $this->search->searchText(
'smithee' ) ),
139 $res = $this->search->searchText(
'smith*' );
143 "Search with wildcards" );
145 $res = $this->search->searchText(
'smithson*' );
149 "Search with wildcards must not find unrelated articles" );
151 $res = $this->search->searchText(
'smith* smithee' );
155 "Search with wildcards can be combined with simple terms" );
157 $res = $this->search->searchText(
'smith* "one who smiths"' );
161 "Search with wildcards can be combined with phrase search" );
165 $res = $this->search->searchText(
'"smithee is one who smiths"' );
171 $res = $this->search->searchText(
'"smithee is who smiths"' );
175 "Phrase search is not sloppy, search terms must be adjacent" );
177 $res = $this->search->searchText(
'"is smithee one who smiths"' );
181 "Phrase search is ordered" );
185 $phrase =
"smithee is one who smiths";
186 $res = $this->search->searchText(
"\"$phrase\"" );
187 $match =
$res->getIterator()->current();
188 $snippet =
"A <span class='searchmatch'>" . $phrase .
"</span>";
189 $this->assertStringStartsWith( $snippet,
190 $match->getTextSnippet(
$res->termMatches() ),
191 "Highlight a phrase search" );
195 $this->search->setNamespaces( [ 0, 1, 4 ] );
199 'Talk:Not Main Page',
201 $this->
fetchIds( $this->search->searchText(
'smithee' ) ),
211 $this->
fetchIds( $this->search->searchTitle(
'smithee' ) ),
216 $this->search->setNamespaces( [ 0, 1, 4 ] );
223 $this->
fetchIds( $this->search->searchTitle(
'smithee' ) ),
224 "Title power search" );
229 'Searching for "smithee" finds Smithee on NS_MAIN' => [
234 'Searching for "search is" will finds "search is not Search" on NS_CATEGORY' => [
236 'Category:search is not Search',
239 'Searching for "Search is" will finds "search is not Search" on NS_CATEGORY' => [
241 'Category:Search is not search',
264 $results = $this->search->completionSearch(
$search );
265 $this->assertEquals( 1, $results->getSize() );
266 $this->assertEquals( $expectedSuggestion, $results->getSuggestions()[0]->getText() );
276 $mockEngine = $this->getMockBuilder( SearchEngine::class )
277 ->setMethods( [
'makeSearchFieldMapping' ] )->getMock();
279 $mockFieldBuilder =
function (
$name,
$type ) {
281 $this->getMockBuilder( SearchIndexFieldDefinition::class )->setConstructorArgs( [
286 $mockField->expects( $this->
any() )->method(
'getMapping' )->willReturn( [
287 'testData' =>
'test',
292 $mockField->expects( $this->
any() )
294 ->willReturn( $mockField );
299 $mockEngine->expects( $this->atLeastOnce() )
300 ->method(
'makeSearchFieldMapping' )
301 ->willReturnCallback( $mockFieldBuilder );
306 $fields[
'testField'] =
311 $fields = $mockEngine->getSearchIndexFields();
312 $this->assertArrayHasKey(
'language', $fields );
313 $this->assertArrayHasKey(
'category', $fields );
314 $this->assertInstanceOf( SearchIndexField::class, $fields[
'testField'] );
316 $mapping = $fields[
'testField']->getMapping( $mockEngine );
317 $this->assertArrayHasKey(
'testData', $mapping );
318 $this->assertEquals(
'test', $mapping[
'testData'] );
327 $this->search->setNamespaces( [ 0, 1, 4 ] );
328 $resultSet = $this->search->searchText(
'smithee' );
331 [
'SearchResultsAugment' => [ [ $this,
'addAugmentors' ] ] ] );
332 $this->search->augmentSearchResults( $resultSet );
333 foreach ( $resultSet as $result ) {
334 $id = $result->getTitle()->getArticleID();
335 $augmentData =
"Result:$id:" . $result->getTitle()->getText();
336 $augmentData2 =
"Result2:$id:" . $result->getTitle()->getText();
337 $this->assertEquals( [
'testSet' => $augmentData,
'testRow' => $augmentData2 ],
338 $result->getExtensionData() );
343 $setAugmentor = $this->createMock( ResultSetAugmentor::class );
344 $setAugmentor->expects( $this->once() )
345 ->method(
'augmentAll' )
348 foreach ( $resultSet as $result ) {
349 $id = $result->getTitle()->getArticleID();
350 $data[$id] =
"Result:$id:" . $result->getTitle()->getText();
354 $setAugmentors[
'testSet'] = $setAugmentor;
356 $rowAugmentor = $this->createMock( ResultAugmentor::class );
357 $rowAugmentor->expects( $this->exactly( 2 ) )
358 ->method(
'augment' )
359 ->willReturnCallback(
function (
SearchResult $result ) {
360 $id = $result->getTitle()->getArticleID();
361 return "Result2:$id:" . $result->getTitle()->getText();
367 $availableResults = [];
368 foreach ( range( 0, 11 ) as $i ) {
369 $title =
"Search_Result_$i";
370 $availableResults[] =
$title;
379 $engine->setLimitOffset( 10, 0 );
380 $results =
$engine->completionSearch(
'foo' );
381 $this->assertEquals( 5, $results->getSize() );
382 $this->assertTrue( $results->hasMoreResults() );
384 $engine->setLimitOffset( 10, 10 );
385 $results =
$engine->completionSearch(
'foo' );
386 $this->assertEquals( 1, $results->getSize() );
387 $this->assertFalse( $results->hasMoreResults() );
391 $page = WikiPage::factory( Title::newFromText( $title ) );
392 $page->doEditContent(
413 'namespace prefix' => [
415 'query' =>
'help:test',
419 'accented namespace prefix with hook' => [
421 'query' =>
'hélp:test',
426 'accented namespace prefix without hook' => [
428 'query' =>
'hélp:test',
433 'all with all keyword allowed' => [
435 'query' =>
'all:test',
440 'all with all keyword disallowed' => [
442 'query' =>
'all:test',
460 'all wins over namespace when first' => [
462 'query' =>
'all:help:test',
465 [
'help:test', null ]
467 'ns wins over all when first' => [
469 'query' =>
'help:all:test',
486 if ( strpos(
$query,
'hélp:' ) === 0 ) {
493 if ( isset(
$params[
'withAll'] ) && isset(
$params[
'withHook'] ) ) {
495 } elseif ( isset(
$params[
'withAll'] ) ) {
498 } elseif ( isset(
$params[
'withHook'] ) ) {
502 $testSet[] =
$params + [
'withAll' =>
true,
'withHook' =>
true ];
503 $testSet[] =
$params + [
'withAll' =>
true,
'withHook' =>
false ];
504 $testSet[] =
$params + [
'withAll' =>
false,
'withHook' =>
false ];
505 $testSet[] =
$params + [
'withAll' =>
true,
'withHook' =>
false ];
508 foreach ( $testSet as $test ) {
509 $actual = SearchEngine::parseNamespacePrefixes( $test[
'query'],
510 $test[
'withAll'], $test[
'withHook'] );
511 $this->assertEquals( $expected, $actual,
'with params: ' . print_r( $test,
true ) );
they could even be mouse clicks or menu items whatever suits your program You should also get your if any
SearchEngine implementation for returning mocked completion search results.
static addMockResults( $query, array $result)
Allows returning arbitrary lists of titles for completion search.
provideDataForParseNamespacePrefix()
testCompletionSearchMustRespectCapitalLinkOverrides( $search, $expectedSuggestion, array $namespaces)
Test that the search query is not munged using wrong CapitalLinks setup (in other test that the defau...
testPhraseSearchHighlight()
provideCompletionSearchMustRespectCapitalLinkOverrides()
hookSearchIndexFields( $mockFieldBuilder, &$fields, SearchEngine $engine)
editSearchResultPage( $title)
addAugmentors(&$setAugmentors, &$rowAugmentors)
testParseNamespacePrefix(array $params, $expected)
provideDataForParseNamespacePrefix
setUp()
Checks for database type & version.
testSearchIndexFields()
SearchEngine::getSearchIndexFields.
testTextTitlePowerSearch()
Contain a class for special pages.
Content object for wiki text pages.
namespace and then decline to actually register it & $namespaces
the value to return A Title object or null for latest all implement SearchIndexField must implement ResultSetAugmentor & $rowAugmentors
namespace and then decline to actually register it file or subcat img or subcat $title
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return true
Allows to change the fields on the form that will be generated $name
the value to return A Title object or null for latest all implement SearchIndexField $engine
null for the local wiki Added should default to null in handler for backwards compatibility add a value to it if you want to add a cookie that have to vary cache options can modify $query
processing should stop and the error should be shown to the user * false
const INDEX_TYPE_TEXT
Field types.
The wiki should then use memcached to cache various data To use multiple just add more items to the array To increase the weight of a make its entry a array("192.168.0.1:11211", 2))