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