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