Skip to content

Commit 4c3ddcd

Browse files
committed
Tests for Markdown in DocTour
1 parent bf25379 commit 4c3ddcd

File tree

4 files changed

+136
-19
lines changed

4 files changed

+136
-19
lines changed

app/client/ui/DocTour.ts

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@ import {dom} from 'grainjs';
1212
import sortBy = require('lodash/sortBy');
1313
import {marked} from "marked";
1414
import {renderer} from 'app/client/ui/DocTutorialRenderer';
15-
import {sanitizeTutorialHTML} from "./sanitizeHTML";
16-
15+
import {sanitizeTourHTML} from "app/client/ui/sanitizeHTML";
1716

1817
const t = makeT('DocTour');
1918

@@ -48,27 +47,24 @@ async function makeDocTour(docData: DocData, docComm: DocComm): Promise<IOnBoard
4847
return String(tableData.getValue(rowId, colId) || "");
4948
}
5049
const title = getValue("Title");
51-
52-
const bodyHtmlContent = sanitizeTutorialHTML(marked.parse(getValue("Body"), {
50+
const bodyHtmlContent = sanitizeTourHTML(marked.parse(getValue("Body"), {
5351
async: false, renderer
5452
}));
55-
const element = document.createElement('div');
56-
element.innerHTML = bodyHtmlContent;
57-
5853

59-
let body: HTMLElement = element;
54+
if (!title && !bodyHtmlContent) {
55+
return null;
56+
}
6057

58+
const element = dom('div', (el) => {
59+
el.innerHTML = bodyHtmlContent;
60+
});
6161

6262
const linkText = getValue("Link_Text");
6363
const linkUrl = getValue("Link_URL");
6464
const linkIcon = getValue("Link_Icon") as IconName;
6565
const locationValue = getValue("Location");
6666
let placement = getValue("Placement");
6767

68-
if (!(title || body)) {
69-
return null;
70-
}
71-
7268
const urlState = sameDocumentUrlState(locationValue);
7369
if (isNarrowScreen() || !placements.includes(placement as Placement)) {
7470
placement = "auto";
@@ -81,10 +77,11 @@ async function makeDocTour(docData: DocData, docComm: DocComm): Promise<IOnBoard
8177
validLinkUrl = false;
8278
}
8379

80+
let body: HTMLElement = element;
8481
if (validLinkUrl && linkText) {
8582
body = dom(
8683
'div',
87-
dom('p', body),
84+
element,
8885
dom('p',
8986
cssButtons(cssLinkBtn(
9087
IconList.includes(linkIcon) ? cssLinkIcon(linkIcon) : null,

app/client/ui/sanitizeHTML.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,13 @@ export function sanitizeHTMLIntoDOM(source: string | Node): DocumentFragment {
2626
}
2727
}
2828

29+
export function sanitizeTourHTML(source: string | Node): string {
30+
return tourPurifier.sanitize(source, {
31+
ADD_TAGS: ['iframe'],
32+
ADD_ATTR: ['allowFullscreen'],
33+
});
34+
}
35+
2936
export function sanitizeTutorialHTML(source: string | Node): string {
3037
return tutorialPurifier.sanitize(source, {
3138
ADD_TAGS: ['iframe'],
@@ -35,6 +42,7 @@ export function sanitizeTutorialHTML(source: string | Node): string {
3542

3643
const defaultPurifier = createDOMPurifier();
3744
const tutorialPurifier = createDOMPurifier();
45+
const tourPurifier = createDOMPurifier();
3846

3947
// If we are executed in a browser, we can add hooks to the purifiers to customize their behavior.
4048
// But sometimes this code is included in tests, where `window` is not defined.

test/fixtures/docs/doctour.grist

-128 KB
Binary file not shown.

test/nbrowser/DocTour.ts

Lines changed: 118 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,19 @@ describe('DocTour', function () {
4747
const docTour = await driver.executeScript('return window._gristDocTour()');
4848
assert.deepEqual(docTour, [
4949
{
50-
body: 'General Kenobi!',
50+
body: '<div>' +
51+
'<p>General Kenobi!</p>\n' +
52+
'</div>',
5153
placement: 'bottom',
5254
selector: '.active_cursor',
5355
showHasModal: false,
5456
title: 'Hello there!',
5557
urlState: {colRef: 2, rowId: 2, sectionId: 1}
5658
},
5759
{
58-
body: 'no title',
60+
body: '<div>' +
61+
'<p>no title</p>\n' +
62+
'</div>',
5963
placement: 'auto',
6064
selector: '.active_cursor',
6165
showHasModal: true,
@@ -64,7 +68,7 @@ describe('DocTour', function () {
6468
},
6569
{
6670
body: '<div>' +
67-
'<p></p>' +
71+
'<div></div>' +
6872
'<p><div class="_grainXXX_">' +
6973
'<a href="https://www.getgrist.com/" target="_blank" class="_grainXXX_ _grainXXX_">' +
7074
'<div class="_grainXXX_ _grainXXX_" style="mask-image: var(--icon-Page);"></div>' +
@@ -79,8 +83,8 @@ describe('DocTour', function () {
7983
urlState: {colRef: 4, rowId: 4, sectionId: 1}
8084
},
8185
{
82-
body: '<div>' +
83-
'<p>Good riddance</p>' +
86+
body: '<div><div>' +
87+
'<p>Good riddance</p>\n</div>' +
8488
'<p><div class="_grainXXX_">' +
8589
'<a href="https://xkcd.com/" target="_blank" class="_grainXXX_ _grainXXX_">' +
8690
'Test link here' +
@@ -92,6 +96,113 @@ describe('DocTour', function () {
9296
showHasModal: true,
9397
title: 'Bye',
9498
urlState: null,
99+
},
100+
{
101+
body: '<div><p>' +
102+
'<strong>bold</strong>' +
103+
' <em>italicized</em>' +
104+
' <code>code</code>' +
105+
' <del>strikethrough</del>' +
106+
'</p>\n' +
107+
'</div>',
108+
placement: 'auto',
109+
selector: '.active_cursor',
110+
showHasModal: true,
111+
title: 'Markdown formating',
112+
urlState: null,
113+
},
114+
{
115+
body: '<div><p></p>' +
116+
'<div class="doc-tutorial-popup-thumbnail">\n' +
117+
' <img title="" src="https://example.com/image.jpg">\n' +
118+
' <div class="doc-tutorial-popup-thumbnail-icon-wrapper">\n' +
119+
' <div class="doc-tutorial-popup-thumbnail-icon"></div>\n' +
120+
' </div>\n</div><p></p>\n' +
121+
'</div>',
122+
placement: 'auto',
123+
selector: '.active_cursor',
124+
showHasModal: false,
125+
title: 'Markdown image',
126+
urlState: {
127+
"colRef": 8,
128+
"rowId": 8,
129+
"sectionId": 1
130+
},
131+
},
132+
{
133+
body: '<div>' +
134+
'<h1>H1</h1>\n<h2>H2</h2>\n<h3>H3</h3>\n' +
135+
'</div>',
136+
placement: 'auto',
137+
selector: '.active_cursor',
138+
showHasModal: true,
139+
title: 'Markdown headings',
140+
urlState: null,
141+
},
142+
{
143+
body: '<div>' +
144+
'<ol>\n<li>First item</li>\n<li>Second item</li>\n<li>Third item</li>\n</ol>\n' +
145+
'</div>',
146+
placement: 'auto',
147+
selector: '.active_cursor',
148+
showHasModal: false,
149+
title: 'Markdown ordered list',
150+
urlState: {
151+
"colRef": 10,
152+
"rowId": 10,
153+
"sectionId": 1
154+
},
155+
},
156+
{
157+
body: '<div>' +
158+
'<ul>\n<li>First item</li>\n<li>Second item</li>\n<li>Third item</li>\n</ul>\n' +
159+
'</div>',
160+
placement: 'auto',
161+
selector: '.active_cursor',
162+
showHasModal: true,
163+
title: 'Markdown unordered list',
164+
urlState: null,
165+
},
166+
{
167+
body: '<div>' +
168+
'<blockquote>\n<p>blockquote</p>\n</blockquote>\n' +
169+
'</div>',
170+
placement: 'auto',
171+
selector: '.active_cursor',
172+
showHasModal: false,
173+
title: 'Markdown quote',
174+
urlState: {
175+
"colRef": 12,
176+
"rowId": 12,
177+
"sectionId": 1
178+
},
179+
},
180+
{
181+
body: '<div>' +
182+
'<pre><code>{\n ' +
183+
'"firstName": "John",\n "lastName": "Smith",\n "age": 25\n' +
184+
'}\n' +
185+
'</code></pre>\n' +
186+
'</div>',
187+
placement: 'auto',
188+
selector: '.active_cursor',
189+
showHasModal: true,
190+
title: 'Markdown code block',
191+
urlState: null,
192+
},
193+
{
194+
body: '<div>' +
195+
'<p><a href="https://www.example.com">title</a></p>\n' +
196+
'</div>',
197+
placement: 'auto',
198+
selector: '.active_cursor',
199+
showHasModal: false,
200+
title: 'Markdown link',
201+
urlState: {
202+
"colRef": 14,
203+
"rowId": 14,
204+
"sectionId": 1
205+
},
95206
}
96207
]);
97208
});
@@ -154,7 +265,8 @@ describe('DocTour', function () {
154265

155266
async function checkDocTourPresent() {
156267
// Check the expected message.
157-
assert.match(await driver.findWait('.test-onboarding-popup', 500).getText(), /General Kenobi/);
268+
assert.isFalse(await driver.findWait('.test-onboarding-popup', 500).findContent('p', 'General Kenobi!').isPresent());
269+
158270
// Check that there is only one popup, and no errors.
159271
assert.lengthOf(await driver.findAll('.test-onboarding-popup'), 1);
160272
await gu.checkForErrors();

0 commit comments

Comments
 (0)