From: jdlrobson Date: Fri, 10 Oct 2014 00:07:14 +0000 (-0700) Subject: resourceloader: Add support for delivering templates X-Git-Tag: 1.31.0-rc.0~13450^2 X-Git-Url: http://git.cyclocoop.org/%22%2C%20generer_url_ecrire%28?a=commitdiff_plain;h=ebeb2972360516dff6b734079dc1c8cc0d3bd390;p=lhc%2Fweb%2Fwiklou.git resourceloader: Add support for delivering templates A base ResourceLoaderModule::getTemplates() exists for subclasses to override. An implementation is provided for ResourceLoaderFileModule. For file modules, templates can be specified in the following manner: 'example' => array( 'templates' => array( 'bar' => 'templates/foo.html', ), 'scripts' => 'example.js', ), The delivery system is template language agnostic, and currently only supports "compiling" plain HTML templates. This also adds template support to the following modules as a POC: * mediawiki.feedback * mediawiki.action.view.postEdit * mediawiki.special.upload Works with $wgResourceLoaderStorageEnabled Change-Id: Ia0c5c8ec960aa6dff12c9626cee41ae9a3286b76 --- diff --git a/includes/resourceloader/ResourceLoader.php b/includes/resourceloader/ResourceLoader.php index 57deb00d21..eecb9366ce 100644 --- a/includes/resourceloader/ResourceLoader.php +++ b/includes/resourceloader/ResourceLoader.php @@ -974,12 +974,20 @@ class ResourceLoader { case 'messages': $out .= self::makeMessageSetScript( new XmlJsCode( $messagesBlob ) ); break; + case 'templates': + $out .= Xml::encodeJsCall( + 'mw.templates.set', + array( $name, (object)$module->getTemplates() ), + ResourceLoader::inDebugMode() + ); + break; default: $out .= self::makeLoaderImplementScript( $name, $scripts, $styles, - new XmlJsCode( $messagesBlob ) + new XmlJsCode( $messagesBlob ), + $module->getTemplates() ); break; } @@ -1044,15 +1052,20 @@ class ResourceLoader { * @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 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 + ) { if ( is_string( $scripts ) ) { $scripts = new XmlJsCode( "function ( $, jQuery ) {\n{$scripts}\n}" ); } elseif ( !is_array( $scripts ) ) { throw new MWException( 'Invalid scripts error. Array of URLs or string of code expected.' ); } + return Xml::encodeJsCall( 'mw.loader.implement', array( @@ -1064,7 +1077,8 @@ class ResourceLoader { // 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 7bbc9bbe6a..d4e8159341 100644 --- a/includes/resourceloader/ResourceLoaderFileModule.php +++ b/includes/resourceloader/ResourceLoaderFileModule.php @@ -34,6 +34,9 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { /** @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: @@ -199,6 +202,9 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { * 'loaderScripts' => [file path string or array of file path strings], * // Modules which must be loaded before this module * 'dependencies' => [module name string or array of module name strings], + * 'templates' => array( + * [template alias with file.ext] => [file path to a template file], + * ), * // Styles to always load * 'styles' => [file path string or array of file path strings], * // Styles to include in specific skin contexts @@ -223,6 +229,8 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { $localBasePath = null, $remoteBasePath = null ) { + // Flag to decide whether to automagically add the mediawiki.template module + $hasTemplates = false; // localBasePath and remoteBasePath both have unbelievably long fallback chains // and need to be handled separately. list( $this->localBasePath, $this->remoteBasePath ) = @@ -238,6 +246,10 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { case 'styles': $this->{$member} = (array)$option; break; + case 'templates': + $hasTemplates = true; + $this->{$member} = (array)$option; + break; // Collated lists of file paths case 'languageScripts': case 'skinScripts': @@ -281,6 +293,9 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { break; } } + if ( $hasTemplates ) { + $this->dependencies[] = 'mediawiki.template'; + } } /** @@ -535,6 +550,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { $files = array_merge( $files, $this->scripts, + $this->templates, $context->getDebug() ? $this->debugScripts : array(), $this->getLanguageScripts( $context->getLanguage() ), self::tryForKey( $this->skinScripts, $context->getSkin(), 'default' ), @@ -590,6 +606,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { 'dependencies', 'messages', 'targets', + 'templates', 'group', 'position', 'skipFunction', @@ -959,4 +976,30 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { protected function getLessCompiler( ResourceLoaderContext $context = null ) { return ResourceLoader::getLessCompiler( $this->getConfig() ); } + + /** + * Takes named templates by the module and returns an array mapping. + * + * @return array of templates mapping template alias to content + */ + public 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; + } else { + $msg = __METHOD__ . ": template file not found: \"$localPath\""; + wfDebugLog( 'resourceloader', $msg ); + throw new MWException( $msg ); + } + } + return $templates; + } } diff --git a/includes/resourceloader/ResourceLoaderModule.php b/includes/resourceloader/ResourceLoaderModule.php index 45eb70f86b..4c49fae5cb 100644 --- a/includes/resourceloader/ResourceLoaderModule.php +++ b/includes/resourceloader/ResourceLoaderModule.php @@ -134,6 +134,16 @@ abstract class ResourceLoaderModule { return ''; } + /** + * Takes named templates by the module and returns an array mapping. + * + * @return array of templates mapping template alias to content + */ + 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 d6163bdea2..145749a74b 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 ca90efa8dc..b2dfe1e6f9 100644 --- a/resources/Resources.php +++ b/resources/Resources.php @@ -779,6 +779,10 @@ return array( 'mediawiki.hlist', ), ), + 'mediawiki.template' => array( + 'scripts' => 'resources/src/mediawiki/mediawiki.template.js', + 'targets' => array( 'desktop', 'mobile' ), + ), 'mediawiki.apipretty' => array( 'styles' => 'resources/src/mediawiki/mediawiki.apipretty.css', 'targets' => array( 'desktop', 'mobile' ), @@ -851,6 +855,9 @@ return array( '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 +1070,9 @@ return array( ), ), '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 +1347,9 @@ return array( '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', diff --git a/resources/src/mediawiki.action/mediawiki.action.view.postEdit.js b/resources/src/mediawiki.action/mediawiki.action.view.postEdit.js index 4d2c47a5d5..95ef62cea5 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 = 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 0000000000..dbb482a667 --- /dev/null +++ b/resources/src/mediawiki.action/templates/postEdit.html @@ -0,0 +1,6 @@ +
+
+
+ × +
+
diff --git a/resources/src/mediawiki.special/mediawiki.special.upload.js b/resources/src/mediawiki.special/mediawiki.special.upload.js index 04bc97879d..87de6464b8 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 = $( '
' + - '
' + - '
' + - '
' + - '
' + - '
' ); + 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/templates/thumbnail.html b/resources/src/mediawiki.special/templates/thumbnail.html new file mode 100644 index 0000000000..73042f2433 --- /dev/null +++ b/resources/src/mediawiki.special/templates/thumbnail.html @@ -0,0 +1,9 @@ +
+
+
+
+
+
+
+
+
diff --git a/resources/src/mediawiki/mediawiki.feedback.js b/resources/src/mediawiki/mediawiki.feedback.js index 1c0d8332e6..6bcb87fdd6 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 = - $( '
' ).append( - $( '
' ).append( - $( '' ).append( - $( '

' ).msg( - 'feedback-bugornote', - $bugNoteLink, - fb.title.getNameText(), - $feedbackPageLink.clone() - ) - ), - $( '

' ) - .msg( 'feedback-subject' ) - .append( - $( '
' ), - $( '' ) - ), - $( '
' ) - .msg( 'feedback-message' ) - .append( - $( '
' ), - $( '' ) - ) - ), - $( '' ).append( - $( '

' ).msg( 'feedback-bugcheck', $bugsListLink ) - ), - $( '

' ) - .msg( 'feedback-adding' ) - .append( - $( '
' ), - $( '' ) - ), - $( '' ).msg( - 'feedback-thanks', fb.title.getNameText(), $feedbackPageLink.clone() - ), - $( '' ).append( - $( '' ) - ) - ); + // 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 e29c734d9b..a709fe59f5 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 */ /** @@ -1170,6 +1176,11 @@ mw.messages.set( registry[module].messages ); } + // Initialise templates + if ( registry[module].templates ) { + mw.templates.set( module, registry[module].templates ); + } + if ( $.isReady || registry[module].async ) { // Make sure we don't run the scripts until all (potentially asynchronous) // stylesheet insertions have completed. @@ -1660,8 +1671,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. */ - 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 +1687,9 @@ if ( !$.isPlainObject( msgs ) ) { throw new Error( 'msgs must be an object, not a ' + typeof msgs ); } + if ( templates !== undefined && !$.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 +1702,8 @@ registry[module].script = script; registry[module].style = style; registry[module].messages = msgs; + // Templates are optional (for back-compat) + 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'; @@ -2057,7 +2074,8 @@ // Unversioned, private, or site-/user-specific ( !descriptor.version || $.inArray( descriptor.group, [ 'private', 'user', 'site' ] ) !== -1 ) || // Partial descriptor - $.inArray( undefined, [ descriptor.script, descriptor.style, descriptor.messages ] ) !== -1 + $.inArray( undefined, [ descriptor.script, descriptor.style, + descriptor.messages, descriptor.templates ] ) !== -1 ) { // Decline to store return false; @@ -2070,7 +2088,8 @@ String( descriptor.script ) : JSON.stringify( descriptor.script ), JSON.stringify( descriptor.style ), - JSON.stringify( descriptor.messages ) + JSON.stringify( descriptor.messages ), + JSON.stringify( descriptor.templates ) ]; // Attempted workaround for a possible Opera bug (bug 57567). // This regex should never match under sane conditions. diff --git a/resources/src/mediawiki/mediawiki.template.js b/resources/src/mediawiki/mediawiki.template.js new file mode 100644 index 0000000000..79f43d1bd4 --- /dev/null +++ b/resources/src/mediawiki/mediawiki.template.js @@ -0,0 +1,123 @@ +/** + * @class mw.template + * @singleton + */ +( function ( mw, $ ) { + var compiledTemplates = {}, + compilers = {}; + + mw.template = { + /** + * Register a new compiler and template. + * + * @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 ) { + throw new Error( 'Compiler must implement compile method.' ); + } + compilers[name] = compiler; + }, + + /** + * Get the name of the compiler associated with a template based on its name. + * + * @param {string} templateName Name of template (including file suffix) + * @return {String} Name of compiler + */ + getCompilerName: function ( templateName ) { + var templateParts = templateName.split( '.' ); + + if ( templateParts.length < 2 ) { + throw new Error( 'Unable to identify compiler. Template name must have a suffix.' ); + } + return templateParts[ templateParts.length - 1 ]; + }, + + /** + * Get the compiler for a given compiler name. + * + * @param {string} compilerName Name of the compiler + * @return {Object} The compiler associated with that name + */ + getCompiler: function ( compilerName ) { + var compiler = compilers[ compilerName ]; + if ( !compiler ) { + throw new Error( 'Unknown compiler ' + compilerName ); + } + return compiler; + }, + + /** + * Register a template associated with a module. + * + * Compiles the newly added template based on the suffix in its name. + * + * @param {string} moduleName Name of ResourceLoader module to get the template from + * @param {string} templateName Name of template to add including file extension + * @param {string} templateBody Contents of a template (e.g. html markup) + * @return {Function} Compiled template + */ + add: function ( moduleName, templateName, templateBody ) { + var compiledTemplate, + compilerName = this.getCompilerName( templateName ); + + if (!compiledTemplates[moduleName]) { + compiledTemplates[moduleName] = {}; + } + + compiledTemplate = this.compile( templateBody, compilerName ); + compiledTemplates[moduleName][ templateName ] = compiledTemplate; + return compiledTemplate; + }, + + /** + * Retrieve a template by module and template name. + * + * @param {string} moduleName Name of the module to retrieve the template from + * @param {string} templateName Name of template to be retrieved + * @return {Object} Compiled template + */ + get: function ( moduleName, templateName ) { + var moduleTemplates, compiledTemplate; + + // 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 || !moduleTemplates[ templateName ] ) { + throw new Error( 'Template ' + templateName + ' not found in module ' + moduleName ); + } + + // Add compiled version + compiledTemplate = this.add( moduleName, templateName, moduleTemplates[ templateName ] ); + } else { + compiledTemplate = compiledTemplates[ moduleName ][ templateName ]; + } + return compiledTemplate; + }, + + /** + * Wrap our template engine of choice. + * + * @param {string} templateBody Template body + * @param {string} compilerName The name of a registered compiler + * @return {Object} Template interface + */ + compile: function ( templateBody, compilerName ) { + return this.getCompiler( compilerName ).compile( templateBody ); + } + }; + + // Register basic html compiler + mw.template.registerCompiler( 'html', { + compile: function ( src ) { + return { + render: function () { + return $( $.parseHTML( $.trim( src ) ) ); + } + }; + } + } ); + +}( mediaWiki, jQuery ) ); diff --git a/resources/src/mediawiki/templates/dialog.html b/resources/src/mediawiki/templates/dialog.html new file mode 100644 index 0000000000..e116f3eecf --- /dev/null +++ b/resources/src/mediawiki/templates/dialog.html @@ -0,0 +1,25 @@ +
+ + + + + +
diff --git a/tests/phpunit/includes/OutputPageTest.php b/tests/phpunit/includes/OutputPageTest.php index d7e8cd31b8..89d1de7bf3 100644 --- a/tests/phpunit/includes/OutputPageTest.php +++ b/tests/phpunit/includes/OutputPageTest.php @@ -172,7 +172,7 @@ mw.test.baz({token:123});mw.loader.state({"test.quux":"ready"}); array( array( 'test.quux', ResourceLoaderModule::TYPE_COMBINED ), ' ' diff --git a/tests/phpunit/includes/resourceloader/ResourceLoaderFileModuleTest.php b/tests/phpunit/includes/resourceloader/ResourceLoaderFileModuleTest.php index fb436ee3b8..95da8473d3 100644 --- a/tests/phpunit/includes/resourceloader/ResourceLoaderFileModuleTest.php +++ b/tests/phpunit/includes/resourceloader/ResourceLoaderFileModuleTest.php @@ -1,5 +1,8 @@ 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 static 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' => "hello\n", + 'templates/template2.html' => "
goodbye
\n", + ), + ), + array( + $modules['aliasedHtmlTemplateModule'], + array( + 'foo.html' => "hello\n", + 'bar.html' => "
goodbye
\n", + ), + ), + ); + } + + public static function providerGetModifiedTime() { + $modules = self::getModules(); + + return array( + // Check the default value when no templates present in module is 1 + array( $modules['noTemplateModule'], 1 ), + ); + } + /** * @covers ResourceLoaderFileModule::getAllSkinStyleFiles */ @@ -58,4 +131,25 @@ class ResourceLoaderFileModuleTest extends ResourceLoaderTestCase { array_map( 'basename', $module->getAllStyleFiles() ) ); } + + /** + * @dataProvider providerGetTemplates + * @covers ResourceLoaderFileModule::getTemplates + */ + public function testGetTemplates( $module, $expected ) { + $rl = new ResourceLoaderFileModule( $module ); + + $this->assertEquals( $rl->getTemplates(), $expected ); + } + + /** + * @dataProvider providerGetModifiedTime + * @covers ResourceLoaderFileModule::getModifiedTime + */ + public function testGetModifiedTime( $module, $expected ) { + $rl = new ResourceLoaderFileModule( $module ); + $ts = $rl->getModifiedTime( new ResourceLoaderContext( + new ResourceLoader, new FauxRequest() ) ); + $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 0000000000..1f6a7d22f6 --- /dev/null +++ b/tests/phpunit/includes/resourceloader/templates/template.html @@ -0,0 +1 @@ +hello diff --git a/tests/phpunit/includes/resourceloader/templates/template2.html b/tests/phpunit/includes/resourceloader/templates/template2.html new file mode 100644 index 0000000000..a322f67de8 --- /dev/null +++ b/tests/phpunit/includes/resourceloader/templates/template2.html @@ -0,0 +1 @@ +
goodbye
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 0000000000..5f5c07d57e --- /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 34007eda56..a6fbfac5fd 100644 --- a/tests/qunit/QUnitTestResources.php +++ b/tests/qunit/QUnitTestResources.php @@ -66,6 +66,7 @@ return array( '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', @@ -106,6 +107,7 @@ return array( 'mediawiki.toc', 'mediawiki.Uri', 'mediawiki.user', + 'mediawiki.template', 'mediawiki.util', 'mediawiki.special.recentchanges', 'mediawiki.language', 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 0000000000..86fd828a9a --- /dev/null +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.template.test.js @@ -0,0 +1,63 @@ +( function ( mw ) { + + QUnit.module( 'mediawiki.template', { + setup: function () { + var abcCompiler = { + compile: function () { + return 'abc default compiler'; + } + }; + + // Register some template compiler languages + mw.template.registerCompiler( 'abc', abcCompiler ); + mw.template.registerCompiler( 'xyz', { + compile: function () { + return 'xyz compiler'; + } + } ); + + // Stub register some templates + this.sandbox.stub( mw.templates, 'get' ).returns( { + 'test_templates_foo.xyz': 'goodbye', + 'test_templates_foo.abc': 'thankyou' + } ); + } + } ); + + QUnit.test( 'add', 1, function ( assert ) { + assert.throws( + function () { + mw.template.add( 'module', 'test_templates_foo', 'hello' ); + }, + 'When no prefix throw exception' + ); + } ); + + QUnit.test( 'compile', 1, function ( assert ) { + assert.throws( + function () { + mw.template.compile( '{{foo}}', 'rainbow' ); + }, + 'Unknown compiler names throw exceptions' + ); + } ); + + QUnit.test( 'get', 4, function ( assert ) { + assert.strictEqual( mw.template.get( 'test.mediawiki.template', 'test_templates_foo.xyz' ), 'xyz compiler' ); + assert.strictEqual( mw.template.get( 'test.mediawiki.template', 'test_templates_foo.abc' ), 'abc default compiler' ); + 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.template', 'hello' ); + }, + 'The template hello should not exist in the mediawiki.templates module and should throw an exception.' + ); + } ); + +}( mediaWiki ) );