Jdlrobson has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/167342

Change subject: Add RL template module with HTML markup language
......................................................................

Add RL template module with HTML markup language

Preparation work for templating in core.
RL should allow us to ship HTML / template markup from server to client.
Use in Special:Upload and mediawiki.feedback as a proof of concept.
Separation of concerns etc...

See Also:
Ia63d6b6868f23a773e4a41daa0036d4bf2cd6724

Change-Id: Ia0c5c8ec960aa6dff12c9626cee41ae9a3286b76
---
M includes/resourceloader/ResourceLoader.php
M includes/resourceloader/ResourceLoaderFileModule.php
M includes/resourceloader/ResourceLoaderModule.php
M maintenance/jsduck/categories.json
M resources/Resources.php
M resources/src/mediawiki.action/mediawiki.action.view.postEdit.js
A resources/src/mediawiki.action/templates/postEdit.html
M resources/src/mediawiki.special/mediawiki.special.upload.js
M resources/src/mediawiki.special/mediawiki.special.userlogin.common.js
A resources/src/mediawiki.special/templates/captcha.html
A resources/src/mediawiki.special/templates/thumbnail.html
M resources/src/mediawiki/mediawiki.feedback.js
M resources/src/mediawiki/mediawiki.js
A resources/src/mediawiki/mediawiki.templates.js
A resources/src/mediawiki/templates/dialog.html
M tests/phpunit/includes/OutputPageTest.php
A tests/phpunit/includes/resourceloader/ResourceLoaderFileModuleTest.php
A tests/phpunit/includes/resourceloader/templates/template.html
A tests/phpunit/includes/resourceloader/templates/template2.html
A tests/phpunit/includes/resourceloader/templates/template_awesome.handlebars
M tests/qunit/QUnitTestResources.php
A tests/qunit/suites/resources/mediawiki/mediawiki.template.test.js
22 files changed, 439 insertions(+), 74 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/core 
refs/changes/42/167342/1

diff --git a/includes/resourceloader/ResourceLoader.php 
b/includes/resourceloader/ResourceLoader.php
index 57deb00..a0b4852 100644
--- a/includes/resourceloader/ResourceLoader.php
+++ b/includes/resourceloader/ResourceLoader.php
@@ -974,12 +974,20 @@
                                        case 'messages':
                                                $out .= 
self::makeMessageSetScript( new XmlJsCode( $messagesBlob ) );
                                                break;
+                                       case 'templates':
+                                               $out .= Xml::encodeJsCall(
+                                                       'mw.templates.set',
+                                                       array( 
(object)$module->getTemplates() ),
+                                                       
ResourceLoader::inDebugMode()
+                                               );
+                                               break;
                                        default:
                                                $out .= 
self::makeLoaderImplementScript(
                                                        $name,
                                                        $scripts,
                                                        $styles,
-                                                       new XmlJsCode( 
$messagesBlob )
+                                                       new XmlJsCode( 
$messagesBlob ),
+                                                       $module->getTemplates()
                                                );
                                                break;
                                }
@@ -1044,10 +1052,14 @@
         * @param mixed $messages List of messages associated with this module. 
May either be an
         *   associative array mapping message key to value, or a JSON-encoded 
message blob containing
         *   the same data, wrapped in an XmlJsCode object.
+        * @param array $templates where keys are name of templates and values 
are the source of
+        *   the template.
         * @throws MWException
         * @return string
         */
-       public static function makeLoaderImplementScript( $name, $scripts, 
$styles, $messages ) {
+       public static function makeLoaderImplementScript( $name, $scripts, 
$styles, $messages,
+               $templates = array()
+       ) {
                if ( is_string( $scripts ) ) {
                        $scripts = new XmlJsCode( "function ( $, jQuery ) 
{\n{$scripts}\n}" );
                } elseif ( !is_array( $scripts ) ) {
@@ -1064,7 +1076,8 @@
                                // PHP/json_encode() consider empty arrays to 
be numerical arrays and
                                // output javascript "[]" instead of "{}". This 
fixes that.
                                (object)$styles,
-                               (object)$messages
+                               (object)$messages,
+                               (object)$templates,
                        ),
                        ResourceLoader::inDebugMode()
                );
diff --git a/includes/resourceloader/ResourceLoaderFileModule.php 
b/includes/resourceloader/ResourceLoaderFileModule.php
index 7bbc9bb..6544813 100644
--- a/includes/resourceloader/ResourceLoaderFileModule.php
+++ b/includes/resourceloader/ResourceLoaderFileModule.php
@@ -34,6 +34,9 @@
        /** @var string Remote base path, see __construct() */
        protected $remoteBasePath = '';
 
+       /** @var array Saves a list of the templates named by the modules. */
+       protected $templates = array();
+
        /**
         * @var array List of paths to JavaScript files to always include
         * @par Usage:
@@ -267,6 +270,10 @@
                                        sort( $option );
 
                                        $this->{$member} = $option;
+                                       break;
+                               // templates
+                               case 'templates':
+                                       $this->{$member} = (array) $option;
                                        break;
                                // Single strings
                                case 'group':
@@ -544,6 +551,9 @@
                        $files[] = $this->skipFunction;
                }
                $files = array_map( array( $this, 'getLocalPath' ), $files );
+               // Templates
+               $templateFiles = array_map( array( $this, 'getLocalPath' ), 
$this->templates );
+               $files = array_merge( $files, $templateFiles );
                // File deps need to be treated separately because they're 
already prefixed
                $files = array_merge( $files, $this->getFileDependencies( 
$context->getSkin() ) );
 
@@ -959,4 +969,26 @@
        protected function getLessCompiler( ResourceLoaderContext $context = 
null ) {
                return ResourceLoader::getLessCompiler( $this->getConfig() );
        }
+
+       /**
+        * Takes named templates by the module and adds them to the JavaScript 
output
+        *
+        * @return array of templates mapping template alias to content
+        */
+       function getTemplates() {
+               $templates = array();
+
+               foreach( $this->templates as $alias => $templatePath ) {
+                       // Alias is optional
+                       if ( is_int( $alias ) ) {
+                               $alias = $templatePath;
+                       }
+                       $localPath = $this->getLocalPath( $templatePath );
+                       if ( file_exists( $localPath ) ) {
+                               $content = file_get_contents( $localPath );
+                               $templates[ $alias ] = $content;
+                       }
+               }
+               return $templates;
+       }
 }
diff --git a/includes/resourceloader/ResourceLoaderModule.php 
b/includes/resourceloader/ResourceLoaderModule.php
index 45eb70f..3f53ca9 100644
--- a/includes/resourceloader/ResourceLoaderModule.php
+++ b/includes/resourceloader/ResourceLoaderModule.php
@@ -135,6 +135,16 @@
        }
 
        /**
+        * Returns JavaScript relating to adding templates to the client.
+        *
+        * @return string JavaScript code
+        */
+       public function getTemplates() {
+               // Stub, override expected.
+               return array();
+       }
+
+       /**
         * @return Config
         * @since 1.24
         */
diff --git a/maintenance/jsduck/categories.json 
b/maintenance/jsduck/categories.json
index d6163bd..145749a 100644
--- a/maintenance/jsduck/categories.json
+++ b/maintenance/jsduck/categories.json
@@ -13,7 +13,8 @@
                                        "mw.html",
                                        "mw.html.Cdata",
                                        "mw.html.Raw",
-                                       "mw.hook"
+                                       "mw.hook",
+                                       "mw.template"
                                ]
                        },
                        {
diff --git a/resources/Resources.php b/resources/Resources.php
index acc937e..e893de6 100644
--- a/resources/Resources.php
+++ b/resources/Resources.php
@@ -767,7 +767,10 @@
        /* MediaWiki */
 
        'mediawiki' => array(
-               'scripts' => 'resources/src/mediawiki/mediawiki.js',
+               'scripts' => array(
+                       'resources/src/mediawiki/mediawiki.js',
+                       'resources/src/mediawiki/mediawiki.templates.js',
+               ),
                'debugScripts' => 'resources/src/mediawiki/mediawiki.log.js',
                'raw' => true,
                'targets' => array( 'desktop', 'mobile' ),
@@ -851,6 +854,9 @@
                'position' => 'bottom',
        ),
        'mediawiki.feedback' => array(
+               'templates' => array(
+                       'dialog.html' => 
'resources/src/mediawiki/templates/dialog.html',
+               ),
                'scripts' => 'resources/src/mediawiki/mediawiki.feedback.js',
                'styles' => 'resources/src/mediawiki/mediawiki.feedback.css',
                'dependencies' => array(
@@ -1063,6 +1069,9 @@
                ),
        ),
        'mediawiki.action.view.postEdit' => array(
+               'templates' => array(
+                       'postEdit.html' => 
'resources/src/mediawiki.action/templates/postEdit.html',
+               ),
                'scripts' => 
'resources/src/mediawiki.action/mediawiki.action.view.postEdit.js',
                'styles' => 
'resources/src/mediawiki.action/mediawiki.action.view.postEdit.css',
                'dependencies' => array(
@@ -1337,6 +1346,9 @@
                'scripts' => 
'resources/src/mediawiki.special/mediawiki.special.undelete.js',
        ),
        'mediawiki.special.upload' => array(
+               'templates' => array(
+                       'thumbnail.html' => 
'resources/src/mediawiki.special/templates/thumbnail.html',
+               ),
                'scripts' => 
'resources/src/mediawiki.special/mediawiki.special.upload.js',
                'messages' => array(
                        'widthheight',
@@ -1378,6 +1390,9 @@
                'position' => 'top',
        ),
        'mediawiki.special.userlogin.common.js' => array(
+               'templates' => array(
+                       'captcha.html' => 
'resources/src/mediawiki.special/templates/captcha.html',
+               ),
                'scripts' => array(
                        
'resources/src/mediawiki.special/mediawiki.special.userlogin.common.js',
                ),
diff --git a/resources/src/mediawiki.action/mediawiki.action.view.postEdit.js 
b/resources/src/mediawiki.action/mediawiki.action.view.postEdit.js
index 4d2c47a..ee2246d 100644
--- a/resources/src/mediawiki.action/mediawiki.action.view.postEdit.js
+++ b/resources/src/mediawiki.action/mediawiki.action.view.postEdit.js
@@ -30,14 +30,7 @@
                        data.message = $.parseHTML( mw.message( 
'postedit-confirmation-saved', data.user || mw.user ).escaped() );
                }
 
-               $div = $(
-                       '<div class="postedit-container">' +
-                               '<div class="postedit">' +
-                                       '<div class="postedit-icon 
postedit-icon-checkmark postedit-content"></div>' +
-                                       '<a href="#" 
class="postedit-close">&times;</a>' +
-                               '</div>' +
-                       '</div>'
-               );
+               $div = $( mw.template.get( 'mediawiki.action.view.postEdit', 
'postEdit.html' ).render() );
 
                if ( typeof data.message === 'string' ) {
                        $div.find( '.postedit-content' ).text( data.message );
diff --git a/resources/src/mediawiki.action/templates/postEdit.html 
b/resources/src/mediawiki.action/templates/postEdit.html
new file mode 100644
index 0000000..dbb482a
--- /dev/null
+++ b/resources/src/mediawiki.action/templates/postEdit.html
@@ -0,0 +1,6 @@
+<div class="postedit-container">
+       <div class="postedit">
+               <div class="postedit-icon postedit-icon-checkmark 
postedit-content"></div>
+               <a href="#" class="postedit-close">&times;</a>
+       </div>
+</div>
diff --git a/resources/src/mediawiki.special/mediawiki.special.upload.js 
b/resources/src/mediawiki.special/mediawiki.special.upload.js
index 04bc978..ab851b3 100644
--- a/resources/src/mediawiki.special/mediawiki.special.upload.js
+++ b/resources/src/mediawiki.special/mediawiki.special.upload.js
@@ -294,12 +294,7 @@
                                ctx,
                                meta,
                                previewSize = 180,
-                               thumb = $( '<div id="mw-upload-thumbnail" 
class="thumb tright">' +
-                                                       '<div 
class="thumbinner">' +
-                                                               '<div 
class="mw-small-spinner" style="width: 180px; height: 180px"></div>' +
-                                                               '<div 
class="thumbcaption"><div class="filename"></div><div 
class="fileinfo"></div></div>' +
-                                                       '</div>' +
-                                               '</div>' );
+                               thumb = $( mw.template.get( 
'mediawiki.special.upload', 'thumbnail.html' ).render() );
 
                        thumb.find( '.filename' ).text( file.name ).end()
                                .find( '.fileinfo' ).text( prettySize( 
file.size ) ).end();
diff --git 
a/resources/src/mediawiki.special/mediawiki.special.userlogin.common.js 
b/resources/src/mediawiki.special/mediawiki.special.userlogin.common.js
index 247f814..ab02e4e 100644
--- a/resources/src/mediawiki.special/mediawiki.special.userlogin.common.js
+++ b/resources/src/mediawiki.special/mediawiki.special.userlogin.common.js
@@ -9,6 +9,7 @@
        function adjustFancyCaptcha( $content, buttonSubmit ) {
                var $submit = $content.find( buttonSubmit ),
                        tabIndex,
+                       $el,
                        $captchaStuff,
                        $captchaImageContainer,
                        // JavaScript can't yet parse the message 
'createacct-imgcaptcha-help' when it
@@ -38,18 +39,12 @@
 
                        // Insert another div before the submit button that 
will include the
                        // repositioned FancyCaptcha div, an input field, and 
possible help.
-                       $submit.closest( 'div' ).before( [
-                               '<div>',
-                                       '<label for="wpCaptchaWord">' + 
mw.message( 'createacct-captcha' ).escaped() + '</label>',
-                                       '<div 
class="mw-createacct-captcha-container">',
-                                               '<div 
class="mw-createacct-captcha-and-reload" />',
-                                               '<input id="wpCaptchaWord" 
class="mw-ui-input" name="wpCaptchaWord" type="text" placeholder="' +
-                                                       mw.message( 
'createacct-imgcaptcha-ph' ).escaped() +
-                                                       '" tabindex="' + 
tabIndex + '" autocapitalize="off" autocorrect="off">',
-                                                       helpHtml,
-                                       '</div>',
-                               '</div>'
-                       ].join( '' ) );
+                       $el = $submit.closest( 'div' ).before(
+                               mw.template.get( 
'mediawiki.special.userlogin.common.js', 'captcha.html' ).render() );
+                       $el.find( 'label' ).text( mw.msg( 'createacct-captcha' 
) );
+                       $el.find( '#wpCaptchaWord' ).attr( 'tabindex', tabIndex 
).
+                               attr( 'placeholder', mw.msg( 
'createacct-imgcaptcha-ph' ) );
+                       $el.find( 'span' ).html( helpHtml );
 
                        // Stick the FancyCaptcha container inside our bordered 
and framed parents.
                        $captchaImageContainer
diff --git a/resources/src/mediawiki.special/templates/captcha.html 
b/resources/src/mediawiki.special/templates/captcha.html
new file mode 100644
index 0000000..d30f191
--- /dev/null
+++ b/resources/src/mediawiki.special/templates/captcha.html
@@ -0,0 +1,9 @@
+<div>
+       <label for="wpCaptchaWord"></label>
+       <div class="mw-createacct-captcha-container">
+               <div class="mw-createacct-captcha-and-reload" />
+               <input id="wpCaptchaWord" class="mw-ui-input" 
name="wpCaptchaWord"
+                       type="text" autocapitalize="off" autocorrect="off">
+               <span/>
+       </div>
+</div>
diff --git a/resources/src/mediawiki.special/templates/thumbnail.html 
b/resources/src/mediawiki.special/templates/thumbnail.html
new file mode 100644
index 0000000..73042f2
--- /dev/null
+++ b/resources/src/mediawiki.special/templates/thumbnail.html
@@ -0,0 +1,9 @@
+<div id="mw-upload-thumbnail" class="thumb tright">
+       <div class="thumbinner">
+               <div class="mw-small-spinner" style="width: 180px; height: 
180px"></div>
+               <div class="thumbcaption">
+                       <div class="filename"></div>
+                       <div class="fileinfo"></div>
+               </div>
+       </div>
+</div>
diff --git a/resources/src/mediawiki/mediawiki.feedback.js 
b/resources/src/mediawiki/mediawiki.feedback.js
index 1c0d833..01a75f2 100644
--- a/resources/src/mediawiki/mediawiki.feedback.js
+++ b/resources/src/mediawiki/mediawiki.feedback.js
@@ -100,47 +100,20 @@
                                target: '_blank'
                        } );
 
-                       // TODO: Use a stylesheet instead of these inline styles
-                       this.$dialog =
-                               $( '<div style="position: relative;"></div>' 
).append(
-                                       $( '<div class="feedback-mode 
feedback-form"></div>' ).append(
-                                               $( '<small>' ).append(
-                                                       $( '<p>' ).msg(
-                                                               
'feedback-bugornote',
-                                                               $bugNoteLink,
-                                                               
fb.title.getNameText(),
-                                                               
$feedbackPageLink.clone()
-                                                       )
-                                               ),
-                                               $( '<div style="margin-top: 
1em;"></div>' )
-                                               .msg( 'feedback-subject' )
-                                               .append(
-                                                       $( '<br>' ),
-                                                       $( '<input type="text" 
class="feedback-subject" name="subject" maxlength="60" style="width: 100%; 
-moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: 
border-box;"/>' )
-                                               ),
-                                               $( '<div style="margin-top: 
0.4em;"></div>' )
-                                               .msg( 'feedback-message' )
-                                               .append(
-                                                       $( '<br>' ),
-                                                       $( '<textarea 
name="message" class="feedback-message" rows="5" cols="60"></textarea>' )
-                                               )
-                                       ),
-                                       $( '<div class="feedback-mode 
feedback-bugs"></div>' ).append(
-                                               $( '<p>' ).msg( 
'feedback-bugcheck', $bugsListLink )
-                                       ),
-                                       $( '<div class="feedback-mode 
feedback-submitting" style="text-align: center; margin: 3em 0;"></div>' )
-                                       .msg( 'feedback-adding' )
-                                       .append(
-                                               $( '<br>' ),
-                                               $( '<span 
class="feedback-spinner"></span>' )
-                                       ),
-                                       $( '<div class="feedback-mode 
feedback-thanks" style="text-align: center; margin:1em"></div>' ).msg(
-                                               'feedback-thanks', 
fb.title.getNameText(), $feedbackPageLink.clone()
-                                       ),
-                                       $( '<div class="feedback-mode 
feedback-error" style="position: relative;"></div>' ).append(
-                                               $( '<div 
class="feedback-error-msg style="color: #990000; margin-top: 0.4em;"></div>' )
-                                       )
-                               );
+                       // TODO: Use a stylesheet instead of these inline 
styles in the template
+                       this.$dialog = $( mw.template.get( 
'mediawiki.feedback', 'dialog.html' ).render() );
+                       this.$dialog.find( '.feedback-mode small p' ).msg(
+                               'feedback-bugornote',
+                               $bugNoteLink,
+                               fb.title.getNameText(),
+                               $feedbackPageLink.clone()
+                       );
+                       this.$dialog.find( '.feedback-form .subject span' 
).msg( 'feedback-subject' );
+                       this.$dialog.find( '.feedback-form .message span' 
).msg( 'feedback-message' );
+                       this.$dialog.find( '.feedback-bugs p' ).msg( 
'feedback-bugcheck', $bugsListLink );
+                       this.$dialog.find( '.feedback-submitting span' ).msg( 
'feedback-adding' );
+                       this.$dialog.find( '.feedback-thanks' ).msg( 
'feedback-thanks', fb.title.getNameText(),
+                               $feedbackPageLink.clone() );
 
                        this.$dialog.dialog( {
                                width: 500,
diff --git a/resources/src/mediawiki/mediawiki.js 
b/resources/src/mediawiki/mediawiki.js
index e29c734..a070986 100644
--- a/resources/src/mediawiki/mediawiki.js
+++ b/resources/src/mediawiki/mediawiki.js
@@ -483,6 +483,12 @@
                 */
                messages: new Map(),
 
+               /**
+                * Templates associated with a module
+                * @property {mw.Map}
+                */
+               templates: new Map(),
+
                /* Public Methods */
 
                /**
@@ -1089,6 +1095,7 @@
                         */
                        function execute( module ) {
                                var key, value, media, i, urls, cssHandle, 
checkCssHandles,
+                                       templates = {},
                                        cssHandlesRegistered = false;
 
                                if ( registry[module] === undefined ) {
@@ -1168,6 +1175,12 @@
                                // Add localizations to message system
                                if ( $.isPlainObject( registry[module].messages 
) ) {
                                        mw.messages.set( 
registry[module].messages );
+                               }
+
+                               // Initialise templates
+                               if ( !$.isEmptyObject( 
registry[module].templates ) ) {
+                                       templates[module] = 
registry[module].templates;
+                                       mw.templates.set( templates );
                                }
 
                                if ( $.isReady || registry[module].async ) {
@@ -1660,8 +1673,9 @@
                                 * whether it's safe to extend the stylesheet 
(see #canExpandStylesheetWith).
                                 *
                                 * @param {Object} msgs List of key/value pairs 
to be added to mw#messages.
+                                * @param {Object} templates List of key/value 
pairs to be added to mw#templates (optional)
                                 */
-                               implement: function ( module, script, style, 
msgs ) {
+                               implement: function ( module, script, style, 
msgs, templates ) {
                                        // Validate input
                                        if ( typeof module !== 'string' ) {
                                                throw new Error( 'module must 
be a string, not a ' + typeof module );
@@ -1675,6 +1689,9 @@
                                        if ( !$.isPlainObject( msgs ) ) {
                                                throw new Error( 'msgs must be 
an object, not a ' + typeof msgs );
                                        }
+                                       if ( templates && !$.isPlainObject( 
templates ) ) {
+                                               throw new Error( 'templates 
must be an object, not a ' + typeof templates );
+                                       }
                                        // Automatically register module
                                        if ( registry[module] === undefined ) {
                                                mw.loader.register( module );
@@ -1687,6 +1704,7 @@
                                        registry[module].script = script;
                                        registry[module].style = style;
                                        registry[module].messages = msgs;
+                                       registry[module].templates = templates;
                                        // The module may already have been 
marked as erroneous
                                        if ( $.inArray( registry[module].state, 
['error', 'missing'] ) === -1 ) {
                                                registry[module].state = 
'loaded';
diff --git a/resources/src/mediawiki/mediawiki.templates.js 
b/resources/src/mediawiki/mediawiki.templates.js
new file mode 100644
index 0000000..a6d26e6
--- /dev/null
+++ b/resources/src/mediawiki/mediawiki.templates.js
@@ -0,0 +1,120 @@
+/**
+ * @class mw.template
+ * @singleton
+ */
+( function ( mw ) {
+       var compiledTemplates = {},
+               compilers = {};
+
+       mw.template = {
+               /**
+                * Register a new compiler and template
+                * @method
+                * @param {String} name of compiler. Should also match with any 
file extensions of templates that want to use it.
+                * @param {Function} compiler which must implement a compile 
function
+                */
+               registerCompiler: function ( name, compiler ) {
+                       if ( compiler.compile ) {
+                               compilers[name] = compiler;
+                       } else {
+                               throw new Error( 'Template compiler must 
implement compile function.' );
+                       }
+               },
+               /**
+                * Work out which compiler is associated with the template 
based on its suffix
+                * @method
+                * @param {String} templateName Name of template to add 
including file extension
+                * @return {Function} compiler
+                */
+               getCompilerFromName: function ( templateName ) {
+                       var templateParts = templateName.split( '.' ), compiler,
+                               ext;
+
+                       if ( templateParts.length > 1 ) {
+                               ext = templateParts[ templateParts.length - 1 ];
+                               if ( compilers[ ext ] ) {
+                                       compiler = compilers[ ext ];
+                               } else {
+                                       throw new Error( 'Template compiler not 
found for: ' + ext );
+                               }
+                       } else {
+                               throw new Error( 'Template has no suffix. 
Unable to identify compiler.' );
+                       }
+                       return compiler;
+               },
+               /**
+                * Define a template. Compiles newly added templates based on
+                * the file extension of name and the available compilers.
+                * @method
+                * @param {String} moduleName Name of RL module to get template 
from
+                * @param {String} templateName Name of template to add 
including file extension
+                * @param {String} markup Associated markup (html)
+                * @return {Function} compiled template
+                */
+               add: function ( moduleName, templateName, markup ) {
+                       var compiledTemplate,
+                               compiler = this.getCompilerFromName( 
templateName );
+
+                       // check module has a compiled template cache
+                       compiledTemplates[moduleName] = 
compiledTemplates[moduleName] || {};
+
+                       compiledTemplate = compiler.compile( markup );
+                       compiledTemplates[moduleName][ templateName ] = 
compiledTemplate;
+                       return compiledTemplate;
+               },
+               /**
+                * Retrieve defined template
+                *
+                * @method
+                * @param {string} name Name of template to be retrieved
+                * @return {Object} template compiler
+                * accepts template data object as its argument.
+                */
+               get: function ( moduleName, templateName ) {
+                       var moduleTemplates;
+
+                       // check if the template has already been compiled, 
compile it if not
+                       if ( !compiledTemplates[ moduleName ] || 
!compiledTemplates[ moduleName ][ templateName ] ) {
+                               moduleTemplates = mw.templates.get( moduleName 
);
+                               if ( !moduleTemplates ) {
+                                       throw new Error( 'No templates 
associated with module: ' + moduleName );
+                               }
+
+                               if ( moduleTemplates[ templateName ] ) {
+                                       // add compiled version
+                                       this.add( moduleName, templateName, 
moduleTemplates[ templateName ] );
+                               } else {
+                                       throw new Error( 'Template in module ' 
+ moduleName + ' not found: ' + templateName );
+                               }
+                       }
+                       return compiledTemplates[ moduleName ][ templateName ];
+               },
+               /**
+                * Wraps our template engine of choice
+                * @method
+                * @param {string} templateBody Template body.
+                * @param {string} compilerName The name of a registered 
compiler
+                * @return {Object} template interface
+                * accepts template data object as its argument.
+                */
+               compile: function ( templateBody, compilerName ) {
+                       var compiler = compilers[ compilerName ];
+                       if ( !compiler ) {
+                               throw new Error( 'Unknown compiler ' + 
compilerName );
+                       }
+                       return compiler.compile( templateBody );
+               }
+       };
+
+       // Register basic html compiler
+       mw.template.registerCompiler( 'html', {
+               compile: function ( src ) {
+                       return {
+                               render: function () {
+                                       return src;
+                               }
+                       };
+               }
+       } );
+
+}( mediaWiki ) );
diff --git a/resources/src/mediawiki/templates/dialog.html 
b/resources/src/mediawiki/templates/dialog.html
new file mode 100644
index 0000000..e116f3e
--- /dev/null
+++ b/resources/src/mediawiki/templates/dialog.html
@@ -0,0 +1,25 @@
+<div style="position: relative; display: block;" class="ui-dialog-content 
ui-widget-content">
+       <div class="feedback-mode feedback-form">
+               <small><p></p></small>
+               <div class="subject" style="margin-top: 1em;">
+                       <span></span><br>
+                       <input type="text" class="feedback-subject" 
name="subject" maxlength="60"
+                               style="width: 100%; -moz-box-sizing: 
border-box; -webkit-box-sizing: border-box; box-sizing: border-box;">
+               </div>
+               <div class="message" style="margin-top: 0.4em;">
+                       <span></span><br>
+                       <textarea name="message" class="feedback-message" 
rows="5" cols="60"></textarea>
+               </div>
+       </div>
+       <div class="feedback-mode feedback-bugs">
+               <p></p>
+       </div>
+       <div class="feedback-mode feedback-submitting" style="text-align: 
center; margin: 3em 0;">
+               <span></span><br>
+               <span class="feedback-spinner"></span>
+       </div>
+       <div class="feedback-mode feedback-thanks" style="text-align: center; 
margin:1em"></div>
+       <div class="feedback-mode feedback-error" style="position: relative;">
+               <div class="feedback-error-msg" style=" color:#990000; 
margin-top:0.4em;"></div>
+       </div>
+</div>
diff --git a/tests/phpunit/includes/OutputPageTest.php 
b/tests/phpunit/includes/OutputPageTest.php
index d7e8cd3..89d1de7 100644
--- a/tests/phpunit/includes/OutputPageTest.php
+++ b/tests/phpunit/includes/OutputPageTest.php
@@ -172,7 +172,7 @@
                        array(
                                array( 'test.quux', 
ResourceLoaderModule::TYPE_COMBINED ),
                                '<script>if(window.mw){
-mw.loader.implement("test.quux",function($,jQuery){mw.test.baz({token:123});},{"css":[".mw-icon{transition:none}\n"]},{});
+mw.loader.implement("test.quux",function($,jQuery){mw.test.baz({token:123});},{"css":[".mw-icon{transition:none}\n"]},{},{});
 
 }</script>
 '
diff --git 
a/tests/phpunit/includes/resourceloader/ResourceLoaderFileModuleTest.php 
b/tests/phpunit/includes/resourceloader/ResourceLoaderFileModuleTest.php
new file mode 100644
index 0000000..474a01b
--- /dev/null
+++ b/tests/phpunit/includes/resourceloader/ResourceLoaderFileModuleTest.php
@@ -0,0 +1,96 @@
+<?php
+
+/**
+ * @group ResourceLoader
+ */
+class ResourceLoaderFileModuleTest extends MediaWikiTestCase {
+       static function getModules() {
+               $base = array(
+                       'localBasePath' => realpath( dirname( __FILE__ ) ),
+               );
+
+               return array(
+                       'noTemplateModule' => array(),
+
+                       'htmlTemplateModule' => $base + array(
+                               'templates' => array(
+                                       'templates/template.html',
+                                       'templates/template2.html',
+                               )
+                       ),
+
+                       'aliasedHtmlTemplateModule' => $base + array(
+                               'templates' => array(
+                                       'foo.html' => 'templates/template.html',
+                                       'bar.html' => 
'templates/template2.html',
+                               )
+                       ),
+
+                       'templateModuleHandlebars' => $base + array(
+                               'templates' => array(
+                                       'templates/template_awesome.handlebars',
+                               ),
+                       ),
+               );
+       }
+
+       public function providerGetTemplates() {
+               $modules = self::getModules();
+
+               return array(
+                       array(
+                               $modules['noTemplateModule'],
+                               array(),
+                       ),
+                       array(
+                               $modules['templateModuleHandlebars'],
+                               array(
+                                       'templates/template_awesome.handlebars' 
=> "wow\n",
+                               ),
+                       ),
+                       array(
+                               $modules['htmlTemplateModule'],
+                               array(
+                                       'templates/template.html' => 
"<strong>hello</strong>\n",
+                                       'templates/template2.html' => 
"<div>goodbye</div>\n",
+                               ),
+                       ),
+                       array(
+                               $modules['aliasedHtmlTemplateModule'],
+                               array(
+                                       'foo.html' => 
"<strong>hello</strong>\n",
+                                       'bar.html' => "<div>goodbye</div>\n",
+                               ),
+                       ),
+               );
+       }
+
+       public function providerGetModifiedTime() {
+               $modules = self::getModules();
+
+               return array(
+                       // Check the default value when no templates present in 
module is 1
+                       array( $modules['noTemplateModule'], 1 ),
+               );
+       }
+
+       // tests
+       /**
+        * @dataProvider providerGetTemplates
+        */
+       public function testGetTemplates( $module, $expected ) {
+               $rl = new ResourceLoaderFileModule( $module );
+
+               $this->assertEquals( $rl->getTemplates(), $expected );
+       }
+
+       /**
+        * @dataProvider providerGetModifiedTime
+        */
+       public function testGetModifiedTime( $module, $expected ) {
+               $rl = new ResourceLoaderFileModule( $module );
+               $ts = $rl->getModifiedTime( new ResourceLoaderContext(
+                       new ResourceLoader, new WebRequest() ) );
+               $this->assertEquals( $ts, $expected );
+       }
+}
diff --git a/tests/phpunit/includes/resourceloader/templates/template.html 
b/tests/phpunit/includes/resourceloader/templates/template.html
new file mode 100644
index 0000000..1f6a7d2
--- /dev/null
+++ b/tests/phpunit/includes/resourceloader/templates/template.html
@@ -0,0 +1 @@
+<strong>hello</strong>
diff --git a/tests/phpunit/includes/resourceloader/templates/template2.html 
b/tests/phpunit/includes/resourceloader/templates/template2.html
new file mode 100644
index 0000000..a322f67
--- /dev/null
+++ b/tests/phpunit/includes/resourceloader/templates/template2.html
@@ -0,0 +1 @@
+<div>goodbye</div>
diff --git 
a/tests/phpunit/includes/resourceloader/templates/template_awesome.handlebars 
b/tests/phpunit/includes/resourceloader/templates/template_awesome.handlebars
new file mode 100644
index 0000000..5f5c07d
--- /dev/null
+++ 
b/tests/phpunit/includes/resourceloader/templates/template_awesome.handlebars
@@ -0,0 +1 @@
+wow
diff --git a/tests/qunit/QUnitTestResources.php 
b/tests/qunit/QUnitTestResources.php
index 34007ed..44d6efc 100644
--- a/tests/qunit/QUnitTestResources.php
+++ b/tests/qunit/QUnitTestResources.php
@@ -66,6 +66,7 @@
                        
'tests/qunit/suites/resources/mediawiki/mediawiki.jscompat.test.js',
                        
'tests/qunit/suites/resources/mediawiki/mediawiki.test.js',
                        
'tests/qunit/suites/resources/mediawiki/mediawiki.Title.test.js',
+                       
'tests/qunit/suites/resources/mediawiki/mediawiki.template.test.js',
                        
'tests/qunit/suites/resources/mediawiki/mediawiki.toc.test.js',
                        
'tests/qunit/suites/resources/mediawiki/mediawiki.Uri.test.js',
                        
'tests/qunit/suites/resources/mediawiki/mediawiki.user.test.js',
diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.template.test.js 
b/tests/qunit/suites/resources/mediawiki/mediawiki.template.test.js
new file mode 100644
index 0000000..96413f1
--- /dev/null
+++ b/tests/qunit/suites/resources/mediawiki/mediawiki.template.test.js
@@ -0,0 +1,51 @@
+( function ( mw, $ ) {
+
+       QUnit.module( 'Templates', {
+               setup: function () {
+                       var abcCompiler = {
+                               registerPartial: $.noop,
+                               compile: function () {
+                                       return 'abc default compiler';
+                               }
+                       };
+                       // Register some template compiler languages
+                       mw.template.registerCompiler( 'abc', abcCompiler );
+                       mw.template.registerCompiler( 'xyz', {
+                               registerPartial: $.noop,
+                               compile: function () {
+                                       return 'xyz compiler';
+                               }
+                       } );
+
+                       // register some templates
+                       mw.templates.set( {
+                               'test.mediawiki.templates': {
+                                       'test_templates_foo.xyz': 'goodbye',
+                                       'test_templates_foo.abc': 'thankyou'
+                               }
+                       } );
+               }
+       } );
+
+       QUnit.test( 'Template, getCompiler - default case', 4, function ( 
assert ) {
+               assert.throws( function () {
+                               mw.template.add( 'module', 
'test_templates_foo', 'hello' );
+                       }, 'When no prefix throw exception.' );
+               assert.throws( function () {
+                               mw.template.compile( '{{foo}}', 'rainbow' );
+                       }, 'Unknown compiler names throw exceptions.' );
+               assert.strictEqual( mw.template.get( 
'test.mediawiki.templates', 'test_templates_foo.xyz' ), 'xyz compiler' );
+               assert.strictEqual( mw.template.get( 
'test.mediawiki.templates', 'test_templates_foo.abc' ), 'abc default compiler' 
);
+       } );
+
+       QUnit.test( 'Template, get module that is not loaded.', 2, function ( 
assert ) {
+               assert.throws( function () {
+                               mw.template.get( 'this.should.not.exist', 
'hello' );
+                       }, 'When bad module name given throw error.' );
+
+               assert.throws( function () {
+                               mw.template.get( 'mediawiki.templates', 'hello' 
);
+                       }, 'The template hello should not exist in the 
mediawiki.templates module and should throw an exception.' );
+       } );
+
+}( mediaWiki, jQuery ) );

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

Gerrit-MessageType: newchange
Gerrit-Change-Id: Ia0c5c8ec960aa6dff12c9626cee41ae9a3286b76
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/core
Gerrit-Branch: master
Gerrit-Owner: Jdlrobson <jrob...@wikimedia.org>

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

Reply via email to