Commit 80384c46 authored by Reinhold Gschweicher's avatar Reinhold Gschweicher

IPython KaTeX rendering of comparison operators for markdown

When rendering IPython notebook markdown cells the formulas are passed
to Katex. Before that the raw string is passed to marked.js to handle
HTML forbidden characters. Two of these characters are the
less-than-operator `<` and the greater-than-operator `>`. These
characters are often used in math formulas and the replacements `&lt;`
and `&gt;` are not well received by Katex.

For the tests use async/await syntax, and check length with
`toHaveLength()` to be more explicit.

Fixes: https://gitlab.com/gitlab-org/gitlab/-/issues/327450
parent 4daa93d9
......@@ -37,6 +37,11 @@ const katexRegexString = `(
.replace(/\s/g, '')
.trim();
function deHTMLify(t) {
// get some specific characters back, that are allowed for KaTex rendering
const text = t.replace(/&#39;/g, "'").replace(/&lt;/g, '<').replace(/&gt;/g, '>');
return text;
}
function renderKatex(t) {
let text = t;
let numInline = 0; // number of successfull converted math formulas
......@@ -57,9 +62,7 @@ function renderKatex(t) {
while (matches !== null) {
try {
const renderedKatex = katex.renderToString(
matches[0].replace(/\$/g, '').replace(/&#39;/g, "'"),
); // get the tick ' back again from HTMLified string
const renderedKatex = katex.renderToString(deHTMLify(matches[0].replace(/\$/g, '')));
text = `${text.replace(matches[0], ` ${renderedKatex}`)}`;
} catch {
numInline -= 1;
......@@ -68,7 +71,7 @@ function renderKatex(t) {
}
} else {
try {
text = katex.renderToString(matches[2].replace(/&#39;/g, "'"));
text = katex.renderToString(deHTMLify(matches[2]));
} catch (error) {
numInline -= 1;
}
......
---
title: IPython KaTeX rendering of comparison operators for markdown
merge_request: 59132
author: Reinhold Gschweicher <pyro4hell@gmail.com>
type: fixed
......@@ -39,16 +39,15 @@ describe('Markdown component', () => {
expect(vm.$el.querySelector('.markdown h1')).not.toBeNull();
});
it('sanitizes output', () => {
it('sanitizes output', async () => {
Object.assign(cell, {
source: [
'[XSS](data:text/html;base64,PHNjcmlwdD5hbGVydChkb2N1bWVudC5kb21haW4pPC9zY3JpcHQ+Cg==)\n',
],
});
return vm.$nextTick().then(() => {
expect(vm.$el.querySelector('a').getAttribute('href')).toBeNull();
});
await vm.$nextTick();
expect(vm.$el.querySelector('a').getAttribute('href')).toBeNull();
});
describe('katex', () => {
......@@ -56,43 +55,40 @@ describe('Markdown component', () => {
json = getJSONFixture('blob/notebook/math.json');
});
it('renders multi-line katex', () => {
it('renders multi-line katex', async () => {
vm = new Component({
propsData: {
cell: json.cells[0],
},
}).$mount();
return vm.$nextTick().then(() => {
expect(vm.$el.querySelector('.katex')).not.toBeNull();
});
await vm.$nextTick();
expect(vm.$el.querySelector('.katex')).not.toBeNull();
});
it('renders inline katex', () => {
it('renders inline katex', async () => {
vm = new Component({
propsData: {
cell: json.cells[1],
},
}).$mount();
return vm.$nextTick().then(() => {
expect(vm.$el.querySelector('p:first-child .katex')).not.toBeNull();
});
await vm.$nextTick();
expect(vm.$el.querySelector('p:first-child .katex')).not.toBeNull();
});
it('renders multiple inline katex', () => {
it('renders multiple inline katex', async () => {
vm = new Component({
propsData: {
cell: json.cells[1],
},
}).$mount();
return vm.$nextTick().then(() => {
expect(vm.$el.querySelectorAll('p:nth-child(2) .katex').length).toBe(4);
});
await vm.$nextTick();
expect(vm.$el.querySelectorAll('p:nth-child(2) .katex')).toHaveLength(4);
});
it('output cell in case of katex error', () => {
it('output cell in case of katex error', async () => {
vm = new Component({
propsData: {
cell: {
......@@ -103,14 +99,13 @@ describe('Markdown component', () => {
},
}).$mount();
return vm.$nextTick().then(() => {
// expect one paragraph with no katex formula in it
expect(vm.$el.querySelectorAll('p').length).toBe(1);
expect(vm.$el.querySelectorAll('p .katex').length).toBe(0);
});
await vm.$nextTick();
// expect one paragraph with no katex formula in it
expect(vm.$el.querySelectorAll('p')).toHaveLength(1);
expect(vm.$el.querySelectorAll('p .katex')).toHaveLength(0);
});
it('output cell and render remaining formula in case of katex error', () => {
it('output cell and render remaining formula in case of katex error', async () => {
vm = new Component({
propsData: {
cell: {
......@@ -121,14 +116,13 @@ describe('Markdown component', () => {
},
}).$mount();
return vm.$nextTick().then(() => {
// expect one paragraph with no katex formula in it
expect(vm.$el.querySelectorAll('p').length).toBe(1);
expect(vm.$el.querySelectorAll('p .katex').length).toBe(1);
});
await vm.$nextTick();
// expect one paragraph with no katex formula in it
expect(vm.$el.querySelectorAll('p')).toHaveLength(1);
expect(vm.$el.querySelectorAll('p .katex')).toHaveLength(1);
});
it('renders math formula in list object', () => {
it('renders math formula in list object', async () => {
vm = new Component({
propsData: {
cell: {
......@@ -139,14 +133,13 @@ describe('Markdown component', () => {
},
}).$mount();
return vm.$nextTick().then(() => {
// expect one list with a katex formula in it
expect(vm.$el.querySelectorAll('li').length).toBe(1);
expect(vm.$el.querySelectorAll('li .katex').length).toBe(2);
});
await vm.$nextTick();
// expect one list with a katex formula in it
expect(vm.$el.querySelectorAll('li')).toHaveLength(1);
expect(vm.$el.querySelectorAll('li .katex')).toHaveLength(2);
});
it("renders math formula with tick ' in it", () => {
it("renders math formula with tick ' in it", async () => {
vm = new Component({
propsData: {
cell: {
......@@ -157,11 +150,44 @@ describe('Markdown component', () => {
},
}).$mount();
return vm.$nextTick().then(() => {
// expect one list with a katex formula in it
expect(vm.$el.querySelectorAll('li').length).toBe(1);
expect(vm.$el.querySelectorAll('li .katex').length).toBe(2);
});
await vm.$nextTick();
// expect one list with a katex formula in it
expect(vm.$el.querySelectorAll('li')).toHaveLength(1);
expect(vm.$el.querySelectorAll('li .katex')).toHaveLength(2);
});
it('renders math formula with less-than-operator < in it', async () => {
vm = new Component({
propsData: {
cell: {
cell_type: 'markdown',
metadata: {},
source: ['- list with inline $a=2$ inline formula $a + b < c$\n', '\n'],
},
},
}).$mount();
await vm.$nextTick();
// expect one list with a katex formula in it
expect(vm.$el.querySelectorAll('li')).toHaveLength(1);
expect(vm.$el.querySelectorAll('li .katex')).toHaveLength(2);
});
it('renders math formula with greater-than-operator > in it', async () => {
vm = new Component({
propsData: {
cell: {
cell_type: 'markdown',
metadata: {},
source: ['- list with inline $a=2$ inline formula $a + b > c$\n', '\n'],
},
},
}).$mount();
await vm.$nextTick();
// expect one list with a katex formula in it
expect(vm.$el.querySelectorAll('li')).toHaveLength(1);
expect(vm.$el.querySelectorAll('li .katex')).toHaveLength(2);
});
});
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment