1<?php
2
3use MediaWiki\Revision\RevisionRecord;
4
5/**
6 * @group API
7 * @group Database
8 * @group medium
9 * @covers ApiComparePages
10 */
11class ApiComparePagesTest extends ApiTestCase {
12
13	protected static $repl = [];
14
15	protected function addPage( $page, $text, $model = CONTENT_MODEL_WIKITEXT ) {
16		$title = Title::newFromText( 'ApiComparePagesTest ' . $page );
17		$content = ContentHandler::makeContent( $text, $title, $model );
18
19		$page = WikiPage::factory( $title );
20		$user = static::getTestSysop()->getUser();
21		$status = $page->doUserEditContent(
22			$content, $user, 'Test for ApiComparePagesTest: ' . $text
23		);
24		if ( !$status->isOK() ) {
25			$this->fail( "Failed to create $title: " . $status->getWikiText( false, false, 'en' ) );
26		}
27		return $status->value['revision-record']->getId();
28	}
29
30	public function addDBDataOnce() {
31		$user = static::getTestSysop()->getUser();
32		self::$repl['creator'] = $user->getName();
33		self::$repl['creatorid'] = $user->getId();
34
35		self::$repl['revA1'] = $this->addPage( 'A', 'A 1' );
36		self::$repl['revA2'] = $this->addPage( 'A', 'A 2' );
37		self::$repl['revA3'] = $this->addPage( 'A', 'A 3' );
38		self::$repl['revA4'] = $this->addPage( 'A', 'A 4' );
39		self::$repl['pageA'] = Title::newFromText( 'ApiComparePagesTest A' )->getArticleID();
40
41		self::$repl['revB1'] = $this->addPage( 'B', 'B 1' );
42		self::$repl['revB2'] = $this->addPage( 'B', 'B 2' );
43		self::$repl['revB3'] = $this->addPage( 'B', 'B 3' );
44		self::$repl['revB4'] = $this->addPage( 'B', 'B 4' );
45		self::$repl['pageB'] = Title::newFromText( 'ApiComparePagesTest B' )->getArticleID();
46		$updateTimestamps = [
47			self::$repl['revB1'] => '20010101011101',
48			self::$repl['revB2'] => '20020202022202',
49			self::$repl['revB3'] => '20030303033303',
50			self::$repl['revB4'] => '20040404044404',
51		];
52		foreach ( $updateTimestamps as $id => $ts ) {
53			$this->db->update(
54				'revision',
55				[ 'rev_timestamp' => $this->db->timestamp( $ts ) ],
56				[ 'rev_id' => $id ],
57				__METHOD__
58			);
59		}
60
61		self::$repl['revC1'] = $this->addPage( 'C', 'C 1' );
62		self::$repl['revC2'] = $this->addPage( 'C', 'C 2' );
63		self::$repl['revC3'] = $this->addPage( 'C', 'C 3' );
64		self::$repl['pageC'] = Title::newFromText( 'ApiComparePagesTest C' )->getArticleID();
65
66		$id = $this->addPage( 'D', 'D 1' );
67		self::$repl['pageD'] = Title::newFromText( 'ApiComparePagesTest D' )->getArticleID();
68		wfGetDB( DB_PRIMARY )->delete( 'revision', [ 'rev_id' => $id ] );
69
70		self::$repl['revE1'] = $this->addPage( 'E', 'E 1' );
71		self::$repl['revE2'] = $this->addPage( 'E', 'E 2' );
72		self::$repl['revE3'] = $this->addPage( 'E', 'E 3' );
73		self::$repl['revE4'] = $this->addPage( 'E', 'E 4' );
74		self::$repl['pageE'] = Title::newFromText( 'ApiComparePagesTest E' )->getArticleID();
75		wfGetDB( DB_PRIMARY )->update(
76			'page', [ 'page_latest' => 0 ], [ 'page_id' => self::$repl['pageE'] ]
77		);
78
79		self::$repl['revF1'] = $this->addPage( 'F', "== Section 1 ==\nF 1.1\n\n== Section 2 ==\nF 1.2" );
80		self::$repl['pageF'] = Title::newFromText( 'ApiComparePagesTest F' )->getArticleID();
81
82		self::$repl['revG1'] = $this->addPage( 'G', "== Section 1 ==\nG 1.1", CONTENT_MODEL_TEXT );
83		self::$repl['pageG'] = Title::newFromText( 'ApiComparePagesTest G' )->getArticleID();
84
85		WikiPage::factory( Title::newFromText( 'ApiComparePagesTest C' ) )
86			->doDeleteArticleReal( 'Test for ApiComparePagesTest', $user );
87
88		RevisionDeleter::createList(
89			'revision',
90			RequestContext::getMain(),
91			Title::newFromText( 'ApiComparePagesTest B' ),
92			[ self::$repl['revB2'] ]
93		)->setVisibility( [
94			'value' => [
95				RevisionRecord::DELETED_TEXT => 1,
96				RevisionRecord::DELETED_USER => 1,
97				RevisionRecord::DELETED_COMMENT => 1,
98			],
99			'comment' => 'Test for ApiComparePages',
100		] );
101
102		RevisionDeleter::createList(
103			'revision',
104			RequestContext::getMain(),
105			Title::newFromText( 'ApiComparePagesTest B' ),
106			[ self::$repl['revB3'] ]
107		)->setVisibility( [
108			'value' => [
109				RevisionRecord::DELETED_USER => 1,
110				RevisionRecord::DELETED_COMMENT => 1,
111				RevisionRecord::DELETED_RESTRICTED => 1,
112			],
113			'comment' => 'Test for ApiComparePages',
114		] );
115
116		Title::clearCaches(); // Otherwise it has the wrong latest revision for some reason
117	}
118
119	protected function doReplacements( &$value ) {
120		if ( is_string( $value ) ) {
121			if ( preg_match( '/^{{REPL:(.+?)}}$/', $value, $m ) ) {
122				$value = self::$repl[$m[1]];
123			} else {
124				$value = preg_replace_callback( '/{{REPL:(.+?)}}/', static function ( $m ) {
125					return self::$repl[$m[1]] ?? $m[0];
126				}, $value );
127			}
128		} elseif ( is_array( $value ) || is_object( $value ) ) {
129			foreach ( $value as &$v ) {
130				$this->doReplacements( $v );
131			}
132			unset( $v );
133		}
134	}
135
136	/**
137	 * @dataProvider provideDiff
138	 */
139	public function testDiff( $params, $expect, $exceptionCode = false, $sysop = false ) {
140		$this->setMwGlobals( [ 'wgDiffEngine' => 'php' ] );
141
142		$this->doReplacements( $params );
143
144		$params += [
145			'action' => 'compare',
146			'errorformat' => 'none',
147		];
148
149		$user = $sysop
150			? static::getTestSysop()->getUser()
151			: static::getTestUser()->getUser();
152		if ( $exceptionCode ) {
153			try {
154				$this->doApiRequest( $params, null, false, $user );
155				$this->fail( 'Expected exception not thrown' );
156			} catch ( ApiUsageException $ex ) {
157				$this->assertTrue( $this->apiExceptionHasCode( $ex, $exceptionCode ),
158					"Exception with code $exceptionCode" );
159			}
160		} else {
161			$apiResult = $this->doApiRequest( $params, null, false, $user );
162			$apiResult = $apiResult[0];
163			$this->doReplacements( $expect );
164			$this->assertEquals( $expect, $apiResult );
165		}
166	}
167
168	private static function makeDeprecationWarnings( ...$params ) {
169		$warn = [];
170		foreach ( $params as $p ) {
171			$warn[] = [
172				'code' => 'deprecation',
173				'data' => [ 'feature' => "action=compare&{$p}" ],
174				'module' => 'compare',
175			];
176			if ( count( $warn ) === 1 ) {
177				$warn[] = [
178					'code' => 'deprecation-help',
179					'module' => 'main',
180				];
181			}
182		}
183
184		return $warn;
185	}
186
187	public static function provideDiff() {
188		// phpcs:disable Generic.Files.LineLength.TooLong
189		return [
190			'Basic diff, titles' => [
191				[
192					'fromtitle' => 'ApiComparePagesTest A',
193					'totitle' => 'ApiComparePagesTest B',
194				],
195				[
196					'compare' => [
197						'fromid' => '{{REPL:pageA}}',
198						'fromrevid' => '{{REPL:revA4}}',
199						'fromns' => 0,
200						'fromtitle' => 'ApiComparePagesTest A',
201						'toid' => '{{REPL:pageB}}',
202						'torevid' => '{{REPL:revB4}}',
203						'tons' => 0,
204						'totitle' => 'ApiComparePagesTest B',
205						'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1">Line 1:</td>' . "\n"
206							. '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
207							. '<tr><td class="diff-marker" data-marker="−"></td><td class="diff-deletedline diff-side-deleted"><div><del class="diffchange diffchange-inline">A </del>4</div></td><td class="diff-marker" data-marker="+"></td><td class="diff-addedline diff-side-added"><div><ins class="diffchange diffchange-inline">B </ins>4</div></td></tr>' . "\n",
208					]
209				],
210			],
211			'Basic diff, page IDs' => [
212				[
213					'fromid' => '{{REPL:pageA}}',
214					'toid' => '{{REPL:pageB}}',
215				],
216				[
217					'compare' => [
218						'fromid' => '{{REPL:pageA}}',
219						'fromrevid' => '{{REPL:revA4}}',
220						'fromns' => 0,
221						'fromtitle' => 'ApiComparePagesTest A',
222						'toid' => '{{REPL:pageB}}',
223						'torevid' => '{{REPL:revB4}}',
224						'tons' => 0,
225						'totitle' => 'ApiComparePagesTest B',
226						'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1">Line 1:</td>' . "\n"
227							. '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
228							. '<tr><td class="diff-marker" data-marker="−"></td><td class="diff-deletedline diff-side-deleted"><div><del class="diffchange diffchange-inline">A </del>4</div></td><td class="diff-marker" data-marker="+"></td><td class="diff-addedline diff-side-added"><div><ins class="diffchange diffchange-inline">B </ins>4</div></td></tr>' . "\n",
229					]
230				],
231			],
232			'Basic diff, revision IDs' => [
233				[
234					'fromrev' => '{{REPL:revA2}}',
235					'torev' => '{{REPL:revA3}}',
236				],
237				[
238					'compare' => [
239						'fromid' => '{{REPL:pageA}}',
240						'fromrevid' => '{{REPL:revA2}}',
241						'fromns' => 0,
242						'fromtitle' => 'ApiComparePagesTest A',
243						'toid' => '{{REPL:pageA}}',
244						'torevid' => '{{REPL:revA3}}',
245						'tons' => 0,
246						'totitle' => 'ApiComparePagesTest A',
247						'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1">Line 1:</td>' . "\n"
248							. '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
249							. '<tr><td class="diff-marker" data-marker="−"></td><td class="diff-deletedline diff-side-deleted"><div>A <del class="diffchange diffchange-inline">2</del></div></td><td class="diff-marker" data-marker="+"></td><td class="diff-addedline diff-side-added"><div>A <ins class="diffchange diffchange-inline">3</ins></div></td></tr>' . "\n",
250					]
251				],
252			],
253			'Basic diff, deleted revision ID as sysop' => [
254				[
255					'fromrev' => '{{REPL:revA2}}',
256					'torev' => '{{REPL:revC2}}',
257				],
258				[
259					'compare' => [
260						'fromid' => '{{REPL:pageA}}',
261						'fromrevid' => '{{REPL:revA2}}',
262						'fromns' => 0,
263						'fromtitle' => 'ApiComparePagesTest A',
264						'toid' => 0,
265						'torevid' => '{{REPL:revC2}}',
266						'tons' => 0,
267						'totitle' => 'ApiComparePagesTest C',
268						'toarchive' => true,
269						'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1">Line 1:</td>' . "\n"
270							. '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
271							. '<tr><td class="diff-marker" data-marker="−"></td><td class="diff-deletedline diff-side-deleted"><div><del class="diffchange diffchange-inline">A </del>2</div></td><td class="diff-marker" data-marker="+"></td><td class="diff-addedline diff-side-added"><div><ins class="diffchange diffchange-inline">C </ins>2</div></td></tr>' . "\n",
272					]
273				],
274				false, true
275			],
276			'Basic diff, revdel as sysop' => [
277				[
278					'fromrev' => '{{REPL:revA2}}',
279					'torev' => '{{REPL:revB2}}',
280				],
281				[
282					'compare' => [
283						'fromid' => '{{REPL:pageA}}',
284						'fromrevid' => '{{REPL:revA2}}',
285						'fromns' => 0,
286						'fromtitle' => 'ApiComparePagesTest A',
287						'toid' => '{{REPL:pageB}}',
288						'torevid' => '{{REPL:revB2}}',
289						'tons' => 0,
290						'totitle' => 'ApiComparePagesTest B',
291						'totexthidden' => true,
292						'touserhidden' => true,
293						'tocommenthidden' => true,
294						'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1">Line 1:</td>' . "\n"
295							. '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
296							. '<tr><td class="diff-marker" data-marker="−"></td><td class="diff-deletedline diff-side-deleted"><div><del class="diffchange diffchange-inline">A </del>2</div></td><td class="diff-marker" data-marker="+"></td><td class="diff-addedline diff-side-added"><div><ins class="diffchange diffchange-inline">B </ins>2</div></td></tr>' . "\n",
297					]
298				],
299				false, true
300			],
301			'Basic diff, text' => [
302				[
303					'fromslots' => 'main',
304					'fromtext-main' => 'From text',
305					'fromcontentmodel-main' => 'wikitext',
306					'toslots' => 'main',
307					'totext-main' => 'To text {{subst:PAGENAME}}',
308					'tocontentmodel-main' => 'wikitext',
309				],
310				[
311					'compare' => [
312						'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1">Line 1:</td>' . "\n"
313							. '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
314							. '<tr><td class="diff-marker" data-marker="−"></td><td class="diff-deletedline diff-side-deleted"><div><del class="diffchange diffchange-inline">From </del>text</div></td><td class="diff-marker" data-marker="+"></td><td class="diff-addedline diff-side-added"><div><ins class="diffchange diffchange-inline">To </ins>text <ins class="diffchange diffchange-inline">{{subst:PAGENAME}}</ins></div></td></tr>' . "\n",
315					]
316				],
317			],
318			'Basic diff, text 2' => [
319				[
320					'fromslots' => 'main',
321					'fromtext-main' => 'From text',
322					'toslots' => 'main',
323					'totext-main' => 'To text {{subst:PAGENAME}}',
324					'tocontentmodel-main' => 'wikitext',
325				],
326				[
327					'compare' => [
328						'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1">Line 1:</td>' . "\n"
329							. '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
330							. '<tr><td class="diff-marker" data-marker="−"></td><td class="diff-deletedline diff-side-deleted"><div><del class="diffchange diffchange-inline">From </del>text</div></td><td class="diff-marker" data-marker="+"></td><td class="diff-addedline diff-side-added"><div><ins class="diffchange diffchange-inline">To </ins>text <ins class="diffchange diffchange-inline">{{subst:PAGENAME}}</ins></div></td></tr>' . "\n",
331					]
332				],
333			],
334			'Basic diff, guessed model' => [
335				[
336					'fromslots' => 'main',
337					'fromtext-main' => 'From text',
338					'toslots' => 'main',
339					'totext-main' => 'To text',
340				],
341				[
342					'warnings' => [ [ 'code' => 'compare-nocontentmodel', 'module' => 'compare' ] ],
343					'compare' => [
344						'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1">Line 1:</td>' . "\n"
345							. '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
346							. '<tr><td class="diff-marker" data-marker="−"></td><td class="diff-deletedline diff-side-deleted"><div><del class="diffchange diffchange-inline">From </del>text</div></td><td class="diff-marker" data-marker="+"></td><td class="diff-addedline diff-side-added"><div><ins class="diffchange diffchange-inline">To </ins>text</div></td></tr>' . "\n",
347					]
348				],
349			],
350			'Basic diff, text with title and PST' => [
351				[
352					'fromslots' => 'main',
353					'fromtext-main' => 'From text',
354					'totitle' => 'Test',
355					'toslots' => 'main',
356					'totext-main' => 'To text {{subst:PAGENAME}}',
357					'topst' => true,
358				],
359				[
360					'compare' => [
361						'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1">Line 1:</td>' . "\n"
362							. '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
363							. '<tr><td class="diff-marker" data-marker="−"></td><td class="diff-deletedline diff-side-deleted"><div><del class="diffchange diffchange-inline">From </del>text</div></td><td class="diff-marker" data-marker="+"></td><td class="diff-addedline diff-side-added"><div><ins class="diffchange diffchange-inline">To </ins>text <ins class="diffchange diffchange-inline">Test</ins></div></td></tr>' . "\n",
364					]
365				],
366			],
367			'Basic diff, text with page ID and PST' => [
368				[
369					'fromslots' => 'main',
370					'fromtext-main' => 'From text',
371					'toid' => '{{REPL:pageB}}',
372					'toslots' => 'main',
373					'totext-main' => 'To text {{subst:PAGENAME}}',
374					'topst' => true,
375				],
376				[
377					'compare' => [
378						'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1">Line 1:</td>' . "\n"
379							. '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
380							. '<tr><td class="diff-marker" data-marker="−"></td><td class="diff-deletedline diff-side-deleted"><div><del class="diffchange diffchange-inline">From </del>text</div></td><td class="diff-marker" data-marker="+"></td><td class="diff-addedline diff-side-added"><div><ins class="diffchange diffchange-inline">To </ins>text <ins class="diffchange diffchange-inline">ApiComparePagesTest B</ins></div></td></tr>' . "\n",
381					]
382				],
383			],
384			'Basic diff, text with revision and PST' => [
385				[
386					'fromslots' => 'main',
387					'fromtext-main' => 'From text',
388					'torev' => '{{REPL:revB2}}',
389					'toslots' => 'main',
390					'totext-main' => 'To text {{subst:PAGENAME}}',
391					'topst' => true,
392				],
393				[
394					'compare' => [
395						'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1">Line 1:</td>' . "\n"
396							. '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
397							. '<tr><td class="diff-marker" data-marker="−"></td><td class="diff-deletedline diff-side-deleted"><div><del class="diffchange diffchange-inline">From </del>text</div></td><td class="diff-marker" data-marker="+"></td><td class="diff-addedline diff-side-added"><div><ins class="diffchange diffchange-inline">To </ins>text <ins class="diffchange diffchange-inline">ApiComparePagesTest B</ins></div></td></tr>' . "\n",
398					]
399				],
400			],
401			'Basic diff, text with deleted revision and PST' => [
402				[
403					'fromslots' => 'main',
404					'fromtext-main' => 'From text',
405					'torev' => '{{REPL:revC2}}',
406					'toslots' => 'main',
407					'totext-main' => 'To text {{subst:PAGENAME}}',
408					'topst' => true,
409				],
410				[
411					'compare' => [
412						'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1">Line 1:</td>' . "\n"
413							. '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
414							. '<tr><td class="diff-marker" data-marker="−"></td><td class="diff-deletedline diff-side-deleted"><div><del class="diffchange diffchange-inline">From </del>text</div></td><td class="diff-marker" data-marker="+"></td><td class="diff-addedline diff-side-added"><div><ins class="diffchange diffchange-inline">To </ins>text <ins class="diffchange diffchange-inline">ApiComparePagesTest C</ins></div></td></tr>' . "\n",
415					]
416				],
417				false, true
418			],
419			'Basic diff, test with sections' => [
420				[
421					'fromtitle' => 'ApiComparePagesTest F',
422					'fromslots' => 'main',
423					'fromtext-main' => "== Section 2 ==\nFrom text?",
424					'fromsection-main' => 2,
425					'totitle' => 'ApiComparePagesTest F',
426					'toslots' => 'main',
427					'totext-main' => "== Section 1 ==\nTo text?",
428					'tosection-main' => 1,
429				],
430				[
431					'compare' => [
432						'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1">Line 1:</td>' . "\n"
433							. '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
434							. '<tr><td class="diff-marker"></td><td class="diff-context diff-side-deleted"><div>== Section 1 ==</div></td><td class="diff-marker"></td><td class="diff-context diff-side-added"><div>== Section 1 ==</div></td></tr>' . "\n"
435							. '<tr><td class="diff-marker" data-marker="−"></td><td class="diff-deletedline diff-side-deleted"><div><del class="diffchange diffchange-inline">F 1.1</del></div></td><td class="diff-marker" data-marker="+"></td><td class="diff-addedline diff-side-added"><div><ins class="diffchange diffchange-inline">To text?</ins></div></td></tr>' . "\n"
436							. '<tr><td class="diff-marker"></td><td class="diff-context diff-side-deleted"><br/></td><td class="diff-marker"></td><td class="diff-context diff-side-added"><br/></td></tr>' . "\n"
437							. '<tr><td class="diff-marker"></td><td class="diff-context diff-side-deleted"><div>== Section 2 ==</div></td><td class="diff-marker"></td><td class="diff-context diff-side-added"><div>== Section 2 ==</div></td></tr>' . "\n"
438							. '<tr><td class="diff-marker" data-marker="−"></td><td class="diff-deletedline diff-side-deleted"><div><del class="diffchange diffchange-inline">From text?</del></div></td><td class="diff-marker" data-marker="+"></td><td class="diff-addedline diff-side-added"><div><ins class="diffchange diffchange-inline">F 1.2</ins></div></td></tr>' . "\n",
439					]
440				],
441			],
442			'Diff with all props' => [
443				[
444					'fromrev' => '{{REPL:revB1}}',
445					'torev' => '{{REPL:revB3}}',
446					'totitle' => 'ApiComparePagesTest B',
447					'prop' => 'diff|diffsize|rel|ids|title|user|comment|parsedcomment|size|timestamp'
448				],
449				[
450					'compare' => [
451						'fromid' => '{{REPL:pageB}}',
452						'fromrevid' => '{{REPL:revB1}}',
453						'fromns' => 0,
454						'fromtitle' => 'ApiComparePagesTest B',
455						'fromsize' => 3,
456						'fromuser' => '{{REPL:creator}}',
457						'fromuserid' => '{{REPL:creatorid}}',
458						'fromcomment' => 'Test for ApiComparePagesTest: B 1',
459						'fromparsedcomment' => 'Test for ApiComparePagesTest: B 1',
460						'fromtimestamp' => '2001-01-01T01:11:01Z',
461						'toid' => '{{REPL:pageB}}',
462						'torevid' => '{{REPL:revB3}}',
463						'tons' => 0,
464						'totitle' => 'ApiComparePagesTest B',
465						'tosize' => 3,
466						'touserhidden' => true,
467						'tocommenthidden' => true,
468						'tosuppressed' => true,
469						'totimestamp' => '2003-03-03T03:33:03Z',
470						'next' => '{{REPL:revB4}}',
471						'diffsize' => 454,
472						'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1">Line 1:</td>' . "\n"
473							. '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
474							. '<tr><td class="diff-marker" data-marker="−"></td><td class="diff-deletedline diff-side-deleted"><div>B <del class="diffchange diffchange-inline">1</del></div></td><td class="diff-marker" data-marker="+"></td><td class="diff-addedline diff-side-added"><div>B <ins class="diffchange diffchange-inline">3</ins></div></td></tr>' . "\n",
475					]
476				],
477			],
478			'Diff with all props as sysop' => [
479				[
480					'fromrev' => '{{REPL:revB2}}',
481					'torev' => '{{REPL:revB3}}',
482					'totitle' => 'ApiComparePagesTest B',
483					'prop' => 'diff|diffsize|rel|ids|title|user|comment|parsedcomment|size|timestamp'
484				],
485				[
486					'compare' => [
487						'fromid' => '{{REPL:pageB}}',
488						'fromrevid' => '{{REPL:revB2}}',
489						'fromns' => 0,
490						'fromtitle' => 'ApiComparePagesTest B',
491						'fromsize' => 3,
492						'fromtexthidden' => true,
493						'fromuserhidden' => true,
494						'fromuser' => '{{REPL:creator}}',
495						'fromuserid' => '{{REPL:creatorid}}',
496						'fromcommenthidden' => true,
497						'fromcomment' => 'Test for ApiComparePagesTest: B 2',
498						'fromparsedcomment' => 'Test for ApiComparePagesTest: B 2',
499						'fromtimestamp' => '2002-02-02T02:22:02Z',
500						'toid' => '{{REPL:pageB}}',
501						'torevid' => '{{REPL:revB3}}',
502						'tons' => 0,
503						'totitle' => 'ApiComparePagesTest B',
504						'tosize' => 3,
505						'touserhidden' => true,
506						'tocommenthidden' => true,
507						'tosuppressed' => true,
508						'totimestamp' => '2003-03-03T03:33:03Z',
509						'prev' => '{{REPL:revB1}}',
510						'next' => '{{REPL:revB4}}',
511						'diffsize' => 454,
512						'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1">Line 1:</td>' . "\n"
513							. '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
514							. '<tr><td class="diff-marker" data-marker="−"></td><td class="diff-deletedline diff-side-deleted"><div>B <del class="diffchange diffchange-inline">2</del></div></td><td class="diff-marker" data-marker="+"></td><td class="diff-addedline diff-side-added"><div>B <ins class="diffchange diffchange-inline">3</ins></div></td></tr>' . "\n",
515					]
516				],
517				false, true
518			],
519			'Text diff with all props' => [
520				[
521					'fromrev' => '{{REPL:revB1}}',
522					'toslots' => 'main',
523					'totext-main' => 'To text {{subst:PAGENAME}}',
524					'tocontentmodel-main' => 'wikitext',
525					'prop' => 'diff|diffsize|rel|ids|title|user|comment|parsedcomment|size|timestamp'
526				],
527				[
528					'compare' => [
529						'fromid' => '{{REPL:pageB}}',
530						'fromrevid' => '{{REPL:revB1}}',
531						'fromns' => 0,
532						'fromtitle' => 'ApiComparePagesTest B',
533						'fromsize' => 3,
534						'fromuser' => '{{REPL:creator}}',
535						'fromuserid' => '{{REPL:creatorid}}',
536						'fromcomment' => 'Test for ApiComparePagesTest: B 1',
537						'fromparsedcomment' => 'Test for ApiComparePagesTest: B 1',
538						'fromtimestamp' => '2001-01-01T01:11:01Z',
539						'diffsize' => 477,
540						'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1">Line 1:</td>' . "\n"
541							. '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
542							. '<tr><td class="diff-marker" data-marker="−"></td><td class="diff-deletedline diff-side-deleted"><div><del class="diffchange diffchange-inline">B 1</del></div></td><td class="diff-marker" data-marker="+"></td><td class="diff-addedline diff-side-added"><div><ins class="diffchange diffchange-inline">To text {{subst:PAGENAME}}</ins></div></td></tr>' . "\n",
543					]
544				],
545			],
546			'Relative diff, cur' => [
547				[
548					'fromrev' => '{{REPL:revA2}}',
549					'torelative' => 'cur',
550					'prop' => 'ids',
551				],
552				[
553					'compare' => [
554						'fromid' => '{{REPL:pageA}}',
555						'fromrevid' => '{{REPL:revA2}}',
556						'toid' => '{{REPL:pageA}}',
557						'torevid' => '{{REPL:revA4}}',
558					]
559				],
560			],
561			'Relative diff, next' => [
562				[
563					'fromrev' => '{{REPL:revE2}}',
564					'torelative' => 'next',
565					'prop' => 'ids|rel',
566				],
567				[
568					'compare' => [
569						'fromid' => '{{REPL:pageE}}',
570						'fromrevid' => '{{REPL:revE2}}',
571						'toid' => '{{REPL:pageE}}',
572						'torevid' => '{{REPL:revE3}}',
573						'prev' => '{{REPL:revE1}}',
574						'next' => '{{REPL:revE4}}',
575					]
576				],
577			],
578			'Relative diff, prev' => [
579				[
580					'fromrev' => '{{REPL:revE3}}',
581					'torelative' => 'prev',
582					'prop' => 'ids|rel',
583				],
584				[
585					'compare' => [
586						'fromid' => '{{REPL:pageE}}',
587						'fromrevid' => '{{REPL:revE2}}',
588						'toid' => '{{REPL:pageE}}',
589						'torevid' => '{{REPL:revE3}}',
590						'prev' => '{{REPL:revE1}}',
591						'next' => '{{REPL:revE4}}',
592					]
593				],
594			],
595			'Relative diff, no prev' => [
596				[
597					'fromrev' => '{{REPL:revA1}}',
598					'torelative' => 'prev',
599					'prop' => 'ids|rel|diff|title|user|comment',
600				],
601				[
602					'warnings' => [
603						[
604							'code' => 'compare-no-prev',
605							'module' => 'compare',
606						],
607					],
608					'compare' => [
609						'toid' => '{{REPL:pageA}}',
610						'torevid' => '{{REPL:revA1}}',
611						'tons' => 0,
612						'totitle' => 'ApiComparePagesTest A',
613						'touser' => '{{REPL:creator}}',
614						'touserid' => '{{REPL:creatorid}}',
615						'tocomment' => 'Test for ApiComparePagesTest: A 1',
616						'toparsedcomment' => 'Test for ApiComparePagesTest: A 1',
617						'next' => '{{REPL:revA2}}',
618						'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1">Line 1:</td>' . "\n"
619							. '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
620							. '<tr><td class="diff-marker" data-marker="−"></td><td class="diff-deletedline diff-side-deleted"><div> </div></td><td class="diff-marker" data-marker="+"></td><td class="diff-addedline diff-side-added"><div><ins class="diffchange diffchange-inline">A 1</ins></div></td></tr>' . "\n",
621					],
622				],
623			],
624			'Relative diff, no next' => [
625				[
626					'fromrev' => '{{REPL:revA4}}',
627					'torelative' => 'next',
628					'prop' => 'ids|rel|diff|title|user|comment',
629				],
630				[
631					'warnings' => [
632						[
633							'code' => 'compare-no-next',
634							'module' => 'compare',
635						],
636					],
637					'compare' => [
638						'fromid' => '{{REPL:pageA}}',
639						'fromrevid' => '{{REPL:revA4}}',
640						'fromns' => 0,
641						'fromtitle' => 'ApiComparePagesTest A',
642						'fromuser' => '{{REPL:creator}}',
643						'fromuserid' => '{{REPL:creatorid}}',
644						'fromcomment' => 'Test for ApiComparePagesTest: A 4',
645						'fromparsedcomment' => 'Test for ApiComparePagesTest: A 4',
646						'prev' => '{{REPL:revA3}}',
647						'body' => '',
648					],
649				],
650			],
651			'Diff for specific slots' => [
652				// @todo Use a page with multiple slots here
653				[
654					'fromrev' => '{{REPL:revA1}}',
655					'torev' => '{{REPL:revA3}}',
656					'prop' => 'diff',
657					'slots' => 'main',
658				],
659				[
660					'compare' => [
661						'bodies' => [
662							'main' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1">Line 1:</td>' . "\n"
663								. '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
664								. '<tr><td class="diff-marker" data-marker="−"></td><td class="diff-deletedline diff-side-deleted"><div>A <del class="diffchange diffchange-inline">1</del></div></td><td class="diff-marker" data-marker="+"></td><td class="diff-addedline diff-side-added"><div>A <ins class="diffchange diffchange-inline">3</ins></div></td></tr>' . "\n",
665						],
666					],
667				],
668			],
669			// @todo Add a test for diffing with a deleted slot. Deleting 'main' doesn't work.
670
671			'Basic diff, deprecated text' => [
672				[
673					'fromtext' => 'From text',
674					'fromcontentmodel' => 'wikitext',
675					'totext' => 'To text {{subst:PAGENAME}}',
676					'tocontentmodel' => 'wikitext',
677				],
678				[
679					'warnings' => self::makeDeprecationWarnings( 'fromtext', 'fromcontentmodel', 'totext', 'tocontentmodel' ),
680					'compare' => [
681						'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1">Line 1:</td>' . "\n"
682							. '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
683							. '<tr><td class="diff-marker" data-marker="−"></td><td class="diff-deletedline diff-side-deleted"><div><del class="diffchange diffchange-inline">From </del>text</div></td><td class="diff-marker" data-marker="+"></td><td class="diff-addedline diff-side-added"><div><ins class="diffchange diffchange-inline">To </ins>text <ins class="diffchange diffchange-inline">{{subst:PAGENAME}}</ins></div></td></tr>' . "\n",
684					]
685				],
686			],
687			'Basic diff, deprecated text 2' => [
688				[
689					'fromtext' => 'From text',
690					'totext' => 'To text {{subst:PAGENAME}}',
691					'tocontentmodel' => 'wikitext',
692				],
693				[
694					'warnings' => self::makeDeprecationWarnings( 'fromtext', 'totext', 'tocontentmodel' ),
695					'compare' => [
696						'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1">Line 1:</td>' . "\n"
697							. '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
698							. '<tr><td class="diff-marker" data-marker="−"></td><td class="diff-deletedline diff-side-deleted"><div><del class="diffchange diffchange-inline">From </del>text</div></td><td class="diff-marker" data-marker="+"></td><td class="diff-addedline diff-side-added"><div><ins class="diffchange diffchange-inline">To </ins>text <ins class="diffchange diffchange-inline">{{subst:PAGENAME}}</ins></div></td></tr>' . "\n",
699					]
700				],
701			],
702			'Basic diff, deprecated text, guessed model' => [
703				[
704					'fromtext' => 'From text',
705					'totext' => 'To text',
706				],
707				[
708					'warnings' => array_merge( self::makeDeprecationWarnings( 'fromtext', 'totext' ), [
709						[ 'code' => 'compare-nocontentmodel', 'module' => 'compare' ],
710					] ),
711					'compare' => [
712						'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1">Line 1:</td>' . "\n"
713							. '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
714							. '<tr><td class="diff-marker" data-marker="−"></td><td class="diff-deletedline diff-side-deleted"><div><del class="diffchange diffchange-inline">From </del>text</div></td><td class="diff-marker" data-marker="+"></td><td class="diff-addedline diff-side-added"><div><ins class="diffchange diffchange-inline">To </ins>text</div></td></tr>' . "\n",
715					]
716				],
717			],
718			'Basic diff, deprecated text with title and PST' => [
719				[
720					'fromtext' => 'From text',
721					'totitle' => 'Test',
722					'totext' => 'To text {{subst:PAGENAME}}',
723					'topst' => true,
724				],
725				[
726					'warnings' => self::makeDeprecationWarnings( 'fromtext', 'totext' ),
727					'compare' => [
728						'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1">Line 1:</td>' . "\n"
729							. '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
730							. '<tr><td class="diff-marker" data-marker="−"></td><td class="diff-deletedline diff-side-deleted"><div><del class="diffchange diffchange-inline">From </del>text</div></td><td class="diff-marker" data-marker="+"></td><td class="diff-addedline diff-side-added"><div><ins class="diffchange diffchange-inline">To </ins>text <ins class="diffchange diffchange-inline">Test</ins></div></td></tr>' . "\n",
731					]
732				],
733			],
734			'Basic diff, deprecated text with page ID and PST' => [
735				[
736					'fromtext' => 'From text',
737					'toid' => '{{REPL:pageB}}',
738					'totext' => 'To text {{subst:PAGENAME}}',
739					'topst' => true,
740				],
741				[
742					'warnings' => self::makeDeprecationWarnings( 'fromtext', 'totext' ),
743					'compare' => [
744						'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1">Line 1:</td>' . "\n"
745							. '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
746							. '<tr><td class="diff-marker" data-marker="−"></td><td class="diff-deletedline diff-side-deleted"><div><del class="diffchange diffchange-inline">From </del>text</div></td><td class="diff-marker" data-marker="+"></td><td class="diff-addedline diff-side-added"><div><ins class="diffchange diffchange-inline">To </ins>text <ins class="diffchange diffchange-inline">ApiComparePagesTest B</ins></div></td></tr>' . "\n",
747					]
748				],
749			],
750			'Basic diff, deprecated text with revision and PST' => [
751				[
752					'fromtext' => 'From text',
753					'torev' => '{{REPL:revB2}}',
754					'totext' => 'To text {{subst:PAGENAME}}',
755					'topst' => true,
756				],
757				[
758					'warnings' => self::makeDeprecationWarnings( 'fromtext', 'totext' ),
759					'compare' => [
760						'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1">Line 1:</td>' . "\n"
761							. '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
762							. '<tr><td class="diff-marker" data-marker="−"></td><td class="diff-deletedline diff-side-deleted"><div><del class="diffchange diffchange-inline">From </del>text</div></td><td class="diff-marker" data-marker="+"></td><td class="diff-addedline diff-side-added"><div><ins class="diffchange diffchange-inline">To </ins>text <ins class="diffchange diffchange-inline">ApiComparePagesTest B</ins></div></td></tr>' . "\n",
763					]
764				],
765			],
766			'Basic diff, deprecated text with deleted revision and PST' => [
767				[
768					'fromtext' => 'From text',
769					'torev' => '{{REPL:revC2}}',
770					'totext' => 'To text {{subst:PAGENAME}}',
771					'topst' => true,
772				],
773				[
774					'warnings' => self::makeDeprecationWarnings( 'fromtext', 'totext' ),
775					'compare' => [
776						'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1">Line 1:</td>' . "\n"
777							. '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
778							. '<tr><td class="diff-marker" data-marker="−"></td><td class="diff-deletedline diff-side-deleted"><div><del class="diffchange diffchange-inline">From </del>text</div></td><td class="diff-marker" data-marker="+"></td><td class="diff-addedline diff-side-added"><div><ins class="diffchange diffchange-inline">To </ins>text <ins class="diffchange diffchange-inline">ApiComparePagesTest C</ins></div></td></tr>' . "\n",
779					]
780				],
781				false, true
782			],
783			'Basic diff, test with deprecated sections' => [
784				[
785					'fromtitle' => 'ApiComparePagesTest F',
786					'fromsection' => 1,
787					'totext' => "== Section 1 ==\nTo text\n\n== Section 2 ==\nTo text?",
788					'tosection' => 2,
789				],
790				[
791					'warnings' => self::makeDeprecationWarnings( 'fromsection', 'totext', 'tosection' ),
792					'compare' => [
793						'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1">Line 1:</td>' . "\n"
794							. '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
795							. '<tr><td class="diff-marker" data-marker="−"></td><td class="diff-deletedline diff-side-deleted"><div>== Section <del class="diffchange diffchange-inline">1 </del>==</div></td><td class="diff-marker" data-marker="+"></td><td class="diff-addedline diff-side-added"><div>== Section <ins class="diffchange diffchange-inline">2 </ins>==</div></td></tr>' . "\n"
796							. '<tr><td class="diff-marker" data-marker="−"></td><td class="diff-deletedline diff-side-deleted"><div><del class="diffchange diffchange-inline">F 1.1</del></div></td><td class="diff-marker" data-marker="+"></td><td class="diff-addedline diff-side-added"><div><ins class="diffchange diffchange-inline">To text?</ins></div></td></tr>' . "\n",
797						'fromid' => '{{REPL:pageF}}',
798						'fromrevid' => '{{REPL:revF1}}',
799						'fromns' => '0',
800						'fromtitle' => 'ApiComparePagesTest F',
801					]
802				],
803			],
804			'Basic diff, test with deprecated sections and revdel, non-sysop' => [
805				[
806					'fromrev' => '{{REPL:revB2}}',
807					'fromsection' => 0,
808					'torev' => '{{REPL:revB4}}',
809					'tosection' => 0,
810				],
811				[],
812				'missingcontent'
813			],
814			'Basic diff, test with deprecated sections and revdel, sysop' => [
815				[
816					'fromrev' => '{{REPL:revB2}}',
817					'fromsection' => 0,
818					'torev' => '{{REPL:revB4}}',
819					'tosection' => 0,
820				],
821				[
822					'warnings' => self::makeDeprecationWarnings( 'fromsection', 'tosection' ),
823					'compare' => [
824						'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1">Line 1:</td>' . "\n"
825							. '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
826							. '<tr><td class="diff-marker" data-marker="−"></td><td class="diff-deletedline diff-side-deleted"><div>B <del class="diffchange diffchange-inline">2</del></div></td><td class="diff-marker" data-marker="+"></td><td class="diff-addedline diff-side-added"><div>B <ins class="diffchange diffchange-inline">4</ins></div></td></tr>' . "\n",
827						'fromid' => '{{REPL:pageB}}',
828						'fromrevid' => '{{REPL:revB2}}',
829						'fromns' => 0,
830						'fromtitle' => 'ApiComparePagesTest B',
831						'fromtexthidden' => true,
832						'fromuserhidden' => true,
833						'fromcommenthidden' => true,
834						'toid' => '{{REPL:pageB}}',
835						'torevid' => '{{REPL:revB4}}',
836						'tons' => 0,
837						'totitle' => 'ApiComparePagesTest B',
838					]
839				],
840				false, true,
841			],
842
843			'Error, missing title' => [
844				[
845					'fromtitle' => 'ApiComparePagesTest X',
846					'totitle' => 'ApiComparePagesTest B',
847				],
848				[],
849				'missingtitle',
850			],
851			'Error, invalid title' => [
852				[
853					'fromtitle' => '<bad>',
854					'totitle' => 'ApiComparePagesTest B',
855				],
856				[],
857				'invalidtitle',
858			],
859			'Error, missing page ID' => [
860				[
861					'fromid' => 8817900,
862					'totitle' => 'ApiComparePagesTest B',
863				],
864				[],
865				'nosuchpageid',
866			],
867			'Error, page with missing revision' => [
868				[
869					'fromtitle' => 'ApiComparePagesTest D',
870					'totitle' => 'ApiComparePagesTest B',
871				],
872				[],
873				'nosuchrevid',
874			],
875			'Error, page with no revision' => [
876				[
877					'fromtitle' => 'ApiComparePagesTest E',
878					'totitle' => 'ApiComparePagesTest B',
879				],
880				[],
881				'nosuchrevid',
882			],
883			'Error, bad rev ID' => [
884				[
885					'fromrev' => 8817900,
886					'totitle' => 'ApiComparePagesTest B',
887				],
888				[],
889				'nosuchrevid',
890			],
891			'Error, deleted revision ID, non-sysop' => [
892				[
893					'fromrev' => '{{REPL:revA2}}',
894					'torev' => '{{REPL:revC2}}',
895				],
896				[],
897				'nosuchrevid',
898			],
899			'Error, deleted revision ID and torelative=prev' => [
900				[
901					'fromrev' => '{{REPL:revC2}}',
902					'torelative' => 'prev',
903				],
904				[],
905				'compare-relative-to-deleted', true
906			],
907			'Error, deleted revision ID and torelative=next' => [
908				[
909					'fromrev' => '{{REPL:revC2}}',
910					'torelative' => 'next',
911				],
912				[],
913				'compare-relative-to-deleted', true
914			],
915			'Deleted revision ID and torelative=cur' => [
916				[
917					'fromrev' => '{{REPL:revC2}}',
918					'torelative' => 'cur',
919				],
920				[],
921				'nosuchrevid', true
922			],
923			'Error, revision-deleted content' => [
924				[
925					'fromrev' => '{{REPL:revA2}}',
926					'torev' => '{{REPL:revB2}}',
927				],
928				[],
929				'missingcontent',
930			],
931			'Error, text with no title and PST' => [
932				[
933					'fromtext' => 'From text',
934					'totext' => 'To text {{subst:PAGENAME}}',
935					'topst' => true,
936				],
937				[],
938				'compare-no-title',
939			],
940			'Error, test with invalid from section ID' => [
941				[
942					'fromtitle' => 'ApiComparePagesTest F',
943					'fromsection' => 5,
944					'totext' => "== Section 1 ==\nTo text\n\n== Section 2 ==\nTo text?",
945					'tosection' => 2,
946				],
947				[],
948				'nosuchfromsection',
949			],
950			'Error, test with invalid to section ID' => [
951				[
952					'fromtitle' => 'ApiComparePagesTest F',
953					'fromsection' => 1,
954					'totext' => "== Section 1 ==\nTo text\n\n== Section 2 ==\nTo text?",
955					'tosection' => 5,
956				],
957				[],
958				'nosuchtosection',
959			],
960			'Error, Relative diff, no from revision' => [
961				[
962					'fromtext' => 'Foo',
963					'torelative' => 'cur',
964					'prop' => 'ids',
965				],
966				[],
967				'compare-relative-to-nothing'
968			],
969			'Error, Relative diff, cur with no current revision' => [
970				[
971					'fromrev' => '{{REPL:revE2}}',
972					'torelative' => 'cur',
973					'prop' => 'ids',
974				],
975				[],
976				'nosuchrevid'
977			],
978			'Error, Relative diff, next revdeleted' => [
979				[
980					'fromrev' => '{{REPL:revB1}}',
981					'torelative' => 'next',
982					'prop' => 'ids',
983				],
984				[],
985				'missingcontent'
986			],
987			'Error, Relative diff, prev revdeleted' => [
988				[
989					'fromrev' => '{{REPL:revB3}}',
990					'torelative' => 'prev',
991					'prop' => 'ids',
992				],
993				[],
994				'missingcontent'
995			],
996			'Error, section diff with no revision' => [
997				[
998					'fromtitle' => 'ApiComparePagesTest F',
999					'toslots' => 'main',
1000					'totext-main' => "== Section 1 ==\nTo text?",
1001					'tosection-main' => 1,
1002				],
1003				[],
1004				'compare-notorevision',
1005			],
1006			'Error, section diff with revdeleted revision' => [
1007				[
1008					'fromtitle' => 'ApiComparePagesTest F',
1009					'torev' => '{{REPL:revB2}}',
1010					'toslots' => 'main',
1011					'totext-main' => "== Section 1 ==\nTo text?",
1012					'tosection-main' => 1,
1013				],
1014				[],
1015				'missingcontent',
1016			],
1017			'Error, section diff with a content model not supporting sections' => [
1018				[
1019					'fromtitle' => 'ApiComparePagesTest G',
1020					'torev' => '{{REPL:revG1}}',
1021					'toslots' => 'main',
1022					'totext-main' => "== Section 1 ==\nTo text?",
1023					'tosection-main' => 1,
1024				],
1025				[],
1026				'sectionsnotsupported',
1027			],
1028			'Error, section diff with bad content model' => [
1029				[
1030					'fromtitle' => 'ApiComparePagesTest F',
1031					'torev' => '{{REPL:revF1}}',
1032					'toslots' => 'main',
1033					'totext-main' => "== Section 1 ==\nTo text?",
1034					'tosection-main' => 1,
1035					'tocontentmodel-main' => CONTENT_MODEL_TEXT,
1036				],
1037				[],
1038				'sectionreplacefailed',
1039			],
1040			'Error, deleting the main slot' => [
1041				[
1042					'fromtitle' => 'ApiComparePagesTest A',
1043					'totitle' => 'ApiComparePagesTest A',
1044					'toslots' => 'main',
1045				],
1046				[],
1047				'compare-maintextrequired',
1048			],
1049			// @todo Add a test for using 'tosection-foo' without 'totext-foo' (can't do it with main)
1050		];
1051		// phpcs:enable
1052	}
1053}
1054