User:Evad37/WikiUnit.js
Appearance
Code that you insert on this page could contain malicious content capable of compromising your account. If you import a script from another page with "importScript", "mw.loader.load", "iusc", or "lusc", take note that this causes you to dynamically load a remote script, which could be changed by others. Editors are responsible for all edits and actions they perform, including by scripts. User scripts are not centrally supported and may malfunction or become inoperable due to software changes. A guide to help you find broken scripts is available. If you are unsure whether code you are adding to this page is safe, you can ask at the appropriate village pump. This code will be executed when previewing this page. |
![]() | This user script seems to have a documentation page at User:Evad37/WikiUnit. |
/**
* WikiUnit
* On-wiki unit testing for gadgets and user scripts, based on QUnit
*
* Tests are defined on a /testcases.js subpage of a javascript page (i.e.
* gadget or userscript). The /testcases.js script contains `QUnit.test`s,
* perhaps grouped into `QUnit.module`s. See the QUnit cookbook[1] and
* documentation[2] for more details.
* [1] https://qunitjs.com/cookbook/
* [2] https://api.qunitjs.com/
*
* To run tests, edit the script and click "Show preview" or "Show changes", or
* visit the /testcases.js subpage and click the "Run tests" tab. If changes
* have been made, tests will run against the changed (not yet saved) code.
*
* Testcases execute in the same scope as the script they test, and can access
* any functions or variables declared in the scripts outer scope. Alternatively
* properties can be added to the `window` object, which can be accessed by
* both the testcases and the script being tested – but be careful to use unique
* names that are unlikely to conflict with outher scripts.
*
* Gadgets which need to load ResourceLoader dependencies should specify those
* depenedencies in a comment in the top of the script, like the one on line 59
* of this script.
*
* For an example, see [[User:Evad37/extra/sandbox.js/testcases.js]]
*
* == Licenses ==
* This script is avialable under the following licenses (you may select the
* license of your choice):
* - CC BY-SA 3.0 License <https://wiki.riteme.site/wiki/WP:CC_BY-SA>
* - GFDL <https://wiki.riteme.site/wiki/WP:FDL>
* - MIT license (see below)
*
* MIT license
* Copyright (c) 2019 Evad37
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/* jshint esversion: 5, laxbreak: true, undef: true, maxerr:999 */
/* globals window, mw, $, OO, QUnit, importStylesheet */
// Example comment to specify ResourceLoader dependencies (usually only needed for gadgets):
/* wikiunit dependencies=mediawiki.api,mediawiki.util,oojs-ui-core,oojs-ui-windows */
// <nowiki>
$.when(
mw.loader.using([
"mediawiki.api", "mediawiki.util", "oojs-ui-core", "oojs-ui-windows",
]),
$.ready
).then(function() {
var config = {
version: "1.0.0",
mw: mw.config.get([
"wgAction",
"wgPageContentModel",
"wgPageName",
"wgRelevantUserName",
"wgUserName",
"wgServer",
"wgUserLanguage",
"wgDBname",
"skin"
]),
};
config.testPageName = config.mw.wgPageName + "/testcases.js";
config.api = new mw.Api( {
ajax: {
headers: {
"Api-User-Agent": "WikiUnit/" + config.version +
" ( https://wiki.riteme.site/wiki/User:Evad37/WikiUnit )"
}
}
} );
// Storing messages here, in English, pending a sane way of doing i18n
var msg = {
"tab-run-text": "Run tests",
"tab-run-tooltip": "Run unit tests",
"btn-previewAndTests": "Show preview & tests",
"btn-changesAndTests": "Show changes & tests",
"confirm-title": "Run unit tests?",
"confirm-message":
"The code on this page, as well as `$1`, will be executed to run "+
"unit tests. If you are unsure whether the code is safe, you can "+
"ask at the appropriate village pump.", // $1 is the page where unit tests are defined
"action-cancel-label": "Back to safety",
"action-accept-label": "Continue",
"heading-unittesting": "Unit testing – $1" // $1 is the page where unit tests are defined
};
function addRunTestsTab() {
var portlet;
var nextNode;
var runTestsUrl = mw.util.getUrl(config.mw.wgPageName.replace(/\/testcases\.js$/, ""), { action: 'submit' });
switch(config.mw.skin) {
case "monobook":
case "modern":
portlet = "p-cactions";
nextNode = "#ca-edit";
break;
case "cologneblue":
portlet = "p-pageoptions";
break;
case "minerva":
$("<a>").text( msg["tab-run-text"] ).attr({
"href": runTestsUrl,
"title": msg["tab-run-tooltip"]
}).appendTo( $(".minerva__tab-container").length
? ".minerva__tab-container"
: "#language-selector"
);
return;
default: // Vector, Timeless
portlet = "p-namespaces";
break;
}
mw.util.addPortletLink(
portlet,
runTestsUrl,
msg["tab-run-text"],
"wikiunit-runtests",
msg["tab-run-tooltip"],
"_",
nextNode
);
}
// If on a /testcases.js subpage, add a Run tests tab
if (/\/testcases\.js$/.test(config.mw.wgPageName) && config.mw.wgPageContentModel === "javascript") {
addRunTestsTab();
}
// Check if previewing a javascript page
if (config.mw.wgPageContentModel !== "javascript") {
return;
}
// Check if /testcases.js exists and is a javascript page
return config.api.get({
action: "query",
format: "json",
prop: "info|revisions",
rvprop: "content",
rvslots: "main",
titles: config.testPageName,
formatversion: "2"
}).then(function(response) {
var page = response.query.pages[0];
if (!page || page.missing || page.contentmodel !== "javascript" ) {
return;
}
addRunTestsTab();
$("#wpPreview").attr("value", msg["btn-previewAndTests"]);
$("#wpDiff").attr("value", msg["btn-changesAndTests"]);
if (config.mw.wgAction !== "submit") {
return;
}
// Warn only if not in MediaWiki namespace and not in your own namespace
var skipWarning = (page.ns === 8 || config.mw.wgRelevantUserName === config.mw.wgUserName);
return $.when(skipWarning || OO.ui.confirm(
msg["confirm-message"].replace(/\$1/g, config.testPageName),
{
title: msg["confirm-title"],
size: "medium",
actions: [
{
action: "accept",
label: msg["action-accept-label"],
flags: "progressive"
},
{
action: "cancel",
label: msg["action-cancel-label"],
flags: "safe"
},
]
})
).then(function(confirmed) {
if (!confirmed) {
return;
}
// Load QUnit. TODO: should be a hidden gadget in MediaWiki namespace
importStylesheet("User:Evad37/qunit-2.8.0.css");
mw.util.$content.prepend(
$('<div>').attr('id', 'qunit'),
$('<div>').attr('id', 'qunit-fixture')
);
window.QUnit = { config: { autostart: false } };
return mw.loader.getScript(
'https://wiki.riteme.site/w/index.php?title=' +
'User:Evad37/qunit-2.8.0.js' +
'&action=raw&ctype=text/javascript'
).then(function() {
QUnit.on( "runEnd", function() {
$('#qunit-header a').text(
msg["heading-unittesting"].replace(/\$1/g, config.testPageName)
).attr({
'href': mw.util.getUrl(config.testPageName),
target:'_blank'
});
});
// Evaluate textbox content and testcases content as javascript,
// so they can execute in the same scope. First get any
// depenedencies that are specified in a wikiunit comment.
var wikiunitComment = /^\/\*\s*wikiunit\s+(.+)\s*\*\/$/m.exec(
$("#wpTextbox1").textSelection("getContents")
);
var dependencies = wikiunitComment && wikiunitComment[1] &&
/dependencies\s*=\s*(\S+)/.exec(wikiunitComment[1]);
var dependenciesList = dependencies && dependencies[1];
// Will need to wait for depenedecies, if there are any
var wrapTop = dependenciesList
? 'mw.loader.using(["' + dependenciesList.replace(/,/g, '","') + '"]).then(function() { \n'
: "$.Deferred().resolve().then(function() { \n";
var wrapBottom = "});";
var textboxContent = $("#wpTextbox1").textSelection("getContents") + "\n";
var testcasesContent = page.revisions[0].slots.main.content + "\n";
// Eval wrapped contents
var evaluatedPromise = eval( // jshint ignore:line
wrapTop + textboxContent + testcasesContent + wrapBottom
);
if (!evaluatedPromise) {
throw new Error("[WikiUnit] Failed to evaluate scripts");
}
evaluatedPromise.then(QUnit.start);
});
});
});
}).catch(console.error);