From d146934f94ba7028fb0ad0a180786bc5856990d6 Mon Sep 17 00:00:00 2001 From: jdlrobson Date: Thu, 9 Oct 2014 17:07:14 -0700 Subject: [PATCH] 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: I6ff38c12897e3164969a1090449e626001926c3b --- includes/resourceloader/ResourceLoader.php | 19 ++- .../ResourceLoaderFileModule.php | 32 +++++ .../resourceloader/ResourceLoaderModule.php | 10 ++ maintenance/jsduck/categories.json | 3 +- resources/Resources.php | 17 ++- .../mediawiki.action.view.postEdit.js | 9 +- .../mediawiki.action/templates/postEdit.html | 6 + .../mediawiki.special.upload.js | 7 +- .../mediawiki.special.userlogin.common.js | 19 +-- .../mediawiki.special/templates/captcha.html | 9 ++ .../templates/thumbnail.html | 9 ++ resources/src/mediawiki/mediawiki.feedback.js | 55 ++------ resources/src/mediawiki/mediawiki.js | 20 ++- .../src/mediawiki/mediawiki.templates.js | 120 ++++++++++++++++++ resources/src/mediawiki/templates/dialog.html | 25 ++++ tests/phpunit/includes/OutputPageTest.php | 2 +- .../ResourceLoaderFileModuleTest.php | 96 ++++++++++++++ .../resourceloader/templates/template.html | 1 + .../resourceloader/templates/template2.html | 1 + .../templates/template_awesome.handlebars | 1 + tests/qunit/QUnitTestResources.php | 1 + .../mediawiki/mediawiki.template.test.js | 51 ++++++++ 22 files changed, 439 insertions(+), 74 deletions(-) create mode 100644 resources/src/mediawiki.action/templates/postEdit.html create mode 100644 resources/src/mediawiki.special/templates/captcha.html create mode 100644 resources/src/mediawiki.special/templates/thumbnail.html create mode 100644 resources/src/mediawiki/mediawiki.templates.js create mode 100644 resources/src/mediawiki/templates/dialog.html create mode 100644 tests/phpunit/includes/resourceloader/ResourceLoaderFileModuleTest.php create mode 100644 tests/phpunit/includes/resourceloader/templates/template.html create mode 100644 tests/phpunit/includes/resourceloader/templates/template2.html create mode 100644 tests/phpunit/includes/resourceloader/templates/template_awesome.handlebars create mode 100644 tests/qunit/suites/resources/mediawiki/mediawiki.template.test.js diff --git a/includes/resourceloader/ResourceLoader.php b/includes/resourceloader/ResourceLoader.php index 57deb00d21..a0b48520ea 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( (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 @@ 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 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 @@ 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..6544813e73 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: @@ -268,6 +271,10 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { $this->{$member} = $option; break; + // templates + case 'templates': + $this->{$member} = (array) $option; + break; // Single strings case 'group': case 'position': @@ -544,6 +551,9 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { $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 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { 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 45eb70f86b..3f53ca9ed1 100644 --- a/includes/resourceloader/ResourceLoaderModule.php +++ b/includes/resourceloader/ResourceLoaderModule.php @@ -134,6 +134,16 @@ abstract class ResourceLoaderModule { return ''; } + /** + * 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 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 acc937e2e7..e893de6a91 100644 --- a/resources/Resources.php +++ b/resources/Resources.php @@ -767,7 +767,10 @@ return array( /* 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 @@ 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 +1069,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 +1346,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', @@ -1378,6 +1390,9 @@ return array( '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 4d2c47a5d5..ee2246dde3 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..ab851b3e3f 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/mediawiki.special.userlogin.common.js b/resources/src/mediawiki.special/mediawiki.special.userlogin.common.js index 247f814133..ab02e4ecf0 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( [ - '
', - '', - '
', - '
', - '', - helpHtml, - '
', - '
' - ].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 0000000000..d30f1910ef --- /dev/null +++ b/resources/src/mediawiki.special/templates/captcha.html @@ -0,0 +1,9 @@ +
+ +
+
+ + +
+
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..01a75f2978 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..a070986bf6 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 ) { @@ -1170,6 +1177,12 @@ 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 ) { // Make sure we don't run the scripts until all (potentially asynchronous) // stylesheet insertions have completed. @@ -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 0000000000..a6d26e6497 --- /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 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 new file mode 100644 index 0000000000..474a01b0f1 --- /dev/null +++ b/tests/phpunit/includes/resourceloader/ResourceLoaderFileModuleTest.php @@ -0,0 +1,96 @@ + 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' => "hello\n", + 'templates/template2.html' => "
goodbye
\n", + ), + ), + array( + $modules['aliasedHtmlTemplateModule'], + array( + 'foo.html' => "hello\n", + 'bar.html' => "
goodbye
\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 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..44d6efcced 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', 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..96413f14a1 --- /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 ) ); -- 2.20.1