Skip to content

Commit ee8c290

Browse files
authored
chore: make mathjax load dynamically instead of on page load (#191)
* chore: make mathjax load dynamically instead of on page load * chore: update test * chore: update tests
1 parent 1b0585b commit ee8c290

File tree

11 files changed

+224
-55
lines changed

11 files changed

+224
-55
lines changed

public/index.html

Lines changed: 0 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -19,56 +19,6 @@
1919
})(window,document,'https://static.hotjar.com/c/hotjar-','.js?sv=');
2020
}
2121
</script>
22-
<script type="text/x-mathjax-config">
23-
MathJax.Hub.Config({
24-
styles: {
25-
'.MathJax_SVG>svg': { 'max-width': '100%', },
26-
// This is to resolve for people who use center mathjax with tables
27-
'table>tbody>tr>td>.MathJax_SVG>svg': { 'max-width': 'inherit'},
28-
},
29-
CommonHTML: { linebreaks: { automatic: true } },
30-
SVG: { linebreaks: { automatic: true } },
31-
"HTML-CSS": { linebreaks: { automatic: true } },
32-
tex2jax: {inlineMath: [ ['$','$'], ["\\(","\\)"]],
33-
displayMath: [ ['$$','$$'], ["\\[","\\]"]],
34-
processEscapes: true},
35-
});
36-
</script>
37-
<script type="text/javascript">
38-
// Activating Mathjax accessibility files
39-
window.MathJax = {
40-
menuSettings: {
41-
collapsible: true,
42-
autocollapse: false,
43-
explorer: true,
44-
},
45-
};
46-
window.addEventListener('resize', MJrenderer);
47-
48-
let t = -1;
49-
let delay = 1000;
50-
let oldWidth = document.documentElement.scrollWidth;
51-
function MJrenderer() {
52-
// don't rerender if the window is the same size as before
53-
if (t >= 0) {
54-
window.clearTimeout(t);
55-
}
56-
if (oldWidth !== document.documentElement.scrollWidth) {
57-
t = window.setTimeout(function () {
58-
oldWidth = document.documentElement.scrollWidth;
59-
MathJax.Hub.Queue(['Rerender', MathJax.Hub]);
60-
t = -1;
61-
}, delay);
62-
}
63-
}
64-
</script>
65-
<!-- This must appear after all mathjax-config blocks, so it is after the imports from the other templates.
66-
It can't be run through static.url because MathJax uses crazy url introspection to do lazy loading of
67-
MathJax extension libraries -->
68-
<script
69-
type="text/javascript"
70-
src="https://cdn.jsdelivr.net/npm/[email protected]/MathJax.js?config=TeX-MML-AM_SVG"
71-
></script>
7222
</head>
7323
<body>
7424
<div id="root"></div>

src/components/TextResponse/index.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React, { useEffect } from 'react';
22
import PropTypes from 'prop-types';
33

44
import { useSubmissionConfig } from 'hooks/app';
5+
import { renderMathJax } from 'utils/index';
56

67
import './index.scss';
78

@@ -13,7 +14,7 @@ const TextResponse = ({ response }) => {
1314

1415
useEffect(() => {
1516
if (allowLatexPreview) {
16-
MathJax.Hub.Queue(['Typeset', MathJax.Hub, textResponseRef.current]);
17+
renderMathJax(textResponseRef.current);
1718
}
1819
}, [allowLatexPreview, response]);
1920

src/components/TextResponse/index.test.jsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React from 'react';
22
import { shallow } from '@edx/react-unit-test-utils';
3+
import { renderMathJax } from 'utils/index';
34

45
import { useSubmissionConfig } from 'hooks/app';
56

@@ -8,6 +9,9 @@ import TextResponse from './index';
89
jest.mock('hooks/app', () => ({
910
useSubmissionConfig: jest.fn(),
1011
}));
12+
jest.mock('utils/index', () => ({
13+
renderMathJax: jest.fn(),
14+
}));
1115

1216
describe('<TextResponse />', () => {
1317
beforeEach(() => {
@@ -23,7 +27,7 @@ describe('<TextResponse />', () => {
2327
expect(wrapper.snapshot).toMatchSnapshot();
2428

2529
React.useEffect.mock.calls[0][0]();
26-
expect(MathJax.Hub.Queue).toHaveBeenCalled();
30+
expect(renderMathJax).toHaveBeenCalled();
2731
});
2832

2933
it('render without allowLatexPreview', () => {
@@ -34,7 +38,7 @@ describe('<TextResponse />', () => {
3438
expect(wrapper.snapshot).toMatchSnapshot();
3539

3640
React.useEffect.mock.calls[0][0]();
37-
expect(MathJax.Hub.Queue).not.toHaveBeenCalled();
41+
expect(renderMathJax).not.toHaveBeenCalled();
3842
});
3943

4044
it('render without textResponseConfig', () => {

src/data/services/lms/fakeData/oraConfig.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const leaderboardConfig = {
3131

3232
const genOption = (index, points) => ({
3333
name: `Option ${index + 1} name`,
34+
label: `Option ${index + 1} label`,
3435
description: `Option ${index + 1} description`,
3536
points,
3637
});

src/data/services/lms/hooks/selectors/oraConfig.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import React from 'react';
2-
import moment from 'moment';
32
import * as data from 'data/services/lms/hooks/data';
43
import * as types from 'data/services/lms/types';
54
import { stepNames } from 'constants/index';

src/utils/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ export { default as keyStore } from './keyStore';
33
export { default as nullMethod } from './nullMethod';
44
export { default as StrictDict } from './StrictDict';
55
export { default as isXblockStep } from './isXblockStep';
6+
export { default as renderMathJax } from './renderMathJax';

src/utils/renderMathJax.js

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// this is a work around for testing
2+
// eslint-disable-next-line import/no-self-import
3+
import * as module from './renderMathJax';
4+
5+
export const loadMathJaxConfig = () => {
6+
window.MathJax = {
7+
menuSettings: {
8+
collapsible: true,
9+
autocollapse: false,
10+
explorer: true,
11+
},
12+
startup: {
13+
ready: () => {
14+
MathJax.startup.defaultReady();
15+
window.onMathJaxReady();
16+
},
17+
},
18+
};
19+
};
20+
21+
export const mathJaxResizeListener = () => {
22+
let t = -1;
23+
const delay = 1000;
24+
let oldWidth = document.documentElement.scrollWidth;
25+
26+
window.addEventListener('resize', () => {
27+
if (t >= 0) {
28+
window.clearTimeout(t);
29+
}
30+
if (oldWidth !== document.documentElement.scrollWidth) {
31+
t = window.setTimeout(() => {
32+
oldWidth = document.documentElement.scrollWidth;
33+
MathJax.Hub.Queue(['Rerender', MathJax.Hub]);
34+
t = -1;
35+
}, delay);
36+
}
37+
});
38+
};
39+
40+
export const loadMathJax = () => {
41+
const config = document.createElement('script');
42+
config.type = 'text/x-mathjax-config';
43+
config.text = `
44+
MathJax.Hub.Config({
45+
styles: {
46+
'.MathJax_SVG>svg': { 'max-width': '100%', },
47+
// This is to resolve for people who use center mathjax with tables
48+
'table>tbody>tr>td>.MathJax_SVG>svg': { 'max-width': 'inherit'},
49+
},
50+
CommonHTML: { linebreaks: { automatic: true } },
51+
SVG: { linebreaks: { automatic: true } },
52+
"HTML-CSS": { linebreaks: { automatic: true } },
53+
tex2jax: {inlineMath: [ ['$','$'], ["\\(","\\)"]],
54+
displayMath: [ ['$$','$$'], ["\\[","\\]"]],
55+
processEscapes: true},
56+
});
57+
`;
58+
document.head.appendChild(config);
59+
60+
// load config
61+
module.loadMathJaxConfig();
62+
// add resize listener
63+
module.mathJaxResizeListener();
64+
65+
const script = document.createElement('script');
66+
script.type = 'text/javascript';
67+
script.src = 'https://cdn.jsdelivr.net/npm/[email protected]/MathJax.js?config=TeX-MML-AM_SVG';
68+
document.head.appendChild(script);
69+
};
70+
71+
export const renderMathJax = (el) => {
72+
if (window.MathJax == null) {
73+
// render the element after MathJax is loaded
74+
window.onMathJaxRendered = () => {
75+
MathJax.Hub.Queue(['Typeset', MathJax.Hub, el]);
76+
77+
delete window.onMathJaxRendered;
78+
};
79+
module.loadMathJax();
80+
} else {
81+
MathJax.Hub.Queue(['Typeset', MathJax.Hub, el]);
82+
}
83+
};
84+
85+
export default renderMathJax;

src/utils/renderMathJax.test.js

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import { loadMathJax, mathJaxResizeListener, loadMathJaxConfig } from './renderMathJax';
2+
3+
jest.mock('./renderMathJax', () => ({
4+
loadMathJax: jest.fn(),
5+
renderMathJax: jest.fn(),
6+
mathJaxResizeListener: jest.fn(),
7+
loadMathJaxConfig: jest.fn(),
8+
}));
9+
10+
describe('loadMathJax', () => {
11+
// eslint-disable-next-line @typescript-eslint/no-shadow
12+
const { loadMathJax } = jest.requireActual('./renderMathJax');
13+
afterEach(() => {
14+
jest.restoreAllMocks();
15+
});
16+
it('should call create script 2 times and have resize script', () => {
17+
jest.spyOn(document, 'createElement');
18+
jest.spyOn(document.head, 'appendChild');
19+
loadMathJax();
20+
expect(document.createElement).toHaveBeenCalledTimes(2);
21+
expect(document.head.appendChild).toHaveBeenCalledTimes(2);
22+
23+
expect(mathJaxResizeListener).toHaveBeenCalled();
24+
expect(loadMathJaxConfig).toHaveBeenCalled();
25+
});
26+
});
27+
28+
describe('renderMathJax', () => {
29+
// eslint-disable-next-line @typescript-eslint/no-shadow
30+
const { renderMathJax } = jest.requireActual('./renderMathJax');
31+
afterEach(() => {
32+
jest.restoreAllMocks();
33+
});
34+
it('should call MathJax.Hub.Queue when MathJax is loaded', () => {
35+
window.MathJax = {
36+
Hub: {
37+
Queue: jest.fn(),
38+
},
39+
};
40+
renderMathJax('el');
41+
expect(window.MathJax.Hub.Queue).toHaveBeenCalled();
42+
expect(loadMathJax).not.toHaveBeenCalled();
43+
44+
delete window.MathJax;
45+
});
46+
47+
it('should call loadMathJax when MathJax is not loaded', () => {
48+
window.MathJax = undefined;
49+
renderMathJax('el');
50+
expect(loadMathJax).toHaveBeenCalled();
51+
52+
// test onMathJaxRendered
53+
window.MathJax = {
54+
Hub: {
55+
Queue: jest.fn(),
56+
},
57+
};
58+
window.onMathJaxRendered();
59+
expect(window.MathJax.Hub.Queue).toHaveBeenCalled();
60+
expect(window.onMathJaxRendered).toBeUndefined();
61+
delete window.MathJax;
62+
});
63+
});
64+
65+
describe('loadMathJaxConfig', () => {
66+
// eslint-disable-next-line @typescript-eslint/no-shadow
67+
const { loadMathJaxConfig } = jest.requireActual('./renderMathJax');
68+
afterEach(() => {
69+
jest.restoreAllMocks();
70+
});
71+
it('should call have onMathJaxReady when it is ready', () => {
72+
window.onMathJaxReady = jest.fn();
73+
loadMathJaxConfig();
74+
window.MathJax.startup.defaultReady = jest.fn();
75+
window.MathJax.startup.ready();
76+
expect(window.MathJax.startup.defaultReady).toHaveBeenCalled();
77+
expect(window.onMathJaxReady).toHaveBeenCalled();
78+
delete window.MathJax;
79+
delete window.onMathJaxReady;
80+
});
81+
});
82+
83+
describe('mathJaxResizeListener', () => {
84+
// eslint-disable-next-line @typescript-eslint/no-shadow
85+
const { mathJaxResizeListener } = jest.requireActual('./renderMathJax');
86+
afterEach(() => {
87+
jest.restoreAllMocks();
88+
});
89+
it('should call window.addEventListener', () => {
90+
jest.spyOn(window, 'addEventListener');
91+
mathJaxResizeListener();
92+
expect(window.addEventListener).toHaveBeenCalled();
93+
});
94+
95+
it('should trigger resize event', () => {
96+
window.MathJax = {
97+
Hub: {
98+
Queue: jest.fn(),
99+
},
100+
};
101+
jest.spyOn(window, 'addEventListener');
102+
const oldWidth = jest.spyOn(document.documentElement, 'scrollWidth', 'get');
103+
const timeout = jest.spyOn(window, 'setTimeout').mockReturnValue(1);
104+
105+
oldWidth.mockReturnValue(100);
106+
107+
mathJaxResizeListener();
108+
109+
const resizeFunc = window.addEventListener.mock.calls[0][1];
110+
// the width change
111+
oldWidth.mockReturnValue(200);
112+
resizeFunc();
113+
expect(setTimeout).toHaveBeenCalled();
114+
const timeoutFunc = timeout.mock.calls[0][0];
115+
timeoutFunc();
116+
expect(MathJax.Hub.Queue).toHaveBeenCalledTimes(1);
117+
});
118+
});

src/views/SubmissionView/TextResponseEditor/LaTexPreview.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import React, { useEffect } from 'react';
22
import PropTypes from 'prop-types';
3+
import { renderMathJax } from 'utils/index';
34

45
const LatexPreview = ({ latexValue }) => {
56
const latexPreviewEl = React.useRef(null);
67

78
useEffect(() => {
8-
MathJax.Hub.Queue(['Typeset', MathJax.Hub, latexPreviewEl.current]);
9+
renderMathJax(latexPreviewEl.current);
910
}, [latexValue]);
1011

1112
return (
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
1+
import React from 'react';
12
import { shallow } from '@edx/react-unit-test-utils';
3+
import { renderMathJax } from 'utils/index';
24

35
import LatexPreview from './LaTexPreview';
46

7+
jest.mock('utils/index', () => ({
8+
renderMathJax: jest.fn(),
9+
}));
10+
511
describe('<LatexPreview />', () => {
612
it('renders', () => {
713
const wrapper = shallow(<LatexPreview latexValue="some latext value" />);
814
expect(wrapper.snapshot).toMatchSnapshot();
15+
16+
React.useEffect.mock.calls[0][0]();
17+
expect(renderMathJax).toHaveBeenCalled();
918
});
1019
});

0 commit comments

Comments
 (0)