* Added RecentChangesPurgeRows hook to allow extensions to purge data that
depends on the recentchanges table.
* Added JS config values wgDiffOldId/wgDiffNewId to the output of diff pages.
+* (T2424) Added direct unwatch links to entries in Special:Watchlist (if the
+ 'watchlistunwatchlinks' preference option is enabled). With JavaScript
+ enabled, these links toggle so the user can also re-watch pages that have
+ just been unwatched.
=== Languages updated in 1.30 ===
'watchlisthidepatrolled' => 0,
'watchlisthidecategorization' => 1,
'watchlistreloadautomatically' => 0,
+ 'watchlistunwatchlinks' => 0,
'watchmoves' => 0,
'watchrollback' => 0,
'wllimit' => 250,
'section' => 'watchlist/advancedwatchlist',
'label-message' => 'tog-watchlistreloadautomatically',
];
+ $defaultPreferences['watchlistunwatchlinks'] = [
+ 'type' => 'toggle',
+ 'section' => 'watchlist/advancedwatchlist',
+ 'label-message' => 'tog-watchlistunwatchlinks',
+ ];
if ( $config->get( 'RCWatchCategoryMembership' ) ) {
$defaultPreferences['watchlisthidecategorization'] = [
protected $rclistOpen;
protected $rcMoveIndex;
+ /** @var callable */
+ protected $changeLinePrefixer;
+
/** @var BagOStuff */
protected $watchMsgCache;
* @return array of classes
*/
protected function getHTMLClasses( $rc, $watched ) {
- $classes = [];
+ $classes = [ self::CSS_CLASS_PREFIX . 'line' ];
$logType = $rc->mAttribs['rc_log_type'];
if ( $logType ) {
+ $classes[] = self::CSS_CLASS_PREFIX . 'log';
$classes[] = Sanitizer::escapeClass( self::CSS_CLASS_PREFIX . 'log-' . $logType );
} else {
+ $classes[] = self::CSS_CLASS_PREFIX . 'edit';
$classes[] = Sanitizer::escapeClass( self::CSS_CLASS_PREFIX . 'ns' .
$rc->mAttribs['rc_namespace'] . '-' . $rc->mAttribs['rc_title'] );
$classes[] = Sanitizer::escapeClass( self::CSS_CLASS_PREFIX . 'ns-' .
return $attrs;
}
+
+ /**
+ * Sets the callable that generates a change line prefix added to the beginning of each line.
+ *
+ * @param callable $prefixer Callable to run that generates the change line prefix.
+ * Takes three parameters: a RecentChange object, a ChangesList object,
+ * and whether the current entry is a grouped entry.
+ */
+ public function setChangeLinePrefixer( callable $prefixer ) {
+ $this->changeLinePrefixer = $prefixer;
+ }
}
$recentChangesFlags = $this->getConfig()->get( 'RecentChangesFlags' );
# Add the namespace and title of the block as part of the class
- $tableClasses = [ 'mw-collapsible', 'mw-collapsed', 'mw-enhanced-rc' ];
+ $tableClasses = [ 'mw-collapsible', 'mw-collapsed', 'mw-enhanced-rc', 'mw-changeslist-line' ];
if ( $block[0]->mAttribs['rc_log_type'] ) {
# Log entry
+ $tableClasses[] = 'mw-changeslist-log';
$tableClasses[] = Sanitizer::escapeClass( 'mw-changeslist-log-'
. $block[0]->mAttribs['rc_log_type'] );
} else {
+ $tableClasses[] = 'mw-changeslist-edit';
$tableClasses[] = Sanitizer::escapeClass( 'mw-changeslist-ns'
. $block[0]->mAttribs['rc_namespace'] . '-' . $block[0]->mAttribs['rc_title'] );
}
implode( $this->message['semicolon-separator'], $users )
)->escaped();
+ $prefix = '';
+ if ( is_callable( $this->changeLinePrefixer ) ) {
+ $prefix = call_user_func( $this->changeLinePrefixer, $block[0], $this, true );
+ }
+
$templateParams = [
'articleLink' => $articleLink,
'charDifference' => $charDifference,
'lines' => $lines,
'logText' => $logText,
'numberofWatchingusers' => $numberofWatchingusers,
+ 'prefix' => $prefix,
'rev-deleted-event' => $revDeletedMsg,
'tableClasses' => $tableClasses,
'timestamp' => $block[0]->timestamp,
$type = $rcObj->mAttribs['rc_type'];
$data = [];
- $lineParams = [];
+ $lineParams = [ 'targetTitle' => $rcObj->getTitle() ];
$classes = [ 'mw-enhanced-rc' ];
if ( $rcObj->watched
if ( $logType ) {
# Log entry
+ $classes[] = 'mw-changeslist-log';
$classes[] = Sanitizer::escapeClass( 'mw-changeslist-log-' . $logType );
} else {
+ $classes[] = 'mw-changeslist-edit';
$classes[] = Sanitizer::escapeClass( 'mw-changeslist-ns' .
$rcObj->mAttribs['rc_namespace'] . '-' . $rcObj->mAttribs['rc_title'] );
}
return $key === 'class' || Sanitizer::isReservedDataAttribute( $key );
} );
+ $prefix = '';
+ if ( is_callable( $this->changeLinePrefixer ) ) {
+ $prefix = call_user_func( $this->changeLinePrefixer, $rcObj, $this, false );
+ }
+
$line = Html::openElement( 'table', $attribs ) . Html::openElement( 'tr' );
- $line .= '<td class="mw-enhanced-rc"><span class="mw-enhancedchanges-arrow-space"></span>';
+ $line .= Html::rawElement( 'td', [], '<span class="mw-enhancedchanges-arrow-space"></span>' );
+ $line .= Html::rawElement( 'td', [ 'class' => 'mw-changeslist-line-prefix' ], $prefix );
+ $line .= '<td class="mw-enhanced-rc">';
if ( isset( $data['recentChangesFlags'] ) ) {
$line .= $this->recentChangesFlags( $data['recentChangesFlags'] );
$line .= ' ' . $data['timestampLink'];
unset( $data['timestampLink'] );
}
- $line .= ' </td><td>';
+ $line .= ' </td>';
+ $line .= Html::openElement( 'td', [
+ 'class' => 'mw-changeslist-line-inner',
+ // Used for reliable determination of the affiliated page
+ 'data-target-page' => $rcObj->getTitle(),
+ ] );
// everything else: makes it easier for extensions to add or remove data
$line .= implode( '', $data );
$html .= ' ' . $this->numberofWatchingusers( $rc->numberofWatchingusers );
}
+ $html = Html::rawElement( 'span', [
+ 'class' => 'mw-changeslist-line-inner',
+ 'data-target-page' => $rc->getTitle(), // Used for reliable determination of the affiliated page
+ ], $html );
+ if ( is_callable( $this->changeLinePrefixer ) ) {
+ $prefix = call_user_func( $this->changeLinePrefixer, $rc, $this, false );
+ $html = Html::rawElement( 'span', [ 'class' => 'mw-changeslist-line-prefix' ], $prefix ) . $html;
+ }
+
return $html;
}
}
'mediawiki.special.changeslist.visitedstatus',
'mediawiki.special.watchlist',
] );
+ $output->addModuleStyles( [ 'mediawiki.special.watchlist.styles' ] );
$mode = SpecialEditWatchlist::getMode( $request, $subpage );
if ( $mode !== false ) {
$list = ChangesList::newFromContext( $this->getContext(), $this->filterGroups );
$list->setWatchlistDivs();
$list->initChangesListRows( $rows );
+ if ( $user->getOption( 'watchlistunwatchlinks' ) ) {
+ $list->setChangeLinePrefixer( function ( RecentChange $rc, ChangesList $cl, $grouped ) {
+ // Don't show unwatch link if the line is a grouped log entry using EnhancedChangesList,
+ // since EnhancedChangesList groups log entries by performer rather than by target article
+ if ( $rc->mAttribs['rc_type'] == RC_LOG && $cl instanceof EnhancedChangesList &&
+ $grouped ) {
+ return '';
+ } else {
+ return $this->getLinkRenderer()
+ ->makeKnownLink( $rc->getTitle(),
+ $this->msg( 'watchlist-unwatch' )->text(), [
+ 'class' => 'mw-unwatch-link',
+ 'title' => $this->msg( 'tooltip-ca-unwatch' )->text()
+ ], [ 'action' => 'unwatch' ] ) . ' ';
+ }
+ } );
+ }
$dbr->dataSeek( $rows, 0 );
if ( $this->getConfig()->get( 'RCShowWatchingUsers' )
<td>
<span class="mw-collapsible-toggle mw-collapsible-arrow mw-enhancedchanges-arrow mw-enhancedchanges-arrow-space"></span>
</td>
+ <td class="mw-changeslist-line-prefix">{{{ prefix }}}</td>
<td class="mw-enhanced-rc">{{{ collectedRcFlags }}} {{ timestamp }} </td>
- <td>
+ <td class="mw-changeslist-line-inner">
{{# rev-deleted-event }}<span class="history-deleted">{{{ . }}}</span>{{/ rev-deleted-event }}
{{{ articleLink }}}{{{ languageDirMark }}}{{{ logText }}}
<span class="mw-changeslist-separator">. .</span>
</tr>
{{# lines }}
<tr class="{{# classes }}{{ . }} {{/ classes }}"{{{ attribs }}}>
+ <td></td>
<td></td>
<td class="mw-enhanced-rc">{{{ recentChangesFlags }}} </td>
- <td class="mw-enhanced-rc-nested">
+ <td class="mw-enhanced-rc-nested" data-target-page="{{ targetTitle }}">
{{# timestampLink }}
<span class="mw-enhanced-rc-time">{{{ . }}}</span>
{{/ timestampLink }}
"tog-watchlisthideminor": "Hide minor edits from the watchlist",
"tog-watchlisthideliu": "Hide edits by logged in users from the watchlist",
"tog-watchlistreloadautomatically": "Reload the watchlist automatically whenever a filter is changed (JavaScript required)",
+ "tog-watchlistunwatchlinks": "Add direct unwatch/watch links to watchlist entries (JavaScript required for toggle functionality)",
"tog-watchlisthideanons": "Hide edits by anonymous users from the watchlist",
"tog-watchlisthidepatrolled": "Hide patrolled edits from the watchlist",
"tog-watchlisthidecategorization": "Hide categorization of pages",
"watching": "Watching...",
"unwatching": "Unwatching...",
"watcherrortext": "An error occurred while changing your watchlist settings for \"$1\".",
+ "watchlist-unwatch": "×",
+ "watchlist-unwatch-undo": "+",
"enotif_reset": "Mark all pages visited",
"enotif_impersonal_salutation": "{{SITENAME}} user",
"enotif_subject_deleted": "{{SITENAME}} page $1 has been {{GENDER:$2|deleted}} by $2",
"tog-watchlisthidebots": "[[Special:Preferences]], tab 'Watchlist'. Offers user to hide bot edits from watchlist. {{Gender}}\n\n{{Related|Preferences-watchlistrc-toggle}}",
"tog-watchlisthideminor": "[[Special:Preferences]], tab 'Watchlist'. Offers user to hide minor edits from watchlist. {{Gender}}\n\n{{Related|Preferences-watchlistrc-toggle}}",
"tog-watchlisthideliu": "Option in tab 'Watchlist' of [[Special:Preferences]]. {{Gender}}\n\n{{Related|Preferences-watchlistrc-toggle}}",
- "tog-watchlistreloadautomatically": "[[Special:Preferences]], tab 'Watchlist'. Offers user to to automatically refresh the watchlist page, when a filter is changed.",
+ "tog-watchlistreloadautomatically": "[[Special:Preferences]], tab 'Watchlist'. Offers user to automatically refresh the watchlist page, when a filter is changed.",
+ "tog-watchlistunwatchlinks": "[[Special:Preferences]], tab 'Watchlist'. Offers user to add an unwatch/watch toggle link to watchlist entries.",
"tog-watchlisthideanons": "Option in tab 'Watchlist' of [[Special:Preferences]]. {{Gender}}\n\n{{Related|Preferences-watchlistrc-toggle}}",
"tog-watchlisthidepatrolled": "Option in Watchlist tab of [[Special:Preferences]]. {{Gender}}\n\n{{Related|Preferences-watchlistrc-toggle}}",
"tog-watchlisthidecategorization": "Option in Watchlist tab of [[Special:Preferences]]. Offers user to hide/show categorization of pages. Appears next to checkboxes with labels such as {{msg-mw|tog-watchlisthideminor}}.",
"watching": "Text displayed when clicked on the watch tab: {{msg-mw|Watch}}. It means the wiki is adding that page to your watchlist.",
"unwatching": "Text displayed when clicked on the unwatch tab: {{msg-mw|Unwatch}}. It means the wiki is removing that page from your watchlist.",
"watcherrortext": "When a user clicked the watch/unwatch tab and the action did not succeed, this message is displayed.\n\nThis message is used raw and should not contain wikitext.\n\nParameters:\n* $1 - ...\nSee also:\n* {{msg-mw|Addedwatchtext}}",
+ "watchlist-unwatch": "Symbol used for the link to unwatch a page from the watchlist.",
+ "watchlist-unwatch-undo": "Symbol used for the link to re-watch a page that has been unwatched from the watchlist.",
"enotif_reset": "Used in [[Special:Watchlist]].\n\nThis should be translated as \"Mark all pages '''as''' visited\".\n\nSee also:\n* {{msg-mw|Watchlist-options|fieldset}}\n* {{msg-mw|Watchlist-details|watchlist header}}\n* {{msg-mw|Wlheader-enotif|watchlist header}}",
"enotif_impersonal_salutation": "Used for impersonal e-mail notifications, suitable for bulk mailing.\n{{Identical|User}}",
"enotif_subject_deleted": "Email notification subject for deleted pages. Parameters:\n* $1 - page title\n* $2 - username who has deleted the page, can be used for GENDER",
],
'mediawiki.special.watchlist' => [
'scripts' => 'resources/src/mediawiki.special/mediawiki.special.watchlist.js',
+ 'messages' => [
+ 'addedwatchtext',
+ 'addedwatchtext-talk',
+ 'removedwatchtext',
+ 'removedwatchtext-talk',
+ 'tooltip-ca-watch',
+ 'tooltip-ca-unwatch',
+ 'watchlist-unwatch',
+ 'watchlist-unwatch-undo',
+ ],
'dependencies' => [
- 'mediawiki.api',
+ 'mediawiki.api.watch',
+ 'mediawiki.jqueryMsg',
+ 'mediawiki.Title',
+ 'mediawiki.util',
'oojs-ui-core',
'user.options',
- ]
+ ],
+ ],
+ 'mediawiki.special.watchlist.styles' => [
+ 'styles' => 'resources/src/mediawiki.special/mediawiki.special.watchlist.css',
],
'mediawiki.special.version' => [
'styles' => 'resources/src/mediawiki.special/mediawiki.special.version.css',
--- /dev/null
+/*!
+ * Styling for elements generated by JavaScript on Special:Watchlist
+ */
+.mw-changelist-line-inner-unwatched {
+ text-decoration: line-through;
+ opacity: 0.5;
+}
+
+span.mw-changeslist-line-prefix {
+ display: inline-block;
+}
+/* This can be either a span or a table cell */
+.mw-changeslist-line-prefix {
+ width: 1.25em;
+}
*/
( function ( mw, $, OO ) {
$( function () {
- var $progressBar, $resetForm = $( '#mw-watchlist-resetbutton' );
+ var api = new mw.Api(), $progressBar, $resetForm = $( '#mw-watchlist-resetbutton' );
// If the user wants to reset their watchlist, use an API call to do so (no reload required)
// Adapted from a user script by User:NQ of English Wikipedia
if ( !$progressBar ) {
$progressBar = new OO.ui.ProgressBarWidget( { progress: false } ).$element;
$progressBar.css( {
- position: 'absolute',
- width: '100%'
+ position: 'absolute', width: '100%'
} );
}
// Show progress bar
// Use action=setnotificationtimestamp to mark all as visited,
// then set all watchlist lines accordingly
- new mw.Api().postWithToken( 'csrf', {
- formatversion: 2,
- action: 'setnotificationtimestamp',
- entirewatchlist: true
+ api.postWithToken( 'csrf', {
+ formatversion: 2, action: 'setnotificationtimestamp', entirewatchlist: true
} ).done( function () {
// Enable button again
$button.prop( 'disabled', false );
$( '#mw-watchlist-form' ).submit();
} );
}
+
+ if ( mw.user.options.get( 'watchlistunwatchlinks' ) ) {
+ // Watch/unwatch toggle link:
+ // If a page is on the watchlist, a '×' is shown which, when clicked, removes the page from the watchlist.
+ // After unwatching a page, the '×' becomes a '+', which if clicked re-watches the page.
+ // Unwatched page entries are struck through and have lowered opacity.
+ $( '.mw-unwatch-link, .mw-watch-link' ).click( function ( event ) {
+ var $unwatchLink = $( this ), // EnhancedChangesList uses <table> for each row, while OldChangesList uses <li> for each row
+ $watchlistLine = $unwatchLink.closest( 'li, table' )
+ .find( '[data-target-page]' ),
+ pageTitle = $watchlistLine.data( 'targetPage' ),
+ isTalk = mw.Title.newFromText( pageTitle ).getNamespaceId() % 2 === 1;
+
+ // Utility function for looping through each watchlist line that matches
+ // a certain page or its associated page (e.g. Talk)
+ function forEachMatchingTitle( title, callback ) {
+
+ var titleObj = mw.Title.newFromText( title ),
+ pageNamespaceId = titleObj.getNamespaceId(),
+ isTalk = pageNamespaceId % 2 === 1,
+ associatedTitle = mw.Title.makeTitle( isTalk ? pageNamespaceId - 1 : pageNamespaceId + 1,
+ titleObj.getMainText() ).getPrefixedText();
+ $( '.mw-changeslist-line' ).each( function () {
+ var $this = $( this ), $row, $unwatchLink;
+
+ $this.find( '[data-target-page]' ).each( function () {
+ var $this = $( this ), rowTitle = $this.data( 'targetPage' );
+ if ( rowTitle === title || rowTitle === associatedTitle ) {
+
+ // EnhancedChangesList groups log entries by performer rather than target page. Therefore...
+ // * If using OldChangesList, use the <li>
+ // * If using EnhancedChangesList and $this is part of a grouped log entry, use the <td> sub-entry
+ // * If using EnhancedChangesList and $this is not part of a grouped log entry, use the <table> grouped entry
+ $row =
+ $this.closest(
+ 'li, table.mw-collapsible.mw-changeslist-log td[data-target-page], table' );
+ $unwatchLink = $row.find( '.mw-unwatch-link, .mw-watch-link' );
+
+ callback( rowTitle, $row, $unwatchLink );
+ }
+ } );
+ } );
+ }
+
+ // Depending on whether we are watching or unwatching, for each entry of the page (and its associated page i.e. Talk),
+ // change the text, tooltip, and non-JS href of the (un)watch button, and update the styling of the watchlist entry.
+ if ( $unwatchLink.hasClass( 'mw-unwatch-link' ) ) {
+ api.unwatch( pageTitle )
+ .done( function () {
+ forEachMatchingTitle( pageTitle,
+ function ( rowPageTitle, $row, $rowUnwatchLink ) {
+ $rowUnwatchLink
+ .text( mw.msg( 'watchlist-unwatch-undo' ) )
+ .attr( 'title', mw.msg( 'tooltip-ca-watch' ) )
+ .attr( 'href',
+ mw.util.getUrl( rowPageTitle, { action: 'watch' } ) )
+ .removeClass( 'mw-unwatch-link loading' )
+ .addClass( 'mw-watch-link' );
+ $row.find(
+ '.mw-changeslist-line-inner, .mw-enhanced-rc-nested' )
+ .addBack( '.mw-enhanced-rc-nested' ) // For matching log sub-entry
+ .addClass( 'mw-changelist-line-inner-unwatched' );
+ } );
+
+ mw.notify(
+ mw.message( isTalk ? 'removedwatchtext-talk' : 'removedwatchtext',
+ pageTitle ), { tag: 'watch-self' } );
+ } );
+ } else {
+ api.watch( pageTitle )
+ .then( function () {
+ forEachMatchingTitle( pageTitle,
+ function ( rowPageTitle, $row, $rowUnwatchLink ) {
+ $rowUnwatchLink
+ .text( mw.msg( 'watchlist-unwatch' ) )
+ .attr( 'title', mw.msg( 'tooltip-ca-unwatch' ) )
+ .attr( 'href',
+ mw.util.getUrl( rowPageTitle, { action: 'unwatch' } ) )
+ .removeClass( 'mw-watch-link loading' )
+ .addClass( 'mw-unwatch-link' );
+ $row.find( '.mw-changelist-line-inner-unwatched' )
+ .addBack( '.mw-enhanced-rc-nested' )
+ .removeClass( 'mw-changelist-line-inner-unwatched' );
+ } );
+
+ mw.notify(
+ mw.message( isTalk ? 'addedwatchtext-talk' : 'addedwatchtext',
+ pageTitle ), { tag: 'watch-self' } );
+ } );
+ }
+
+ event.preventDefault();
+ event.stopPropagation();
+ $unwatchLink.blur();
+ } );
+ }
} );
-}( mediaWiki, jQuery, OO ) );
+}( mediaWiki, jQuery, OO )
+);
$this->assertEquals( '', $html );
}
+ public function testRecentChangesPrefix() {
+ $mockContext = $this->getMockBuilder( RequestContext::class )
+ ->setMethods( [ 'getTitle' ] )
+ ->getMock();
+ $mockContext->method( 'getTitle' )
+ ->will( $this->returnValue( Title::newFromText( 'Expected Context Title' ) ) );
+
+ // One group of two lines
+ $enhancedChangesList = $this->newEnhancedChangesList();
+ $enhancedChangesList->setContext( $mockContext );
+ $enhancedChangesList->setChangeLinePrefixer( function ( $rc, $changesList ) {
+ // Make sure RecentChange and ChangesList objects are the same
+ $this->assertEquals( 'Expected Context Title', $changesList->getContext()->getTitle() );
+ $this->assertTrue( $rc->getTitle() == 'Cat' || $rc->getTitle() == 'Dog' );
+ return 'Hello world prefix';
+ } );
+ $enhancedChangesList->beginRecentChangesList();
+
+ $recentChange = $this->getEditChange( '20131103092153' );
+ $enhancedChangesList->recentChangesLine( $recentChange );
+ $recentChange = $this->getEditChange( '20131103092154' );
+ $enhancedChangesList->recentChangesLine( $recentChange );
+
+ $html = $enhancedChangesList->endRecentChangesList();
+
+ $this->assertRegExp( '/Hello world prefix/', $html );
+
+ // Two separate lines
+ $enhancedChangesList->beginRecentChangesList();
+
+ $recentChange = $this->getEditChange( '20131103092153' );
+ $enhancedChangesList->recentChangesLine( $recentChange );
+ $recentChange = $this->getEditChange( '20131103092154', 'Dog' );
+ $enhancedChangesList->recentChangesLine( $recentChange );
+
+ $html = $enhancedChangesList->endRecentChangesList();
+
+ preg_match_all( '/Hello world prefix/', $html, $matches );
+ $this->assertCount( 2, $matches[0] );
+ }
+
public function testCategorizationLineFormatting() {
$html = $this->createCategorizationLine(
$this->getCategorizationChange( '20150629191735', 0, 0 )
preg_match_all( '/td class="mw-enhanced-rc-nested"/', $html, $matches );
$this->assertCount( 2, $matches[0] );
+ preg_match_all( '/data-target-page="Cat"/', $html, $matches );
+ $this->assertCount( 2, $matches[0] );
+
$recentChange3 = $this->getLogChange();
$enhancedChangesList->recentChangesLine( $recentChange3, false );
$html = $enhancedChangesList->endRecentChangesList();
$this->assertContains( 'data-mw-logaction="foo/bar"', $html );
$this->assertContains( 'data-mw-logid="25"', $html );
+ $this->assertContains( 'data-target-page="Title"', $html );
}
/**
/**
* @return RecentChange
*/
- private function getEditChange( $timestamp ) {
+ private function getEditChange( $timestamp, $pageTitle = 'Cat' ) {
$user = $this->getMutableTestUser()->getUser();
$recentChange = $this->testRecentChangesHelper->makeEditRecentChange(
- $user, 'Cat', 0, 5, 191, $timestamp, 0, 0
+ $user, $pageTitle, 0, 5, 191, $timestamp, 0, 0
);
return $recentChange;
$this->assertRegExp( "/watchlist-0-Cat/", $line );
}
+ public function testRecentChangesLine_dataAttribute() {
+ $oldChangesList = $this->getOldChangesList();
+ $oldChangesList->setWatchlistDivs( true );
+
+ $recentChange = $this->getEditChange();
+ $line = $oldChangesList->recentChangesLine( $recentChange, false, 1 );
+ $this->assertRegExp( '/data-target-page=\"Cat\"/', $line );
+
+ $recentChange = $this->getLogChange( 'delete', 'delete' );
+ $line = $oldChangesList->recentChangesLine( $recentChange, false, 1 );
+ $this->assertRegExp( '/data-target-page="Abc"/', $line );
+ }
+
+ public function testRecentChangesLine_prefix() {
+ $mockContext = $this->getMockBuilder( RequestContext::class )
+ ->setMethods( [ 'getTitle' ] )
+ ->getMock();
+ $mockContext->method( 'getTitle' )
+ ->will( $this->returnValue( Title::newFromText( 'Expected Context Title' ) ) );
+
+ $oldChangesList = $this->getOldChangesList();
+ $oldChangesList->setContext( $mockContext );
+ $recentChange = $this->getEditChange();
+
+ $oldChangesList->setChangeLinePrefixer( function ( $rc, $changesList ) {
+ // Make sure RecentChange and ChangesList objects are the same
+ $this->assertEquals( 'Expected Context Title', $changesList->getContext()->getTitle() );
+ $this->assertEquals( 'Cat', $rc->getTitle() );
+ return 'I am a prefix';
+ } );
+ $line = $oldChangesList->recentChangesLine( $recentChange );
+ $this->assertRegExp( "/I am a prefix/", $line );
+ }
+
private function getNewBotEditChange() {
$user = $this->getMutableTestUser()->getUser();
'watchlisthidepatrolled' => 0,
'watchlisthidecategorization' => 1,
'watchlistreloadautomatically' => 0,
+ 'watchlistunwatchlinks' => 0,
]
);
}