diff --git a/.circleci/config.yml b/.circleci/config.yml index 6a2f771ed88..9804dd57802 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -89,7 +89,7 @@ jobs: environment: # Alaska time (arbitrary timezone to test date logic) TZ: "America/Anchorage" - parallelism: 8 + parallelism: 12 working_directory: ~/plotly.js steps: - browser-tools/install-browser-tools: &browser-versions @@ -216,6 +216,24 @@ jobs: name: Test MathJax on firefox-latest command: .circleci/test.sh mathjax-firefox82+ + make-baselines-mathjax3: + docker: + - image: circleci/python:3.8.9 + working_directory: ~/plotly.js + steps: + - attach_workspace: + at: ~/ + - run: + name: Install kaleido, plotly.io and required fonts + command: .circleci/env_image.sh + - run: + name: Create mathjax v3 png files + command: .circleci/test.sh make-baselines-mathjax3 + - persist_to_workspace: + root: ~/ + paths: + - plotly.js + make-baselines: parallelism: 4 docker: @@ -249,6 +267,20 @@ jobs: path: build destination: / + test-baselines-mathjax3: + docker: + - image: circleci/node:16.8.0 + working_directory: ~/plotly.js + steps: + - attach_workspace: + at: ~/ + - run: + name: Compare pixels of mathjax v3 baselines + command: .circleci/test.sh test-image-mathjax3 + - store_artifacts: + path: build + destination: / + make-exports: docker: - image: circleci/python:3.8.9 @@ -413,6 +445,12 @@ workflows: - flaky-no-gl-jasmine: requires: - install-and-cibuild + - make-baselines-mathjax3: + requires: + - install-and-cibuild + - test-baselines-mathjax3: + requires: + - make-baselines-mathjax3 - make-baselines: requires: - install-and-cibuild diff --git a/.circleci/test.sh b/.circleci/test.sh index 211b7bc2e69..ecb9b0649c7 100755 --- a/.circleci/test.sh +++ b/.circleci/test.sh @@ -75,7 +75,15 @@ case $1 in ;; mathjax-firefox82+) - ./node_modules/karma/bin/karma start test/jasmine/karma.conf.js --FF --skip-tags=noFF82 --bundleTest=mathjax --nowatch || EXIT_STATE=$? + ./node_modules/karma/bin/karma start test/jasmine/karma.conf.js --FF --bundleTest=mathjax --skip-tags=noFF82 --nowatch && + ./node_modules/karma/bin/karma start test/jasmine/karma.conf.js --FF --bundleTest=mathjax --mathjax3 --skip-tags=noFF82 --nowatch && + ./node_modules/karma/bin/karma start test/jasmine/karma.conf.js --FF --bundleTest=mathjax_config --mathjax3 --nowatch && + ./node_modules/karma/bin/karma start test/jasmine/karma.conf.js --FF --bundleTest=mathjax_config --nowatch || EXIT_STATE=$? + exit $EXIT_STATE + ;; + + make-baselines-mathjax3) + python3 test/image/make_baseline.py mathjax3 legend_mathjax_title_and_items mathjax parcats_grid_subplots table_latex_multitrace_scatter table_plain_birds table_wrapped_birds ternary-mathjax || EXIT_STATE=$? exit $EXIT_STATE ;; @@ -90,6 +98,11 @@ case $1 in exit $EXIT_STATE ;; + test-image-mathjax3) + node test/image/compare_pixels_test.js mathjax3 || { tar -cvf build/baselines.tar build/test_images/*.png ; exit 1 ; } || EXIT_STATE=$? + exit $EXIT_STATE + ;; + source-syntax) npm run lint || EXIT_STATE=$? npm run test-syntax || EXIT_STATE=$? diff --git a/README.md b/README.md index 1f82761abca..fbf90208ff1 100644 --- a/README.md +++ b/README.md @@ -87,11 +87,19 @@ While non-minified source files may contain characters outside UTF-8, it is reco > Please note that as of v2 the "plotly-latest" outputs (e.g. https://cdn.plot.ly/plotly-latest.min.js) will no longer be updated on the CDN, and will stay at the last v1 patch v1.58.5. Therefore, to use the CDN with plotly.js v2 and higher, you must specify an exact plotly.js version. -To support MathJax, you need to load version two of MathJax e.g. `v2.7.5` files from CDN or npm. +To support MathJax, you could load either version two or version three of MathJax files, for example: ```html ``` +```html + +``` + +> When using MathJax version 3, it is also possible to use `chtml` output on the other parts of the page in addition to `svg` output for the plotly graph. +Please refer to `devtools/test_dashboard/index-mathjax3chtml.html` to see an example. + + ## Bundles There are two kinds of plotly.js bundles: 1. Complete and partial official bundles that are distributed to `npm` and the `CDN`, described in [the dist README](https://github.com/plotly/plotly.js/blob/master/dist/README.md). diff --git a/devtools/test_dashboard/index-mathjax3.html b/devtools/test_dashboard/index-mathjax3.html new file mode 100644 index 00000000000..1c56ae3001f --- /dev/null +++ b/devtools/test_dashboard/index-mathjax3.html @@ -0,0 +1,43 @@ + + + + Plotly.js Devtools - MathJax v3 loaded with svg output + + + + +
+ + + + + +
+ +
+ +

MathJax $V^3$ with $svg$ output on the page as well as plotly graphs

+

no MathJax: Apple: $2, Orange: $3

+ +
+
+
+
+ + + + + + + diff --git a/devtools/test_dashboard/index-mathjax3chtml.html b/devtools/test_dashboard/index-mathjax3chtml.html new file mode 100644 index 00000000000..c2264215762 --- /dev/null +++ b/devtools/test_dashboard/index-mathjax3chtml.html @@ -0,0 +1,67 @@ + + + + Plotly.js Devtools - MathJax v3 loaded with chtml output + + + + +
+ + + + + +
+ +
+ +

MathJax $V^3$ with $chtml$ output on the page and svg output on the plotly graphs

+
+
+
+
+ + + + + + + + diff --git a/devtools/test_dashboard/index-strict.html b/devtools/test_dashboard/index-strict.html index db6c43b49db..5598c1c319d 100644 --- a/devtools/test_dashboard/index-strict.html +++ b/devtools/test_dashboard/index-strict.html @@ -22,7 +22,7 @@
- + diff --git a/devtools/test_dashboard/index.html b/devtools/test_dashboard/index.html index 501d171cdbf..0c0c9e89760 100644 --- a/devtools/test_dashboard/index.html +++ b/devtools/test_dashboard/index.html @@ -20,7 +20,7 @@
- + diff --git a/devtools/test_dashboard/server.js b/devtools/test_dashboard/server.js index eb3bbb59e04..36bdd7dad4f 100644 --- a/devtools/test_dashboard/server.js +++ b/devtools/test_dashboard/server.js @@ -13,6 +13,8 @@ var shortcutPaths = require('../../tasks/util/shortcut_paths'); var args = minimist(process.argv.slice(2), {}); var PORT = args.port || 3000; var strict = args.strict; +var mathjax3 = args.mathjax3; +var mathjax3chtml = args.mathjax3chtml; // Create server var server = http.createServer(ecstatic({ @@ -25,7 +27,11 @@ var server = http.createServer(ecstatic({ // Make watchified bundle for plotly.js var bundlePlotly = makeWatchifiedBundle(strict, function() { // open up browser window on first bundle callback - open('http://localhost:' + PORT + '/devtools/test_dashboard/index' + (strict ? '-strict' : '') + '.html'); + open('http://localhost:' + PORT + '/devtools/test_dashboard/index' + ( + strict ? '-strict' : + mathjax3 ? '-mathjax3' : + mathjax3chtml ? '-mathjax3chtml' : '' + ) + '.html'); }); // Bundle devtools code diff --git a/draftlogs/add_6073.md b/draftlogs/add_6073.md new file mode 100644 index 00000000000..14a84b0613c --- /dev/null +++ b/draftlogs/add_6073.md @@ -0,0 +1,2 @@ + - Add support for MathJax version 3 and `typesetMath` attribute to config [[#6073](https://github.com/plotly/plotly.js/pull/6073)], + with thanks to [Equinor](https://www.equinor.com) for sponsoring the related development! diff --git a/package-lock.json b/package-lock.json index 91b8340ec8e..7e28ae8c273 100644 --- a/package-lock.json +++ b/package-lock.json @@ -92,7 +92,8 @@ "karma-viewport": "1.0.2", "lodash": "^4.17.21", "madge": "^5.0.1", - "mathjax": "2.7.5", + "mathjax-v2": "npm:mathjax@2.7.5", + "mathjax-v3": "npm:mathjax@^3.2.0", "minify-stream": "^2.1.0", "npm-link-check": "^4.0.0", "open": "^8.4.0", @@ -6608,12 +6609,20 @@ "node": ">=0.10.0" } }, - "node_modules/mathjax": { + "node_modules/mathjax-v2": { + "name": "mathjax", "version": "2.7.5", "resolved": "https://registry.npmjs.org/mathjax/-/mathjax-2.7.5.tgz", "integrity": "sha512-OzsJNitEHAJB3y4IIlPCAvS0yoXwYjlo2Y4kmm9KQzyIBZt2d8yKRalby3uTRNN4fZQiGL2iMXjpdP1u2Rq2DQ==", "dev": true }, + "node_modules/mathjax-v3": { + "name": "mathjax", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mathjax/-/mathjax-3.2.0.tgz", + "integrity": "sha512-PL+rdYRK4Wxif+SQ94zP/L0sv6/oW/1WdQiIx0Jvn9FZaU5W9E6nlIv8liYAXBNPL2Fw/i+o/mZ1212eSzn0Cw==", + "dev": true + }, "node_modules/md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", @@ -15967,12 +15976,18 @@ "resolved": "https://registry.npmjs.org/math-log2/-/math-log2-1.0.1.tgz", "integrity": "sha1-+4lBvl9evol55xjmJzsXjlhpRWU=" }, - "mathjax": { - "version": "2.7.5", + "mathjax-v2": { + "version": "npm:mathjax@2.7.5", "resolved": "https://registry.npmjs.org/mathjax/-/mathjax-2.7.5.tgz", "integrity": "sha512-OzsJNitEHAJB3y4IIlPCAvS0yoXwYjlo2Y4kmm9KQzyIBZt2d8yKRalby3uTRNN4fZQiGL2iMXjpdP1u2Rq2DQ==", "dev": true }, + "mathjax-v3": { + "version": "npm:mathjax@3.2.0", + "resolved": "https://registry.npmjs.org/mathjax/-/mathjax-3.2.0.tgz", + "integrity": "sha512-PL+rdYRK4Wxif+SQ94zP/L0sv6/oW/1WdQiIx0Jvn9FZaU5W9E6nlIv8liYAXBNPL2Fw/i+o/mZ1212eSzn0Cw==", + "dev": true + }, "md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", diff --git a/package.json b/package.json index c22da995aa9..34bc2570498 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,8 @@ "test-requirejs": "node tasks/test_requirejs.js", "test-plain-obj": "node tasks/test_plain_obj.js", "test": "npm run test-jasmine -- --nowatch && npm run test-bundle && npm run test-image && npm run test-export && npm run test-syntax && npm run lint", + "mathjax3": "node devtools/test_dashboard/server.js --mathjax3", + "mathjax3chtml": "node devtools/test_dashboard/server.js --mathjax3chtml", "strict": "node devtools/test_dashboard/server.js --strict", "start": "node devtools/test_dashboard/server.js", "baseline": "node test/image/make_baseline.js", @@ -152,7 +154,8 @@ "karma-viewport": "1.0.2", "lodash": "^4.17.21", "madge": "^5.0.1", - "mathjax": "2.7.5", + "mathjax-v2": "npm:mathjax@2.7.5", + "mathjax-v3": "npm:mathjax@^3.2.0", "minify-stream": "^2.1.0", "npm-link-check": "^4.0.0", "open": "^8.4.0", diff --git a/src/lib/svg_text_utils.js b/src/lib/svg_text_utils.js index caa6b13e076..622845826d9 100644 --- a/src/lib/svg_text_utils.js +++ b/src/lib/svg_text_utils.js @@ -19,6 +19,7 @@ exports.convertToTspans = function(_context, gd, _callback) { // Until we get tex integrated more fully (so it can be used along with non-tex) // allow some elements to prohibit it by attaching 'data-notex' to the original var tex = (!_context.attr('data-notex')) && + gd && gd._context.typesetMath && (typeof MathJax !== 'undefined') && str.match(FIND_TEX); @@ -173,70 +174,154 @@ function cleanEscapesForTex(s) { .replace(GT_MATCH, '\\gt '); } +var inlineMath = [['$', '$'], ['\\(', '\\)']]; + function texToSVG(_texString, _config, _callback) { + var MathJaxVersion = parseInt( + (MathJax.version || '').split('.')[0] + ); + + if( + MathJaxVersion !== 2 && + MathJaxVersion !== 3 + ) { + Lib.warn('No MathJax version:', MathJax.version); + return; + } + var originalRenderer, originalConfig, originalProcessSectionDelay, tmpDiv; - MathJax.Hub.Queue( - function() { + var setConfig2 = function() { originalConfig = Lib.extendDeepAll({}, MathJax.Hub.config); originalProcessSectionDelay = MathJax.Hub.processSectionDelay; if(MathJax.Hub.processSectionDelay !== undefined) { - // MathJax 2.5+ + // MathJax 2.5+ but not 3+ MathJax.Hub.processSectionDelay = 0; } return MathJax.Hub.Config({ messageStyle: 'none', tex2jax: { - inlineMath: [['$', '$'], ['\\(', '\\)']] + inlineMath: inlineMath }, displayAlign: 'left', }); - }, - function() { - // Get original renderer + }; + + var setConfig3 = function() { + originalConfig = Lib.extendDeepAll({}, MathJax.config); + + if(!MathJax.config.tex) { + MathJax.config.tex = {}; + } + + MathJax.config.tex.inlineMath = inlineMath; + }; + + var setRenderer2 = function() { originalRenderer = MathJax.Hub.config.menuSettings.renderer; if(originalRenderer !== 'SVG') { return MathJax.Hub.setRenderer('SVG'); } - }, - function() { + }; + + var setRenderer3 = function() { + originalRenderer = MathJax.config.startup.output; + if(originalRenderer !== 'svg') { + MathJax.config.startup.output = 'svg'; + } + }; + + var initiateMathJax = function() { var randomID = 'math-output-' + Lib.randstr({}, 64); tmpDiv = d3.select('body').append('div') .attr({id: randomID}) - .style({visibility: 'hidden', position: 'absolute'}) - .style({'font-size': _config.fontSize + 'px'}) + .style({ + visibility: 'hidden', + position: 'absolute', + 'font-size': _config.fontSize + 'px' + }) .text(cleanEscapesForTex(_texString)); - return MathJax.Hub.Typeset(tmpDiv.node()); - }, - function() { - var glyphDefs = d3.select('body').select('#MathJax_SVG_glyphs'); + var tmpNode = tmpDiv.node(); + + return MathJaxVersion === 2 ? + MathJax.Hub.Typeset(tmpNode) : + MathJax.typeset([tmpNode]); + }; + + var finalizeMathJax = function() { + var sel = tmpDiv.select( + MathJaxVersion === 2 ? '.MathJax_SVG' : '.MathJax' + ); - if(tmpDiv.select('.MathJax_SVG').empty() || !tmpDiv.select('svg').node()) { + var node = !sel.empty() && tmpDiv.select('svg').node(); + if(!node) { Lib.log('There was an error in the tex syntax.', _texString); _callback(); } else { - var svgBBox = tmpDiv.select('svg').node().getBoundingClientRect(); - _callback(tmpDiv.select('.MathJax_SVG'), glyphDefs, svgBBox); + var nodeBBox = node.getBoundingClientRect(); + var glyphDefs; + if(MathJaxVersion === 2) { + glyphDefs = d3.select('body').select('#MathJax_SVG_glyphs'); + } else { + glyphDefs = sel.select('defs'); + } + _callback(sel, glyphDefs, nodeBBox); } tmpDiv.remove(); + }; + var resetRenderer2 = function() { if(originalRenderer !== 'SVG') { return MathJax.Hub.setRenderer(originalRenderer); } - }, - function() { + }; + + var resetRenderer3 = function() { + if(originalRenderer !== 'svg') { + MathJax.config.startup.output = originalRenderer; + } + }; + + var resetConfig2 = function() { if(originalProcessSectionDelay !== undefined) { MathJax.Hub.processSectionDelay = originalProcessSectionDelay; } return MathJax.Hub.Config(originalConfig); - }); + }; + + var resetConfig3 = function() { + MathJax.config = originalConfig; + }; + + if(MathJaxVersion === 2) { + MathJax.Hub.Queue( + setConfig2, + setRenderer2, + initiateMathJax, + finalizeMathJax, + resetRenderer2, + resetConfig2 + ); + } else if(MathJaxVersion === 3) { + setConfig3(); + setRenderer3(); + MathJax.startup.defaultReady(); + + MathJax.startup.promise.then(function() { + initiateMathJax(); + finalizeMathJax(); + + resetRenderer3(); + resetConfig3(); + }); + } } var TAG_STYLES = { diff --git a/src/plot_api/plot_config.js b/src/plot_api/plot_config.js index 23968c4e0b3..cf843260731 100644 --- a/src/plot_api/plot_config.js +++ b/src/plot_api/plot_config.js @@ -22,6 +22,15 @@ var configAttributes = { ].join(' ') }, + typesetMath: { + valType: 'boolean', + dflt: true, + description: [ + 'Determines whether math should be typeset or not,', + 'when MathJax (either v2 or v3) is present on the page.' + ].join(' ') + }, + plotlyServerURL: { valType: 'string', dflt: '', diff --git a/tasks/noci_test.sh b/tasks/noci_test.sh index f1605c1c0f0..c26cf75a0cd 100755 --- a/tasks/noci_test.sh +++ b/tasks/noci_test.sh @@ -28,7 +28,7 @@ test_image () { $root/../orca/bin/orca.js graph \ $root/test/image/mocks/mapbox_osm-style.json \ $root/test/image/mocks/mapbox_density0-legend.json \ - --mathjax $root/node_modules/mathjax/MathJax.js \ + --mathjax $root/node_modules/mathjax-v2/MathJax.js \ --plotly $root/build/plotly.js \ --mapbox-access-token "pk.eyJ1IjoicGxvdGx5LWpzLXRlc3RzIiwiYSI6ImNrNG9meTJmOTAxa3UzZm10dWdteDQ2eWMifQ.2REjOFyIrleMqwS8H8y1-A" \ --output-dir $root/test/image/baselines/ \ diff --git a/test/image/baselines/mathjax3___legend_mathjax_title_and_items.png b/test/image/baselines/mathjax3___legend_mathjax_title_and_items.png new file mode 100644 index 00000000000..5ac7ca0992c Binary files /dev/null and b/test/image/baselines/mathjax3___legend_mathjax_title_and_items.png differ diff --git a/test/image/baselines/mathjax3___mathjax.png b/test/image/baselines/mathjax3___mathjax.png new file mode 100644 index 00000000000..9374e9fa6af Binary files /dev/null and b/test/image/baselines/mathjax3___mathjax.png differ diff --git a/test/image/baselines/mathjax3___parcats_grid_subplots.png b/test/image/baselines/mathjax3___parcats_grid_subplots.png new file mode 100644 index 00000000000..c00175ae64a Binary files /dev/null and b/test/image/baselines/mathjax3___parcats_grid_subplots.png differ diff --git a/test/image/baselines/mathjax3___table_latex_multitrace_scatter.png b/test/image/baselines/mathjax3___table_latex_multitrace_scatter.png new file mode 100644 index 00000000000..094b1dafa06 Binary files /dev/null and b/test/image/baselines/mathjax3___table_latex_multitrace_scatter.png differ diff --git a/test/image/baselines/mathjax3___table_plain_birds.png b/test/image/baselines/mathjax3___table_plain_birds.png new file mode 100644 index 00000000000..7ecf490e622 Binary files /dev/null and b/test/image/baselines/mathjax3___table_plain_birds.png differ diff --git a/test/image/baselines/mathjax3___table_wrapped_birds.png b/test/image/baselines/mathjax3___table_wrapped_birds.png new file mode 100644 index 00000000000..26387cd297f Binary files /dev/null and b/test/image/baselines/mathjax3___table_wrapped_birds.png differ diff --git a/test/image/baselines/mathjax3___ternary-mathjax.png b/test/image/baselines/mathjax3___ternary-mathjax.png new file mode 100644 index 00000000000..9dabccd5af6 Binary files /dev/null and b/test/image/baselines/mathjax3___ternary-mathjax.png differ diff --git a/test/image/compare_pixels_test.js b/test/image/compare_pixels_test.js index ac57770a978..aac79c81d2b 100644 --- a/test/image/compare_pixels_test.js +++ b/test/image/compare_pixels_test.js @@ -41,16 +41,33 @@ if(argv._.length === 0) { // Build list of mocks to compare var allMockList = []; +var mathjax3; argv._.forEach(function(pattern) { - var mockList = getMockList(pattern); + if(pattern === 'mathjax3') { + mathjax3 = true; + } else { + var mockList = getMockList(pattern); - if(mockList.length === 0) { - throw 'No mocks found with pattern ' + pattern; - } + if(mockList.length === 0) { + throw 'No mocks found with pattern ' + pattern; + } - allMockList = allMockList.concat(mockList); + allMockList = allMockList.concat(mockList); + } }); +if(mathjax3) { + allMockList = [ + 'legend_mathjax_title_and_items', + 'mathjax', + 'parcats_grid_subplots', + 'table_latex_multitrace_scatter', + 'table_plain_birds', + 'table_wrapped_birds', + 'ternary-mathjax' + ]; +} + // To get rid of duplicates function unique(value, index, self) { return self.indexOf(value) === index; @@ -75,11 +92,19 @@ for(var i = 0; i < allMockList.length; i++) { continue; } + var isMapbox = mockName.substr(0, 7) === 'mapbox_'; + var isOtherFlaky = [ + // list flaky mocks other than mapbox: + 'gl3d_bunny-hull' + ].indexOf(mockName) !== -1; + + if(mathjax3) mockName = 'mathjax3___' + mockName; + var imagePaths = getImagePaths(mockName); var base = imagePaths.baseline; var test = imagePaths.test; - if(!common.doesFileExist(test)) { + if(!common.doesFileExist(test) && !mathjax3) { console.log('- skip:', mockName); skipped.push(mockName); continue; @@ -114,12 +139,6 @@ for(var i = 0; i < allMockList.length; i++) { height: height }); - var isMapbox = mockName.substr(0, 7) === 'mapbox_'; - var isOtherFlaky = [ - // list flaky mocks other than mapbox: - 'gl3d_bunny-hull' - ].indexOf(mockName) !== -1; - var shouldBePixelPerfect = !(isMapbox || isOtherFlaky); var numDiffPixels = pixelmatch(img0.data, img1.data, diff.data, width, height, { diff --git a/test/image/make_baseline.js b/test/image/make_baseline.js index 884befdaf57..11ab01dfad0 100644 --- a/test/image/make_baseline.js +++ b/test/image/make_baseline.js @@ -25,29 +25,49 @@ var getMockList = require('./assets/get_mock_list'); * * npm run baseline -- gl3d_* * + * Generate or (re-generate) baselines using mathjax3: + * + * npm run baseline mathjax3 + * */ var argv = minimist(process.argv.slice(2), {}); var allMockList = []; +var mathjax3; argv._.forEach(function(pattern) { - var mockList = getMockList(pattern); + if(pattern === 'mathjax3') { + mathjax3 = true; + } else { + var mockList = getMockList(pattern); - if(mockList.length === 0) { - throw new Error('No mocks found with pattern ' + pattern); - } + if(mockList.length === 0) { + throw new Error('No mocks found with pattern ' + pattern); + } - allMockList = allMockList.concat(mockList); + allMockList = allMockList.concat(mockList); + } }); +if(mathjax3) { + allMockList = [ + 'legend_mathjax_title_and_items', + 'mathjax', + 'parcats_grid_subplots', + 'table_latex_multitrace_scatter', + 'table_plain_birds', + 'table_wrapped_birds', + 'ternary-mathjax' + ]; +} + if(allMockList.length) console.log(allMockList); console.log('Please wait for the process to complete.'); var p = spawn( - 'python3', - [ + 'python3', [ path.join('test', 'image', 'make_baseline.py'), - '= ' + allMockList.join(' ') + (mathjax3 ? 'mathjax3' : '') + '= ' + allMockList.join(' ') ] ); try { diff --git a/test/image/make_baseline.py b/test/image/make_baseline.py index 18f3d0c22ce..58c9d40614a 100644 --- a/test/image/make_baseline.py +++ b/test/image/make_baseline.py @@ -13,14 +13,28 @@ dirIn = os.path.join(root, 'test', 'image', 'mocks') dirOut = os.path.join(root, 'build', 'test_images') +# N.B. equal is the falg to write to baselines not test_images + if '=' in args : args = args[args.index('=') + 1:] dirOut = os.path.join(root, 'test', 'image', 'baselines') - print('output to', dirOut) + +if 'mathjax3=' in sys.argv : + dirOut = os.path.join(root, 'test', 'image', 'baselines') + +print('output to', dirOut) + +mathjax_version = 2 +if 'mathjax3' in sys.argv or 'mathjax3=' in sys.argv : + # until https://github.com/plotly/Kaleido/issues/124 is addressed + # we are uanble to use local mathjax v3 installed in node_modules + # for now let's download it from the internet: + pio.kaleido.scope.mathjax = 'https://cdn.jsdelivr.net/npm/mathjax@3.2.0/es5/tex-svg.js' + mathjax_version = 3 + print('Kaleido using MathJax v3') pio.templates.default = 'none' pio.kaleido.scope.plotlyjs = os.path.join(root, 'build', 'plotly.js') -# TODO: specify local mathjax and plotly-geo-assets files? _credentials = open(os.path.join(root, 'build', 'credentials.json'), 'r') pio.kaleido.scope.mapbox_access_token = json.load(_credentials)['MAPBOX_ACCESS_TOKEN'] @@ -73,7 +87,11 @@ failed = [] for name in allNames : - print(name) + outName = name + if mathjax_version == 3 : + outName = 'mathjax3___' + name + + print(outName) created = False @@ -95,7 +113,7 @@ try : pio.write_image( fig=fig, - file=os.path.join(dirOut, name + '.png'), + file=os.path.join(dirOut, outName + '.png'), width=width, height=height, validate=False @@ -106,7 +124,7 @@ if attempt < MAX_RETRY : print('retry', attempt + 1, '/', MAX_RETRY) else : - failed.append(name) + failed.append(outName) if(created) : break diff --git a/test/jasmine/assets/load_script.js b/test/jasmine/assets/load_script.js new file mode 100644 index 00000000000..09dac01ce56 --- /dev/null +++ b/test/jasmine/assets/load_script.js @@ -0,0 +1,11 @@ +'use strict'; + +module.exports = function loadScript(src, done) { + var newScript; + newScript = document.createElement('script'); + newScript.src = src; + newScript.type = 'text/javascript'; + newScript.onload = done; + newScript.onerror = done.fail; + document.body.appendChild(newScript); +}; diff --git a/test/jasmine/bundle_tests/mathjax_config_test.js b/test/jasmine/bundle_tests/mathjax_config_test.js new file mode 100644 index 00000000000..963a4666417 --- /dev/null +++ b/test/jasmine/bundle_tests/mathjax_config_test.js @@ -0,0 +1,88 @@ +/* eslint-disable new-cap */ + +var Plotly = require('@lib/index'); + +var delay = require('../assets/delay'); +var createGraphDiv = require('../assets/create_graph_div'); +var destroyGraphDiv = require('../assets/destroy_graph_div'); +var loadScript = require('../assets/load_script'); + +// eslint-disable-next-line no-undef +var mathjaxVersion = __karma__.config.mathjaxVersion; + +describe('Test MathJax v' + mathjaxVersion + ' config test:', function() { + var gd; + + beforeAll(function(done) { + gd = createGraphDiv(); + + if(mathjaxVersion === 3) { + window.MathJax = { + startup: { + output: 'chtml', + tex: { + inlineMath: [['|', '|']] + } + } + }; + } + + var src = mathjaxVersion === 3 ? + '/base/node_modules/mathjax-v3/es5/tex-svg.js' : + '/base/node_modules/mathjax-v2/MathJax.js?config=TeX-AMS_SVG'; + + loadScript(src, done); + }); + + afterAll(destroyGraphDiv); + + it('should maintain startup renderer & inlineMath after SVG rendering', function(done) { + if(mathjaxVersion === 2) { + window.MathJax.Hub.Config({ + tex2jax: { + inlineMath: [['|', '|']] + } + }); + + window.MathJax.Hub.setRenderer('CHTML'); + } + + // before plot + if(mathjaxVersion === 3) { + expect(window.MathJax.config.startup.output).toEqual('chtml'); + expect(window.MathJax.config.startup.tex.inlineMath).toEqual([['|', '|']]); + } + if(mathjaxVersion === 2) { + expect(window.MathJax.Hub.config.menuSettings.renderer).toEqual(''); + expect(window.MathJax.Hub.config.tex2jax.inlineMath).toEqual([['|', '|']]); + } + + Plotly.newPlot(gd, { + data: [{ + y: [1, 2] + }], + layout: { + title: { + text: '$E=mc^2$' + } + } + }) + .then(function() { + // after plot + if(mathjaxVersion === 3) { + expect(window.MathJax.config.startup.output).toEqual('chtml'); + expect(window.MathJax.config.startup.tex.inlineMath).toEqual([['|', '|']]); + } + if(mathjaxVersion === 2) { + expect(window.MathJax.Hub.config.menuSettings.renderer).toEqual(''); + } + }) + .then(delay(1000)) // TODO: why we need this delay for mathjax v2 here? + .then(function() { + if(mathjaxVersion === 2) { + expect(window.MathJax.Hub.config.tex2jax.inlineMath).toEqual([['|', '|']]); + } + }) + .then(done, done.fail); + }); +}); diff --git a/test/jasmine/bundle_tests/mathjax_test.js b/test/jasmine/bundle_tests/mathjax_test.js index 51104ae71be..4b3fc65e3ea 100644 --- a/test/jasmine/bundle_tests/mathjax_test.js +++ b/test/jasmine/bundle_tests/mathjax_test.js @@ -3,29 +3,26 @@ var d3Select = require('../../strict-d3').select; var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); +var loadScript = require('../assets/load_script'); +// eslint-disable-next-line no-undef +var mathjaxVersion = __karma__.config.mathjaxVersion; -describe('Test MathJax:', function() { - var mathJaxScriptTag; - - // N.B. we have to load MathJax "dynamically" as Karam - // does not undefined the MathJax's `?config=` parameter. - // - // Now with the mathjax_config no longer needed, - // it might be nice to move these tests in the "regular" test - // suites, but to do that we'll need to find a way to remove MathJax from - // page without breaking things downstream. +describe('Test MathJax v' + mathjaxVersion + ':', function() { beforeAll(function(done) { - mathJaxScriptTag = document.createElement('script'); - mathJaxScriptTag.type = 'text/javascript'; - mathJaxScriptTag.onload = function() { - done(); - }; - mathJaxScriptTag.onerror = function() { - fail('MathJax failed to load'); - }; - mathJaxScriptTag.src = '/base/node_modules/mathjax/MathJax.js?config=TeX-AMS-MML_SVG'; - document.body.appendChild(mathJaxScriptTag); + var src = mathjaxVersion === 3 ? + '/base/node_modules/mathjax-v3/es5/tex-svg.js' : + '/base/node_modules/mathjax-v2/MathJax.js?config=TeX-AMS-MML_SVG'; + + // N.B. we have to load MathJax "dynamically" as Karma + // does not undefined the MathJax's `?config=` parameter. + // + // Now with the mathjax_config no longer needed, + // it might be nice to move these tests in the "regular" test + // suites, but to do that we'll need to find a way to remove MathJax from + // page without breaking things downstream. + + loadScript(src, done); }); describe('Test axis title scoot:', function() { diff --git a/test/jasmine/karma.conf.js b/test/jasmine/karma.conf.js index c52e76048a0..2704daf4258 100644 --- a/test/jasmine/karma.conf.js +++ b/test/jasmine/karma.conf.js @@ -9,6 +9,7 @@ var isCI = Boolean(process.env.CI); var argv = minimist(process.argv.slice(4), { string: ['bundleTest', 'width', 'height'], 'boolean': [ + 'mathjax3', 'info', 'nowatch', 'randomize', 'failFast', 'doNotFailOnEmptyTestSuite', @@ -60,6 +61,7 @@ if(argv.info) { '', 'Other options:', ' - `--info`: show this info message', + ' - `--mathjax3`: to load mathjax v3 in relevant test', ' - `--Chrome` (alias `--chrome`): run test in (our custom) Chrome browser', ' - `--Firefox` (alias `--FF`, `--firefox`): run test in (our custom) Firefox browser', ' - `--IE11` (alias -- `ie11`)`: run test in IE11 browser', @@ -122,7 +124,8 @@ var pathToJQuery = path.join(__dirname, 'assets', 'jquery-1.8.3.min.js'); var pathToCustomMatchers = path.join(__dirname, 'assets', 'custom_matchers.js'); var pathToUnpolyfill = path.join(__dirname, 'assets', 'unpolyfill.js'); var pathToSaneTopojsonDist = path.join(__dirname, '..', '..', 'node_modules', 'sane-topojson', 'dist'); -var pathToMathJax = path.join(__dirname, '..', '..', 'node_modules', 'mathjax'); +var pathToMathJax2 = path.join(__dirname, '..', '..', 'node_modules', 'mathjax-v2'); +var pathToMathJax3 = path.join(__dirname, '..', '..', 'node_modules', 'mathjax-v3'); var reporters = []; if(argv['report-progress'] || argv['report-spec'] || argv['report-dots']) { @@ -180,9 +183,10 @@ func.defaultConfig = { files: [ pathToCustomMatchers, pathToUnpolyfill, - // available to fetch from /base/node_modules/mathjax/ + // available to fetch from /base/node_modules/mathjax-v2/ // more info: http://karma-runner.github.io/3.0/config/files.html - {pattern: pathToMathJax + '/**', included: false, watched: false, served: true}, + {pattern: pathToMathJax2 + '/**', included: false, watched: false, served: true}, + {pattern: pathToMathJax3 + '/**', included: false, watched: false, served: true}, // available to fetch from /base/node_modules/sane-topojson/dist/ {pattern: pathToSaneTopojsonDist + '/**', included: false, watched: false, served: true} ], @@ -285,6 +289,8 @@ func.defaultConfig = { tagPrefix: '@', skipTags: isCI ? 'noCI' : null, + mathjaxVersion: argv.mathjax3 ? 3 : 2, + // See https://jasmine.github.io/api/3.4/Configuration.html jasmine: { random: argv.randomize, diff --git a/test/plot-schema.json b/test/plot-schema.json index 32289cb0ffe..787cc995138 100644 --- a/test/plot-schema.json +++ b/test/plot-schema.json @@ -365,6 +365,11 @@ "noBlank": true, "valType": "string" }, + "typesetMath": { + "description": "Determines whether math should be typeset or not, when MathJax (either v2 or v3) is present on the page.", + "dflt": true, + "valType": "boolean" + }, "watermark": { "description": "watermark the images with the company's logo", "dflt": false,