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 42346525..a33cea82 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 2fc960b1..1ec8308c 100644
--- a/xblocks_contrib/problem/assets/spec/display_spec.js
+++ b/xblocks_contrib/problem/assets/spec/display_spec.js
@@ -3,13 +3,22 @@ describe("Problem", function () {
const 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");
@@ -52,11 +61,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());
@@ -83,12 +91,28 @@ data-url='/problem/quiz/'> \
it("bind the math input", function () {
expect($("input.math")).toHandleWith("keyup", this.problem.refreshMath);
});
+
+ it("does not bind refreshMath to formula equation inputs", function () {
+ // Formula-equation inputs are exclusively managed by formula_equation_preview.js.
+ $.ajax.and.callFake(function (settings) {
+ if (settings.url.match(/.+\/problem_get$/)) {
+ settings.success({
+ html: readFixtures("problem_content.html") +
+ '',
+ });
+ }
+ });
+ this.problem = new Problem(mockRuntime, $(".xblock-student_view"));
+ MathJax.typesetClear.calls.reset();
+ $(".formulaequationinput input").trigger("keyup");
+ expect(MathJax.typesetClear).not.toHaveBeenCalled();
+ });
});
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"));
});
@@ -993,18 +1017,21 @@ 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();
+ });
+
+ it("preserves backslash-delimited TeX without wrapping in AsciiMath backticks", function () {
+ $("#input_example_1").val("\\\(E=mc^2\\\)");
+ this.problem.refreshMath({ target: $("#input_example_1").get(0) });
+ expect($("#display_example_1").text()).toBe("\\\(E=mc^2\\\)");
});
});
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 () {
@@ -1012,19 +1039,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 e2d50837..5fc49796 100644
--- a/xblocks_contrib/problem/assets/static/js/display.js
+++ b/xblocks_contrib/problem/assets/static/js/display.js
@@ -57,12 +57,21 @@ Problem.prototype.$ = function (selector) {
return $(selector, this.el);
};
+// MathJax v3/v4 exposes readiness and typesetting through startup/typesetPromise.
+const isMathJaxTypesetReady = () => typeof MathJax !== "undefined" && MathJax !== null
+ && MathJax.startup && MathJax.startup.promise && typeof MathJax.typesetPromise === "function";
+const isMathJaxRefreshReady = () => isMathJaxTypesetReady()
+ && typeof MathJax.typesetClear === "function"
+ && MathJax.startup.document && typeof MathJax.startup.document.getMathItemsWithin === "function";
+const isMathJaxMathMLReady = () => typeof MathJax !== "undefined" && MathJax !== null
+ && MathJax.startup && MathJax.startup.promise && typeof MathJax.startup.toMML === "function";
+
Problem.prototype.bind = function () {
const that = this;
let problemPrefix;
- 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")) {
@@ -110,10 +119,11 @@ Problem.prototype.bind = function () {
this.submitAnswersAndSubmitButton(true);
}
Collapsible.setCollapsibles(this.el);
+ // Formula equation inputs use formula_equation_preview.js to manage their server-rendered preview.
this.$("input.math").keyup(this.refreshMath);
- if (typeof MathJax !== "undefined" && MathJax !== null) {
+ if (isMathJaxTypesetReady()) {
this.$("input.math").each(function (index, element) {
- return MathJax.Hub.Queue([that.refreshMath, null, element]);
+ return MathJax.startup.promise.then(() => that.refreshMath(null, element));
});
}
};
@@ -699,9 +709,9 @@ Problem.prototype.show = function () {
}
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");
@@ -754,36 +764,58 @@ Problem.prototype.save_internal = function () {
};
Problem.prototype.refreshMath = function (event, element) {
- let elid, eqn, jax, mathjaxPreprocessor, preprocessorTag, target;
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];
- eqn = $(element).val();
- if (mathjaxPreprocessor) {
- eqn = mathjaxPreprocessor(eqn);
- }
- MathJax.Hub.Queue(["Text", jax, eqn], [this.updateMathML, jax, element]);
+ const elid = element.id.replace(/^input_/, "");
+ const preprocessor = this.inputtypeDisplays[`inputtype_${elid}`];
+ if (!isMathJaxRefreshReady()) {
+ return;
}
+ // Use getElementById so IDs with : or . do not break as CSS selectors.
+ const math = document.getElementById(`display_${elid}`) || document.getElementById(`${element.id}_preview`);
+ if (!math) {
+ return;
+ }
+ let eqn = $(element).val();
+ if (preprocessor) {
+ eqn = preprocessor(eqn);
+ }
+ MathJax.typesetClear([math]);
+ if (!eqn) {
+ math.textContent = "";
+ const dynEl = document.getElementById(`${element.id}_dynamath`);
+ if (dynEl) { dynEl.value = ""; }
+ return;
+ }
+ const 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));
+ math.textContent = isTexDelimited ? eqn : `\`${eqn}\``;
+ MathJax.typesetPromise([math]).then(() => {
+ const jax = MathJax.startup.document.getMathItemsWithin(math)[0];
+ if (jax) {
+ this.updateMathML(jax, element);
+ }
+ }).catch(() => {
+ const dynEl = document.getElementById(`${element.id}_dynamath`);
+ if (dynEl) { dynEl.value = ""; }
+ });
};
Problem.prototype.updateMathML = function (jax, element) {
+ if (!isMathJaxMathMLReady()) {
+ return;
+ }
try {
- $(`#${element.id}_dynamath`).val(jax.root.toMathML(""));
+ const dynEl = document.getElementById(`${element.id}_dynamath`);
+ if (dynEl) { dynEl.value = MathJax.startup.toMML(jax.root); }
} catch (exception) {
if (!exception.restart) {
throw exception;
}
- if (typeof MathJax !== "undefined" && MathJax !== null) {
- MathJax.Callback.After([this.refreshMath, jax], exception.restart);
- }
+ MathJax.startup.promise.then(() => this.refreshMath(null, element));
}
};
@@ -1250,7 +1282,9 @@ Problem.prototype.hint_button = function () {
const 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) {