Merge "tipsy: using user class borks positioning of tip"
[lhc/web/wiklou.git] / tests / phpunit / includes / EditPageTest.php
1 <?php
2
3 /**
4 * @group Editing
5 *
6 * @group Database
7 * ^--- tell jenkins this test needs the database
8 *
9 * @group medium
10 * ^--- tell phpunit that these test cases may take longer than 2 seconds.
11 */
12 class EditPageTest extends MediaWikiLangTestCase {
13
14 /**
15 * @dataProvider provideExtractSectionTitle
16 * @covers EditPage::extractSectionTitle
17 */
18 public function testExtractSectionTitle( $section, $title ) {
19 $extracted = EditPage::extractSectionTitle( $section );
20 $this->assertEquals( $title, $extracted );
21 }
22
23 public static function provideExtractSectionTitle() {
24 return array(
25 array(
26 "== Test ==\n\nJust a test section.",
27 "Test"
28 ),
29 array(
30 "An initial section, no header.",
31 false
32 ),
33 array(
34 "An initial section with a fake heder (bug 32617)\n\n== Test == ??\nwtf",
35 false
36 ),
37 array(
38 "== Section ==\nfollowed by a fake == Non-section == ??\nnoooo",
39 "Section"
40 ),
41 array(
42 "== Section== \t\r\n followed by whitespace (bug 35051)",
43 'Section',
44 ),
45 );
46 }
47
48 protected function forceRevisionDate( WikiPage $page, $timestamp ) {
49 $dbw = wfGetDB( DB_MASTER );
50
51 $dbw->update( 'revision',
52 array( 'rev_timestamp' => $dbw->timestamp( $timestamp ) ),
53 array( 'rev_id' => $page->getLatest() ) );
54
55 $page->clear();
56 }
57
58 /**
59 * User input text is passed to rtrim() by edit page. This is a simple
60 * wrapper around assertEquals() which calls rrtrim() to normalize the
61 * expected and actual texts.
62 */
63 protected function assertEditedTextEquals( $expected, $actual, $msg = '' ) {
64 return $this->assertEquals( rtrim( $expected ), rtrim( $actual ), $msg );
65 }
66
67 /**
68 * Performs an edit and checks the result.
69 *
70 * @param String|Title $title The title of the page to edit
71 * @param String|null $baseText Some text to create the page with before attempting the edit.
72 * @param User|String|null $user The user to perform the edit as.
73 * @param array $edit An array of request parameters used to define the edit to perform.
74 * Some well known fields are:
75 * * wpTextbox1: the text to submit
76 * * wpSummary: the edit summary
77 * * wpEditToken: the edit token (will be inserted if not provided)
78 * * wpEdittime: timestamp of the edit's base revision (will be inserted if not provided)
79 * * wpStarttime: timestamp when the edit started (will be inserted if not provided)
80 * * wpSectionTitle: the section to edit
81 * * wpMinorEdit: mark as minor edit
82 * * wpWatchthis: whether to watch the page
83 * @param int|null $expectedCode The expected result code (EditPage::AS_XXX constants).
84 * Set to null to skip the check. Defaults to EditPage::AS_OK.
85 * @param String|null $expectedText The text expected to be on the page after the edit.
86 * Set to null to skip the check.
87 * @param String|null $message An optional message to show along with any error message.
88 *
89 * @return WikiPage The page that was just edited, useful for getting the edit's rev_id, etc.
90 */
91 protected function assertEdit( $title, $baseText, $user = null, array $edit,
92 $expectedCode = EditPage::AS_OK, $expectedText = null, $message = null
93 ) {
94 if ( is_string( $title ) ) {
95 $ns = $this->getDefaultWikitextNS();
96 $title = Title::newFromText( $title, $ns );
97 }
98
99 if ( is_string( $user ) ) {
100 $user = User::newFromName( $user );
101
102 if ( $user->getId() === 0 ) {
103 $user->addToDatabase();
104 }
105 }
106
107 $page = WikiPage::factory( $title );
108
109 if ( $baseText !== null ) {
110 $content = ContentHandler::makeContent( $baseText, $title );
111 $page->doEditContent( $content, "base text for test" );
112 $this->forceRevisionDate( $page, '20120101000000' );
113
114 //sanity check
115 $page->clear();
116 $currentText = ContentHandler::getContentText( $page->getContent() );
117
118 # EditPage rtrim() the user input, so we alter our expected text
119 # to reflect that.
120 $this->assertEditedTextEquals( $baseText, $currentText );
121 }
122
123 if ( $user == null ) {
124 $user = $GLOBALS['wgUser'];
125 } else {
126 $this->setMwGlobals( 'wgUser', $user );
127 }
128
129 if ( !isset( $edit['wpEditToken'] ) ) {
130 $edit['wpEditToken'] = $user->getEditToken();
131 }
132
133 if ( !isset( $edit['wpEdittime'] ) ) {
134 $edit['wpEdittime'] = $page->exists() ? $page->getTimestamp() : '';
135 }
136
137 if ( !isset( $edit['wpStarttime'] ) ) {
138 $edit['wpStarttime'] = wfTimestampNow();
139 }
140
141 $req = new FauxRequest( $edit, true ); // session ??
142
143 $ep = new EditPage( new Article( $title ) );
144 $ep->setContextTitle( $title );
145 $ep->importFormData( $req );
146
147 $bot = isset( $edit['bot'] ) ? (bool)$edit['bot'] : false;
148
149 // this is where the edit happens!
150 // Note: don't want to use EditPage::AttemptSave, because it messes with $wgOut
151 // and throws exceptions like PermissionsError
152 $status = $ep->internalAttemptSave( $result, $bot );
153
154 if ( $expectedCode !== null ) {
155 // check edit code
156 $this->assertEquals( $expectedCode, $status->value,
157 "Expected result code mismatch. $message" );
158 }
159
160 $page = WikiPage::factory( $title );
161
162 if ( $expectedText !== null ) {
163 // check resulting page text
164 $content = $page->getContent();
165 $text = ContentHandler::getContentText( $content );
166
167 # EditPage rtrim() the user input, so we alter our expected text
168 # to reflect that.
169 $this->assertEditedTextEquals( $expectedText, $text,
170 "Expected article text mismatch. $message" );
171 }
172
173 return $page;
174 }
175
176 /**
177 * @todo split into a dataprovider and test method
178 * @covers EditPage
179 */
180 public function testCreatePage() {
181 $this->assertEdit(
182 'EditPageTest_testCreatePage',
183 null,
184 null,
185 array(
186 'wpTextbox1' => "Hello World!",
187 ),
188 EditPage::AS_SUCCESS_NEW_ARTICLE,
189 "Hello World!",
190 "expected article being created"
191 )->doDeleteArticleReal( 'EditPageTest_testCreatePage' );
192
193 $this->assertEdit(
194 'EditPageTest_testCreatePage',
195 null,
196 null,
197 array(
198 'wpTextbox1' => "",
199 ),
200 EditPage::AS_BLANK_ARTICLE,
201 null,
202 "expected article not being created if empty"
203 );
204
205 $this->assertEdit(
206 'MediaWiki:January',
207 null,
208 'UTSysop',
209 array(
210 'wpTextbox1' => "Not January",
211 ),
212 EditPage::AS_SUCCESS_NEW_ARTICLE,
213 "Not January",
214 "expected MediaWiki: page being created"
215 )->doDeleteArticleReal( 'EditPageTest_testCreatePage' );
216
217 $this->assertEdit(
218 'MediaWiki:EditPageTest_testCreatePage',
219 null,
220 'UTSysop',
221 array(
222 'wpTextbox1' => "",
223 ),
224 EditPage::AS_BLANK_ARTICLE,
225 null,
226 "expected not-registered MediaWiki: page not being created if empty"
227 );
228
229 $this->assertEdit(
230 'MediaWiki:January',
231 null,
232 'UTSysop',
233 array(
234 'wpTextbox1' => "",
235 ),
236 EditPage::AS_SUCCESS_NEW_ARTICLE,
237 "",
238 "expected registered MediaWiki: page being created even if empty"
239 )->doDeleteArticleReal( 'EditPageTest_testCreatePage' );
240
241 $this->assertEdit(
242 'MediaWiki:Ipb-default-expiry',
243 null,
244 'UTSysop',
245 array(
246 'wpTextbox1' => "",
247 ),
248 EditPage::AS_BLANK_ARTICLE,
249 "",
250 "expected registered MediaWiki: page whose default content is empty not being created if empty"
251 );
252
253 $this->assertEdit(
254 'MediaWiki:January',
255 null,
256 'UTSysop',
257 array(
258 'wpTextbox1' => "January",
259 ),
260 EditPage::AS_BLANK_ARTICLE,
261 null,
262 "expected MediaWiki: page not being created if text equals default message"
263 );
264 }
265
266 public function testUpdatePage() {
267 $text = "one";
268 $edit = array(
269 'wpTextbox1' => $text,
270 'wpSummary' => 'first update',
271 );
272
273 $page = $this->assertEdit( 'EditPageTest_testUpdatePage', "zero", null, $edit,
274 EditPage::AS_SUCCESS_UPDATE, $text,
275 "expected successfull update with given text" );
276
277 $this->forceRevisionDate( $page, '20120101000000' );
278
279 $text = "two";
280 $edit = array(
281 'wpTextbox1' => $text,
282 'wpSummary' => 'second update',
283 );
284
285 $this->assertEdit( 'EditPageTest_testUpdatePage', null, null, $edit,
286 EditPage::AS_SUCCESS_UPDATE, $text,
287 "expected successfull update with given text" );
288 }
289
290 public static function provideSectionEdit() {
291 $text = 'Intro
292
293 == one ==
294 first section.
295
296 == two ==
297 second section.
298 ';
299
300 $sectionOne = '== one ==
301 hello
302 ';
303
304 $newSection = '== new section ==
305
306 hello
307 ';
308
309 $textWithNewSectionOne = preg_replace(
310 '/== one ==.*== two ==/ms',
311 "$sectionOne\n== two ==", $text
312 );
313
314 $textWithNewSectionAdded = "$text\n$newSection";
315
316 return array(
317 array( #0
318 $text,
319 '',
320 'hello',
321 'replace all',
322 'hello'
323 ),
324
325 array( #1
326 $text,
327 '1',
328 $sectionOne,
329 'replace first section',
330 $textWithNewSectionOne,
331 ),
332
333 array( #2
334 $text,
335 'new',
336 'hello',
337 'new section',
338 $textWithNewSectionAdded,
339 ),
340 );
341 }
342
343 /**
344 * @dataProvider provideSectionEdit
345 * @covers EditPage
346 */
347 public function testSectionEdit( $base, $section, $text, $summary, $expected ) {
348 $edit = array(
349 'wpTextbox1' => $text,
350 'wpSummary' => $summary,
351 'wpSection' => $section,
352 );
353
354 $this->assertEdit( 'EditPageTest_testSectionEdit', $base, null, $edit,
355 EditPage::AS_SUCCESS_UPDATE, $expected,
356 "expected successfull update of section" );
357 }
358
359 public static function provideAutoMerge() {
360 $tests = array();
361
362 $tests[] = array( #0: plain conflict
363 "Elmo", # base edit user
364 "one\n\ntwo\n\nthree\n",
365 array( #adam's edit
366 'wpStarttime' => 1,
367 'wpTextbox1' => "ONE\n\ntwo\n\nthree\n",
368 ),
369 array( #berta's edit
370 'wpStarttime' => 2,
371 'wpTextbox1' => "(one)\n\ntwo\n\nthree\n",
372 ),
373 EditPage::AS_CONFLICT_DETECTED, # expected code
374 "ONE\n\ntwo\n\nthree\n", # expected text
375 'expected edit conflict', # message
376 );
377
378 $tests[] = array( #1: successful merge
379 "Elmo", # base edit user
380 "one\n\ntwo\n\nthree\n",
381 array( #adam's edit
382 'wpStarttime' => 1,
383 'wpTextbox1' => "ONE\n\ntwo\n\nthree\n",
384 ),
385 array( #berta's edit
386 'wpStarttime' => 2,
387 'wpTextbox1' => "one\n\ntwo\n\nTHREE\n",
388 ),
389 EditPage::AS_SUCCESS_UPDATE, # expected code
390 "ONE\n\ntwo\n\nTHREE\n", # expected text
391 'expected automatic merge', # message
392 );
393
394 $text = "Intro\n\n";
395 $text .= "== first section ==\n\n";
396 $text .= "one\n\ntwo\n\nthree\n\n";
397 $text .= "== second section ==\n\n";
398 $text .= "four\n\nfive\n\nsix\n\n";
399
400 // extract the first section.
401 $section = preg_replace( '/.*(== first section ==.*)== second section ==.*/sm', '$1', $text );
402
403 // generate expected text after merge
404 $expected = str_replace( 'one', 'ONE', str_replace( 'three', 'THREE', $text ) );
405
406 $tests[] = array( #2: merge in section
407 "Elmo", # base edit user
408 $text,
409 array( #adam's edit
410 'wpStarttime' => 1,
411 'wpTextbox1' => str_replace( 'one', 'ONE', $section ),
412 'wpSection' => '1'
413 ),
414 array( #berta's edit
415 'wpStarttime' => 2,
416 'wpTextbox1' => str_replace( 'three', 'THREE', $section ),
417 'wpSection' => '1'
418 ),
419 EditPage::AS_SUCCESS_UPDATE, # expected code
420 $expected, # expected text
421 'expected automatic section merge', # message
422 );
423
424 // see whether it makes a difference who did the base edit
425 $testsWithAdam = array_map( function ( $test ) {
426 $test[0] = 'Adam'; // change base edit user
427 return $test;
428 }, $tests );
429
430 $testsWithBerta = array_map( function ( $test ) {
431 $test[0] = 'Berta'; // change base edit user
432 return $test;
433 }, $tests );
434
435 return array_merge( $tests, $testsWithAdam, $testsWithBerta );
436 }
437
438 /**
439 * @dataProvider provideAutoMerge
440 * @covers EditPage
441 */
442 public function testAutoMerge( $baseUser, $text, $adamsEdit, $bertasEdit,
443 $expectedCode, $expectedText, $message = null
444 ) {
445 $this->checkHasDiff3();
446
447 //create page
448 $ns = $this->getDefaultWikitextNS();
449 $title = Title::newFromText( 'EditPageTest_testAutoMerge', $ns );
450 $page = WikiPage::factory( $title );
451
452 if ( $page->exists() ) {
453 $page->doDeleteArticle( "clean slate for testing" );
454 }
455
456 $baseEdit = array(
457 'wpTextbox1' => $text,
458 );
459
460 $page = $this->assertEdit( 'EditPageTest_testAutoMerge', null,
461 $baseUser, $baseEdit, null, null, __METHOD__ );
462
463 $this->forceRevisionDate( $page, '20120101000000' );
464
465 $edittime = $page->getTimestamp();
466
467 // start timestamps for conflict detection
468 if ( !isset( $adamsEdit['wpStarttime'] ) ) {
469 $adamsEdit['wpStarttime'] = 1;
470 }
471
472 if ( !isset( $bertasEdit['wpStarttime'] ) ) {
473 $bertasEdit['wpStarttime'] = 2;
474 }
475
476 $starttime = wfTimestampNow();
477 $adamsTime = wfTimestamp( TS_MW, (int)wfTimestamp( TS_UNIX, $starttime ) + (int)$adamsEdit['wpStarttime'] );
478 $bertasTime = wfTimestamp( TS_MW, (int)wfTimestamp( TS_UNIX, $starttime ) + (int)$bertasEdit['wpStarttime'] );
479
480 $adamsEdit['wpStarttime'] = $adamsTime;
481 $bertasEdit['wpStarttime'] = $bertasTime;
482
483 $adamsEdit['wpSummary'] = 'Adam\'s edit';
484 $bertasEdit['wpSummary'] = 'Bertas\'s edit';
485
486 $adamsEdit['wpEdittime'] = $edittime;
487 $bertasEdit['wpEdittime'] = $edittime;
488
489 // first edit
490 $this->assertEdit( 'EditPageTest_testAutoMerge', null, 'Adam', $adamsEdit,
491 EditPage::AS_SUCCESS_UPDATE, null, "expected successfull update" );
492
493 // second edit
494 $this->assertEdit( 'EditPageTest_testAutoMerge', null, 'Berta', $bertasEdit,
495 $expectedCode, $expectedText, $message );
496 }
497 }