Merge "Selenium: replace UserLoginPage with BlankPage where possible"
[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 $this->overrideMwServices();
65 }
66
67 public function tearDown() {
68 parent::tearDown();
69
70 TestingAccessWrapper::newFromClass( Hooks::class )->handlers = $this->originalHandlers;
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 'Namespace with case sensitive first letter',
173 'query' => 'NonCap:upper',
174 'results' => []
175 ] ],
176 [ [
177 'Multinamespace search',
178 'query' => 'B',
179 'results' => [
180 'Bar',
181 'NonCap:Bar',
182 ],
183 'namespaces' => [ NS_MAIN, self::NS_NONCAP ],
184 ] ],
185 [ [
186 'Multinamespace search with lowercase first letter',
187 'query' => 'sand',
188 'results' => [
189 'Sandbox',
190 'NonCap:sandbox',
191 ],
192 'namespaces' => [ NS_MAIN, self::NS_NONCAP ],
193 ] ],
194 ];
195 }
196
197 /**
198 * @dataProvider provideSearch
199 * @covers PrefixSearch::search
200 * @covers PrefixSearch::searchBackend
201 */
202 public function testSearch( array $case ) {
203 // FIXME: fails under postgres
204 $this->markTestSkippedIfDbType( 'postgres' );
205 $this->searchProvision( null );
206
207 $namespaces = $case['namespaces'] ?? [];
208
209 if ( wfGetDB( DB_REPLICA )->getType() === 'postgres' ) {
210 // Postgres will sort lexicographically on utf8 code units (" " before "/")
211 sort( $case['results'], SORT_STRING );
212 }
213
214 $searcher = new StringPrefixSearch;
215 $results = $searcher->search( $case['query'], 3, $namespaces );
216 $this->assertEquals(
217 $case['results'],
218 $results,
219 $case[0]
220 );
221 }
222
223 /**
224 * @dataProvider provideSearch
225 * @covers PrefixSearch::search
226 * @covers PrefixSearch::searchBackend
227 */
228 public function testSearchWithOffset( array $case ) {
229 // FIXME: fails under postgres
230 $this->markTestSkippedIfDbType( 'postgres' );
231 $this->searchProvision( null );
232
233 $namespaces = $case['namespaces'] ?? [];
234
235 $searcher = new StringPrefixSearch;
236 $results = $searcher->search( $case['query'], 3, $namespaces, 1 );
237
238 if ( wfGetDB( DB_REPLICA )->getType() === 'postgres' ) {
239 // Postgres will sort lexicographically on utf8 code units (" " before "/")
240 sort( $case['results'], SORT_STRING );
241 }
242
243 // We don't expect the first result when offsetting
244 array_shift( $case['results'] );
245 // And sometimes we expect a different last result
246 $expected = isset( $case['offsetresult'] ) ?
247 array_merge( $case['results'], $case['offsetresult'] ) :
248 $case['results'];
249
250 $this->assertEquals(
251 $expected,
252 $results,
253 $case[0]
254 );
255 }
256
257 public static function provideSearchBackend() {
258 return [
259 [ [
260 'Simple case',
261 'provision' => [
262 'Bar',
263 'Barcelona',
264 'Barbara',
265 ],
266 'query' => 'Bar',
267 'results' => [
268 'Bar',
269 'Barcelona',
270 'Barbara',
271 ],
272 ] ],
273 [ [
274 'Exact match not on top (T72958)',
275 'provision' => [
276 'Barcelona',
277 'Bar',
278 'Barbara',
279 ],
280 'query' => 'Bar',
281 'results' => [
282 'Bar',
283 'Barcelona',
284 'Barbara',
285 ],
286 ] ],
287 [ [
288 'Exact match missing (T72958)',
289 'provision' => [
290 'Barcelona',
291 'Barbara',
292 'Bart',
293 ],
294 'query' => 'Bar',
295 'results' => [
296 'Bar',
297 'Barcelona',
298 'Barbara',
299 ],
300 ] ],
301 [ [
302 'Exact match missing and not existing',
303 'provision' => [
304 'Exile',
305 'Exist',
306 'External',
307 ],
308 'query' => 'Ex',
309 'results' => [
310 'Exile',
311 'Exist',
312 'External',
313 ],
314 ] ],
315 [ [
316 "Exact match shouldn't override already found match if " .
317 "exact is redirect and found isn't",
318 'provision' => [
319 // Target of the exact match is low in the list
320 'Redirect Test Worse Result',
321 'Redirect Test',
322 ],
323 'query' => 'redirect test',
324 'results' => [
325 // Redirect target is pulled up and exact match isn't added
326 'Redirect Test',
327 'Redirect Test Worse Result',
328 ],
329 ] ],
330 [ [
331 "Exact match shouldn't override already found match if " .
332 "both exact match and found match are redirect",
333 'provision' => [
334 // Another redirect to the same target as the exact match
335 // is low in the list
336 'Redirect Test2 Worse Result',
337 'Redirect test2',
338 ],
339 'query' => 'redirect TEST2',
340 'results' => [
341 // Found redirect is pulled to the top and exact match isn't
342 // added
343 'Redirect test2',
344 'Redirect Test2 Worse Result',
345 ],
346 ] ],
347 [ [
348 "Exact match should override any already found matches that " .
349 "are redirects to it",
350 'provision' => [
351 // Another redirect to the same target as the exact match
352 // is low in the list
353 'Redirect Test Worse Result',
354 'Redirect test',
355 ],
356 'query' => 'Redirect Test',
357 'results' => [
358 // Found redirect is pulled to the top and exact match isn't
359 // added
360 'Redirect Test',
361 'Redirect Test Worse Result',
362 ],
363 ] ],
364 ];
365 }
366
367 /**
368 * @dataProvider provideSearchBackend
369 * @covers PrefixSearch::searchBackend
370 */
371 public function testSearchBackend( array $case ) {
372 $this->searchProvision( $case['provision'] );
373 $searcher = new StringPrefixSearch;
374 $results = $searcher->search( $case['query'], 3 );
375 $this->assertEquals(
376 $case['results'],
377 $results,
378 $case[0]
379 );
380 }
381 }