jenkins-bot has submitted this change and it was merged.

Change subject: Add imageinfo, thumbnail info, repoinfo provider
......................................................................


Add imageinfo, thumbnail info, repoinfo provider

Change-Id: I80ffec39ee6c9e0ea0b37be2fc48315063b5ff8a
Mingle: https://wikimedia.mingle.thoughtworks.com/projects/multimedia/cards/123
---
M MultimediaViewer.php
M MultimediaViewerHooks.php
M resources/mmv/model/mmv.model.Image.js
M resources/mmv/provider/mmv.provider.Api.js
A resources/mmv/provider/mmv.provider.FileRepoInfo.js
M resources/mmv/provider/mmv.provider.GlobalUsage.js
A resources/mmv/provider/mmv.provider.ImageInfo.js
M resources/mmv/provider/mmv.provider.ImageUsage.js
A resources/mmv/provider/mmv.provider.ThumbnailInfo.js
A tests/qunit/provider/mmv.provider.Api.test.js
A tests/qunit/provider/mmv.provider.FileRepoInfo.test.js
A tests/qunit/provider/mmv.provider.ImageInfo.test.js
A tests/qunit/provider/mmv.provider.ThumbnailInfo.test.js
13 files changed, 963 insertions(+), 42 deletions(-)

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



diff --git a/MultimediaViewer.php b/MultimediaViewer.php
index ba55b3d..131b5a1 100644
--- a/MultimediaViewer.php
+++ b/MultimediaViewer.php
@@ -147,6 +147,9 @@
                        'mmv.provider.Api.js',
                        'mmv.provider.ImageUsage.js',
                        'mmv.provider.GlobalUsage.js',
+                       'mmv.provider.ImageInfo.js',
+                       'mmv.provider.FileRepoInfo.js',
+                       'mmv.provider.ThumbnailInfo.js',
                ),
 
                'dependencies' => array(
diff --git a/MultimediaViewerHooks.php b/MultimediaViewerHooks.php
index b66e33e..5c4cd9e 100644
--- a/MultimediaViewerHooks.php
+++ b/MultimediaViewerHooks.php
@@ -122,8 +122,12 @@
                                'tests/qunit/mmv.testhelpers.js',
                                'tests/qunit/mmv.test.js',
                                'tests/qunit/mmv.model.test.js',
+                               'tests/qunit/provider/mmv.provider.Api.test.js',
                                
'tests/qunit/provider/mmv.provider.ImageUsage.test.js',
                                
'tests/qunit/provider/mmv.provider.GlobalUsage.test.js',
+                               
'tests/qunit/provider/mmv.provider.ImageInfo.test.js',
+                               
'tests/qunit/provider/mmv.provider.FileRepoInfo.test.js',
+                               
'tests/qunit/provider/mmv.provider.ThumbnailInfo.test.js',
                                'tests/qunit/mmv.lightboxinterface.test.js',
                                'tests/qunit/mmv.ui.description.test.js',
                                'tests/qunit/mmv.ui.fileUsage.test.js',
diff --git a/resources/mmv/model/mmv.model.Image.js 
b/resources/mmv/model/mmv.model.Image.js
index 88a6d6b..65e6e09 100644
--- a/resources/mmv/model/mmv.model.Image.js
+++ b/resources/mmv/model/mmv.model.Image.js
@@ -29,8 +29,8 @@
         * @param {string} descriptionUrl URL to the image description page
         * @param {string} repo The repository this image belongs to
         * @param {string} lastUploader The last person to upload a version of 
this image.
-        * @param {string} lastUploadDateTime The time and date the last upload 
occurred
-        * @param {string} originalUploadDateTime The time and date the 
original upload occurred
+        * @param {string} uploadDateTime The time and date the last upload 
occurred
+        * @param {string} creationDateTime The time and date the original 
upload occurred
         * @param {string} description
         * @param {string} source
         * @param {string} author
diff --git a/resources/mmv/provider/mmv.provider.Api.js 
b/resources/mmv/provider/mmv.provider.Api.js
index 9f0247e..cd091e7 100644
--- a/resources/mmv/provider/mmv.provider.Api.js
+++ b/resources/mmv/provider/mmv.provider.Api.js
@@ -15,8 +15,7 @@
  * along with MultimediaViewer.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-( function ( mw ) {
-
+( function ( mw, $ ) {
        /**
         * @class mw.mmv.provider.Api
         * Base class for API-based data providers.
@@ -62,6 +61,82 @@
                return errorMessage;
        };
 
+       /**
+        * @method
+        * Returns a fixed a title based on the API response.
+        * The title of the returned file might be different from the requested 
title, e.g.
+        * if we used a namespace alias. If that happens the old and new title 
will be set in
+        * data.query.normalized; this method creates an updated title based on 
that.
+        * @param {mw.Title} title
+        * @param {Object} data
+        * @return {mw.Title}
+        */
+       Api.prototype.getNormalizedTitle = function( title, data ) {
+               if ( data && data.query && data.query.normalized ) {
+                       for ( var normalized = data.query.normalized, length = 
normalized.length, i = 0; i < length; i++ ) {
+                               if ( normalized[i].from === 
title.getPrefixedText() ) {
+                                       title = new mw.Title( normalized[i].to 
);
+                                       break;
+                               }
+                       }
+               }
+               return title;
+       };
+
+       /**
+        * @method
+        * Returns a promise with the specified field from the API result.
+        * This is intended to be used as a .then() callback for action=query 
APIs.
+        * @param {string} field
+        * @param {Object} data
+        * @return {jQuery.Promise} when successful, the first argument will be 
the field data,
+        *     when unsuccessful, it will be an error message. The second 
argument is always
+        *     the full API response.
+        */
+       Api.prototype.getQueryField = function( field, data ) {
+               if ( data && data.query && data.query[field] ) {
+                       return $.Deferred().resolve( data.query[field], data );
+               } else {
+                       return $.Deferred().reject( this.getErrorMessage( data 
), data );
+               }
+       };
+
+       /**
+        * @method
+        * Returns a promise with the specified page from the API result.
+        * This is intended to be used as a .then() callback for 
action=query&prop=(...) APIs.
+        * @param {mw.Title} title
+        * @param {Object} data
+        * @return {jQuery.Promise} when successful, the first argument will be 
the page data,
+        *     when unsuccessful, it will be an error message. The second 
argument is always
+        *     the full API response.
+        */
+       Api.prototype.getQueryPage = function( title, data ) {
+               var pageName, pageData = null;
+               if ( data && data.query && data.query.pages ) {
+                       title = this.getNormalizedTitle( title, data );
+                       pageName = title.getPrefixedText();
+
+                       // pages is an associative array indexed by pageid,
+                       // we need to iterate through to find the right page
+                       $.each( data.query.pages, function ( id, page ) {
+                               if ( page.title === pageName ) {
+                                       pageData = page;
+                                       return false;
+                               }
+                       } );
+
+                       if ( pageData ) {
+                               return $.Deferred().resolve( pageData, data );
+                       }
+               }
+
+               // If we got to this point either the pages array is missing 
completely, or we iterated
+               // through it and the requested page was not found. Neither is 
supposed to happen
+               // (if the page simply did not exist, there would still be a 
record for it).
+               return $.Deferred().reject( this.getErrorMessage( data ), data 
);
+       };
+
        mw.mmv.provider = {};
        mw.mmv.provider.Api = Api;
-}( mediaWiki ) );
+}( mediaWiki, jQuery ) );
diff --git a/resources/mmv/provider/mmv.provider.FileRepoInfo.js 
b/resources/mmv/provider/mmv.provider.FileRepoInfo.js
new file mode 100644
index 0000000..799750f
--- /dev/null
+++ b/resources/mmv/provider/mmv.provider.FileRepoInfo.js
@@ -0,0 +1,60 @@
+/*
+ * This file is part of the MediaWiki extension MultimediaViewer.
+ *
+ * MultimediaViewer is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MultimediaViewer is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MultimediaViewer.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+( function ( mw, oo, $ ) {
+
+       /**
+        * @class mw.mmv.provider.FileRepoInfo
+        * Gets file repo information.
+        * @extends mw.mmv.provider.Api
+        * @inheritdoc
+        * @param {mw.Api} api
+        */
+       function FileRepoInfo( api ) {
+               mw.mmv.provider.Api.call( this, api );
+       }
+       oo.inheritClass( FileRepoInfo, mw.mmv.provider.Api );
+
+       /**
+        * @method
+        * Runs an API GET request to get the repo info.
+        * @return {jQuery.Promise} a promise which resolves to an array of 
mw.mmv.model.Repo objects.
+        */
+       FileRepoInfo.prototype.get = function() {
+               var provider = this;
+
+               if ( !this.cache['*'] ) {
+                       this.cache['*'] = this.api.get( {
+                               action: 'query',
+                               meta: 'filerepoinfo',
+                               format: 'json'
+                       } ).then( function( data ) {
+                               return provider.getQueryField( 'repos', data );
+                       } ).then( function( reposArray ) {
+                               var reposHash = {};
+                               $.each( reposArray, function ( i, repo ) {
+                                       reposHash[repo.name] = 
mw.mmv.model.Repo.newFromRepoInfo( repo );
+                               } );
+                               return reposHash;
+                       } );
+               }
+
+               return this.cache['*'];
+       };
+
+       mw.mmv.provider.FileRepoInfo = FileRepoInfo;
+}( mediaWiki, OO, jQuery ) );
diff --git a/resources/mmv/provider/mmv.provider.GlobalUsage.js 
b/resources/mmv/provider/mmv.provider.GlobalUsage.js
index 9a1f4b3..b7b852c 100644
--- a/resources/mmv/provider/mmv.provider.GlobalUsage.js
+++ b/resources/mmv/provider/mmv.provider.GlobalUsage.js
@@ -74,27 +74,22 @@
                                gulimit: this.options.apiLimit,
                                format: 'json'
                        } ).then( function( data ) {
+                               return provider.getQueryPage( file, data );
+                       } ).then( function( pageData, data ) {
                                var pages;
-                               if ( data && data.query && data.query.pages ) {
-                                       // pages is an associative array 
indexed by pageid, turn it into proper array
-                                       pages = $.map( data.query.pages, 
function ( v ) { return v; } );
-                                       // the API returns a result for 
non-existent files as well so pages[0] will always exist
-                                       pages = $.map( pages[0].globalusage || 
{}, function( item ) {
-                                               return {
-                                                       wiki: item.wiki,
-                                                       page: new mw.Title( 
item.title, item.ns )
-                                               };
-                                       } );
-                                       return new mw.mmv.model.FileUsage(
-                                               file,
-                                               
mw.mmv.model.FileUsage.Scope.GLOBAL,
-                                               pages.slice( 0, 
provider.options.dataLimit ),
-                                               pages.length,
-                                               !!( data['query-continue'] && 
data['query-continue'].globalusage )
-                                       );
-                               } else {
-                                       return $.Deferred().reject( 
provider.getErrorMessage( data ) );
-                               }
+                               pages = $.map( pageData.globalusage || {}, 
function( item ) {
+                                       return {
+                                               wiki: item.wiki,
+                                               page: new mw.Title( item.title, 
item.ns )
+                                       };
+                               } );
+                               return new mw.mmv.model.FileUsage(
+                                       file,
+                                       mw.mmv.model.FileUsage.Scope.GLOBAL,
+                                       pages.slice( 0, 
provider.options.dataLimit ),
+                                       pages.length,
+                                       !!( data['query-continue'] && 
data['query-continue'].globalusage )
+                               );
                        } );
                }
 
diff --git a/resources/mmv/provider/mmv.provider.ImageInfo.js 
b/resources/mmv/provider/mmv.provider.ImageInfo.js
new file mode 100644
index 0000000..b2c8c49
--- /dev/null
+++ b/resources/mmv/provider/mmv.provider.ImageInfo.js
@@ -0,0 +1,97 @@
+/*
+ * This file is part of the MediaWiki extension MultimediaViewer.
+ *
+ * MultimediaViewer is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MultimediaViewer is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MultimediaViewer.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+( function ( mw, oo, $ ) {
+
+       /**
+        * @class mw.mmv.provider.ImageInfo
+        * Gets file information.
+        * See https://www.mediawiki.org/wiki/API:Properties#imageinfo_.2F_ii
+        * @extends mw.mmv.provider.Api
+        * @inheritdoc
+        * @param {mw.Api} api
+        */
+       function ImageInfo( api ) {
+               mw.mmv.provider.Api.call( this, api );
+       }
+       oo.inheritClass( ImageInfo, mw.mmv.provider.Api );
+
+       /**
+        * List of imageinfo API properties which are needed to construct an 
Image model.
+        * @type {string[]}
+        */
+       ImageInfo.prototype.iiprop = [
+               'timestamp',
+               'user',
+               'url',
+               'size',
+               'mime',
+               'mediatype',
+               'extmetadata'
+       ];
+
+       /**
+        * List of imageinfo extmetadata fields which are needed to construct 
an Image model.
+        * @type {string[]}
+        */
+       ImageInfo.prototype.iiextmetadatafilter = [
+               'DateTime',
+               'DateTimeOriginal',
+               'ImageDescription',
+               'License',
+               'Credit',
+               'Artist',
+               'GPSLatitude',
+               'GPSLongitude'
+       ];
+
+       /**
+        * @method
+        * Runs an API GET request to get the image info.
+        * @param {mw.Title} file
+        * @return {jQuery.Promise} a promise which resolves to an 
mw.mmv.model.Image object.
+        */
+       ImageInfo.prototype.get = function( file ) {
+               var provider = this,
+                       cacheKey = file.getPrefixedDb();
+
+               if ( !this.cache[cacheKey] ) {
+                       this.cache[cacheKey] = this.api.get( {
+                               action: 'query',
+                               prop: 'imageinfo',
+                               titles: file.getPrefixedDb(),
+                               iiprop: this.iiprop,
+                               iiextmetadatafilter: this.iiextmetadatafilter,
+                               format: 'json'
+                       } ).then( function( data ) {
+                               return provider.getQueryPage( file, data );
+                       } ).then( function( page ) {
+                               if ( page.imageinfo && page.imageinfo.length ) {
+                                       return 
mw.mmv.model.Image.newFromImageInfo( file, page );
+                               } else if ( page.missing === '' && 
page.imagerepository === '' ) {
+                                       return $.Deferred().reject( 'file does 
not exist: ' + file.getPrefixedDb() );
+                               } else {
+                                       return $.Deferred().reject( 'unknown 
error' );
+                               }
+                       } );
+               }
+
+               return this.cache[cacheKey];
+       };
+
+       mw.mmv.provider.ImageInfo = ImageInfo;
+}( mediaWiki, OO, jQuery ) );
diff --git a/resources/mmv/provider/mmv.provider.ImageUsage.js 
b/resources/mmv/provider/mmv.provider.ImageUsage.js
index 69a4711..114245a 100644
--- a/resources/mmv/provider/mmv.provider.ImageUsage.js
+++ b/resources/mmv/provider/mmv.provider.ImageUsage.js
@@ -60,24 +60,22 @@
                                iulimit: this.options.apiLimit,
                                format: 'json'
                        } ).then( function( data ) {
+                               return provider.getQueryField( 'imageusage', 
data );
+                       } ).then( function( imageusage, data ) {
                                var pages;
-                               if ( data && data.query && 
data.query.imageusage ) {
-                                       pages = $.map( data.query.imageusage, 
function( item ) {
-                                               return {
-                                                       wiki: null,
-                                                       page: new mw.Title( 
item.title, item.ns )
-                                               };
-                                       } );
-                                       return new mw.mmv.model.FileUsage(
-                                               file,
-                                               
mw.mmv.model.FileUsage.Scope.LOCAL,
-                                               pages.slice( 0, 
provider.options.dataLimit ),
-                                               pages.length,
-                                               !!( data['query-continue'] && 
data['query-continue'].imageusage )
-                                       );
-                               } else {
-                                       return $.Deferred().reject( 
provider.getErrorMessage( data ) );
-                               }
+                               pages = $.map( imageusage, function( item ) {
+                                       return {
+                                               wiki: null,
+                                               page: new mw.Title( item.title, 
item.ns )
+                                       };
+                               } );
+                               return new mw.mmv.model.FileUsage(
+                                       file,
+                                       mw.mmv.model.FileUsage.Scope.LOCAL,
+                                       pages.slice( 0, 
provider.options.dataLimit ),
+                                       pages.length,
+                                       !!( data['query-continue'] && 
data['query-continue'].imageusage )
+                               );
                        } );
                }
 
diff --git a/resources/mmv/provider/mmv.provider.ThumbnailInfo.js 
b/resources/mmv/provider/mmv.provider.ThumbnailInfo.js
new file mode 100644
index 0000000..f5c044d
--- /dev/null
+++ b/resources/mmv/provider/mmv.provider.ThumbnailInfo.js
@@ -0,0 +1,69 @@
+/*
+ * This file is part of the MediaWiki extension MultimediaViewer.
+ *
+ * MultimediaViewer is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MultimediaViewer is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MultimediaViewer.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+( function ( mw, oo, $ ) {
+
+       /**
+        * @class mw.mmv.provider.ThumbnailInfo
+        * Gets thumbnail information.
+        * See https://www.mediawiki.org/wiki/API:Properties#imageinfo_.2F_ii
+        * @extends mw.mmv.provider.Api
+        * @inheritdoc
+        * @param {mw.Api} api
+        */
+       function ThumbnailInfo( api ) {
+               mw.mmv.provider.Api.call( this, api );
+       }
+       oo.inheritClass( ThumbnailInfo, mw.mmv.provider.Api );
+
+       /**
+        * @method
+        * Runs an API GET request to get the thumbnail info.
+        * @param {mw.Title} file
+        * @param {number} width thumbnail width
+        * @return {jQuery.Promise} a promise which resolves to the thumbnail 
URL
+        */
+       ThumbnailInfo.prototype.get = function( file, width ) {
+               var provider = this,
+                       cacheKey = file.getPrefixedDb() + '|' + width;
+
+               if ( !this.cache[cacheKey] ) {
+                       this.cache[cacheKey] = this.api.get( {
+                               action: 'query',
+                               prop: 'imageinfo',
+                               titles: file.getPrefixedDb(),
+                               iiprop: ['url'],
+                               iiurlwidth: width,
+                               format: 'json'
+                       } ).then( function( data ) {
+                               return provider.getQueryPage( file, data );
+                       } ).then( function( page ) {
+                               if ( page.imageinfo && page.imageinfo[0] ) {
+                                       return  page.imageinfo[0].thumburl;
+                               } else if ( page.missing === '' && 
page.imagerepository === '' ) {
+                                       return $.Deferred().reject( 'file does 
not exist: ' + file.getPrefixedDb() );
+                               } else {
+                                       return $.Deferred().reject( 'unknown 
error' );
+                               }
+                       } );
+               }
+
+               return this.cache[cacheKey];
+       };
+
+       mw.mmv.provider.ThumbnailInfo = ThumbnailInfo;
+}( mediaWiki, OO, jQuery ) );
diff --git a/tests/qunit/provider/mmv.provider.Api.test.js 
b/tests/qunit/provider/mmv.provider.Api.test.js
new file mode 100644
index 0000000..1602e8a
--- /dev/null
+++ b/tests/qunit/provider/mmv.provider.Api.test.js
@@ -0,0 +1,177 @@
+/*
+ * This file is part of the MediaWiki extension MultimediaViewer.
+ *
+ * MultimediaViewer is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MultimediaViewer is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MultimediaViewer.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+( function ( mw ) {
+       QUnit.module( 'mmv.provider.Api', QUnit.newMwEnvironment() );
+
+       QUnit.test( 'Api constructor sanity check', 2, function ( assert ) {
+               var api = { get: function() {} },
+                       options = {},
+                       apiProvider = new mw.mmv.provider.Api( api, options ),
+                       ApiProviderWithNoOptions = new mw.mmv.provider.Api( api 
);
+
+               assert.ok( apiProvider );
+               assert.ok( ApiProviderWithNoOptions );
+       } );
+
+       QUnit.test( 'getErrorMessage test', 2, function ( assert ) {
+               var api = { get: function() {} },
+                       apiProvider = new mw.mmv.provider.Api( api ),
+                       errorMessage;
+
+               errorMessage = apiProvider.getErrorMessage( {
+                       servedby: 'mw1194',
+                       error: {
+                               code: 'unknown_action',
+                               info: 'Unrecognized value for parameter 
\'action\': FOO'
+                       }
+               } );
+               assert.strictEqual( errorMessage,
+                       'unknown_action: Unrecognized value for parameter 
\'action\': FOO',
+                       'error message is parsed correctly');
+
+               assert.strictEqual( apiProvider.getErrorMessage( {} ), 'unknown 
error', 'missing error message is handled');
+       } );
+
+       QUnit.test( 'getNormalizedTitle test', 3, function ( assert ) {
+               var api = { get: function() {} },
+                       apiProvider = new mw.mmv.provider.Api( api ),
+                       title = new mw.Title( 'Image:Stuff.jpg' ),
+                       normalizedTitle;
+
+               normalizedTitle = apiProvider.getNormalizedTitle( title, {} );
+               assert.strictEqual( normalizedTitle, title, 'missing 
normalization block is handled' );
+
+               normalizedTitle = apiProvider.getNormalizedTitle( title, {
+                       query: {
+                               normalized: [
+                                       {
+                                               from: 'Image:Foo.jpg',
+                                               to: 'File:Foo.jpg'
+                                       }
+                               ]
+                       }
+               } );
+               assert.strictEqual( normalizedTitle, title, 'irrelevant 
normalization info is skipped' );
+
+               normalizedTitle = apiProvider.getNormalizedTitle( title, {
+                       query: {
+                               normalized: [
+                                       {
+                                               from: 'Image:Stuff.jpg',
+                                               to: 'File:Stuff.jpg'
+                                       }
+                               ]
+                       }
+               } );
+               assert.strictEqual( normalizedTitle.getPrefixedDb(), 
'File:Stuff.jpg', 'normalization happens' );
+       } );
+
+       QUnit.test( 'getQueryField test', 3, function ( assert ) {
+               var api = { get: function() {} },
+                       apiProvider = new mw.mmv.provider.Api( api ),
+                       data;
+
+               data = {
+                       query: {
+                               imageusage: [
+                                       {
+                                               pageid: 736,
+                                               ns: 0,
+                                               title: 'Albert Einstein'
+                                       }
+                               ]
+                       }
+               };
+               QUnit.stop();
+               apiProvider.getQueryField( 'imageusage', data ).then( function 
( field ) {
+                       assert.strictEqual( field, data.query.imageusage, 
'specified field is found');
+                       QUnit.start();
+               } );
+
+               QUnit.stop();
+               apiProvider.getQueryField( 'imageusage', {} ).fail( function () 
{
+                       assert.ok( true, 'promise rejected when data is 
missing');
+                       QUnit.start();
+               } );
+
+               QUnit.stop();
+               apiProvider.getQueryField( 'imageusage', { data: { query: {} } 
} ).fail( function () {
+                       assert.ok( true, 'promise rejected when field is 
missing');
+                       QUnit.start();
+               } );
+       } );
+
+       QUnit.test( 'getQueryPage test', 6, function ( assert ) {
+               var api = { get: function() {} },
+                       apiProvider = new mw.mmv.provider.Api( api ),
+                       title = new mw.Title( 'File:Stuff.jpg' ),
+                       titleWithNamespaceAlias = new mw.Title( 
'Image:Stuff.jpg' ),
+                       otherTitle = new mw.Title( 'File:Foo.jpg' ),
+                       data;
+
+               data = {
+                       normalized: [
+                               {
+                                       from: 'Image:Stuff.jpg',
+                                       to: 'File:Stuff.jpg'
+                               }
+                       ],
+                       query: {
+                               pages: {
+                                       '-1': {
+                                               title: 'File:Stuff.jpg'
+                                       }
+                               }
+                       }
+               };
+               QUnit.stop();
+               apiProvider.getQueryPage( title, data ).then( function ( field 
) {
+                       assert.strictEqual( field, data.query.pages['-1'], 
'specified page is found');
+                       QUnit.start();
+               } );
+               QUnit.stop();
+               apiProvider.getQueryPage( titleWithNamespaceAlias, data ).then( 
function ( field ) {
+                       assert.strictEqual( field, data.query.pages['-1'],
+                               'specified page is found even if its title was 
normalized');
+                       QUnit.start();
+               } );
+               QUnit.stop();
+               apiProvider.getQueryPage( otherTitle, {} ).fail( function () {
+                       assert.ok( true, 'promise rejected when page has 
different title');
+                       QUnit.start();
+               } );
+
+               QUnit.stop();
+               apiProvider.getQueryPage( title, {} ).fail( function () {
+                       assert.ok( true, 'promise rejected when data is 
missing');
+                       QUnit.start();
+               } );
+
+               QUnit.stop();
+               apiProvider.getQueryPage( title, { data: { query: {} } } 
).fail( function () {
+                       assert.ok( true, 'promise rejected when pages are 
missing');
+                       QUnit.start();
+               } );
+
+               QUnit.stop();
+               apiProvider.getQueryPage( title, { data: { query: { pages: {} } 
} } ).fail( function () {
+                       assert.ok( true, 'promise rejected when pages are 
empty');
+                       QUnit.start();
+               } );
+       } );
+}( mediaWiki ) );
diff --git a/tests/qunit/provider/mmv.provider.FileRepoInfo.test.js 
b/tests/qunit/provider/mmv.provider.FileRepoInfo.test.js
new file mode 100644
index 0000000..c42308c
--- /dev/null
+++ b/tests/qunit/provider/mmv.provider.FileRepoInfo.test.js
@@ -0,0 +1,126 @@
+/*
+ * This file is part of the MediaWiki extension MultimediaViewer.
+ *
+ * MultimediaViewer is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MultimediaViewer is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MultimediaViewer.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+( function ( mw, $ ) {
+       QUnit.module( 'mmv.provider.FileRepoInfo', QUnit.newMwEnvironment() );
+
+       QUnit.test( 'FileRepoInfo constructor sanity check', 1, function ( 
assert ) {
+               var api = { get: function() {} },
+                       fileRepoInfoProvider = new 
mw.mmv.provider.FileRepoInfo( api );
+
+               assert.ok( fileRepoInfoProvider );
+       } );
+
+       QUnit.asyncTest( 'FileRepoInfo get test', 14, function ( assert ) {
+               var apiCallCount = 0,
+                       api = { get: function() {
+                               apiCallCount++;
+                               return $.Deferred().resolve( {
+                                       'query': {
+                                               'repos': [
+                                                       {
+                                                               'name': 
'shared',
+                                                               'displayname': 
'Wikimedia Commons',
+                                                               'rootUrl': 
'//upload.beta.wmflabs.org/wikipedia/commons',
+                                                               'local': false,
+                                                               'url': 
'//upload.beta.wmflabs.org/wikipedia/commons',
+                                                               'thumbUrl': 
'//upload.beta.wmflabs.org/wikipedia/commons/thumb',
+                                                               
'initialCapital': true,
+                                                               'descBaseUrl': 
'//commons.wikimedia.beta.wmflabs.org/wiki/File:',
+                                                               'scriptDirUrl': 
'//commons.wikimedia.beta.wmflabs.org/w',
+                                                               
'fetchDescription': true,
+                                                               'favicon': 
'http://bits.wikimedia.org/favicon/wikipedia.ico'
+                                                       },
+                                                       {
+                                                               'name': 
'wikimediacommons',
+                                                               'displayname': 
'Wikimedia Commons',
+                                                               'rootUrl': 
'//upload.beta.wmflabs.org/wikipedia/en',
+                                                               'local': false,
+                                                               'url': 
'//upload.beta.wmflabs.org/wikipedia/en',
+                                                               'thumbUrl': 
'//upload.beta.wmflabs.org/wikipedia/en/thumb',
+                                                               
'initialCapital': true,
+                                                               'scriptDirUrl': 
'http://commons.wikimedia.org/w',
+                                                               
'fetchDescription': true,
+                                                               
'descriptionCacheExpiry': 43200,
+                                                               'apiurl': 
'http://commons.wikimedia.org/w/api.php',
+                                                               'articlepath': 
'/wiki/$1',
+                                                               'server': 
'//commons.wikimedia.org',
+                                                               'favicon': 
'//bits.wikimedia.org/favicon/commons.ico'
+                                                       },
+                                                       {
+                                                               'name': 'local',
+                                                               'displayname': 
null,
+                                                               'rootUrl': 
'//upload.beta.wmflabs.org/wikipedia/en',
+                                                               'local': true,
+                                                               'url': 
'//upload.beta.wmflabs.org/wikipedia/en',
+                                                               'thumbUrl': 
'//upload.beta.wmflabs.org/wikipedia/en/thumb',
+                                                               
'initialCapital': true,
+                                                               'scriptDirUrl': 
'/w',
+                                                               'favicon': 
'http://bits.wikimedia.org/favicon/wikipedia.ico'
+                                                       }
+                                               ]
+                                       }
+                               } );
+                       } },
+                       fileRepoInfoProvider = new 
mw.mmv.provider.FileRepoInfo( api );
+
+               fileRepoInfoProvider.get().then( function( repos ) {
+                       assert.strictEqual( repos.shared.displayName,
+                               'Wikimedia Commons', 'displayName is set 
correctly' );
+                       assert.strictEqual( repos.shared.favIcon,
+                               
'http://bits.wikimedia.org/favicon/wikipedia.ico', 'favIcon is set correctly' );
+                       assert.strictEqual( repos.shared.isLocal, false, 
'isLocal is set correctly' );
+                       assert.strictEqual( repos.shared.descBaseUrl,
+                               
'//commons.wikimedia.beta.wmflabs.org/wiki/File:', 'descBaseUrl is set 
correctly' );
+
+                       assert.strictEqual( repos.wikimediacommons.displayName,
+                               'Wikimedia Commons', 'displayName is set 
correctly' );
+                       assert.strictEqual( repos.wikimediacommons.favIcon,
+                               '//bits.wikimedia.org/favicon/commons.ico', 
'favIcon is set correctly' );
+                       assert.strictEqual( repos.wikimediacommons.isLocal, 
false, 'isLocal is set correctly' );
+                       assert.strictEqual( repos.wikimediacommons.apiUrl,
+                               'http://commons.wikimedia.org/w/api.php', 
'apiUrl is set correctly' );
+                       assert.strictEqual( repos.wikimediacommons.server,
+                               '//commons.wikimedia.org', 'server is set 
correctly' );
+                       assert.strictEqual( repos.wikimediacommons.articlePath,
+                               '/wiki/$1', 'articlePath is set correctly' );
+
+                       assert.strictEqual( repos.local.displayName, null, 
'displayName is set correctly' );
+                       assert.strictEqual( repos.local.favIcon,
+                               
'http://bits.wikimedia.org/favicon/wikipedia.ico', 'favIcon is set correctly' );
+                       assert.strictEqual( repos.local.isLocal, true, 'isLocal 
is set correctly' );
+               } ).then( function() {
+                       // call the data provider a second time to check caching
+                       return fileRepoInfoProvider.get();
+               } ).then( function() {
+                       assert.strictEqual( apiCallCount, 1 );
+                       QUnit.start();
+               } );
+       } );
+
+       QUnit.asyncTest( 'FileRepoInfo fail test', 1, function ( assert ) {
+               var api = { get: function() {
+                               return $.Deferred().resolve( {} );
+                       } },
+                       fileRepoInfoProvider = new 
mw.mmv.provider.FileRepoInfo( api );
+
+               fileRepoInfoProvider.get().fail( function() {
+                       assert.ok( true, 'promise rejected when no data is 
returned' );
+                       QUnit.start();
+               } );
+       } );
+}( mediaWiki, jQuery ) );
diff --git a/tests/qunit/provider/mmv.provider.ImageInfo.test.js 
b/tests/qunit/provider/mmv.provider.ImageInfo.test.js
new file mode 100644
index 0000000..71d53ea
--- /dev/null
+++ b/tests/qunit/provider/mmv.provider.ImageInfo.test.js
@@ -0,0 +1,185 @@
+/*
+ * This file is part of the MediaWiki extension MultimediaViewer.
+ *
+ * MultimediaViewer is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MultimediaViewer is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MultimediaViewer.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+( function ( mw, $ ) {
+       QUnit.module( 'mmv.provider.ImageInfo', QUnit.newMwEnvironment() );
+
+       QUnit.test( 'ImageInfo constructor sanity check', 1, function ( assert 
) {
+               var api = { get: function() {} },
+                       imageInfoProvider = new mw.mmv.provider.ImageInfo( api 
);
+
+               assert.ok( imageInfoProvider );
+       } );
+
+       QUnit.asyncTest( 'ImageInfo get test', 18, function ( assert ) {
+               var apiCallCount = 0,
+                       api = { get: function() {
+                               apiCallCount++;
+                               return $.Deferred().resolve( {
+                                       query: {
+                                               pages: {
+                                                       '-1': {
+                                                               ns: 6,
+                                                               title: 
'File:Stuff.jpg',
+                                                               missing: '',
+                                                               
imagerepository: 'shared',
+                                                               imageinfo: [
+                                                                       {
+                                                                               
timestamp: '2013-08-25T14:41:02Z',
+                                                                               
user: 'Dylanbot11',
+                                                                               
userid: '3053121',
+                                                                               
size: 346684,
+                                                                               
width: 720,
+                                                                               
height: 1412,
+                                                                               
comment: 'User created page with UploadWizard',
+                                                                               
url: 'https://upload.wikimedia.org/wikipedia/commons/1/19/Stuff.jpg',
+                                                                               
descriptionurl: 'https://commons.wikimedia.org/wiki/File:Stuff.jpg',
+                                                                               
sha1: 'a1ba23d471f4dad208b71c143e2e105a0e3032db',
+                                                                               
metadata: [],
+                                                                               
extmetadata: {
+                                                                               
        License: {
+                                                                               
                value: 'cc0',
+                                                                               
                source: 'commons-templates',
+                                                                               
                hidden: ''
+                                                                               
        },
+                                                                               
        GPSLatitude: {
+                                                                               
                value: '90.000000',
+                                                                               
                source: 'commons-desc-page'
+                                                                               
        },
+                                                                               
        GPSLongitude: {
+                                                                               
                value: ' 180.000000',
+                                                                               
                source: 'commons-desc-page'
+                                                                               
        },
+                                                                               
        ImageDescription: {
+                                                                               
                value: 'Wikis stuff',
+                                                                               
                source: 'commons-desc-page'
+                                                                               
        },
+                                                                               
        DateTimeOriginal: {
+                                                                               
                value: '2013-08-25, 15:38:38',
+                                                                               
                source: 'commons-desc-page'
+                                                                               
        },
+                                                                               
        DateTime: {
+                                                                               
                value: '2013-08-25T14:41:02Z',
+                                                                               
                source: 'commons-desc-page'
+                                                                               
        },
+                                                                               
        Credit: {
+                                                                               
                value: 'Wikipedia',
+                                                                               
                source: 'commons-desc-page',
+                                                                               
                hidden: ''
+                                                                               
        },
+                                                                               
        Artist: {
+                                                                               
                value: 'Wikimeda',
+                                                                               
                source: 'commons-desc-page'
+                                                                               
        }
+                                                                               
},
+                                                                               
mime: 'image/jpeg',
+                                                                               
mediatype: 'BITMAP'
+                                                                       }
+                                                               ]
+                                                       }
+                                               }
+                                       }
+                               } );
+                       } },
+                       file = new mw.Title( 'File:Stuff.jpg' ),
+                       imageInfoProvider = new mw.mmv.provider.ImageInfo( api 
);
+
+               imageInfoProvider.get( file ).then( function( image ) {
+                       assert.strictEqual( image.title.getPrefixedDb(), 
'File:Stuff.jpg', 'title is set correctly' );
+                       assert.strictEqual( image.size, 346684, 'size is set 
correctly' );
+                       assert.strictEqual( image.width, 720, 'width is set 
correctly' );
+                       assert.strictEqual( image.height, 1412, 'height is set 
correctly' );
+                       assert.strictEqual( image.mimeType, 'image/jpeg', 
'mimeType is set correctly' );
+                       assert.strictEqual( image.url, 
'https://upload.wikimedia.org/wikipedia/commons/1/19/Stuff.jpg', 'url is set 
correctly' );
+                       assert.strictEqual( image.descriptionUrl, 
'https://commons.wikimedia.org/wiki/File:Stuff.jpg', 'descriptionUrl is set 
correctly' );
+                       assert.strictEqual( image.repo, 'shared', 'repo is set 
correctly' );
+                       assert.strictEqual( image.lastUploader, 'Dylanbot11', 
'lastUploader is set correctly' );
+                       assert.strictEqual( image.uploadDateTime, 
'2013-08-25T14:41:02Z', 'uploadDateTime is set correctly' );
+                       assert.strictEqual( image.creationDateTime, 
'2013-08-25, 15:38:38', 'creationDateTime is set correctly' );
+                       assert.strictEqual( image.description, 'Wikis stuff', 
'description is set correctly' );
+                       assert.strictEqual( image.source, 'Wikipedia', 'source 
is set correctly' );
+                       assert.strictEqual( image.author, 'Wikimeda', 'author 
is set correctly' );
+                       assert.strictEqual( image.license, 'cc0', 'license is 
set correctly' );
+                       assert.strictEqual( image.latitude, 90, 'latitude is 
set correctly' );
+                       assert.strictEqual( image.longitude, 180, 'longitude is 
set correctly' );
+               } ).then( function() {
+                       // call the data provider a second time to check caching
+                       return imageInfoProvider.get( file );
+               } ).then( function() {
+                       assert.strictEqual( apiCallCount, 1 );
+                       QUnit.start();
+               } );
+       } );
+
+       QUnit.asyncTest( 'ImageInfo fail test', 1, function ( assert ) {
+               var api = { get: function() {
+                               return $.Deferred().resolve( {} );
+                       } },
+                       file = new mw.Title( 'File:Stuff.jpg' ),
+                       imageInfoProvider = new mw.mmv.provider.ImageInfo( api 
);
+
+               imageInfoProvider.get( file ).fail( function() {
+                       assert.ok( true, 'promise rejected when no data is 
returned' );
+                       QUnit.start();
+               } );
+       } );
+
+       QUnit.asyncTest( 'ImageInfo fail test 2', 1, function ( assert ) {
+               var api = { get: function() {
+                               return $.Deferred().resolve( {
+                                       query: {
+                                               pages: {
+                                                       '-1': {
+                                                               title: 
'File:Stuff.jpg'
+                                                       }
+                                               }
+                                       }
+                               } );
+                       } },
+                       file = new mw.Title( 'File:Stuff.jpg' ),
+                       imageInfoProvider = new mw.mmv.provider.ImageInfo( api 
);
+
+               imageInfoProvider.get( file ).fail( function() {
+                       assert.ok( true, 'promise rejected when imageinfo is 
missing' );
+                       QUnit.start();
+               } );
+       } );
+
+       QUnit.asyncTest( 'ImageInfo missing page test', 1, function ( assert ) {
+               var api = { get: function() {
+                               return $.Deferred().resolve( {
+                                       query: {
+                                               pages: {
+                                                       '-1': {
+                                                               title: 
'File:Stuff.jpg',
+                                                               missing: '',
+                                                               
imagerepository: ''
+                                                       }
+                                               }
+                                       }
+                               } );
+                       } },
+                       file = new mw.Title( 'File:Stuff.jpg' ),
+                       imageInfoProvider = new mw.mmv.provider.ImageInfo( api 
);
+
+               imageInfoProvider.get( file ).fail( function( errorMessage ) {
+                       assert.strictEqual(errorMessage, 'file does not exist: 
File:Stuff.jpg',
+                               'error message is set correctly for missing 
file');
+                       QUnit.start();
+               } );
+       } );
+}( mediaWiki, jQuery ) );
diff --git a/tests/qunit/provider/mmv.provider.ThumbnailInfo.test.js 
b/tests/qunit/provider/mmv.provider.ThumbnailInfo.test.js
new file mode 100644
index 0000000..f3d9168
--- /dev/null
+++ b/tests/qunit/provider/mmv.provider.ThumbnailInfo.test.js
@@ -0,0 +1,132 @@
+/*
+ * This file is part of the MediaWiki extension MultimediaViewer.
+ *
+ * MultimediaViewer is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MultimediaViewer is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MultimediaViewer.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+( function ( mw, $ ) {
+       QUnit.module( 'mmv.provider.ThumbnailInfo', QUnit.newMwEnvironment() );
+
+       QUnit.test( 'ThumbnailInfo constructor sanity check', 1, function ( 
assert ) {
+               var api = { get: function() {} },
+                       thumbnailInfoProvider = new 
mw.mmv.provider.ThumbnailInfo( api );
+
+               assert.ok( thumbnailInfoProvider );
+       } );
+
+       QUnit.asyncTest( 'ThumbnailInfo get test', 4, function ( assert ) {
+               var apiCallCount = 0,
+                       api = { get: function() {
+                               apiCallCount++;
+                               return $.Deferred().resolve( {
+                                       query: {
+                                               pages: {
+                                                       '-1': {
+                                                               ns: 6,
+                                                               title: 
'File:Stuff.jpg',
+                                                               missing: '',
+                                                               
imagerepository: 'shared',
+                                                               imageinfo: [
+                                                                       {
+                                                                               
thumburl: 
'https://upload.wikimedia.org/wikipedia/commons/thumb/1/19/Stuff.jpg/51px-Stuff.jpg',
+                                                                               
thumbwidth: 100,
+                                                                               
thumbheight: 200,
+                                                                               
url: 'https://upload.wikimedia.org/wikipedia/commons/1/19/Stuff.jpg',
+                                                                               
descriptionurl: 'https://commons.wikimedia.org/wiki/File:Stuff.jpg'
+                                                                       }
+                                                               ]
+                                                       }
+                                               }
+                                       }
+                               } );
+                       } },
+                       file = new mw.Title( 'File:Stuff.jpg' ),
+                       thumbnailInfoProvider = new 
mw.mmv.provider.ThumbnailInfo( api );
+
+               thumbnailInfoProvider.get( file, 100 ).then( function( 
thumnailUrl ) {
+                       assert.strictEqual( thumnailUrl,
+                               
'https://upload.wikimedia.org/wikipedia/commons/thumb/1/19/Stuff.jpg/51px-Stuff.jpg',
+                               'URL is set correctly' );
+               } ).then( function() {
+                       assert.strictEqual( apiCallCount, 1 );
+                       // call the data provider a second time to check caching
+                       return thumbnailInfoProvider.get( file, 100 );
+               } ).then( function() {
+                       assert.strictEqual( apiCallCount, 1 );
+                       // call a third time with different size to check 
caching
+                       return thumbnailInfoProvider.get( file, 110 );
+               } ).then( function() {
+                       assert.strictEqual( apiCallCount, 2 );
+                       QUnit.start();
+               } );
+       } );
+
+       QUnit.asyncTest( 'ThumbnailInfo fail test', 1, function ( assert ) {
+               var api = { get: function() {
+                               return $.Deferred().resolve( {} );
+                       } },
+                       file = new mw.Title( 'File:Stuff.jpg' ),
+                       thumbnailInfoProvider = new 
mw.mmv.provider.ThumbnailInfo( api );
+
+               thumbnailInfoProvider.get( file, 100 ).fail( function() {
+                       assert.ok( true, 'promise rejected when no data is 
returned' );
+                       QUnit.start();
+               } );
+       } );
+
+       QUnit.asyncTest( 'ThumbnailInfo fail test 2', 1, function ( assert ) {
+               var api = { get: function() {
+                               return $.Deferred().resolve( {
+                                       query: {
+                                               pages: {
+                                                       '-1': {
+                                                               title: 
'File:Stuff.jpg'
+                                                       }
+                                               }
+                                       }
+                               } );
+                       } },
+                       file = new mw.Title( 'File:Stuff.jpg' ),
+                       thumbnailInfoProvider = new 
mw.mmv.provider.ThumbnailInfo( api );
+
+               thumbnailInfoProvider.get( file, 100 ).fail( function() {
+                       assert.ok( true, 'promise rejected when imageinfo is 
missing' );
+                       QUnit.start();
+               } );
+       } );
+
+       QUnit.asyncTest( 'ThumbnailInfo missing page test', 1, function ( 
assert ) {
+               var api = { get: function() {
+                               return $.Deferred().resolve( {
+                                       query: {
+                                               pages: {
+                                                       '-1': {
+                                                               title: 
'File:Stuff.jpg',
+                                                               missing: '',
+                                                               
imagerepository: ''
+                                                       }
+                                               }
+                                       }
+                               } );
+                       } },
+                       file = new mw.Title( 'File:Stuff.jpg' ),
+                       thumbnailInfoProvider = new 
mw.mmv.provider.ThumbnailInfo( api );
+
+               thumbnailInfoProvider.get( file ).fail( function( errorMessage 
) {
+                       assert.strictEqual(errorMessage, 'file does not exist: 
File:Stuff.jpg',
+                               'error message is set correctly for missing 
file');
+                       QUnit.start();
+               } );
+       } );
+}( mediaWiki, jQuery ) );

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

Gerrit-MessageType: merged
Gerrit-Change-Id: I80ffec39ee6c9e0ea0b37be2fc48315063b5ff8a
Gerrit-PatchSet: 9
Gerrit-Project: mediawiki/extensions/MultimediaViewer
Gerrit-Branch: master
Gerrit-Owner: GergÅ‘ Tisza <[email protected]>
Gerrit-Reviewer: Aarcos <[email protected]>
Gerrit-Reviewer: GergÅ‘ Tisza <[email protected]>
Gerrit-Reviewer: Gilles <[email protected]>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to