3 use MediaWiki\MediaWikiServices
;
4 use Wikimedia\TestingAccessWrapper
;
10 class SearchEnginePrefixTest
extends MediaWikiLangTestCase
{
11 private $originalHandlers;
18 public function addDBDataOnce() {
19 if ( !$this->isWikitextNS( NS_MAIN
) ) {
20 // tests are skipped if NS_MAIN is not wikitext
24 $this->insertPage( 'Sandbox' );
25 $this->insertPage( 'Bar' );
26 $this->insertPage( 'Example' );
27 $this->insertPage( 'Example Bar' );
28 $this->insertPage( 'Example Foo' );
29 $this->insertPage( 'Example Foo/Bar' );
30 $this->insertPage( 'Example/Baz' );
31 $this->insertPage( 'Sample' );
32 $this->insertPage( 'Sample Ban' );
33 $this->insertPage( 'Sample Eat' );
34 $this->insertPage( 'Sample Who' );
35 $this->insertPage( 'Sample Zoo' );
36 $this->insertPage( 'Redirect test', '#REDIRECT [[Redirect Test]]' );
37 $this->insertPage( 'Redirect Test' );
38 $this->insertPage( 'Redirect Test Worse Result' );
39 $this->insertPage( 'Redirect test2', '#REDIRECT [[Redirect Test2]]' );
40 $this->insertPage( 'Redirect TEST2', '#REDIRECT [[Redirect Test2]]' );
41 $this->insertPage( 'Redirect Test2' );
42 $this->insertPage( 'Redirect Test2 Worse Result' );
44 $this->insertPage( 'Talk:Sandbox' );
45 $this->insertPage( 'Talk:Example' );
47 $this->insertPage( 'User:Example' );
48 $this->insertPage( 'Barcelona' );
49 $this->insertPage( 'Barbara' );
50 $this->insertPage( 'External' );
53 protected function setUp() {
56 if ( !$this->isWikitextNS( NS_MAIN
) ) {
57 $this->markTestSkipped( 'Main namespace does not support wikitext.' );
60 // Avoid special pages from extensions interferring with the tests
61 $this->setMwGlobals( [
62 'wgSpecialPages' => [],
66 $this->search
= MediaWikiServices
::getInstance()->newSearchEngine();
67 $this->search
->setNamespaces( [] );
69 $this->originalHandlers
= TestingAccessWrapper
::newFromClass( Hooks
::class )->handlers
;
70 TestingAccessWrapper
::newFromClass( Hooks
::class )->handlers
= [];
72 SpecialPageFactory
::resetList();
75 public function tearDown() {
78 TestingAccessWrapper
::newFromClass( Hooks
::class )->handlers
= $this->originalHandlers
;
80 SpecialPageFactory
::resetList();
83 protected function searchProvision( array $results = null ) {
84 if ( $results === null ) {
85 $this->setMwGlobals( 'wgHooks', [] );
87 $this->setMwGlobals( 'wgHooks', [
88 'PrefixSearchBackend' => [
89 function ( $namespaces, $search, $limit, &$srchres ) use ( $results ) {
98 public static function provideSearch() {
106 'Main namespace with title prefix',
113 // Third result when testing offset
119 'Talk namespace prefix',
127 'User namespace prefix',
134 'Special namespace prefix',
135 'query' => 'Special:',
137 'Special:ActiveUsers',
138 'Special:AllMessages',
139 'Special:AllMyUploads',
141 // Third result when testing offset
147 'Special namespace with prefix',
148 'query' => 'Special:Un',
151 'Special:UncategorizedCategories',
152 'Special:UncategorizedFiles',
154 // Third result when testing offset
156 'Special:UncategorizedPages',
161 'query' => 'Special:EditWatchlist',
163 'Special:EditWatchlist',
167 'Special page subpages',
168 'query' => 'Special:EditWatchlist/',
170 'Special:EditWatchlist/clear',
171 'Special:EditWatchlist/raw',
175 'Special page subpages with prefix',
176 'query' => 'Special:EditWatchlist/cl',
178 'Special:EditWatchlist/clear',
185 * @dataProvider provideSearch
186 * @covers SearchEngine::defaultPrefixSearch
188 public function testSearch( array $case ) {
189 $this->search
->setLimitOffset( 3 );
190 $results = $this->search
->defaultPrefixSearch( $case['query'] );
191 $results = array_map( function ( Title
$t ) {
192 return $t->getPrefixedText();
203 * @dataProvider provideSearch
204 * @covers SearchEngine::defaultPrefixSearch
206 public function testSearchWithOffset( array $case ) {
207 $this->search
->setLimitOffset( 3, 1 );
208 $results = $this->search
->defaultPrefixSearch( $case['query'] );
209 $results = array_map( function ( Title
$t ) {
210 return $t->getPrefixedText();
213 // We don't expect the first result when offsetting
214 array_shift( $case['results'] );
215 // And sometimes we expect a different last result
216 $expected = isset( $case['offsetresult'] ) ?
217 array_merge( $case['results'], $case['offsetresult'] ) :
227 public static function provideSearchBackend() {
244 'Exact match not in first result should be moved to the first result (T72958)',
258 'Exact match missing from results should be added as first result (T72958)',
272 'Exact match missing and not existing pages should be dropped',
284 "Exact match shouldn't override already found match if " .
285 "exact is redirect and found isn't",
287 // Target of the exact match is low in the list
288 'Redirect Test Worse Result',
291 'query' => 'redirect test',
293 // Redirect target is pulled up and exact match isn't added
295 'Redirect Test Worse Result',
299 "Exact match shouldn't override already found match if " .
300 "both exact match and found match are redirect",
302 // Another redirect to the same target as the exact match
303 // is low in the list
304 'Redirect Test2 Worse Result',
307 'query' => 'redirect TEST2',
309 // Found redirect is pulled to the top and exact match isn't
312 'Redirect Test2 Worse Result',
316 "Exact match should override any already found matches that " .
317 "are redirects to it",
319 // Another redirect to the same target as the exact match
320 // is low in the list
321 'Redirect Test Worse Result',
324 'query' => 'Redirect Test',
326 // Found redirect is pulled to the top and exact match isn't
329 'Redirect Test Worse Result',
334 "Extra results must not be returned",
352 * @dataProvider provideSearchBackend
353 * @covers PrefixSearch::searchBackend
355 public function testSearchBackend( array $case ) {
356 $search = $this->mockSearchWithResults( $case['provision'] );
357 $results = $search->completionSearch( $case['query'] );
359 $results = $results->map( function ( SearchSuggestion
$s ) {
360 return $s->getText();
370 public function paginationProvider() {
371 $res = [ 'Example', 'Example Bar', 'Example Foo', 'Example Foo/Bar' ];
373 'With less than requested results no pagination' => [
374 false, array_slice( $res, 0, 2 ),
376 'With same as requested results no pagination' => [
377 false, array_slice( $res, 0, 3 ),
379 'With extra result returned offer pagination' => [
386 * @dataProvider paginationProvider
388 public function testPagination( $hasMoreResults, $provision ) {
389 $search = $this->mockSearchWithResults( $provision );
390 $results = $search->completionSearch( 'irrelevant' );
392 $this->assertEquals( $hasMoreResults, $results->hasMoreResults() );
395 private function mockSearchWithResults( $titleStrings, $limit = 3 ) {
396 $search = $stub = $this->getMockBuilder( SearchEngine
::class )
397 ->setMethods( [ 'completionSearchBackend' ] )->getMock();
399 $return = SearchSuggestionSet
::fromStrings( $titleStrings );
401 $search->expects( $this->any() )
402 ->method( 'completionSearchBackend' )
403 ->will( $this->returnValue( $return ) );
405 $search->setLimitOffset( $limit );