Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ test: ## Run the pure-PHP test suites
php tests/QuotesTest.php
php tests/RevisionOverridesTest.php
php tests/IgnoreHtmlClassTest.php
php tests/JsDefaultsTest.php

phpcs: vendor/autoload.php ## parallel-lint + minus-x + phpcs against the MediaWiki coding standard
composer test
Expand Down
53 changes: 43 additions & 10 deletions resources/ext.SimpleMathJax.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,51 @@ mw.libs.smj = mw.libs.smj || {};
var loaded = false;
var scriptLoadPromise = null;

// Defaults match extension.json. The page HTML that carries these settings
// can be cached (CDN, reverse proxy) separately from this module, so after an
// upgrade the two may come from different versions; fall back instead of
// failing to render any formula.
var defaults = {
wgSmjCdnEnabled: true,
wgSmjCdnVersion: '4',
wgSmjDelimitersEnabled: false,
wgSmjDelimitersInlineMath: [],
wgSmjDelimitersDisplayMath: [],
wgSmjIgnoreHtmlClass: 'mathjax_ignore|comment|diff-(context|addedline|deletedline)',
wgSmjScale: 1
};

// Pre-1.0 names of settings that were only renamed (see docs/mig-1.0.md),
// consulted for pages cached before the upgrade. Settings whose meaning or
// default changed in 1.0 (e.g. delimiters) are not mapped.
var legacyNames = {
wgSmjCdnEnabled: 'wgSmjUseCdn'
};

function isSet(value) {
return value !== null && value !== undefined;
}

function config(name) {
var value = mw.config.get(name);
if (!isSet(value) && legacyNames[name]) {
value = mw.config.get(legacyNames[name]);
}
return isSet(value) ? value : defaults[name];
}

function ensureLoaded() {
if (loaded) {
return;
}
loaded = true;
window.MathJax = {
tex: {
inlineMath: mw.config.get('wgSmjDelimitersInlineMath').concat([['[math]','[/math]']]),
displayMath: mw.config.get('wgSmjDelimitersDisplayMath'),
inlineMath: config('wgSmjDelimitersInlineMath').concat([['[math]','[/math]']]),
displayMath: config('wgSmjDelimitersDisplayMath'),
processEnvironments: true,
processRefs: mw.config.get('wgSmjDelimitersEnabled'),
processEscapes: mw.config.get('wgSmjDelimitersEnabled'),
processRefs: config('wgSmjDelimitersEnabled'),
processEscapes: config('wgSmjDelimitersEnabled'),
packages: mw.config.exists('smjPreloadChem') ? {'[+]': ['autoload','mhchem']} : {'[+]': ['autoload']},
macros: {
AA: "{\u00c5}",
Expand Down Expand Up @@ -124,17 +157,17 @@ function ensureLoaded() {
}
},
options: {
ignoreHtmlClass: mw.config.get('wgSmjIgnoreHtmlClass'),
ignoreHtmlClass: config('wgSmjIgnoreHtmlClass'),
processHtmlClass: "mathjax_process|smj-container"
},
chtml: {
scale: mw.config.get('wgSmjScale'),
scale: config('wgSmjScale'),
},
loader: {
load: ['ui/safe','[tex]/autoload'].concat(mw.config.exists('smjPreloadChem') ? ['[tex]/mhchem'] : [])
},
startup: {
elements: mw.config.get('wgSmjDelimitersEnabled') ? null : ["span.smj-container"],
elements: config('wgSmjDelimitersEnabled') ? null : ["span.smj-container"],
pageReady: () => {
return MathJax.startup.defaultPageReady().then(() => {
document.querySelectorAll("span.smj-container > .MathJax").forEach((mjx) => {
Expand All @@ -146,8 +179,8 @@ function ensureLoaded() {
};

var script = document.createElement('script');
script.src = mw.config.get('wgSmjCdnEnabled')
? 'https://cdn.jsdelivr.net/npm/mathjax@' + mw.config.get('wgSmjCdnVersion') + '/tex-chtml.js'
script.src = config('wgSmjCdnEnabled')
? 'https://cdn.jsdelivr.net/npm/mathjax@' + config('wgSmjCdnVersion') + '/tex-chtml.js'
: mw.config.get('wgExtensionAssetsPath') + '/SimpleMathJax/resources/MathJax/tex-chtml.js';
script.async = true;
scriptLoadPromise = new Promise((resolve, reject) => {
Expand Down Expand Up @@ -183,7 +216,7 @@ mw.hook('wikipage.content').add(function ($content) {
return;
}
var $containers = $content.filter('.smj-container').add($content.find('.smj-container'));
var delimitersEnabled = mw.config.get('wgSmjDelimitersEnabled');
var delimitersEnabled = config('wgSmjDelimitersEnabled');
if (!delimitersEnabled && !$containers.length) {
return;
}
Expand Down
62 changes: 62 additions & 0 deletions tests/JsDefaultsTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php
// The client module falls back to its own defaults when the page HTML does not
// carry a setting (e.g. HTML cached before an upgrade). Keep those defaults in
// sync with extension.json.

$failures = 0;

function assert_same( $name, $expected, $actual ) {
global $failures;
if ( $expected === $actual ) {
echo "ok $name\n";
return;
}
$failures++;
echo "FAIL $name\n";
echo ' expected: ' . var_export( $expected, true ) . "\n";
echo ' actual: ' . var_export( $actual, true ) . "\n";
}

$extension = json_decode( file_get_contents( __DIR__ . '/../extension.json' ), true );
$js = file_get_contents( __DIR__ . '/../resources/ext.SimpleMathJax.js' );

if ( !preg_match( '/var defaults = (\{.*?\});/s', $js, $m ) ) {
echo "FAIL defaults object not found in ext.SimpleMathJax.js\n";
exit( 1 );
}
// The object literal uses unquoted keys and single-quoted strings.
$json = preg_replace( '/^(\s*)(\w+):/m', '$1"$2":', $m[1] );
$json = preg_replace_callback( "/'((?:[^'\\\\]|\\\\.)*)'/", static function ( $s ) {
return json_encode( stripcslashes( $s[1] ) );
}, $json );
$defaults = json_decode( $json, true );
assert_same( 'defaults object parses', true, is_array( $defaults ) );

// Every setting the module reads must have a default.
preg_match_all( "/config\\('(wgSmj\\w+)'\\)/", $js, $reads );
foreach ( array_unique( $reads[1] ) as $name ) {
assert_same( "$name has a client default", true, array_key_exists( $name, $defaults ?? [] ) );
}

foreach ( $defaults ?? [] as $name => $value ) {
$key = substr( $name, 2 );
$expected = $extension['config'][$key]['value'] ?? null;
assert_same( "$name default matches extension.json", $expected, $value );
}

// Legacy names are only for settings the migration guide lists as renamed.
if ( preg_match( '/var legacyNames = (\{.*?\});/s', $js, $m ) ) {
preg_match_all( "/(\w+): '(\w+)'/", $m[1], $pairs, PREG_SET_ORDER );
$guide = file_get_contents( __DIR__ . '/../docs/mig-1.0.md' );
foreach ( $pairs as [ , $name, $legacy ] ) {
assert_same( "$name has a client default", true, array_key_exists( $name, $defaults ?? [] ) );
assert_same(
"$legacy -> $name is a rename in docs/mig-1.0.md",
1,
preg_match( '/\| `\$' . $legacy . '` \| `\$' . $name . '` \|/', $guide )
);
}
}

echo $failures === 0 ? "\nAll tests passed.\n" : "\n$failures test(s) FAILED.\n";
exit( $failures === 0 ? 0 : 1 );
Loading