diff --git a/xblocks_contrib/__init__.py b/xblocks_contrib/__init__.py
index e7be4706..8c0c5922 100644
--- a/xblocks_contrib/__init__.py
+++ b/xblocks_contrib/__init__.py
@@ -9,4 +9,4 @@
from .video import VideoBlock
from .word_cloud import WordCloudBlock
-__version__ = "0.17.0"
+__version__ = "0.17.1"
diff --git a/xblocks_contrib/discussion/assets/static/js/common/mathjax_include.js b/xblocks_contrib/discussion/assets/static/js/common/mathjax_include.js
index 07435456..468ad704 100644
--- a/xblocks_contrib/discussion/assets/static/js/common/mathjax_include.js
+++ b/xblocks_contrib/discussion/assets/static/js/common/mathjax_include.js
@@ -1,56 +1,35 @@
-// See common/templates/mathjax_include.html for info on Fast Preview mode.
-var disableFastPreview = true,
- vendorScript;
if (typeof MathJax === 'undefined') {
- if (disableFastPreview) {
- window.MathJax = {
- menuSettings: {CHTMLpreview: false}
- };
- }
-
- vendorScript = document.createElement('script');
- vendorScript.onload = function() {
- 'use strict';
-
- var MathJax = window.MathJax,
- setMathJaxDisplayDivSettings;
- MathJax.Hub.Config({
- tex2jax: {
- inlineMath: [
- ['\\(', '\\)'],
- ['[mathjaxinline]', '[/mathjaxinline]']
- ],
- displayMath: [
- ['\\[', '\\]'],
- ['[mathjax]', '[/mathjax]']
- ]
- }
- });
- if (disableFastPreview) {
- MathJax.Hub.processSectionDelay = 0;
- }
- MathJax.Hub.signal.Interest(function(message) {
- if (message[0] === 'End Math') {
- setMathJaxDisplayDivSettings();
- }
- });
- setMathJaxDisplayDivSettings = function() {
- $('.MathJax_Display').each(function() {
- this.setAttribute('tabindex', '0');
- this.setAttribute('aria-live', 'off');
- this.removeAttribute('role');
- this.removeAttribute('aria-readonly');
- });
- };
- };
- // Automatic loading of Mathjax accessibility files
window.MathJax = {
- menuSettings: {
- collapsible: true,
- autocollapse: false,
- explorer: true
+ tex: {
+ inlineMath: [
+ ['\\(', '\\)'],
+ ['[mathjaxinline]', '[/mathjaxinline]']
+ ],
+ displayMath: [
+ ['\\[', '\\]'],
+ ['[mathjax]', '[/mathjax]']
+ ],
+ autoload: {
+ color: [],
+ colorv2: ['color']
+ },
+ packages: {'[+]': ['noerrors']}
+ },
+ options: {
+ ignoreHtmlClass: 'tex2jax_ignore',
+ processHtmlClass: 'tex2jax_process',
+ menuOptions: {
+ settings: {
+ collapsible: true,
+ explorer: true
+ },
+ },
+ },
+ loader: {
+ load: ['input/asciimath', '[tex]/noerrors']
}
};
- vendorScript.src = 'https://cdn.jsdelivr.net/npm/mathjax@2.7.5/MathJax.js?config=TeX-MML-AM_HTMLorMML';
+ var vendorScript = document.createElement('script');
+ vendorScript.src = 'https://cdn.jsdelivr.net/npm/mathjax@4.1.2/tex-mml-chtml.js';
document.body.appendChild(vendorScript);
}
diff --git a/xblocks_contrib/discussion/assets/static/js/common/utils.js b/xblocks_contrib/discussion/assets/static/js/common/utils.js
index 3f0a7a44..cc09e13f 100644
--- a/xblocks_contrib/discussion/assets/static/js/common/utils.js
+++ b/xblocks_contrib/discussion/assets/static/js/common/utils.js
@@ -493,8 +493,12 @@
};
DiscussionUtil.typesetMathJax = function(element) {
- if (typeof MathJax !== 'undefined' && MathJax !== null && typeof MathJax.Hub !== 'undefined') {
- MathJax.Hub.Queue(['Typeset', MathJax.Hub, element[0]]);
+ if (typeof MathJax !== 'undefined' && MathJax !== null &&
+ typeof MathJax.startup !== 'undefined' && MathJax.startup !== null &&
+ typeof MathJax.startup.promise !== 'undefined' &&
+ typeof MathJax.typesetPromise === 'function') {
+ MathJax.startup.promise
+ .then(() => MathJax.typesetPromise([element[0]]));
}
};
diff --git a/xblocks_contrib/discussion/assets/static/js/customwmd.js b/xblocks_contrib/discussion/assets/static/js/customwmd.js
index 0f4cfcca..13d898a5 100644
--- a/xblocks_contrib/discussion/assets/static/js/customwmd.js
+++ b/xblocks_contrib/discussion/assets/static/js/customwmd.js
@@ -42,7 +42,7 @@ Mostly adapted from math.stackexchange.com: http://cdn.sstatic.net/js/mathjax-ed
MathJaxProcessor.prototype.processMath = function(start, last, preProcess) {
var block, i, j, ref, ref1;
block = this.blocks.slice(start, last + 1).join("").replace(/&/g, "&").replace(//g, ">");
- if (MathJax.Hub.Browser.isMSIE) {
+ if (window.navigator && /msie|trident/i.test(window.navigator.userAgent)) {
block = block.replace(/(%[^\n]*)\n/g, "$1
\n");
}
for (i = j = ref = start + 1, ref1 = last; ref <= ref1 ? j <= ref1 : j >= ref1; i = ref <= ref1 ? ++j : --j) {
diff --git a/xblocks_contrib/discussion/assets/static/js/mathjax_delay_renderer.js b/xblocks_contrib/discussion/assets/static/js/mathjax_delay_renderer.js
index e7b10b40..6517dff1 100644
--- a/xblocks_contrib/discussion/assets/static/js/mathjax_delay_renderer.js
+++ b/xblocks_contrib/discussion/assets/static/js/mathjax_delay_renderer.js
@@ -13,8 +13,6 @@
MathJaxDelayRenderer.prototype.maxDelay = 3000;
- MathJaxDelayRenderer.prototype.mathjaxRunning = false;
-
MathJaxDelayRenderer.prototype.elapsedTime = 0;
MathJaxDelayRenderer.prototype.mathjaxDelay = 0;
@@ -59,8 +57,15 @@
if (preprocessor != null) {
text = preprocessor(text);
}
+ if (typeof MathJax !== 'undefined' && MathJax !== null && typeof MathJax.typesetClear === 'function') {
+ MathJax.typesetClear([$(elem)[0]]);
+ }
$(elem).html(text); // xss-lint: disable=javascript-jquery-html
- return MathJax.Hub.Queue(["Typeset", MathJax.Hub, $(elem).attr("id")]);
+ if (typeof MathJax !== 'undefined' && MathJax !== null && MathJax.startup &&
+ MathJax.startup.promise && typeof MathJax.typesetPromise === "function") {
+ return MathJax.startup.promise
+ .then(() => MathJax.typesetPromise([$(elem)[0]]));
+ }
} else {
if (this.mathjaxTimeout) {
window.clearTimeout(this.mathjaxTimeout);
@@ -70,31 +75,38 @@
renderer = (function(_this) {
return function() {
var curTime, prevTime;
- if (_this.mathjaxRunning) {
- return;
- }
prevTime = getTime();
if (preprocessor != null) {
text = preprocessor(text);
}
+ if (typeof MathJax !== 'undefined' && MathJax !== null && typeof MathJax.typesetClear === 'function') {
+ MathJax.typesetClear([_this.$buffer[0]]);
+ }
_this.$buffer.html(text); // xss-lint: disable=javascript-jquery-html
curTime = getTime();
_this.elapsedTime = curTime - prevTime;
- if (typeof MathJax !== "undefined" && MathJax !== null) {
+ if (typeof MathJax !== "undefined" && MathJax !== null && MathJax.startup &&
+ MathJax.startup.promise && typeof MathJax.typesetPromise === "function") {
prevTime = getTime();
- _this.mathjaxRunning = true;
- return MathJax.Hub.Queue(["Typeset", MathJax.Hub, _this.$buffer.attr("id")], function() {
- _this.mathjaxRunning = false;
- curTime = getTime();
- _this.mathjaxDelay = curTime - prevTime;
- if (previewSetter) {
- return previewSetter($(_this.$buffer).html());
- } else {
- return $(elem).html($(_this.$buffer).html()); // xss-lint: disable=javascript-jquery-html
- }
- });
+ return MathJax.startup.promise
+ .then(
+ () => MathJax.typesetPromise([_this.$buffer[0]]).then(() => {
+ curTime = getTime();
+ _this.mathjaxDelay = curTime - prevTime;
+ if (previewSetter) {
+ return previewSetter($(_this.$buffer).html());
+ } else {
+ return $(elem).html($(_this.$buffer).html()); // xss-lint: disable=javascript-jquery-html
+ }
+ })
+ );
} else {
- return _this.mathjaxDelay = 0;
+ _this.mathjaxDelay = 0;
+ if (previewSetter) {
+ return previewSetter($(_this.$buffer).html());
+ } else {
+ return $(elem).html($(_this.$buffer).html()); // xss-lint: disable=javascript-jquery-html
+ }
}
};
})(this);
diff --git a/xblocks_contrib/html/html.py b/xblocks_contrib/html/html.py
index 2d3b85a0..ada7cd82 100644
--- a/xblocks_contrib/html/html.py
+++ b/xblocks_contrib/html/html.py
@@ -191,7 +191,17 @@ def student_view(self, _context):
"""Return a fragment that contains the html for the student view."""
frag = Fragment(self.get_html())
frag.add_css(resource_loader.load_unicode("static/css/html.css"))
- frag.add_javascript("""function HtmlBlock(runtime, element){}""")
+ frag.add_javascript("""
+function HtmlBlock(runtime, element) {
+ if (typeof MathJax !== "undefined" && MathJax !== null &&
+ MathJax.startup && MathJax.startup.promise &&
+ typeof MathJax.typesetPromise === "function") {
+ MathJax.startup.promise.then(function() {
+ MathJax.typesetPromise([element]);
+ });
+ }
+}
+""")
frag.initialize_js("HtmlBlock")
return frag
diff --git a/xblocks_contrib/problem/assets/spec/display_spec.js b/xblocks_contrib/problem/assets/spec/display_spec.js
index 843ea6a9..e8a6481e 100644
--- a/xblocks_contrib/problem/assets/spec/display_spec.js
+++ b/xblocks_contrib/problem/assets/spec/display_spec.js
@@ -9,13 +9,22 @@ describe("Problem", function () {
var mockRuntime = {};
beforeEach(function () {
- // Stub MathJax
+ // Stub MathJax v3/v4 API
window.MathJax = {
- Hub: jasmine.createSpyObj("MathJax.Hub", ["getAllJax", "Queue"]),
- Callback: jasmine.createSpyObj("MathJax.Callback", ["After"]),
+ startup: {
+ promise: { then: function(cb) { cb(); return this; }, catch: function() { return this; } },
+ document: {
+ getMathItemsWithin: jasmine.createSpy("getMathItemsWithin"),
+ },
+ toMML: jasmine.createSpy("startup.toMML"),
+ },
+ typesetPromise: jasmine.createSpy("typesetPromise"),
+ typesetClear: jasmine.createSpy("typesetClear"),
};
- this.stubbedJax = { root: jasmine.createSpyObj("jax.root", ["toMathML"]) };
- MathJax.Hub.getAllJax.and.returnValue([this.stubbedJax]);
+ this.stubbedJax = { root: {} };
+ MathJax.startup.document.getMathItemsWithin.and.returnValue([this.stubbedJax]);
+ MathJax.startup.toMML.and.returnValue("");
+ MathJax.typesetPromise.and.returnValue(Promise.resolve());
window.update_schematics = function () {};
spyOn(SR, "readText");
spyOn(SR, "readTexts");
@@ -58,11 +67,10 @@ data-url='/problem/quiz/'> \
describe("bind", function () {
beforeEach(function () {
spyOn(window, "update_schematics");
- MathJax.Hub.getAllJax.and.returnValue([this.stubbedJax]);
this.problem = new Problem(mockRuntime, $(".xblock-student_view"));
});
- it("set mathjax typeset", () => expect(MathJax.Hub.Queue).toHaveBeenCalled());
+ it("set mathjax typeset", () => expect(MathJax.typesetPromise).toHaveBeenCalled());
it("update schematics", () => expect(window.update_schematics).toHaveBeenCalled());
@@ -94,7 +102,6 @@ data-url='/problem/quiz/'> \
describe("bind_with_custom_input_id", function () {
beforeEach(function () {
spyOn(window, "update_schematics");
- MathJax.Hub.getAllJax.and.returnValue([this.stubbedJax]);
this.problem = new Problem(mockRuntime, $(".xblock-student_view"));
return $(this).html(readFixtures("problem_content_1240.html"));
});
@@ -999,18 +1006,15 @@ data-url='/problem/quiz/'> \
this.problem.refreshMath({ target: $("#input_example_1").get(0) });
});
- it("should queue the conversion and MathML element update", function () {
- expect(MathJax.Hub.Queue).toHaveBeenCalledWith(
- ["Text", this.stubbedJax, "E=mc^2"],
- [this.problem.updateMathML, this.stubbedJax, $("#input_example_1").get(0)],
- );
+ it("should trigger MathJax v4 typesetting via typesetClear + typesetPromise", function () {
+ expect(MathJax.typesetClear).toHaveBeenCalled();
+ expect(MathJax.typesetPromise).toHaveBeenCalled();
});
});
describe("updateMathML", function () {
beforeEach(function () {
this.problem = new Problem(mockRuntime, $(".xblock-student_view"));
- this.stubbedJax.root.toMathML.and.returnValue("");
});
describe("when there is no exception", function () {
@@ -1018,19 +1022,24 @@ data-url='/problem/quiz/'> \
this.problem.updateMathML(this.stubbedJax, $("#input_example_1").get(0));
});
- it("convert jax to MathML", () => expect($("#input_example_1_dynamath")).toHaveValue(""));
+ it("convert jax to MathML via startup.toMML", function () {
+ expect(MathJax.startup.toMML).toHaveBeenCalledWith(this.stubbedJax.root);
+ expect($("#input_example_1_dynamath")).toHaveValue("");
+ });
});
- describe("when there is an exception", function () {
+ describe("when there is an exception with restart", function () {
beforeEach(function () {
const error = new Error();
error.restart = true;
- this.stubbedJax.root.toMathML.and.throwError(error);
+ MathJax.startup.toMML.and.throwError(error);
this.problem.updateMathML(this.stubbedJax, $("#input_example_1").get(0));
});
- it("should queue up the exception", function () {
- expect(MathJax.Callback.After).toHaveBeenCalledWith([this.problem.refreshMath, this.stubbedJax], true);
+ it("should catch the error and not propagate", function () {
+ // v4: caught by try/catch, restart=true → startup.promise.then schedules refreshMath.
+ // Minimal assertion: toMML was invoked, no uncaught exception.
+ expect(MathJax.startup.toMML).toHaveBeenCalled();
});
});
});
diff --git a/xblocks_contrib/problem/assets/static/js/display.js b/xblocks_contrib/problem/assets/static/js/display.js
index 96fd56b2..5ed836b8 100644
--- a/xblocks_contrib/problem/assets/static/js/display.js
+++ b/xblocks_contrib/problem/assets/static/js/display.js
@@ -20,6 +20,31 @@
return -1;
};
+ /**
+ * Check whether MathJax has been fully loaded and initialized (not just the config object).
+ * In v3/v4, startup.promise is only set after the MathJax component loader completes.
+ */
+ var isMathJaxTypesetReady = function () {
+ return typeof MathJax !== "undefined" && MathJax !== null &&
+ typeof MathJax.startup !== "undefined" && MathJax.startup !== null &&
+ typeof MathJax.startup.promise !== "undefined" &&
+ typeof MathJax.typesetPromise === "function";
+ };
+
+ var isMathJaxRefreshReady = function () {
+ return isMathJaxTypesetReady() &&
+ typeof MathJax.typesetClear === "function" &&
+ typeof MathJax.startup.document !== "undefined" && MathJax.startup.document !== null &&
+ typeof MathJax.startup.document.getMathItemsWithin === "function";
+ };
+
+ var isMathJaxMathMLReady = function () {
+ return typeof MathJax !== "undefined" && MathJax !== null &&
+ typeof MathJax.startup !== "undefined" && MathJax.startup !== null &&
+ typeof MathJax.startup.promise !== "undefined" &&
+ typeof MathJax.startup.toMML === "function";
+ };
+
this.Problem = function () {
function Problem(runtime, element) {
var that = this;
@@ -164,9 +189,9 @@
Problem.prototype.bind = function () {
var problemPrefix,
that = this;
- if (typeof MathJax !== "undefined" && MathJax !== null) {
+ if (isMathJaxTypesetReady()) {
this.el.find(".problem > div").each(function (index, element) {
- return MathJax.Hub.Queue(["Typeset", MathJax.Hub, element]);
+ return MathJax.startup.promise.then(() => MathJax.typesetPromise([element]));
});
}
if (window.hasOwnProperty("update_schematics")) {
@@ -215,10 +240,10 @@
this.submitAnswersAndSubmitButton(true);
}
Collapsible.setCollapsibles(this.el);
- this.$("input.math").keyup(this.refreshMath);
- if (typeof MathJax !== "undefined" && MathJax !== null) {
- this.$("input.math").each(function (index, element) {
- return MathJax.Hub.Queue([that.refreshMath, null, element]);
+ this.$("input.math, .formulaequationinput input").keyup(this.refreshMath);
+ if (isMathJaxTypesetReady()) {
+ this.$("input.math, .formulaequationinput input").each(function (index, element) {
+ return MathJax.startup.promise.then(() => that.refreshMath(null, element));
});
}
};
@@ -820,9 +845,9 @@
}
return results;
});
- if (typeof MathJax !== "undefined" && MathJax !== null) {
+ if (isMathJaxTypesetReady()) {
that.el.find(".problem > div").each(function (index, element) {
- return MathJax.Hub.Queue(["Typeset", MathJax.Hub, element]);
+ return MathJax.startup.promise.then(() => MathJax.typesetPromise([element]));
});
}
that.el.find(".show").attr("disabled", "disabled");
@@ -876,35 +901,81 @@
};
Problem.prototype.refreshMath = function (event, element) {
- var elid, eqn, jax, mathjaxPreprocessor, preprocessorTag, target;
+ var elid,
+ eqn,
+ jax,
+ mathjaxPreprocessor,
+ preprocessorTag,
+ isTexDelimited;
if (!element) {
element = event.target; // eslint-disable-line no-param-reassign
}
elid = element.id.replace(/^input_/, "");
- target = "display_" + elid;
// MathJax preprocessor is loaded by 'setupInputTypes'
preprocessorTag = "inputtype_" + elid;
mathjaxPreprocessor = this.inputtypeDisplays[preprocessorTag];
- if (typeof MathJax !== "undefined" && MathJax !== null && MathJax.Hub.getAllJax(target)[0]) {
- jax = MathJax.Hub.getAllJax(target)[0];
+ if (isMathJaxRefreshReady()) {
+ // Use getElementById so IDs with : . etc. don't break as CSS selectors
+ var math = document.getElementById("display_" + elid);
+ if (!math) {
+ math = document.getElementById(element.id + "_preview");
+ }
+ if (!math) {
+ return;
+ }
eqn = $(element).val();
if (mathjaxPreprocessor) {
eqn = mathjaxPreprocessor(eqn);
}
- MathJax.Hub.Queue(["Text", jax, eqn], [this.updateMathML, jax, element]);
+ MathJax.typesetClear([math]);
+ if (!eqn) {
+ math.textContent = "";
+ var dynEl = document.getElementById(element.id + "_dynamath");
+ if (dynEl) { dynEl.value = ""; }
+ return;
+ }
+ // Determine rendering mode:
+ // isTexDelimited -> preserve user-written TeX delimiters as-is
+ // else -> calculator/AsciiMath syntax via backtick delimiters
+ isTexDelimited = (
+ (/^\s*\\\(/.test(eqn) && /\\\)\s*$/.test(eqn)) ||
+ (/^\s*\\\[/.test(eqn) && /\\\]\s*$/.test(eqn)) ||
+ (/^\s*\$\$/.test(eqn) && /\$\$\s*$/.test(eqn)) ||
+ (/^\s*\$[^$]/.test(eqn) && /[^$]\$\s*$/.test(eqn))
+ );
+ if (isTexDelimited) {
+ math.textContent = eqn;
+ } else {
+ // Calculator/AsciiMath syntax: backtick-delimited for AsciiMath input jax
+ math.textContent = "`" + eqn + "`";
+ }
+ MathJax.typesetPromise([math]).then(function () {
+ jax = MathJax.startup.document.getMathItemsWithin(math)[0];
+ if (jax) {
+ this.updateMathML(jax, element);
+ }
+ }.bind(this)).catch(function () {
+ // clear hidden field on MathJax failure so stale value isn't sent
+ var dynEl = document.getElementById(element.id + "_dynamath");
+ if (dynEl) { dynEl.value = ""; }
+ });
}
};
Problem.prototype.updateMathML = function (jax, element) {
- try {
- $("#" + element.id + "_dynamath").val(jax.root.toMathML(""));
- } catch (exception) {
- if (!exception.restart) {
- throw exception;
- }
- if (typeof MathJax !== "undefined" && MathJax !== null) {
- MathJax.Callback.After([this.refreshMath, jax], exception.restart);
+ if (isMathJaxMathMLReady()) {
+ try {
+ var dynEl = document.getElementById(element.id + "_dynamath");
+ if (dynEl) { dynEl.value = MathJax.startup.toMML(jax.root); }
+ } catch (exception) {
+ if (!exception.restart) {
+ throw exception;
+ }
+ MathJax.startup.promise
+ .then(function () {
+ this.refreshMath(null, element);
+ }.bind(this));
}
}
};
@@ -1391,7 +1462,10 @@
hintMsgContainer = that.$(".problem-hint .notification-message");
hintContainer.attr("hint_index", response.hint_index);
edx.HtmlUtils.setHtml(hintMsgContainer, edx.HtmlUtils.HTML(response.msg));
- MathJax.Hub.Queue(["Typeset", MathJax.Hub, hintContainer[0]]);
+ if (isMathJaxTypesetReady()) {
+ MathJax.startup.promise
+ .then(() => MathJax.typesetPromise([hintContainer[0]]));
+ }
if (response.should_enable_next_hint) {
that.hintButton.removeAttr("disabled");
} else {
diff --git a/xblocks_contrib/problem/capa/static/js/capa/spec/formula_equation_preview_spec.js b/xblocks_contrib/problem/capa/static/js/capa/spec/formula_equation_preview_spec.js
index dc4e1ff9..ded798ee 100644
--- a/xblocks_contrib/problem/capa/static/js/capa/spec/formula_equation_preview_spec.js
+++ b/xblocks_contrib/problem/capa/static/js/capa/spec/formula_equation_preview_spec.js
@@ -80,23 +80,18 @@ describe('Formula Equation Preview', function() {
ajaxTimes.push(Date.now());
});
- // Spy on MathJax
- this.jax = 'OUTPUT_JAX';
+ // Spy on MathJax (v3/v4 API)
this.oldMathJax = window.MathJax;
- window.MathJax = {Hub: {}};
- window.MathJax.Hub.getAllJax = jasmine.createSpy('MathJax.Hub.getAllJax')
- .and.returnValue([this.jax]);
- window.MathJax.Hub.Queue = function(callback) {
- if (typeof callback === 'function') {
- callback();
- }
+ // Synchronous mock promise — .then executes callback immediately like old Hub.Queue
+ var mockPromise = {
+ then: function(callback) { callback(); return this; },
+ catch: function() { return this; }
};
- spyOn(window.MathJax.Hub, 'Queue').and.callThrough();
- window.MathJax.Hub.Startup = jasmine.createSpy('MathJax.Hub.Startup');
- window.MathJax.Hub.Startup.signal = jasmine.createSpy('MathJax.Hub.Startup.signal');
- window.MathJax.Hub.Startup.signal.Interest = function(callback) {
- callback('End');
+ window.MathJax = {
+ startup: { promise: mockPromise },
+ typesetClear: jasmine.createSpy('MathJax.typesetClear'),
+ typesetPromise: jasmine.createSpy('MathJax.typesetPromise')
};
});
@@ -184,7 +179,7 @@ describe('Formula Equation Preview', function() {
jasmine.waitUntil(function() {
// (Short circuit if `inputAjax` is indeed called)
return window.Problem.inputAjax.calls.count() > 0
- || window.MathJax.Hub.Queue.calls.count() > 0;
+ || window.MathJax.typesetPromise.calls.count() > 0;
}).then(function() {
// Expect the request not to have been called.
expect(window.Problem.inputAjax).not.toHaveBeenCalled();
@@ -256,8 +251,6 @@ describe('Formula Equation Preview', function() {
it('updates MathJax and loading icon on callback', function(done) {
formulaEquationPreview.enable();
- var jax = this.jax;
-
jasmine.waitUntil(function() {
return window.Problem.inputAjax.calls.count() > 0;
}).then(function() {
@@ -268,21 +261,19 @@ describe('Formula Equation Preview', function() {
request_start: args[3].request_start
});
- // The only request returned--it should hide the loading icon.
- expect($('img.loading').css('visibility')).toEqual('hidden');
+ // The only request returned—mathjax-preview span is inserted, img.loading preserved.
+ expect($('img.loading').length).toEqual(1);
- // We should look in the preview div for the MathJax.
+ // Clear and typeset the stable mathjax-preview span.
var previewDiv = $('#input_THE_ID_preview')[0];
- expect(window.MathJax.Hub.getAllJax).toHaveBeenCalledWith(previewDiv);
-
- // Refresh the MathJax.
- expect(window.MathJax.Hub.Queue).toHaveBeenCalledWith(
- ['Text', jax, 'THE_FORMULA']
- );
+ var mathSpan = previewDiv.querySelector('.mathjax-preview');
+ expect(window.MathJax.typesetClear).toHaveBeenCalledWith([mathSpan]);
+ expect(mathSpan.textContent).toEqual('\\(THE_FORMULA\\)');
+ expect(window.MathJax.typesetPromise).toHaveBeenCalledWith([mathSpan]);
}).always(done);
});
- it('finds alternatives if MathJax hasn\'t finished loading', function(done) {
+ it('falls back to text-only when MathJax startup is not ready', function(done) {
formulaEquationPreview.enable();
$('#input_THE_ID').val('user_input').trigger('input');
@@ -292,8 +283,8 @@ describe('Formula Equation Preview', function() {
var args = window.Problem.inputAjax.calls.mostRecent().args;
var callback = args[4];
- // Cannot find MathJax.
- window.MathJax.Hub.getAllJax.and.returnValue([]);
+ // Simulate MathJax startup not ready yet.
+ window.MathJax.startup = null;
spyOn(console, 'log');
callback({
@@ -302,22 +293,23 @@ describe('Formula Equation Preview', function() {
});
// Tests.
- expect(console.log).toHaveBeenCalled();
+ expect(console.log).toHaveBeenCalledWith(
+ '[FormulaEquationInput] Oops no mathjax for ', 'THE_FORMULA'
+ );
- // We should look in the preview div for the MathJax.
+ // Preview span should contain the raw LaTeX.
var previewElement = $('#input_THE_ID_preview')[0];
- expect(previewElement.firstChild.data).toEqual('\\(THE_FORMULA\\)');
+ var mathSpan = previewElement.querySelector('.mathjax-preview');
+ expect(mathSpan.textContent).toEqual('\\(THE_FORMULA\\)');
- // Refresh the MathJax.
- expect(window.MathJax.Hub.Queue).toHaveBeenCalledWith(
- ['Typeset', jasmine.any(Object), jasmine.any(Element)]
- );
+ // typesetClear/typesetPromise should NOT be called (fallback path).
+ expect(window.MathJax.typesetClear).not.toHaveBeenCalled();
+ expect(window.MathJax.typesetPromise).not.toHaveBeenCalled();
}).always(done);
});
it('displays errors from the server well', function(done) {
var $img = $('img.loading');
- var jax = this.jax;
formulaEquationPreview.enable();
jasmine.waitUntil(function() {
@@ -329,16 +321,18 @@ describe('Formula Equation Preview', function() {
error: 'OOPSIE',
request_start: args[3].request_start
});
- expect(window.MathJax.Hub.Queue).not.toHaveBeenCalled();
+ expect(window.MathJax.typesetClear).not.toHaveBeenCalled();
expect($img.css('visibility')).toEqual('visible');
}).then(function() {
jasmine.waitUntil(function() {
- return window.MathJax.Hub.Queue.calls.count() > 0;
+ return window.MathJax.typesetPromise.calls.count() > 0;
}).then(function() {
- // Refresh the MathJax.
- expect(window.MathJax.Hub.Queue).toHaveBeenCalledWith(
- ['Text', jax, '\\text{OOPSIE}']
- );
+ // Error text should be displayed.
+ var previewDiv = $('#input_THE_ID_preview')[0];
+ var mathSpan = previewDiv.querySelector('.mathjax-preview');
+ expect(window.MathJax.typesetClear).toHaveBeenCalledWith([mathSpan]);
+ expect(mathSpan.textContent).toEqual('\\(\\text{OOPSIE}\\)');
+ expect(window.MathJax.typesetPromise).toHaveBeenCalledWith([mathSpan]);
expect($img.css('visibility')).toEqual('hidden');
}).then(done);
});
@@ -376,59 +370,70 @@ describe('Formula Equation Preview', function() {
it('updates requests sequentially', function() {
var $img = $('img.loading');
+ var previewDiv = $('#input_THE_ID_preview')[0];
expect($img.css('visibility')).toEqual('visible');
this.callbacks[0](this.responses[0]);
- expect(window.MathJax.Hub.Queue).toHaveBeenCalledWith(
- ['Text', this.jax, 'THE_FORMULA_0']
- );
+ expect(window.MathJax.typesetClear.calls.count()).toEqual(1);
+ expect(window.MathJax.typesetPromise.calls.count()).toEqual(1);
+ var mathSpan = previewDiv.querySelector('.mathjax-preview');
+ expect(mathSpan.textContent).toEqual('\\(THE_FORMULA_0\\)');
expect($img.css('visibility')).toEqual('visible');
this.callbacks[1](this.responses[1]);
- expect(window.MathJax.Hub.Queue).toHaveBeenCalledWith(
- ['Text', this.jax, 'THE_FORMULA_1']
- );
+ expect(window.MathJax.typesetClear.calls.count()).toEqual(2);
+ expect(window.MathJax.typesetPromise.calls.count()).toEqual(2);
+ expect(mathSpan.textContent).toEqual('\\(THE_FORMULA_1\\)');
expect($img.css('visibility')).toEqual('hidden');
});
it("doesn't display outdated information", function() {
var $img = $('img.loading');
+ var previewDiv = $('#input_THE_ID_preview')[0];
expect($img.css('visibility')).toEqual('visible');
// Switch the order (1 returns before 0)
this.callbacks[1](this.responses[1]);
- expect(window.MathJax.Hub.Queue).toHaveBeenCalledWith(
- ['Text', this.jax, 'THE_FORMULA_1']
- );
+ expect(window.MathJax.typesetClear.calls.count()).toEqual(1);
+ expect(window.MathJax.typesetPromise.calls.count()).toEqual(1);
+ var mathSpan = previewDiv.querySelector('.mathjax-preview');
+ expect(mathSpan.textContent).toEqual('\\(THE_FORMULA_1\\)');
expect($img.css('visibility')).toEqual('hidden');
- window.MathJax.Hub.Queue.calls.reset();
+ window.MathJax.typesetClear.calls.reset();
+ window.MathJax.typesetPromise.calls.reset();
this.callbacks[0](this.responses[0]);
- expect(window.MathJax.Hub.Queue).not.toHaveBeenCalled();
+ expect(window.MathJax.typesetClear).not.toHaveBeenCalled();
+ expect(window.MathJax.typesetPromise).not.toHaveBeenCalled();
expect($img.css('visibility')).toEqual('hidden');
});
it("doesn't show an error if the responses are close together", function(done) {
+ var previewDiv = $('#input_THE_ID_preview')[0];
+
this.callbacks[0]({
error: 'OOPSIE',
request_start: this.responses[0].request_start
});
- expect(window.MathJax.Hub.Queue).not.toHaveBeenCalled();
+ expect(window.MathJax.typesetClear).not.toHaveBeenCalled();
- // Error message waiting to be displayed
+ // Error message waiting to be displayed, then a good response arrives first.
this.callbacks[1](this.responses[1]);
- expect(window.MathJax.Hub.Queue).toHaveBeenCalledWith(
- ['Text', this.jax, 'THE_FORMULA_1']
- );
-
- // Make sure that it doesn't indeed show up later
- window.MathJax.Hub.Queue.calls.reset();
+ var mathSpan = previewDiv.querySelector('.mathjax-preview');
+ expect(window.MathJax.typesetClear).toHaveBeenCalledWith([mathSpan]);
+ expect(mathSpan.textContent).toEqual('\\(THE_FORMULA_1\\)');
+ expect(window.MathJax.typesetPromise).toHaveBeenCalledWith([mathSpan]);
+
+ // Make sure that the error doesn't show up later.
+ window.MathJax.typesetClear.calls.reset();
+ window.MathJax.typesetPromise.calls.reset();
jasmine.waitUntil(function() {
return formulaEquationPreview.errorDelay * 1.1;
}).then(function() {
- expect(window.MathJax.Hub.Queue).not.toHaveBeenCalled();
+ expect(window.MathJax.typesetClear).not.toHaveBeenCalled();
+ expect(window.MathJax.typesetPromise).not.toHaveBeenCalled();
}).then(done);
});
});
diff --git a/xblocks_contrib/problem/capa/static/js/capa/src/formula_equation_preview.js b/xblocks_contrib/problem/capa/static/js/capa/src/formula_equation_preview.js
index ba0f7fc4..db533bd9 100644
--- a/xblocks_contrib/problem/capa/static/js/capa/src/formula_equation_preview.js
+++ b/xblocks_contrib/problem/capa/static/js/capa/src/formula_equation_preview.js
@@ -169,30 +169,38 @@ formulaEquationPreview.enable = function() {
}
function display(latex) {
- MathJax.Hub.Startup.signal.Interest(function(message) {
- if (message === 'End') {
- var previewElement = inputData.$preview[0];
- MathJax.Hub.Queue(function() {
- inputData.jax = MathJax.Hub.getAllJax(previewElement)[0];
- });
-
- MathJax.Hub.Queue(function() {
- // Check if MathJax is loaded
- if (inputData.jax) {
- // Set the text as the latex code, and then update the MathJax.
- MathJax.Hub.Queue(
- ['Text', inputData.jax, latex]
- );
- } else if (latex) {
- console.log('[FormulaEquationInput] Oops no mathjax for ', latex);
- // Fall back to modifying the actual element.
- var textNode = previewElement.childNodes[0];
- textNode.data = '\\(' + latex + '\\)';
- MathJax.Hub.Queue(['Typeset', MathJax.Hub, previewElement]);
- }
- });
+ var previewElement = inputData.$preview[0];
+ // Preserve only span.mathjax-preview and img.loading; drop everything else
+ var mathSpan = previewElement.querySelector('.mathjax-preview');
+ var img = previewElement.querySelector('img.loading');
+ var childNodes = previewElement.childNodes;
+ for (var i = childNodes.length - 1; i >= 0; i--) {
+ var node = childNodes[i];
+ if (node.nodeType !== 1 || (node !== mathSpan && node !== img)) {
+ previewElement.removeChild(node);
}
- });
+ }
+ if (!mathSpan) {
+ mathSpan = document.createElement('span');
+ mathSpan.className = 'mathjax-preview';
+ if (img) {
+ previewElement.insertBefore(mathSpan, img);
+ } else {
+ previewElement.appendChild(mathSpan);
+ }
+ }
+ if (typeof MathJax !== 'undefined' && MathJax.startup && MathJax.startup.promise) {
+ MathJax.startup.promise.then(function() {
+ MathJax.typesetClear([mathSpan]);
+ mathSpan.textContent = '\\(' + (latex || '') + '\\)';
+ return MathJax.typesetPromise([mathSpan]);
+ }).catch(function(err) {
+ console.log('[FormulaEquationInput] MathJax error for "' + latex + '":', err);
+ });
+ } else if (latex) {
+ console.log('[FormulaEquationInput] Oops no mathjax for ', latex);
+ mathSpan.textContent = '\\(' + latex + '\\)';
+ }
}
if (response.error) {