Merge "Rank aliases in search in order they appear in the messages file."
[lhc/web/wiklou.git] / tests / phpunit / includes / search / SearchEnginePrefixTest.php
1 <?php
2 use MediaWiki\MediaWikiServices;
3
4 /**
5 * @group Search
6 * @group Database
7 */
8 class SearchEnginePrefixTest extends MediaWikiLangTestCase {
9 private $originalHandlers;
10
11 /**
12 * @var SearchEngine
13 */
14 private $search;
15
16 public function addDBDataOnce() {
17 if ( !$this->isWikitextNS( NS_MAIN ) ) {
18 // tests are skipped if NS_MAIN is not wikitext
19 return;
20 }
21
22 $this->insertPage( 'Sandbox' );
23 $this->insertPage( 'Bar' );
24 $this->insertPage( 'Example' );
25 $this->insertPage( 'Example Bar' );
26 $this->insertPage( 'Example Foo' );
27 $this->insertPage( 'Example Foo/Bar' );
28 $this->insertPage( 'Example/Baz' );
29 $this->insertPage( 'Redirect test', '#REDIRECT [[Redirect Test]]' );
30 $this->insertPage( 'Redirect Test' );
31 $this->insertPage( 'Redirect Test Worse Result' );
32 $this->insertPage( 'Redirect test2', '#REDIRECT [[Redirect Test2]]' );
33 $this->insertPage( 'Redirect TEST2', '#REDIRECT [[Redirect Test2]]' );
34 $this->insertPage( 'Redirect Test2' );
35 $this->insertPage( 'Redirect Test2 Worse Result' );
36
37 $this->insertPage( 'Talk:Sandbox' );
38 $this->insertPage( 'Talk:Example' );
39
40 $this->insertPage( 'User:Example' );
41 }
42
43 protected function setUp() {
44 parent::setUp();
45
46 if ( !$this->isWikitextNS( NS_MAIN ) ) {
47 $this->markTestSkipped( 'Main namespace does not support wikitext.' );
48 }
49
50 // Avoid special pages from extensions interferring with the tests
51 $this->setMwGlobals( [
52 'wgSpecialPages' => [],
53 'wgHooks' => [],
54 ] );
55
56 $this->search = MediaWikiServices::getInstance()->newSearchEngine();
57 $this->search->setNamespaces( [] );
58
59 $this->originalHandlers = TestingAccessWrapper::newFromClass( 'Hooks' )->handlers;
60 TestingAccessWrapper::newFromClass( 'Hooks' )->handlers = [];
61
62 SpecialPageFactory::resetList();
63 }
64
65 public function tearDown() {
66 parent::tearDown();
67
68 TestingAccessWrapper::newFromClass( 'Hooks' )->handlers = $this->originalHandlers;
69
70 SpecialPageFactory::resetList();
71 }
72
73 protected function searchProvision( array $results = null ) {
74 if ( $results === null ) {
75 $this->setMwGlobals( 'wgHooks', [] );
76 } else {
77 $this->setMwGlobals( 'wgHooks', [
78 'PrefixSearchBackend' => [
79 function ( $namespaces, $search, $limit, &$srchres ) use ( $results ) {
80 $srchres = $results;
81 return false;
82 }
83 ],
84 ] );
85 }
86 }
87
88 public static function provideSearch() {
89 return [
90 [ [
91 'Empty string',
92 'query' => '',
93 'results' => [],
94 ] ],
95 [ [
96 'Main namespace with title prefix',
97 'query' => 'Ex',
98 'results' => [
99 'Example',
100 'Example/Baz',
101 'Example Bar',
102 ],
103 // Third result when testing offset
104 'offsetresult' => [
105 'Example Foo',
106 ],
107 ] ],
108 [ [
109 'Talk namespace prefix',
110 'query' => 'Talk:',
111 'results' => [
112 'Talk:Example',
113 'Talk:Sandbox',
114 ],
115 ] ],
116 [ [
117 'User namespace prefix',
118 'query' => 'User:',
119 'results' => [
120 'User:Example',
121 ],
122 ] ],
123 [ [
124 'Special namespace prefix',
125 'query' => 'Special:',
126 'results' => [
127 'Special:ActiveUsers',
128 'Special:AllMessages',
129 'Special:AllMyUploads',
130 ],
131 // Third result when testing offset
132 'offsetresult' => [
133 'Special:AllPages',
134 ],
135 ] ],
136 [ [
137 'Special namespace with prefix',
138 'query' => 'Special:Un',
139 'results' => [
140 'Special:Unblock',
141 'Special:UncategorizedCategories',
142 'Special:UncategorizedFiles',
143 ],
144 // Third result when testing offset
145 'offsetresult' => [
146 'Special:UncategorizedPages',
147 ],
148 ] ],
149 [ [
150 'Special page name',
151 'query' => 'Special:EditWatchlist',
152 'results' => [
153 'Special:EditWatchlist',
154 ],
155 ] ],
156 [ [
157 'Special page subpages',
158 'query' => 'Special:EditWatchlist/',
159 'results' => [
160 'Special:EditWatchlist/clear',
161 'Special:EditWatchlist/raw',
162 ],
163 ] ],
164 [ [
165 'Special page subpages with prefix',
166 'query' => 'Special:EditWatchlist/cl',
167 'results' => [
168 'Special:EditWatchlist/clear',
169 ],
170 ] ],
171 ];
172 }
173
174 /**
175 * @dataProvider provideSearch
176 * @covers SearchEngine::defaultPrefixSearch
177 */
178 public function testSearch( array $case ) {
179 $this->search->setLimitOffset( 3 );
180 $results = $this->search->defaultPrefixSearch( $case['query'] );
181 $results = array_map( function( Title $t ) {
182 return $t->getPrefixedText();
183 }, $results );
184 $this->assertEquals(
185 $case['results'],
186 $results,
187 $case[0]
188 );
189 }
190
191 /**
192 * @dataProvider provideSearch
193 * @covers SearchEngine::defaultPrefixSearch
194 */
195 public function testSearchWithOffset( array $case ) {
196 $this->search->setLimitOffset( 3, 1 );
197 $results = $this->search->defaultPrefixSearch( $case['query'] );
198 $results = array_map( function( Title $t ) {
199 return $t->getPrefixedText();
200 }, $results );
201
202 // We don't expect the first result when offsetting
203 array_shift( $case['results'] );
204 // And sometimes we expect a different last result
205 $expected = isset( $case['offsetresult'] ) ?
206 array_merge( $case['results'], $case['offsetresult'] ) :
207 $case['results'];
208
209 $this->assertEquals(
210 $expected,
211 $results,
212 $case[0]
213 );
214 }
215
216 public static function provideSearchBackend() {
217 return [
218 [ [
219 'Simple case',
220 'provision' => [
221 'Bar',
222 'Barcelona',
223 'Barbara',
224 ],
225 'query' => 'Bar',
226 'results' => [
227 'Bar',
228 'Barcelona',
229 'Barbara',
230 ],
231 ] ],
232 [ [
233 'Exact match not on top (bug 70958)',
234 'provision' => [
235 'Barcelona',
236 'Bar',
237 'Barbara',
238 ],
239 'query' => 'Bar',
240 'results' => [
241 'Bar',
242 'Barcelona',
243 'Barbara',
244 ],
245 ] ],
246 [ [
247 'Exact match missing (bug 70958)',
248 'provision' => [
249 'Barcelona',
250 'Barbara',
251 'Bart',
252 ],
253 'query' => 'Bar',
254 'results' => [
255 'Bar',
256 'Barcelona',
257 'Barbara',
258 ],
259 ] ],
260 [ [
261 'Exact match missing and not existing',
262 'provision' => [
263 'Exile',
264 'Exist',
265 'External',
266 ],
267 'query' => 'Ex',
268 'results' => [
269 'Exile',
270 'Exist',
271 'External',
272 ],
273 ] ],
274 [ [
275 "Exact match shouldn't override already found match if " .
276 "exact is redirect and found isn't",
277 'provision' => [
278 // Target of the exact match is low in the list
279 'Redirect Test Worse Result',
280 'Redirect Test',
281 ],
282 'query' => 'redirect test',
283 'results' => [
284 // Redirect target is pulled up and exact match isn't added
285 'Redirect Test',
286 'Redirect Test Worse Result',
287 ],
288 ] ],
289 [ [
290 "Exact match shouldn't override already found match if " .
291 "both exact match and found match are redirect",
292 'provision' => [
293 // Another redirect to the same target as the exact match
294 // is low in the list
295 'Redirect Test2 Worse Result',
296 'Redirect test2',
297 ],
298 'query' => 'redirect TEST2',
299 'results' => [
300 // Found redirect is pulled to the top and exact match isn't
301 // added
302 'Redirect test2',
303 'Redirect Test2 Worse Result',
304 ],
305 ] ],
306 [ [
307 "Exact match should override any already found matches that " .
308 "are redirects to it",
309 'provision' => [
310 // Another redirect to the same target as the exact match
311 // is low in the list
312 'Redirect Test Worse Result',
313 'Redirect test',
314 ],
315 'query' => 'Redirect Test',
316 'results' => [
317 // Found redirect is pulled to the top and exact match isn't
318 // added
319 'Redirect Test',
320 'Redirect Test Worse Result',
321 'Redirect test',
322 ],
323 ] ],
324 ];
325 }
326
327 /**
328 * @dataProvider provideSearchBackend
329 * @covers PrefixSearch::searchBackend
330 */
331 public function testSearchBackend( array $case ) {
332 $search = $stub = $this->getMockBuilder( 'SearchEngine' )
333 ->setMethods( [ 'completionSearchBackend' ] )->getMock();
334
335 $return = SearchSuggestionSet::fromStrings( $case['provision'] );
336
337 $search->expects( $this->any() )
338 ->method( 'completionSearchBackend' )
339 ->will( $this->returnValue( $return ) );
340
341 $search->setLimitOffset( 3 );
342 $results = $search->completionSearch( $case['query'] );
343
344 $results = $results->map( function( SearchSuggestion $s ) {
345 return $s->getText();
346 } );
347
348 $this->assertEquals(
349 $case['results'],
350 $results,
351 $case[0]
352 );
353 }
354 }