MediaWiki:Gadget-tidy-test.js
Hoppa till navigering
Hoppa till sök
OBS: Efter du har publicerat sidan kan du behöva tömma din webbläsares cache för att se ändringarna.
- Firefox / Safari: Håll ned Skift och klicka på Uppdatera sidan eller tryck Ctrl-F5 eller Ctrl-R (⌘-R på Mac)
- Google Chrome: Tryck Ctrl-Skift-R (⌘-Skift-R på Mac)
- Internet Explorer / Edge: Håll ned Ctrl och klicka på Uppdatera eller tryck Ctrl-F5
- Opera: Tryck Ctrl-F5.
// @ts-check
/**
* @typedef {import("./tidy.js").MediaWiki} MediaWiki
* @typedef {import("./tidy.js").JQuery} JQuery
* @typedef {import("./tidy.js").JQueryStatic} JQueryStatic
* @typedef {import("./tidy.js").TidyExports} TidyExports
* @typedef {import("./tidy-on-save.js").Context} Context
*
* @typedef {{
* success: true;
* expected: unknown;
* actual: unknown;
* }} OkTestResult
* @typedef {{
* success: false;
* expected: unknown;
* actual: unknown;
* error: JQuery;
* }} FailTestResult
* @typedef {OkTestResult | FailTestResult} TestResult
*/
/** @type {MediaWiki} */
var mw = /** @type {*} */ (globalThis).mw;
/** @type {JQueryStatic} */
var $ = /** @type {*} */ (globalThis).$;
/** @type {TidyExports | undefined} */
var tidy;
if (mw.config.get("wgPageName") === "Wiktionary:Finesser/Tidy/Test") {
mw.loader.using(["ext.gadget.tidy"]).then(function () {
tidy = globalThis.tidy;
var onlyPerf = location.search === "?perf";
if (!onlyPerf) {
mw.hook("wikipage.content").add(function (parent) {
$(".tidy-tests-toolbar").remove();
var toolbar = $("<div>", {
class: "tidy-tests-toolbar",
text: "Kör...",
})
.css(commonCss)
.css({ padding: "1em", display: "inline-block" });
parent.prepend(toolbar);
setTimeout(function () {
runTests(parent, toolbar);
}, 20);
});
}
mw.hook("wikipage.content").add(runPerformanceTests);
mw.hook("wikipage.content").add(showErrorReporting);
});
}
var commonCss = {
border: "1px solid #009",
marginBottom: "-1px",
paddingLeft: "0.5em",
};
var okCss = {
border: "1px solid #090",
borderLeftWidth: "0.5em",
background: "#efe",
position: "static",
zIndex: 0,
};
var failCss = {
border: "0.5em double #900",
background: "#fee",
position: "relative",
zIndex: 1,
};
/**
* @param {JQuery} parent
* @param {JQuery} toolbar
*/
function runTests(parent, toolbar) {
var okCount = 0;
var failCount = 0;
parent.find("[data-test-case]").each(function (_i, testCase) {
var $testCase = $(testCase);
var res = runTestCase($testCase);
$testCase.on("click", function () {
console.log({
expected: res.expected,
actual: res.actual,
});
});
if (res.success) {
okCount++;
$testCase.css(okCss).addClass("tidy-test-ok");
} else {
failCount++;
$testCase.css(failCss).append(res.error);
}
});
if (okCount === 0 && failCount === 0) {
toolbar.remove();
return;
}
var showSuccessful = true;
var sessionStorageKey = "tidy-test:showSuccessful";
var toggleButton = $("<button>", { text: "Dölj lyckade" }).on(
"click",
function () {
showSuccessful = !showSuccessful;
$(".tidy-test-ok").toggle();
$(this).text(showSuccessful ? "Dölj lyckade" : "Visa lyckade");
sessionStorage.setItem(sessionStorageKey, "" + showSuccessful);
}
);
if (failCount && sessionStorage.getItem(sessionStorageKey) === "false") {
toggleButton.trigger("click");
}
toolbar
.text("")
.css(failCount ? failCss : okCss)
.append(
okCount + "/" + (okCount + failCount) + " test lyckades",
failCount ? [" ", toggleButton] : ""
);
}
/**
* @param {JQuery} $testCase
* @return {TestResult}
*/
function runTestCase($testCase) {
var before = trimEnd($testCase.find("[data-test-before] pre").text());
var after = trimEnd($testCase.find("[data-test-after] pre").text());
var warnings = Array.from($testCase.find("[data-test-warnings] li"))
.map(function (x) {
return x.textContent || "";
})
.sort();
var transformCats = $testCase
.find("[data-test-transformation-category] li")
.text()
.trim()
.split(", ")
.filter(Boolean);
var key = $testCase.find("[data-test-key]").text();
if (
before &&
after &&
// `warnings` is optional.
// `transformCats` is optional.
!key
) {
return runTestCaseOnSave({
before: before,
after: after,
warnings: warnings,
transformCats: transformCats,
});
}
if (before && after && key && !warnings.length && !transformCats.length) {
return runTestCaseOnKeydown({
before: before,
after: after,
key: key,
});
}
return {
success: false,
expected: {
before: before,
after: after,
warnings: warnings,
transformCats: transformCats,
key: key,
},
actual: undefined,
error: $("<ul>").append(
$("<li>", {
text: 'För att testa "vid sparning" krävs före= och efter=.',
}),
$("<li>", {
text: 'För att testa "medan man skriver" krävs före=, efter= och tangent=.',
})
),
};
}
/**
* @param {{
* before: string;
* after: string;
* warnings: string[];
* transformCats: string[];
* }} expected
* @return {TestResult}
*/
function runTestCaseOnSave(expected) {
if (!tidy) throw new Error("tidy invariant");
/**
* @type {{
* wikitext: string;
* warnings: string[];
* transformCats: string[];
* wikitextSecondRun: string;
* }}
*/
var actual;
try {
var context = tidy.processOnSave(expected.before, tidy.getData());
var actualWikitext = trimEnd(context.unopaque(context.wikitext));
var context2 = tidy.processOnSave(actualWikitext, tidy.getData());
var actualWikitext2 = trimEnd(context2.unopaque(context2.wikitext));
if (actualWikitext === actualWikitext2) {
actualWikitext2 = "ingen ytterligare förändring";
}
actual = {
wikitext: actualWikitext,
warnings: Array.from(context.warnings).sort(),
transformCats: context.transformCats.sort(),
wikitextSecondRun: actualWikitext2,
};
if (!actual.transformCats.length) {
actual.transformCats.push("ingen");
}
} catch (e) {
actual = {
wikitext: "ERROR",
warnings: [String(e)],
transformCats: ["ERROR"],
wikitextSecondRun: "",
};
}
if (actual.wikitext === expected.before) {
actual.wikitext = "ingen förändring";
}
var wikitextOk, warningsOk, transformCatsOk, secondRunOk;
wikitextOk = actual.wikitext === expected.after;
warningsOk = actual.warnings.join() === expected.warnings.join();
if (expected.transformCats.length) {
transformCatsOk =
actual.transformCats.join() === expected.transformCats.join();
} else {
transformCatsOk = true;
}
secondRunOk = actual.wikitextSecondRun === "ingen ytterligare förändring";
if (wikitextOk && warningsOk && transformCatsOk && secondRunOk) {
return {
success: true,
expected: expected,
actual: actual,
};
}
if (!actual.warnings.length) {
actual.warnings = ["Inga"];
}
return {
success: false,
expected: expected,
actual: actual,
error: $("<div>").append(
wikitextOk ? "✅" : "❌",
" Faktiskt resultat",
$("<pre>", {
text: actual.wikitext,
}),
warningsOk ? "✅" : "❌",
" Faktiska varningar",
$("<ul>").append(
actual.warnings.map(function (x) {
return $("<li>", { text: x });
})
),
expected.transformCats.length === 0
? "➖"
: transformCatsOk
? "✅"
: "❌",
" Faktiska transformationskategorier",
$("<ul>").append($("<li>", { text: actual.transformCats.join(", ") })),
secondRunOk ? "✅" : "❌",
" Körning igen",
$("<pre>", { text: actual.wikitextSecondRun })
),
};
}
/**
* @param {{
* before: string;
* after: string;
* key: string;
* }} expected
* @return {TestResult}
*/
function runTestCaseOnKeydown(expected) {
if (!tidy) throw new Error("tidy invariant");
var CURSOR = "<markör>";
var cursor = {
before: expected.before.indexOf(CURSOR),
after: expected.after.indexOf(CURSOR),
};
if (
cursor.before === -1 ||
(cursor.after === -1 && expected.after !== "vanlig inmatning")
) {
return {
success: false,
expected: expected,
actual: undefined,
error: $("<div>", {
text:
"före= måste innehålla " +
CURSOR +
". efter= måste innehålla " +
CURSOR +
' eller vara "vanlig inmatning".',
}),
};
}
var actualInserted = tidy.processOnKeydown(
expected.key,
function () {
return {
text: expected.before.replace(CURSOR, ""),
cursor: cursor.before,
};
},
tidy.getData()
);
var actual =
actualInserted === undefined
? "vanlig inmatning"
: expected.before.slice(0, cursor.before) +
actualInserted[0] +
CURSOR +
actualInserted[1] +
expected.before.slice(cursor.before + CURSOR.length);
if (actual === expected.after) {
return { success: true, expected: expected, actual: actual };
}
return {
success: false,
expected: expected,
actual: actual,
error: $("<div>").append(
"❌ Faktiskt resultat",
$("<pre>", { text: actual })
),
};
}
/**
* @param {JQuery} parent
* @param {"start"} [autoStart]
*/
function runPerformanceTests(parent, autoStart) {
/** @type {TidyExports} */
var tidy = /** @type {*} */ (globalThis).tidy;
var data = tidy.getData();
/** Class for elements added by the perf test. Will be removed if test is restarted. */
var addedClass = "tidy-perf-test-added";
parent.find("." + addedClass).remove();
/** @type {number} */
var initTime = 0;
var startButton = $("<button>", { text: "Starta" }).on("click", start);
var perfOnlyLink = $("<a>", { text: "Endast prestandatest" }).prop({
href: location.pathname + "?perf" + location.hash,
});
var toolbar = $("<p>")
.addClass(addedClass)
.append(startButton, " ", perfOnlyLink);
parent.find("[data-test-performance]").prepend(toolbar);
if (autoStart === "start") {
start();
}
function start() {
startButton.prop({ disabled: true });
var links = Array.from(parent.find("[data-test-performance] li a"));
getPages(links).then(function (pages) {
initTime = performance.now();
process(pages, 0, 0);
});
}
/**
* @typedef {{
* output: JQuery;
* wikitext?: string;
* processCount: number;
* processTime: number;
* maxProcessTime: number;
* }} PerfPage
*
* @param {HTMLElement[]} links
* @returns {Promise<PerfPage[]>}
*/
function getPages(links) {
return Promise.all(
links.map(function (link) {
return fetch(
"https://sv.wiktionary.org/w/rest.php/v1/page/" +
encodeURIComponent(link.title)
)
.then(function (response) {
return response.json();
})
.then(function (json) {
return json.source;
})
.catch(function () {})
.then(function (source) {
var output = $("<div>").addClass(addedClass);
$(link).parent().append(output);
return {
output: output,
wikitext: source,
processCount: 0,
processTime: 0,
maxProcessTime: 0,
};
});
})
);
}
/**
* @param {PerfPage[]} pages
* @param {number} index
* @param {number} count
*/
function process(pages, index, count) {
var maxDelay = 40;
var totalCount = 500;
var now = Date.now();
for (; index < pages.length; index++, count = 0) {
var page = pages[index];
if (!page.wikitext) {
page.output.text("Kunde inte ladda sidan");
continue;
}
for (; count < totalCount; count++) {
if (now + maxDelay < Date.now()) {
// Allow the browser to regain control.
setTimeout(function () {
process(pages, index, count);
});
return;
}
var exactNow = performance.now();
var context = tidy.processOnSave(page.wikitext, data);
var diff = performance.now() - exactNow;
page.processCount++;
page.processTime += diff;
page.maxProcessTime = Math.max(page.maxProcessTime, diff);
page.output.text(
[
page.processCount + " körningar",
"max " + round(page.maxProcessTime) + " ms",
"genomsnitt " + round(page.processTime / page.processCount) + " ms",
context.warnings.size && "obs: varningar",
]
.filter(Boolean)
.join(" - ")
);
}
}
// The whole performance test is complete now.
toolbar.text("").append(
$("<button>", { text: "Start om" }).on("click", function () {
parent.find("." + addedClass).remove();
runPerformanceTests(parent, "start");
}),
" ",
perfOnlyLink,
" Testkörning: " + round((performance.now() - initTime) / 1000) + " sek"
);
}
}
/** @param {JQuery} parent */
function showErrorReporting(parent) {
var opts = mw.user.options.get();
var enabledGadgets = Object.entries(opts)
.filter(function (x) {
return x[0].startsWith("gadget-") && normalizeOpt(x[1]) === "ja";
})
.map(function (x) {
return x[0].replace("gadget-", "");
})
.join(", ");
var data = [
{ name: "Tema", val: opts.skin },
{
name: "Liveförhandsgranskning",
long: "Förhandsgranskning utan att uppdatera sidan",
val: normalizeOpt(opts.uselivepreview),
},
{ name: "Syntaxmarkering", val: normalizeOpt(opts.usecodemirror) },
{
name: "Nytt wikitextläge",
val: normalizeOpt(opts["visualeditor-newwikitext"]),
},
{ long: "Finesser", val: enabledGadgets },
{ long: "Webbläsare", val: navigator.userAgent },
];
var copyable = data
.map(function (x) {
return (x.name ? x.name + ":" : "") + x.val;
})
.join(", ");
var copyButton = $("<button>", { text: "Kopiera" }).on("click", function () {
navigator.clipboard.writeText(copyable);
copyButton.text("Kopierat");
setTimeout(function () {
copyButton.text("Kopiera");
}, 1000);
});
parent
.find("#error-reporting")
.text("")
.css(commonCss)
.css({ padding: "1em", display: "inline-block" })
.append(
$("<div>")
.append(
"Skicka också med följande information om din webbläsare och dina inställningar vid felrapportering ",
copyButton,
$("<ul>")
.css({ fontSize: "0.8em" })
.append(
data.map(function (x) {
return $("<li>", { text: (x.long || x.name) + ": " + x.val });
})
)
)
.prop({ title: copyable })
);
/** @param {*} value */
function normalizeOpt(value) {
return [0, 1, "0", "1"].includes(value) ? (+value ? "ja" : "nej") : value;
}
}
/** @param {number} num */
function round(num) {
return Math.round(100 * num) / 100;
}
/**
* Trim final newlines.
* @param {string} str
*/
function trimEnd(str) {
return str.replace(/\n+$/, "");
}