jenkins-bot has submitted this change and it was merged. ( 
https://gerrit.wikimedia.org/r/336963 )

Change subject: ChangesList: Expose basic properties of lines as data attributes
......................................................................


ChangesList: Expose basic properties of lines as data attributes

We have several types of change lists (old RC/watchlist/related
changes, enhanced RC/watchlist, history) with slightly different
HTML, each with their own idiosyncracies. JavaScript code trying
to identify lines by log ID / revision ID has to jump through all
kinds of hoops to work with that.

To simplify the lives of frontend / gadget maintainers and provide
something approaching an API for these pages, we now expose the basic
attributes of each change line (revision ID for edits, log type/action
and ID for log events) as data attributes.

The OldChangesListRecentChangesLine, EnhancedChangesListModifyLineData,
EnhancedChangesListModifyBlockLineData, PageHistoryLine,
ContributionsLineEnding and DeletedContributionsLineEnding hooks
are updated accordingly. New hooks (LogEventsListLineEnding and
NewPagesLineEnding) are added for the change list pages which did
not yet have them.

Change-Id: I6dd006d0b1b0fd35c0020f0f9eea9113eca30b35
---
M RELEASE-NOTES-1.30
M docs/hooks.txt
M includes/GlobalFunctions.php
M includes/Sanitizer.php
M includes/actions/HistoryAction.php
M includes/changes/ChangesList.php
M includes/changes/EnhancedChangesList.php
M includes/changes/OldChangesList.php
M includes/logging/LogEventsList.php
M includes/specials/SpecialNewpages.php
M includes/specials/pagers/ContribsPager.php
M includes/specials/pagers/DeletedContribsPager.php
M includes/templates/EnhancedChangesListGroup.mustache
A tests/phpunit/includes/GlobalFunctions/wfArrayFilterTest.php
M tests/phpunit/includes/SanitizerTest.php
M tests/phpunit/includes/changes/EnhancedChangesListTest.php
M tests/phpunit/includes/changes/OldChangesListTest.php
17 files changed, 262 insertions(+), 32 deletions(-)

Approvals:
  20after4: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/RELEASE-NOTES-1.30 b/RELEASE-NOTES-1.30
index 343c296..cd800da 100644
--- a/RELEASE-NOTES-1.30
+++ b/RELEASE-NOTES-1.30
@@ -99,6 +99,13 @@
   or wikilinks.
 * (T163966) Page moves are now counted as edits for the purposes of
   autopromotion, i.e., they increment the user_editcount field in the database.
+* Two new hooks, LogEventsListLineEnding and NewPagesLineEnding were added for
+  manipulating Special:Log and Special:NewPages lines.
+* The OldChangesListRecentChangesLine, EnhancedChangesListModifyLineData,
+  PageHistoryLineEnding, ContributionsLineEnding and 
DeletedContributionsLineEnding
+  hooks have an additional parameter, for manipulating HTML data attributes of
+  RC/history lines. EnhancedChangesListModifyBlockLineData can do that via the
+  $data['attribs'] subarray.
 
 == Compatibility ==
 MediaWiki 1.30 requires PHP 5.5.9 or later. There is experimental support for
diff --git a/docs/hooks.txt b/docs/hooks.txt
index 0e8b508..3d310c3 100644
--- a/docs/hooks.txt
+++ b/docs/hooks.txt
@@ -1155,6 +1155,9 @@
 &$ret: the HTML line
 $row: the DB row for this line
 &$classes: the classes to add to the surrounding <li>
+&$attribs: associative array of other HTML attributes for the <li> element.
+  Currently only data attributes reserved to MediaWiki are allowed
+  (see Sanitizer::isReservedDataAttribute).
 
 'ContributionsToolLinks': Change tool links above Special:Contributions
 $id: User identifier
@@ -1200,6 +1203,9 @@
 &$ret: the HTML line
 $row: the DB row for this line
 &$classes: the classes to add to the surrounding <li>
+&$attribs: associative array of other HTML attributes for the <li> element.
+  Currently only data attributes reserved to MediaWiki are allowed
+  (see Sanitizer::isReservedDataAttribute).
 
 'DifferenceEngineAfterLoadNewText': called in DifferenceEngine::loadNewText()
 after the new revision's content has been loaded into the class member variable
@@ -1512,6 +1518,9 @@
 $block: An array of RecentChange objects in that block
 $rc: The RecentChange object for this line
 &$classes: An array of classes to change
+&$attribs: associative array of other HTML attributes for the <tr> element.
+  Currently only data attributes reserved to MediaWiki are allowed
+  (see Sanitizer::isReservedDataAttribute).
 
 'EnhancedChangesListModifyBlockLineData': to alter data used to build
 a non-grouped recent change line in EnhancedChangesList.
@@ -1999,6 +2008,16 @@
 &$attribs: the attributes to be applied
 &$ret: the value to return if your hook returns false
 
+'LogEventsListLineEnding': Called before a Special:Log line is finished
+$page: the LogEventsList object
+&$ret: the HTML line
+$entry: the DatabaseLogEntry object for this row
+&$classes: the classes to add to the surrounding <li>
+&$attribs: associative array of other HTML attributes for the <li> element.
+  Currently only data attributes reserved to MediaWiki are allowed
+  (see Sanitizer::isReservedDataAttribute).
+
+
 'HtmlPageLinkRendererBegin':
 Used when generating internal and interwiki links in
 LinkRenderer, before processing starts.  Return false to skip default
@@ -2284,6 +2303,16 @@
 $old: the ?old= param value from the url
 $new: the ?new= param value from the url
 
+'NewPagesLineEnding': Called before a NewPages line is finished.
+$page: the SpecialNewPages object
+&$ret: the HTML line
+$row: the database row for this page (the recentchanges record and a few 
extras - see
+  NewPagesPager::getQueryInfo)
+&$classes: the classes to add to the surrounding <li>
+&$attribs: associative array of other HTML attributes for the <li> element.
+  Currently only data attributes reserved to MediaWiki are allowed
+  (see Sanitizer::isReservedDataAttribute).
+
 'NewRevisionFromEditComplete': Called when a revision was inserted due to an
 edit.
 $wikiPage: the WikiPage edited
@@ -2296,7 +2325,10 @@
 &$changeslist: The OldChangesList instance.
 &$s: HTML of the form "<li>...</li>" containing one RC entry.
 $rc: The RecentChange object.
-&$classes: array of css classes for the <li> element
+&$classes: array of css classes for the <li> element.
+&$attribs: associative array of other HTML attributes for the <li> element.
+  Currently only data attributes reserved to MediaWiki are allowed
+  (see Sanitizer::isReservedDataAttribute).
 
 'OpenSearchUrls': Called when constructing the OpenSearch description XML. 
Hooks
 can alter or append to the array of URLs for search & suggestion formats.
@@ -2404,6 +2436,9 @@
 &$row: the revision row for this line
 &$s: the string representing this parsed line
 &$classes: array containing the <li> element classes
+&$attribs: associative array of other HTML attributes for the <li> element.
+  Currently only data attributes reserved to MediaWiki are allowed
+  (see Sanitizer::isReservedDataAttribute).
 
 'PageHistoryPager::doBatchLookups': Called after the pager query was run, 
before
 any output is generated, to allow batch lookups for prefetching information
diff --git a/includes/GlobalFunctions.php b/includes/GlobalFunctions.php
index c6ccf31..2090d90 100644
--- a/includes/GlobalFunctions.php
+++ b/includes/GlobalFunctions.php
@@ -204,6 +204,38 @@
 }
 
 /**
+ * Like array_filter with ARRAY_FILTER_USE_BOTH, but works pre-5.6.
+ *
+ * @param array $arr
+ * @param callable $callback Will be called with the array value and key (in 
that order) and
+ *   should return a bool which will determine whether the array element is 
kept.
+ * @return array
+ */
+function wfArrayFilter( array $arr, callable $callback ) {
+       if ( defined( 'ARRAY_FILTER_USE_BOTH' ) ) {
+               return array_filter( $arr, $callback, ARRAY_FILTER_USE_BOTH );
+       }
+       $filteredKeys = array_filter( array_keys( $arr ), function ( $key ) use 
( $arr, $callback ) {
+               return call_user_func( $callback, $arr[$key], $key );
+       } );
+       return array_intersect_key( $arr, array_fill_keys( $filteredKeys, true 
) );
+}
+
+/**
+ * Like array_filter with ARRAY_FILTER_USE_KEY, but works pre-5.6.
+ *
+ * @param array $arr
+ * @param callable $callback Will be called with the array key and should 
return a bool which
+ *   will determine whether the array element is kept.
+ * @return array
+ */
+function wfArrayFilterByKey( array $arr, callable $callback ) {
+       return wfArrayFilter( $arr, function ( $val, $key ) use ( $callback ) {
+               return call_user_func( $callback, $key );
+       } );
+}
+
+/**
  * Appends to second array if $value differs from that in $default
  *
  * @param string|int $key
diff --git a/includes/Sanitizer.php b/includes/Sanitizer.php
index c4883ba..5aaa3ed 100644
--- a/includes/Sanitizer.php
+++ b/includes/Sanitizer.php
@@ -782,15 +782,12 @@
 
                        # Allow any attribute beginning with "data-"
                        # However:
-                       # * data-ooui is reserved for ooui
-                       # * data-mw and data-parsoid are reserved for parsoid
-                       # * data-mw-<name here> is reserved for extensions (or 
core) if
-                       #   they need to communicate some data to the client 
and want to be
-                       #   sure that it isn't coming from an untrusted user.
+                       # * Disallow data attributes used by MediaWiki code
                        # * Ensure that the attribute is not namespaced by 
banning
                        #   colons.
-                       if ( !preg_match( '/^data-(?!ooui|mw|parsoid)[^:]*$/i', 
$attribute )
+                       if ( !preg_match( '/^data-[^:]*$/i', $attribute )
                                && !isset( $whitelist[$attribute] )
+                               || self::isReservedDataAttribute( $attribute )
                        ) {
                                continue;
                        }
@@ -859,6 +856,24 @@
        }
 
        /**
+        * Given an attribute name, checks whether it is a reserved data 
attribute
+        * (such as data-mw-foo) which is unavailable to user-generated HTML so 
MediaWiki
+        * core and extension code can safely use it to communicate with 
frontend code.
+        * @param string $attr Attribute name.
+        * @return bool
+        */
+       public static function isReservedDataAttribute( $attr ) {
+               // data-ooui is reserved for ooui.
+               // data-mw and data-parsoid are reserved for parsoid.
+               // data-mw-<name here> is reserved for extensions (or core) if
+               // they need to communicate some data to the client and want to 
be
+               // sure that it isn't coming from an untrusted user.
+               // We ignore the possibility of namespaces since user-generated 
HTML
+               // can't use them anymore.
+               return (bool)preg_match( '/^data-(ooui|mw|parsoid)/i', $attr );
+       }
+
+       /**
         * Merge two sets of HTML attributes.  Conflicting items in the second 
set
         * will override those in the first, except for 'class' attributes which
         * will be combined (if they're both strings).
diff --git a/includes/actions/HistoryAction.php 
b/includes/actions/HistoryAction.php
index d1be7d4..7460340 100644
--- a/includes/actions/HistoryAction.php
+++ b/includes/actions/HistoryAction.php
@@ -780,9 +780,11 @@
                        $s .= ' <span class="mw-changeslist-separator">. 
.</span> ' . $s2;
                }
 
-               Hooks::run( 'PageHistoryLineEnding', [ $this, &$row, &$s, 
&$classes ] );
+               $attribs = [ 'data-mw-revid' => $rev->getId() ];
 
-               $attribs = [];
+               Hooks::run( 'PageHistoryLineEnding', [ $this, &$row, &$s, 
&$classes, &$attribs ] );
+               $attribs = wfArrayFilterByKey( $attribs, [ Sanitizer::class, 
'isReservedDataAttribute' ] );
+
                if ( $classes ) {
                        $attribs['class'] = implode( ' ', $classes );
                }
diff --git a/includes/changes/ChangesList.php b/includes/changes/ChangesList.php
index 92a3d3f..00d842f 100644
--- a/includes/changes/ChangesList.php
+++ b/includes/changes/ChangesList.php
@@ -739,4 +739,26 @@
                        && intval( $rcObj->getAttribute( 'rc_this_oldid' ) ) 
=== 0;
        }
 
+       /**
+        * Get recommended data attributes for a change line.
+        * @param RecentChange $rc
+        * @return string[] attribute name => value
+        */
+       protected function getDataAttributes( RecentChange $rc ) {
+               $type = $rc->getAttribute( 'rc_source' );
+               switch ( $type ) {
+                       case RecentChange::SRC_EDIT:
+                       case RecentChange::SRC_NEW:
+                               return [
+                                       'data-mw-revid' => 
$rc->mAttribs['rc_this_oldid'],
+                               ];
+                       case RecentChange::SRC_LOG:
+                               return [
+                                       'data-mw-logid' => 
$rc->mAttribs['rc_logid'],
+                                       'data-mw-logaction' => 
$rc->mAttribs['rc_log_type'] . '/' . $rc->mAttribs['rc_log_action'],
+                               ];
+                       default:
+                               return [];
+               }
+       }
 }
diff --git a/includes/changes/EnhancedChangesList.php 
b/includes/changes/EnhancedChangesList.php
index b34a33f..03f63f6 100644
--- a/includes/changes/EnhancedChangesList.php
+++ b/includes/changes/EnhancedChangesList.php
@@ -447,13 +447,16 @@
                # Tags
                $data['tags'] = $this->getTags( $rcObj, $classes );
 
+               $attribs = $this->getDataAttributes( $rcObj );
+
                // give the hook a chance to modify the data
                $success = Hooks::run( 'EnhancedChangesListModifyLineData',
-                       [ $this, &$data, $block, $rcObj, &$classes ] );
+                       [ $this, &$data, $block, $rcObj, &$classes, &$attribs ] 
);
                if ( !$success ) {
                        // skip entry if hook aborted it
                        return [];
                }
+               $attribs = wfArrayFilterByKey( $attribs, [ Sanitizer::class, 
'isReservedDataAttribute' ] );
 
                $lineParams['recentChangesFlagsRaw'] = [];
                if ( isset( $data['recentChangesFlags'] ) ) {
@@ -469,6 +472,7 @@
                }
 
                $lineParams['classes'] = array_values( $classes );
+               $lineParams['attribs'] = Html::expandAttributes( $attribs );
 
                // everything else: makes it easier for extensions to add or 
remove data
                $lineParams['data'] = array_values( $data );
@@ -671,6 +675,8 @@
                # Show how many people are watching this if enabled
                $data['watchingUsers'] = $this->numberofWatchingusers( 
$rcObj->numberofWatchingusers );
 
+               $data['attribs'] = array_merge( $this->getDataAttributes( 
$rcObj ), [ 'class' => $classes ] );
+
                // give the hook a chance to modify the data
                $success = Hooks::run( 'EnhancedChangesListModifyBlockLineData',
                        [ $this, &$data, $rcObj ] );
@@ -678,9 +684,11 @@
                        // skip entry if hook aborted it
                        return '';
                }
+               $attribs = $data['attribs'];
+               unset( $data['attribs'] );
+               $attribs = wfArrayFilterByKey( $attribs, [ Sanitizer::class, 
'isReservedDataAttribute' ] );
 
-               $line = Html::openElement( 'table', [ 'class' => $classes ] ) .
-                       Html::openElement( 'tr' );
+               $line = Html::openElement( 'table', $attribs ) . 
Html::openElement( 'tr' );
                $line .= '<td class="mw-enhanced-rc"><span 
class="mw-enhancedchanges-arrow-space"></span>';
 
                if ( isset( $data['recentChangesFlags'] ) ) {
diff --git a/includes/changes/OldChangesList.php 
b/includes/changes/OldChangesList.php
index a5d5191..2a53d66 100644
--- a/includes/changes/OldChangesList.php
+++ b/includes/changes/OldChangesList.php
@@ -50,16 +50,23 @@
                                $rc->mAttribs['rc_namespace'] . '-' . 
$rc->mAttribs['rc_title'] );
                }
 
+               $attribs = $this->getDataAttributes( $rc );
+
                // Avoid PHP 7.1 warning from passing $this by reference
                $list = $this;
-               if ( !Hooks::run( 'OldChangesListRecentChangesLine', [ &$list, 
&$html, $rc, &$classes ] ) ) {
+               if ( !Hooks::run( 'OldChangesListRecentChangesLine',
+                       [ &$list, &$html, $rc, &$classes, &$attribs ] )
+               ) {
                        return false;
                }
+               $attribs = wfArrayFilterByKey( $attribs, [ Sanitizer::class, 
'isReservedDataAttribute' ] );
 
                $dateheader = ''; // $html now contains only <li>...</li>, for 
hooks' convenience.
                $this->insertDateHeader( $dateheader, 
$rc->mAttribs['rc_timestamp'] );
 
-               return "$dateheader<li class=\"" . implode( ' ', $classes ) . 
"\">" . $html . "</li>\n";
+               $attribs['class'] = implode( ' ', $classes );
+
+               return $dateheader . Html::rawElement( 'li', $attribs,  $html ) 
. "\n";
        }
 
        /**
diff --git a/includes/logging/LogEventsList.php 
b/includes/logging/LogEventsList.php
index 317652a..c5501cb 100644
--- a/includes/logging/LogEventsList.php
+++ b/includes/logging/LogEventsList.php
@@ -390,9 +390,18 @@
                        [ 'mw-logline-' . $entry->getType() ],
                        $newClasses
                );
+               $attribs = [
+                       'data-mw-logid' => $entry->getId(),
+                       'data-mw-logaction' => $entry->getFullType(),
+               ];
+               $ret = "$del $time $action $comment $revert $tagDisplay";
 
-               return Html::rawElement( 'li', [ 'class' => $classes ],
-                       "$del $time $action $comment $revert $tagDisplay" ) . 
"\n";
+               // Let extensions add data
+               Hooks::run( 'LogEventsListLineEnding', [ $this, &$ret, $entry, 
&$classes, &$attribs ] );
+               $attribs = wfArrayFilterByKey( $attribs, [ Sanitizer::class, 
'isReservedDataAttribute' ] );
+               $attribs['class'] = implode( ' ', $classes );
+
+               return Html::rawElement( 'li', $attribs, $ret ) . "\n";
        }
 
        /**
diff --git a/includes/specials/SpecialNewpages.php 
b/includes/specials/SpecialNewpages.php
index be8ad8f..83482f6 100644
--- a/includes/specials/SpecialNewpages.php
+++ b/includes/specials/SpecialNewpages.php
@@ -314,6 +314,7 @@
                $rev->setTitle( $title );
 
                $classes = [];
+               $attribs = [ 'data-mw-revid' => $result->rev_id ];
 
                $lang = $this->getLanguage();
                $dm = $lang->getDirMark();
@@ -378,11 +379,19 @@
                        $tagDisplay = '';
                }
 
-               $css = count( $classes ) ? ' class="' . implode( ' ', $classes 
) . '"' : '';
-
                # Display the old title if the namespace/title has been changed
                $oldTitleText = '';
                $oldTitle = Title::makeTitle( $result->rc_namespace, 
$result->rc_title );
+               $ret = "{$time} {$dm}{$plink} {$hist} {$dm}{$length} 
{$dm}{$ulink} {$comment} "
+                       . "{$tagDisplay} {$oldTitleText}";
+
+               // Let extensions add data
+               Hooks::run( 'NewPagesLineEnding', [ $this, &$ret, $result, 
&$classes, &$attribs ] );
+               $attribs = wfArrayFilterByKey( $attribs, [ Sanitizer::class, 
'isReservedDataAttribute' ] );
+
+               if ( count( $classes ) ) {
+                       $attribs['class'] = implode( ' ', $classes );
+               }
 
                if ( !$title->equals( $oldTitle ) ) {
                        $oldTitleText = $oldTitle->getPrefixedText();
@@ -393,8 +402,7 @@
                        );
                }
 
-               return "<li{$css}>{$time} {$dm}{$plink} {$hist} {$dm}{$length} "
-                       . "{$dm}{$ulink} {$comment} {$tagDisplay} 
{$oldTitleText}</li>\n";
+               return Html::rawElement( 'li', $attribs, $ret ) . "\n";
        }
 
        /**
diff --git a/includes/specials/pagers/ContribsPager.php 
b/includes/specials/pagers/ContribsPager.php
index a3880ee..6bd7eb0 100644
--- a/includes/specials/pagers/ContribsPager.php
+++ b/includes/specials/pagers/ContribsPager.php
@@ -365,9 +365,9 @@
         * @return string
         */
        function formatRow( $row ) {
-
                $ret = '';
                $classes = [];
+               $attribs = [];
 
                $linkRenderer = 
MediaWikiServices::getInstance()->getLinkRenderer();
 
@@ -388,7 +388,7 @@
                MediaWiki\restoreWarnings();
 
                if ( $validRevision ) {
-                       $classes = [];
+                       $attribs['data-mw-revid'] = $rev->getId();
 
                        $page = Title::newFromRow( $row );
                        $link = $linkRenderer->makeLink(
@@ -535,19 +535,21 @@
                }
 
                // Let extensions add data
-               Hooks::run( 'ContributionsLineEnding', [ $this, &$ret, $row, 
&$classes ] );
+               Hooks::run( 'ContributionsLineEnding', [ $this, &$ret, $row, 
&$classes, &$attribs ] );
+               $attribs = wfArrayFilterByKey( $attribs, [ Sanitizer::class, 
'isReservedDataAttribute' ] );
 
                // TODO: Handle exceptions in the catch block above.  Do any 
extensions rely on
                // receiving empty rows?
 
-               if ( $classes === [] && $ret === '' ) {
+               if ( $classes === [] && $attribs === [] && $ret === '' ) {
                        wfDebug( "Dropping Special:Contribution row that could 
not be formatted\n" );
                        return "<!-- Could not format Special:Contribution row. 
-->\n";
                }
+               $attribs['class'] = $classes;
 
                // FIXME: The signature of the ContributionsLineEnding hook 
makes it
                // very awkward to move this LI wrapper into the template.
-               return Html::rawElement( 'li', [ 'class' => $classes ], $ret ) 
. "\n";
+               return Html::rawElement( 'li', $attribs, $ret ) . "\n";
        }
 
        /**
diff --git a/includes/specials/pagers/DeletedContribsPager.php 
b/includes/specials/pagers/DeletedContribsPager.php
index 78e1092..43d7ad4 100644
--- a/includes/specials/pagers/DeletedContribsPager.php
+++ b/includes/specials/pagers/DeletedContribsPager.php
@@ -195,6 +195,7 @@
        function formatRow( $row ) {
                $ret = '';
                $classes = [];
+               $attribs = [];
 
                /*
                 * There may be more than just revision rows. To make sure that 
we'll only be processing
@@ -213,17 +214,20 @@
                MediaWiki\restoreWarnings();
 
                if ( $validRevision ) {
+                       $attribs['data-mw-revid'] = $rev->getId();
                        $ret = $this->formatRevisionRow( $row );
                }
 
                // Let extensions add data
-               Hooks::run( 'DeletedContributionsLineEnding', [ $this, &$ret, 
$row, &$classes ] );
+               Hooks::run( 'DeletedContributionsLineEnding', [ $this, &$ret, 
$row, &$classes, &$attribs ] );
+               $attribs = wfArrayFilterByKey( $attribs, [ Sanitizer::class, 
'isReservedDataAttribute' ] );
 
-               if ( $classes === [] && $ret === '' ) {
+               if ( $classes === [] && $attribs === [] && $ret === '' ) {
                        wfDebug( "Dropping Special:DeletedContribution row that 
could not be formatted\n" );
                        $ret = "<!-- Could not format 
Special:DeletedContribution row. -->\n";
                } else {
-                       $ret = Html::rawElement( 'li', [ 'class' => $classes ], 
$ret ) . "\n";
+                       $attribs['class'] = $classes;
+                       $ret = Html::rawElement( 'li', $attribs, $ret ) . "\n";
                }
 
                return $ret;
diff --git a/includes/templates/EnhancedChangesListGroup.mustache 
b/includes/templates/EnhancedChangesListGroup.mustache
index 352eb17..3a37c2e 100644
--- a/includes/templates/EnhancedChangesListGroup.mustache
+++ b/includes/templates/EnhancedChangesListGroup.mustache
@@ -14,7 +14,7 @@
                </td>
        </tr>
        {{# lines }}
-       <tr class="{{# classes }}{{ . }} {{/ classes }}">
+       <tr class="{{# classes }}{{ . }} {{/ classes }}"{{{ attribs }}}>
                <td></td>
                <td class="mw-enhanced-rc">{{{ recentChangesFlags }}}&#160;</td>
                <td class="mw-enhanced-rc-nested">
diff --git a/tests/phpunit/includes/GlobalFunctions/wfArrayFilterTest.php 
b/tests/phpunit/includes/GlobalFunctions/wfArrayFilterTest.php
new file mode 100644
index 0000000..afd80ff
--- /dev/null
+++ b/tests/phpunit/includes/GlobalFunctions/wfArrayFilterTest.php
@@ -0,0 +1,37 @@
+<?php
+
+class WfArrayFilterTest extends \PHPUnit_Framework_TestCase {
+       public function testWfArrayFilter() {
+               $arr = [ 'a' => 1, 'b' => 2, 'c' => 3 ];
+               $filtered = wfArrayFilter( $arr, function( $val, $key ) {
+                       return $key !== 'b';
+               } );
+               $this->assertSame( [ 'a' => 1, 'c' => 3 ], $filtered );
+
+               $arr = [ 'a' => 1, 'b' => 2, 'c' => 3 ];
+               $filtered = wfArrayFilter( $arr, function( $val, $key ) {
+                       return $val !== 2;
+               } );
+               $this->assertSame( [ 'a' => 1, 'c' => 3 ], $filtered );
+
+               $arr = [ 'a', 'b', 'c' ];
+               $filtered = wfArrayFilter( $arr, function( $val, $key ) {
+                       return $key !== 0;
+               } );
+               $this->assertSame( [ 1 => 'b',  2 => 'c' ], $filtered );
+       }
+
+       public function testWfArrayFilterByKey() {
+               $arr = [ 'a' => 1, 'b' => 2, 'c' => 3 ];
+               $filtered = wfArrayFilterByKey( $arr, function( $key ) {
+                       return $key !== 'b';
+               } );
+               $this->assertSame( [ 'a' => 1, 'c' => 3 ], $filtered );
+
+               $arr = [ 'a', 'b', 'c' ];
+               $filtered = wfArrayFilterByKey( $arr, function( $key ) {
+                       return $key !== 0;
+               } );
+               $this->assertSame( [ 1 => 'b',  2 => 'c' ], $filtered );
+       }
+}
diff --git a/tests/phpunit/includes/SanitizerTest.php 
b/tests/phpunit/includes/SanitizerTest.php
index 862b7d0..c237c50 100644
--- a/tests/phpunit/includes/SanitizerTest.php
+++ b/tests/phpunit/includes/SanitizerTest.php
@@ -362,4 +362,25 @@
                        [ '+1 +2', '+1', '+2' ],
                ];
        }
+
+       /**
+        * @dataProvider provideIsReservedDataAttribute
+        */
+       public function testIsReservedDataAttribute( $attr, $expected ) {
+               $this->assertSame( $expected, 
Sanitizer::isReservedDataAttribute( $attr ) );
+       }
+
+       public static function provideIsReservedDataAttribute() {
+               return [
+                       [ 'foo', false ],
+                       [ 'data', false ],
+                       [ 'data-foo', false ],
+                       [ 'data-mw', true ],
+                       [ 'data-ooui', true ],
+                       [ 'data-parsoid', true ],
+                       [ 'data-mw-foo', true ],
+                       [ 'data-ooui-foo', true ],
+                       [ 'data-mwfoo', true ], // could be false but this is 
how it's implemented currently
+               ];
+       }
 }
diff --git a/tests/phpunit/includes/changes/EnhancedChangesListTest.php 
b/tests/phpunit/includes/changes/EnhancedChangesListTest.php
index 308e6de..029d1fe 100644
--- a/tests/phpunit/includes/changes/EnhancedChangesListTest.php
+++ b/tests/phpunit/includes/changes/EnhancedChangesListTest.php
@@ -98,6 +98,9 @@
                $recentChange = $this->getEditChange( '20131103092153' );
                $enhancedChangesList->recentChangesLine( $recentChange, false );
 
+               $html = $enhancedChangesList->endRecentChangesList();
+               $this->assertContains( 'data-mw-revid="5"', $html );
+
                $recentChange2 = $this->getEditChange( '20131103092253' );
                $enhancedChangesList->recentChangesLine( $recentChange2, false 
);
 
@@ -105,6 +108,13 @@
 
                preg_match_all( '/td class="mw-enhanced-rc-nested"/', $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 );
        }
 
        /**
@@ -129,6 +139,15 @@
                return $recentChange;
        }
 
+       private function getLogChange() {
+               $user = $this->getMutableTestUser()->getUser();
+               $recentChange = 
$this->testRecentChangesHelper->makeLogRecentChange( 'foo', 'bar', $user,
+                       'Title', '20131103092153', 0, 0
+               );
+
+               return $recentChange;
+       }
+
        /**
         * @return RecentChange
         */
diff --git a/tests/phpunit/includes/changes/OldChangesListTest.php 
b/tests/phpunit/includes/changes/OldChangesListTest.php
index 51cfadc..f892eb7 100644
--- a/tests/phpunit/includes/changes/OldChangesListTest.php
+++ b/tests/phpunit/includes/changes/OldChangesListTest.php
@@ -119,15 +119,17 @@
                );
        }
 
-       public function testRecentChangesLine_Tags() {
+       public function testRecentChangesLine_Attribs() {
                $recentChange = $this->getEditChange();
                $recentChange->mAttribs['ts_tags'] = 'vandalism,newbie';
 
                $oldChangesList = $this->getOldChangesList();
                $line = $oldChangesList->recentChangesLine( $recentChange, 
false, 1 );
 
-               $this->assertRegExp( '/<li 
class="[\w\s-]*mw-tag-vandalism[\w\s-]*">/', $line );
-               $this->assertRegExp( '/<li 
class="[\w\s-]*mw-tag-newbie[\w\s-]*">/', $line );
+               $this->assertRegExp( '/<li data-mw-revid="\d+" 
class="[\w\s-]*mw-tag-vandalism[\w\s-]*">/',
+                       $line );
+               $this->assertRegExp( '/<li data-mw-revid="\d+" 
class="[\w\s-]*mw-tag-newbie[\w\s-]*">/',
+                       $line );
        }
 
        public function testRecentChangesLine_numberOfWatchingUsers() {

-- 
To view, visit https://gerrit.wikimedia.org/r/336963
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I6dd006d0b1b0fd35c0020f0f9eea9113eca30b35
Gerrit-PatchSet: 15
Gerrit-Project: mediawiki/core
Gerrit-Branch: master
Gerrit-Owner: Gergő Tisza <gti...@wikimedia.org>
Gerrit-Reviewer: 20after4 <mmod...@wikimedia.org>
Gerrit-Reviewer: Addshore <addshorew...@gmail.com>
Gerrit-Reviewer: Anomie <bjor...@wikimedia.org>
Gerrit-Reviewer: Brion VIBBER <br...@wikimedia.org>
Gerrit-Reviewer: Catrope <r...@wikimedia.org>
Gerrit-Reviewer: Daniel Kinzler <daniel.kinz...@wikimedia.de>
Gerrit-Reviewer: Gergő Tisza <gti...@wikimedia.org>
Gerrit-Reviewer: Jforrester <jforres...@wikimedia.org>
Gerrit-Reviewer: Ladsgroup <ladsgr...@gmail.com>
Gerrit-Reviewer: Legoktm <lego...@member.fsf.org>
Gerrit-Reviewer: Mattflaschen <mflasc...@wikimedia.org>
Gerrit-Reviewer: Mooeypoo <mor...@gmail.com>
Gerrit-Reviewer: Sbisson <sbis...@wikimedia.org>
Gerrit-Reviewer: Thiemo Mättig (WMDE) <thiemo.maet...@wikimedia.de>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to