1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-24 10:27:10 +02:00

Chore: Mobile: Fix CodeMirror test failures (#7522)

This commit is contained in:
Henry Heino 2022-12-30 09:25:31 -08:00 committed by GitHub
parent 767213cdc1
commit e7386e6fe3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 293 additions and 252 deletions

View File

@ -933,6 +933,9 @@ packages/app-mobile/components/NoteEditor/CodeMirror/testUtil/createEditor.js.ma
packages/app-mobile/components/NoteEditor/CodeMirror/testUtil/forceFullParse.d.ts
packages/app-mobile/components/NoteEditor/CodeMirror/testUtil/forceFullParse.js
packages/app-mobile/components/NoteEditor/CodeMirror/testUtil/forceFullParse.js.map
packages/app-mobile/components/NoteEditor/CodeMirror/testUtil/loadLanguages.d.ts
packages/app-mobile/components/NoteEditor/CodeMirror/testUtil/loadLanguages.js
packages/app-mobile/components/NoteEditor/CodeMirror/testUtil/loadLanguages.js.map
packages/app-mobile/components/NoteEditor/CodeMirror/theme.d.ts
packages/app-mobile/components/NoteEditor/CodeMirror/theme.js
packages/app-mobile/components/NoteEditor/CodeMirror/theme.js.map

3
.gitignore vendored
View File

@ -921,6 +921,9 @@ packages/app-mobile/components/NoteEditor/CodeMirror/testUtil/createEditor.js.ma
packages/app-mobile/components/NoteEditor/CodeMirror/testUtil/forceFullParse.d.ts
packages/app-mobile/components/NoteEditor/CodeMirror/testUtil/forceFullParse.js
packages/app-mobile/components/NoteEditor/CodeMirror/testUtil/forceFullParse.js.map
packages/app-mobile/components/NoteEditor/CodeMirror/testUtil/loadLanguages.d.ts
packages/app-mobile/components/NoteEditor/CodeMirror/testUtil/loadLanguages.js
packages/app-mobile/components/NoteEditor/CodeMirror/testUtil/loadLanguages.js.map
packages/app-mobile/components/NoteEditor/CodeMirror/theme.d.ts
packages/app-mobile/components/NoteEditor/CodeMirror/theme.js
packages/app-mobile/components/NoteEditor/CodeMirror/theme.js.map

View File

@ -7,6 +7,7 @@ import { initCodeMirror } from './CodeMirror';
import { themeStyle } from '@joplin/lib/theme';
import Setting from '@joplin/lib/models/Setting';
import { forceParsing } from '@codemirror/language';
import loadLangauges from './testUtil/loadLanguages';
const createEditorSettings = (themeId: number) => {
@ -27,6 +28,7 @@ describe('CodeMirror', () => {
const initialText = `${headerLineText}\nThis is a test.`;
const editorSettings = createEditorSettings(Setting.THEME_LIGHT);
await loadLangauges();
const editor = initCodeMirror(document.body, initialText, editorSettings);
// Force the generation of the syntax tree now.

View File

@ -10,10 +10,11 @@ describe('markdownCommands.bulletedVsChecklist', () => {
const bulletedListPart = '- Test\n- This is a test.\n- 3\n- 4\n- 5';
const checklistPart = '- [ ] This is a checklist\n- [ ] with multiple items.\n- [ ] ☑';
const initialDocText = `${bulletedListPart}\n\n${checklistPart}`;
const expectedTags = ['BulletList', 'Task'];
it('should remove a checklist following a bulleted list without modifying the bulleted list', () => {
const editor = createEditor(
initialDocText, EditorSelection.cursor(bulletedListPart.length + 5)
it('should remove a checklist following a bulleted list without modifying the bulleted list', async () => {
const editor = await createEditor(
initialDocText, EditorSelection.cursor(bulletedListPart.length + 5), expectedTags
);
toggleList(ListType.CheckList)(editor);
@ -22,9 +23,9 @@ describe('markdownCommands.bulletedVsChecklist', () => {
);
});
it('should remove an unordered list following a checklist without modifying the checklist', () => {
const editor = createEditor(
initialDocText, EditorSelection.cursor(bulletedListPart.length - 5)
it('should remove an unordered list following a checklist without modifying the checklist', async () => {
const editor = await createEditor(
initialDocText, EditorSelection.cursor(bulletedListPart.length - 5), expectedTags
);
toggleList(ListType.UnorderedList)(editor);
@ -33,9 +34,9 @@ describe('markdownCommands.bulletedVsChecklist', () => {
);
});
it('should replace a selection of unordered and task lists with a correctly-numbered list', () => {
const editor = createEditor(
initialDocText, EditorSelection.range(0, initialDocText.length)
it('should replace a selection of unordered and task lists with a correctly-numbered list', async () => {
const editor = await createEditor(
initialDocText, EditorSelection.range(0, initialDocText.length), expectedTags
);
toggleList(ListType.OrderedList)(editor);

View File

@ -2,36 +2,18 @@
* @jest-environment jsdom
*/
import { EditorSelection, EditorState, SelectionRange } from '@codemirror/state';
import { EditorView } from '@codemirror/view';
import { EditorSelection } from '@codemirror/state';
import {
toggleBolded, toggleCode, toggleHeaderLevel, toggleItalicized, toggleMath, updateLink,
} from './markdownCommands';
import { GFM as GithubFlavoredMarkdownExt } from '@lezer/markdown';
import { markdown } from '@codemirror/lang-markdown';
import { MarkdownMathExtension } from './markdownMathParser';
import { indentUnit } from '@codemirror/language';
// Creates and returns a minimal editor with markdown extensions
const createEditor = (initialText: string, initialSelection: SelectionRange): EditorView => {
return new EditorView({
doc: initialText,
selection: EditorSelection.create([initialSelection]),
extensions: [
markdown({
extensions: [MarkdownMathExtension, GithubFlavoredMarkdownExt],
}),
indentUnit.of('\t'),
EditorState.tabSize.of(4),
],
});
};
import createEditor from './testUtil/createEditor';
import { blockMathTagName } from './markdownMathParser';
describe('markdownCommands', () => {
it('should bold/italicize everything selected', () => {
it('should bold/italicize everything selected', async () => {
const initialDocText = 'Testing...';
const editor = createEditor(
initialDocText, EditorSelection.range(0, initialDocText.length)
const editor = await createEditor(
initialDocText, EditorSelection.range(0, initialDocText.length), []
);
toggleBolded(editor);
@ -55,10 +37,10 @@ describe('markdownCommands', () => {
expect(editor.state.doc.toString()).toBe('Testing...');
});
it('for a cursor, bolding, then italicizing, should produce a bold-italic region', () => {
it('for a cursor, bolding, then italicizing, should produce a bold-italic region', async () => {
const initialDocText = '';
const editor = createEditor(
initialDocText, EditorSelection.cursor(0)
const editor = await createEditor(
initialDocText, EditorSelection.cursor(0), []
);
toggleBolded(editor);
@ -73,9 +55,9 @@ describe('markdownCommands', () => {
expect(editor.state.doc.toString()).toBe('***Test*** Test');
});
it('toggling math should both create and navigate out of math regions', () => {
it('toggling math should both create and navigate out of math regions', async () => {
const initialDocText = 'Testing... ';
const editor = createEditor(initialDocText, EditorSelection.cursor(initialDocText.length));
const editor = await createEditor(initialDocText, EditorSelection.cursor(initialDocText.length), []);
toggleMath(editor);
expect(editor.state.doc.toString()).toBe('Testing... $$');
@ -89,9 +71,9 @@ describe('markdownCommands', () => {
expect(editor.state.doc.toString()).toBe('Testing... $3 + 3 \\neq 5$...');
});
it('toggling inline code should both create and navigate out of an inline code region', () => {
it('toggling inline code should both create and navigate out of an inline code region', async () => {
const initialDocText = 'Testing...\n\n';
const editor = createEditor(initialDocText, EditorSelection.cursor(initialDocText.length));
const editor = await createEditor(initialDocText, EditorSelection.cursor(initialDocText.length), []);
toggleCode(editor);
editor.dispatch(editor.state.replaceSelection('f(x) = ...'));
@ -101,9 +83,9 @@ describe('markdownCommands', () => {
expect(editor.state.doc.toString()).toBe('Testing...\n\n`f(x) = ...` is a function.');
});
it('should set headers to the proper levels (when toggling)', () => {
it('should set headers to the proper levels (when toggling)', async () => {
const initialDocText = 'Testing...\nThis is a test.';
const editor = createEditor(initialDocText, EditorSelection.cursor(3));
const editor = await createEditor(initialDocText, EditorSelection.cursor(3), []);
toggleHeaderLevel(1)(editor);
@ -127,11 +109,12 @@ describe('markdownCommands', () => {
expect(mainSel.from).toBe('Testing...'.length);
});
it('headers should toggle properly within block quotes', () => {
it('headers should toggle properly within block quotes', async () => {
const initialDocText = 'Testing...\n\n> This is a test.\n> ...a test';
const editor = createEditor(
const editor = await createEditor(
initialDocText,
EditorSelection.cursor('Testing...\n\n> This'.length)
EditorSelection.cursor('Testing...\n\n> This'.length),
['Blockquote']
);
toggleHeaderLevel(1)(editor);
@ -150,69 +133,48 @@ describe('markdownCommands', () => {
);
});
// We need to disable this test because it randomly fails on CI.
//
// ● markdownCommands › block math should properly toggle within block quotes
//
// expect(received).toEqual(expected) // deep equality
//
// - Expected - 1
// + Received + 3
//
// Testing...
//
// - > This is a test.
// + > $$
// + > This is$$ a test.
// > y = mx + b
// + > $$
// > ...a test
//
// 179 | toggleMath(editor);
// 180 | mainSel = editor.state.selection.main;
// > 181 | expect(editor.state.doc.toString()).toEqual(initialDocText);
// | ^
// 182 | expect(mainSel.from).toBe('Testing...\n\n'.length);
// 183 | expect(mainSel.to).toBe('Testing...\n\n> This is a test.\n> y = mx + b'.length);
// 184 | });
it('block math should be created correctly within block quotes', async () => {
const initialDocText = 'Testing...\n\n> This is a test.\n> y = mx + b\n> ...a test';
const editor = await createEditor(
initialDocText,
EditorSelection.range(
'Testing...\n\n> This'.length,
'Testing...\n\n> This is a test.\n> y = mx + b'.length
),
['Blockquote']
);
toggleMath(editor);
// it('block math should properly toggle within block quotes', () => {
// const initialDocText = 'Testing...\n\n> This is a test.\n> y = mx + b\n> ...a test';
// const editor = createEditor(
// initialDocText,
// EditorSelection.range(
// 'Testing...\n\n> This'.length,
// 'Testing...\n\n> This is a test.\n> y = mx + b'.length
// )
// );
// Toggling math should surround the content in '$$'s
const mainSel = editor.state.selection.main;
expect(editor.state.doc.toString()).toEqual(
'Testing...\n\n> $$\n> This is a test.\n> y = mx + b\n> $$\n> ...a test'
);
expect(mainSel.from).toBe('Testing...\n\n'.length);
expect(mainSel.to).toBe('Testing...\n\n> $$\n> This is a test.\n> y = mx + b\n> $$'.length);
});
// toggleMath(editor);
it('block math should be correctly removed within block quotes', async () => {
const initialDocText = 'Testing...\n\n> $$\n> This is a test.\n> y = mx + b\n> $$\n> ...a test';
// // Toggling math should surround the content in '$$'s
// let mainSel = editor.state.selection.main;
// expect(editor.state.doc.toString()).toEqual(
// 'Testing...\n\n> $$\n> This is a test.\n> y = mx + b\n> $$\n> ...a test'
// );
// expect(mainSel.from).toBe('Testing...\n\n'.length);
// expect(mainSel.to).toBe('Testing...\n\n> $$\n> This is a test.\n> y = mx + b\n> $$'.length);
const editor = await createEditor(
initialDocText,
EditorSelection.cursor('Testing...\n\n> $$\n> This is'.length),
['Blockquote', blockMathTagName]
);
// // Change to a cursor --- test cursor expansion
// editor.dispatch({
// selection: EditorSelection.cursor('Testing...\n\n> $$\n> This is'.length),
// });
// Toggling math should remove the '$$'s
toggleMath(editor);
const mainSel = editor.state.selection.main;
expect(editor.state.doc.toString()).toEqual('Testing...\n\n> This is a test.\n> y = mx + b\n> ...a test');
expect(mainSel.from).toBe('Testing...\n\n'.length);
expect(mainSel.to).toBe('Testing...\n\n> This is a test.\n> y = mx + b'.length);
});
// // Toggling math again should remove the '$$'s
// toggleMath(editor);
// mainSel = editor.state.selection.main;
// expect(editor.state.doc.toString()).toEqual(initialDocText);
// expect(mainSel.from).toBe('Testing...\n\n'.length);
// expect(mainSel.to).toBe('Testing...\n\n> This is a test.\n> y = mx + b'.length);
// });
it('updateLink should replace link titles and isolate URLs if no title is given', () => {
it('updateLink should replace link titles and isolate URLs if no title is given', async () => {
const initialDocText = '[foo](http://example.com/)';
const editor = createEditor(initialDocText, EditorSelection.cursor('[f'.length));
const editor = await createEditor(initialDocText, EditorSelection.cursor('[f'.length), ['Link']);
updateLink('bar', 'https://example.com/')(editor);
expect(editor.state.doc.toString()).toBe(
@ -225,9 +187,9 @@ describe('markdownCommands', () => {
);
});
it('toggling math twice, starting on a line with content, should a math block', () => {
it('toggling math twice, starting on a line with content, should a math block', async () => {
const initialDocText = 'Testing... ';
const editor = createEditor(initialDocText, EditorSelection.cursor(initialDocText.length));
const editor = await createEditor(initialDocText, EditorSelection.cursor(initialDocText.length), []);
toggleMath(editor);
toggleMath(editor);
@ -235,9 +197,9 @@ describe('markdownCommands', () => {
expect(editor.state.doc.toString()).toBe('Testing... \n$$\nf(x) = ...\n$$');
});
it('toggling math twice on an empty line should create an empty math block', () => {
it('toggling math twice on an empty line should create an empty math block', async () => {
const initialDocText = 'Testing...\n\n';
const editor = createEditor(initialDocText, EditorSelection.cursor(initialDocText.length));
const editor = await createEditor(initialDocText, EditorSelection.cursor(initialDocText.length), []);
toggleMath(editor);
toggleMath(editor);
@ -245,9 +207,9 @@ describe('markdownCommands', () => {
expect(editor.state.doc.toString()).toBe('Testing...\n\n$$\nf(x) = ...\n$$');
});
it('toggling code twice on an empty line should create an empty code block', () => {
it('toggling code twice on an empty line should create an empty code block', async () => {
const initialDocText = 'Testing...\n\n';
const editor = createEditor(initialDocText, EditorSelection.cursor(initialDocText.length));
const editor = await createEditor(initialDocText, EditorSelection.cursor(initialDocText.length), []);
// Toggling code twice should create a block code region
toggleCode(editor);
@ -259,9 +221,9 @@ describe('markdownCommands', () => {
expect(editor.state.doc.toString()).toBe('Testing...\n\nf(x) = ...\n');
});
it('toggling math twice inside a block quote should produce an empty math block', () => {
it('toggling math twice inside a block quote should produce an empty math block', async () => {
const initialDocText = '> Testing...> \n> ';
const editor = createEditor(initialDocText, EditorSelection.cursor(initialDocText.length));
const editor = await createEditor(initialDocText, EditorSelection.cursor(initialDocText.length), ['Blockquote']);
toggleMath(editor);
toggleMath(editor);
@ -278,9 +240,9 @@ describe('markdownCommands', () => {
expect(sel.to).toBe(editor.state.doc.length);
});
it('toggling inline code should both create and navigate out of an inline code region', () => {
it('toggling inline code should both create and navigate out of an inline code region', async () => {
const initialDocText = 'Testing...\n\n';
const editor = createEditor(initialDocText, EditorSelection.cursor(initialDocText.length));
const editor = await createEditor(initialDocText, EditorSelection.cursor(initialDocText.length), []);
toggleCode(editor);
editor.dispatch(editor.state.replaceSelection('f(x) = ...'));

View File

@ -10,25 +10,27 @@ import { ListType } from '../types';
import createEditor from './testUtil/createEditor';
describe('markdownCommands.toggleList', () => {
it('should remove the same type of list', () => {
const initialDocText = '- testing\n- this is a test';
it('should remove the same type of list', async () => {
const initialDocText = '- testing\n- this is a `test`\n';
const editor = createEditor(
const editor = await createEditor(
initialDocText,
EditorSelection.cursor(5)
EditorSelection.cursor(5),
['BulletList', 'InlineCode']
);
toggleList(ListType.UnorderedList)(editor);
expect(editor.state.doc.toString()).toBe(
'testing\nthis is a test'
'testing\nthis is a `test`\n'
);
});
it('should insert a numbered list with correct numbering', () => {
it('should insert a numbered list with correct numbering', async () => {
const initialDocText = 'Testing...\nThis is a test\nof list toggling...';
const editor = createEditor(
const editor = await createEditor(
initialDocText,
EditorSelection.cursor('Testing...\nThis is a'.length)
EditorSelection.cursor('Testing...\nThis is a'.length),
[]
);
toggleList(ListType.OrderedList)(editor);
@ -47,12 +49,13 @@ describe('markdownCommands.toggleList', () => {
);
});
const numberedListText = '- 1\n- 2\n- 3\n- 4\n- 5\n- 6\n- 7';
const unorderedListText = '- 1\n- 2\n- 3\n- 4\n- 5\n- 6\n- 7';
it('should correctly replace an unordered list with a numbered list', () => {
const editor = createEditor(
numberedListText,
EditorSelection.cursor(numberedListText.length)
it('should correctly replace an unordered list with a numbered list', async () => {
const editor = await createEditor(
unorderedListText,
EditorSelection.cursor(unorderedListText.length),
['BulletList']
);
toggleList(ListType.OrderedList)(editor);
@ -62,10 +65,11 @@ describe('markdownCommands.toggleList', () => {
});
it('should correctly replace an unordered list with a checklist', () => {
const editor = createEditor(
numberedListText,
EditorSelection.cursor(numberedListText.length)
it('should correctly replace an unordered list with a checklist', async () => {
const editor = await createEditor(
unorderedListText,
EditorSelection.cursor(unorderedListText.length),
['BulletList']
);
toggleList(ListType.CheckList)(editor);
@ -74,13 +78,14 @@ describe('markdownCommands.toggleList', () => {
);
});
it('should properly toggle a sublist of a bulleted list', () => {
it('should properly toggle a sublist of a bulleted list', async () => {
const preSubListText = '# List test\n * This\n * is\n';
const initialDocText = `${preSubListText}\t* a\n\t* test\n * of list toggling`;
const editor = createEditor(
const editor = await createEditor(
initialDocText,
EditorSelection.cursor(preSubListText.length + '\t* a'.length)
EditorSelection.cursor(preSubListText.length + '\t* a'.length),
['BulletList', 'ATXHeading1']
);
// Indentation should be preserved when changing list types
@ -94,6 +99,17 @@ describe('markdownCommands.toggleList', () => {
expect(editor.state.selection.main.to).toBe(
`${preSubListText}\t1. a\n\t2. test`.length
);
});
it('should not preserve indentation when removing sublists', async () => {
const preSubListText = '# List test\n * This\n * is\n';
const initialDocText = `${preSubListText}\t1. a\n\t2. test\n * of list toggling`;
const editor = await createEditor(
initialDocText,
EditorSelection.range(preSubListText.length, `${preSubListText}\t1. a\n\t2. test`.length),
['ATXHeading1', 'BulletList', 'OrderedList']
);
// Indentation should not be preserved when removing lists
toggleList(ListType.OrderedList)(editor);
@ -102,51 +118,47 @@ describe('markdownCommands.toggleList', () => {
'# List test\n * This\n * is\na\ntest\n * of list toggling'
);
// The below test:
// `expect(editor.state.doc.toString()).toBe(expectedChecklistPart)`
// randomly fails on CI, so disabling it for now.
// Put the cursor in the middle of the list
editor.dispatch({ selection: EditorSelection.cursor(preSubListText.length) });
// Sublists should be changed
toggleList(ListType.CheckList)(editor);
const expectedChecklistPart =
'# List test\n - [ ] This\n - [ ] is\n - [ ] a\n - [ ] test\n - [ ] of list toggling';
expect(editor.state.doc.toString()).toBe(
expectedChecklistPart
);
// // Put the cursor in the middle of the list
// editor.dispatch({ selection: EditorSelection.cursor(preSubListText.length) });
editor.dispatch({ selection: EditorSelection.cursor(editor.state.doc.length) });
editor.dispatch(editor.state.replaceSelection('\n\n\n'));
// // Sublists should be changed
// toggleList(ListType.CheckList)(editor);
// const expectedChecklistPart =
// '# List test\n - [ ] This\n - [ ] is\n - [ ] a\n - [ ] test\n - [ ] of list toggling';
// expect(editor.state.doc.toString()).toBe(
// expectedChecklistPart
// );
// toggleList should also create a new list if the cursor is on an empty line.
toggleList(ListType.OrderedList)(editor);
editor.dispatch(editor.state.replaceSelection('Test.\n2. Test2\n3. Test3'));
// editor.dispatch({ selection: EditorSelection.cursor(editor.state.doc.length) });
// editor.dispatch(editor.state.replaceSelection('\n\n\n'));
expect(editor.state.doc.toString()).toBe(
`${expectedChecklistPart}\n\n\n1. Test.\n2. Test2\n3. Test3`
);
// // toggleList should also create a new list if the cursor is on an empty line.
// toggleList(ListType.OrderedList)(editor);
// editor.dispatch(editor.state.replaceSelection('Test.\n2. Test2\n3. Test3'));
toggleList(ListType.CheckList)(editor);
expect(editor.state.doc.toString()).toBe(
`${expectedChecklistPart}\n\n\n- [ ] Test.\n- [ ] Test2\n- [ ] Test3`
);
// expect(editor.state.doc.toString()).toBe(
// `${expectedChecklistPart}\n\n\n1. Test.\n2. Test2\n3. Test3`
// );
// toggleList(ListType.CheckList)(editor);
// expect(editor.state.doc.toString()).toBe(
// `${expectedChecklistPart}\n\n\n- [ ] Test.\n- [ ] Test2\n- [ ] Test3`
// );
// // The entire checklist should have been selected (and thus will now be indented)
// increaseIndent(editor);
// expect(editor.state.doc.toString()).toBe(
// `${expectedChecklistPart}\n\n\n\t- [ ] Test.\n\t- [ ] Test2\n\t- [ ] Test3`
// );
// The entire checklist should have been selected (and thus will now be indented)
increaseIndent(editor);
expect(editor.state.doc.toString()).toBe(
`${expectedChecklistPart}\n\n\n\t- [ ] Test.\n\t- [ ] Test2\n\t- [ ] Test3`
);
});
it('should toggle a numbered list without changing its sublists', () => {
it('should toggle a numbered list without changing its sublists', async () => {
const initialDocText = '1. Foo\n2. Bar\n3. Baz\n\t- Test\n\t- of\n\t- sublists\n4. Foo';
const editor = createEditor(
const editor = await createEditor(
initialDocText,
EditorSelection.cursor(0)
EditorSelection.cursor(0),
['OrderedList', 'BulletList']
);
toggleList(ListType.CheckList)(editor);
@ -155,12 +167,13 @@ describe('markdownCommands.toggleList', () => {
);
});
it('should toggle a sublist without changing the parent list', () => {
it('should toggle a sublist without changing the parent list', async () => {
const initialDocText = '1. This\n2. is\n3. ';
const editor = createEditor(
const editor = await createEditor(
initialDocText,
EditorSelection.cursor(initialDocText.length)
EditorSelection.cursor(initialDocText.length),
['OrderedList']
);
increaseIndent(editor);
@ -177,11 +190,12 @@ describe('markdownCommands.toggleList', () => {
);
});
it('should toggle lists properly within block quotes', () => {
it('should toggle lists properly within block quotes', async () => {
const preSubListText = '> # List test\n> * This\n> * is\n';
const initialDocText = `${preSubListText}> \t* a\n> \t* test\n> * of list toggling`;
const editor = createEditor(
initialDocText, EditorSelection.cursor(preSubListText.length + 3)
const editor = await createEditor(
initialDocText, EditorSelection.cursor(preSubListText.length + 3),
['BlockQuote', 'BulletList']
);
toggleList(ListType.OrderedList)(editor);

View File

@ -1,24 +1,17 @@
import { markdown } from '@codemirror/lang-markdown';
/**
* @jest-environment jsdom
*/
import { syntaxTree } from '@codemirror/language';
import { SyntaxNode } from '@lezer/common';
import { EditorState } from '@codemirror/state';
import { blockMathTagName, inlineMathTagName, MarkdownMathExtension } from './markdownMathParser';
import { GFM as GithubFlavoredMarkdownExt } from '@lezer/markdown';
import forceFullParse from './testUtil/forceFullParse';
import { EditorSelection, EditorState } from '@codemirror/state';
import { blockMathTagName, inlineMathContentTagName, inlineMathTagName } from './markdownMathParser';
import createEditor from './testUtil/createEditor';
// Creates an EditorState with math and markdown extensions
const createEditorState = (initialText: string): EditorState => {
const editorState = EditorState.create({
doc: initialText,
extensions: [
markdown({
extensions: [MarkdownMathExtension, GithubFlavoredMarkdownExt],
}),
],
});
forceFullParse(editorState);
return editorState;
const createEditorState = async (initialText: string, expectedTags: string[]): Promise<EditorState> => {
return (await createEditor(initialText, EditorSelection.cursor(0), expectedTags)).state;
};
// Returns a list of all nodes with the given name in the given editor's syntax tree.
@ -38,31 +31,29 @@ const findNodesWithName = (editor: EditorState, nodeName: string) => {
describe('markdownMathParser', () => {
// Disable flaky test - randomly fails on line `expect(inlineMathContentNodes.length).toBe(0);`
it('should parse inline math that contains space characters, numbers, and symbols', async () => {
const documentText = '$3 + 3$';
const editor = await createEditorState(documentText, [inlineMathTagName, 'number']);
const inlineMathNodes = findNodesWithName(editor, inlineMathTagName);
const inlineMathContentNodes = findNodesWithName(editor, inlineMathContentTagName);
// it('should parse inline math that contains space characters, numbers, and symbols', () => {
// const documentText = '$3 + 3$';
// const editor = createEditorState(documentText);
// const inlineMathNodes = findNodesWithName(editor, inlineMathTagName);
// const inlineMathContentNodes = findNodesWithName(editor, inlineMathContentTagName);
// There should only be one inline node
expect(inlineMathNodes.length).toBe(1);
// // There should only be one inline node
// expect(inlineMathNodes.length).toBe(1);
expect(inlineMathNodes[0].from).toBe(0);
expect(inlineMathNodes[0].to).toBe(documentText.length);
// expect(inlineMathNodes[0].from).toBe(0);
// expect(inlineMathNodes[0].to).toBe(documentText.length);
// The content tag should be replaced by the internal sTeX parser
expect(inlineMathContentNodes.length).toBe(0);
});
// // The content tag should be replaced by the internal sTeX parser
// expect(inlineMathContentNodes.length).toBe(0);
// });
it('should parse comment within multi-word inline math', () => {
it('should parse comment within multi-word inline math', async () => {
const beforeMath = '# Testing!\n\nThis is a test of ';
const mathRegion = '$\\TeX % TeX Comment!$';
const afterMath = ' formatting.';
const documentText = `${beforeMath}${mathRegion}${afterMath}`;
const editor = createEditorState(documentText);
const editor = await createEditorState(documentText, [inlineMathTagName, 'comment']);
const inlineMathNodes = findNodesWithName(editor, inlineMathTagName);
const blockMathNodes = findNodesWithName(editor, blockMathTagName);
const commentNodes = findNodesWithName(editor, 'comment');
@ -75,30 +66,30 @@ describe('markdownMathParser', () => {
expect(inlineMathNodes[0].to).toBe(beforeMath.length + mathRegion.length);
});
it('shouldn\'t start inline math if there is no ending $', () => {
const documentText = 'This is a $test\n\nof inline math$...';
const editor = createEditorState(documentText);
it('shouldn\'t start inline math if there is no ending $', async () => {
const documentText = '*This* is a $test\n\nof inline math$...';
const editor = await createEditorState(documentText, ['Emphasis']);
const inlineMathNodes = findNodesWithName(editor, inlineMathTagName);
// Math should end if there is no matching '$'.
expect(inlineMathNodes.length).toBe(0);
});
it('shouldn\'t start if math would have a space just after the $', () => {
const documentText = 'This is a $ test of inline math$...\n\n$Testing... $...';
const editor = createEditorState(documentText);
it('shouldn\'t start if math would have a space just after the $', async () => {
const documentText = 'This *is* a $ test of inline math$...\n\n$Testing... $...';
const editor = await createEditorState(documentText, ['Emphasis']);
expect(findNodesWithName(editor, inlineMathTagName).length).toBe(0);
});
it('shouldn\'t start inline math if $ is escaped', () => {
const documentText = 'This is a \\$test of inline math$...';
const editor = createEditorState(documentText);
it('shouldn\'t start inline math if $ is escaped', async () => {
const documentText = 'This is a \\$test of inline math$... **Testing...**';
const editor = await createEditorState(documentText, ['StrongEmphasis']);
expect(findNodesWithName(editor, inlineMathTagName).length).toBe(0);
});
it('should correctly parse document containing just block math', () => {
const documentText = '$$\n\t\\{ 1, 1, 2, 3, 5, ... \\}\n$$';
const editor = createEditorState(documentText);
it('should correctly parse document containing just block math', async () => {
const documentText = '$$\n\t\\{ 1, 1, 2, 3, 5, ... \\} % Comment\n$$';
const editor = await createEditorState(documentText, [blockMathTagName, 'comment']);
const inlineMathNodes = findNodesWithName(editor, inlineMathTagName);
const blockMathNodes = findNodesWithName(editor, blockMathTagName);
@ -109,10 +100,10 @@ describe('markdownMathParser', () => {
expect(blockMathNodes[0].to).toBe(documentText.length);
});
it('should correctly parse comment in block math', () => {
it('should correctly parse comment in block math', async () => {
const startingText = '$$ % Testing...\n\t\\text{Test.}\n$$';
const afterMath = '\nTest.';
const editor = createEditorState(startingText + afterMath);
const editor = await createEditorState(startingText + afterMath, ['comment', blockMathTagName]);
const inlineMathNodes = findNodesWithName(editor, inlineMathTagName);
const blockMathNodes = findNodesWithName(editor, blockMathTagName);
const texParserComments = findNodesWithName(editor, 'comment');
@ -130,10 +121,10 @@ describe('markdownMathParser', () => {
});
});
it('should extend block math without ending tag to end of document', () => {
it('should extend block math without ending tag to end of document', async () => {
const beforeMath = '# Testing...\n\n';
const documentText = `${beforeMath}$$\n\t\\text{Testing...}\n\n\t3 + 3 = 6`;
const editor = createEditorState(documentText);
const documentText = `${beforeMath}$$\n\t\\text{Testing...}\n\n\t3 + 3 = 6 % Comment`;
const editor = await createEditorState(documentText, ['ATXHeading1', blockMathTagName, 'comment']);
const blockMathNodes = findNodesWithName(editor, blockMathTagName);
expect(blockMathNodes.length).toBe(1);
@ -141,9 +132,9 @@ describe('markdownMathParser', () => {
expect(blockMathNodes[0].to).toBe(documentText.length);
});
it('should parse block math declared on a single line', () => {
it('should parse block math declared on a single line', async () => {
const documentText = '$$ Test. $$';
const editor = createEditorState(documentText);
const editor = await createEditorState(documentText, [blockMathTagName]);
const blockMathNodes = findNodesWithName(editor, blockMathTagName);
expect(blockMathNodes.length).toBe(1);

View File

@ -1,13 +1,19 @@
import { markdown } from '@codemirror/lang-markdown';
import { GFM as GithubFlavoredMarkdownExt } from '@lezer/markdown';
import { indentUnit } from '@codemirror/language';
import { indentUnit, syntaxTree } from '@codemirror/language';
import { SelectionRange, EditorSelection, EditorState } from '@codemirror/state';
import { EditorView } from '@codemirror/view';
import { MarkdownMathExtension } from '../markdownMathParser';
import forceFullParse from './forceFullParse';
import loadLangauges from './loadLanguages';
// Creates and returns a minimal editor with markdown extensions. Waits to return the editor
// until all syntax tree tags in `expectedSyntaxTreeTags` exist.
const createEditor = async (
initialText: string, initialSelection: SelectionRange, expectedSyntaxTreeTags: string[]
): Promise<EditorView> => {
await loadLangauges();
// Creates and returns a minimal editor with markdown extensions
const createEditor = (initialText: string, initialSelection: SelectionRange): EditorView => {
const editor = new EditorView({
doc: initialText,
selection: EditorSelection.create([initialSelection]),
@ -20,7 +26,39 @@ const createEditor = (initialText: string, initialSelection: SelectionRange): Ed
],
});
forceFullParse(editor.state);
let sawExpectedTagCount = 0;
while (sawExpectedTagCount < expectedSyntaxTreeTags.length) {
forceFullParse(editor.state);
sawExpectedTagCount = 0;
const seenTags = new Set<string>();
syntaxTree(editor.state).iterate({
from: 0,
to: editor.state.doc.length,
enter: (node) => {
for (const expectedTag of expectedSyntaxTreeTags) {
if (node.name === expectedTag) {
seenTags.add(node.name);
sawExpectedTagCount ++;
break;
}
}
},
});
if (sawExpectedTagCount < expectedSyntaxTreeTags.length) {
const missingTags = expectedSyntaxTreeTags.filter(tagName => {
return !seenTags.has(tagName);
});
console.warn(`Didn't find all expected tags. Missing ${missingTags}. Retrying...`);
await new Promise(resolve => {
setTimeout(resolve, 500);
});
}
}
return editor;
};

View File

@ -0,0 +1,12 @@
import syntaxHighlightingLanguages from '../syntaxHighlightingLanguages';
// Ensure languages we use are loaded. Without this, tests may randomly fail (LanguageDescriptions
// are loaded asyncronously, in the background).
const loadLangauges = async () => {
const allLanguages = syntaxHighlightingLanguages;
for (const lang of allLanguages) {
await lang.load();
}
};
export default loadLangauges;

View File

@ -81,11 +81,11 @@
"@codemirror/lang-markdown": "6.0.5",
"@codemirror/lang-php": "6.0.1",
"@codemirror/lang-rust": "6.0.1",
"@codemirror/language": "6.3.1",
"@codemirror/language": "6.3.2",
"@codemirror/legacy-modes": "6.3.1",
"@codemirror/search": "6.2.3",
"@codemirror/state": "6.1.4",
"@codemirror/view": "6.6.0",
"@codemirror/view": "6.7.1",
"@joplin/tools": "~2.10",
"@lezer/highlight": "1.1.3",
"@types/fs-extra": "9.0.13",

View File

@ -3173,8 +3173,8 @@ __metadata:
linkType: hard
"@codemirror/autocomplete@npm:^6.0.0":
version: 6.3.4
resolution: "@codemirror/autocomplete@npm:6.3.4"
version: 6.4.0
resolution: "@codemirror/autocomplete@npm:6.4.0"
dependencies:
"@codemirror/language": ^6.0.0
"@codemirror/state": ^6.0.0
@ -3185,7 +3185,7 @@ __metadata:
"@codemirror/state": ^6.0.0
"@codemirror/view": ^6.0.0
"@lezer/common": ^1.0.0
checksum: dafb6b3dee11551ed7a2ec1d20fa05641abefe2e0b5da045d4a3383146bb04f0b9650448a378a5921cc183944d626482a608b71f3da5a036a881a873006b8dbf
checksum: 3470fee01da60d3d71b8b4f8728629c0f0441e704b8b828592f98c000d75fdb2c9077727e82685626cf45b95cadbc0c1a03968261df2f0cfb4162418b5f4dd1f
languageName: node
linkType: hard
@ -3250,7 +3250,7 @@ __metadata:
languageName: node
linkType: hard
"@codemirror/lang-javascript@npm:6.1.1, @codemirror/lang-javascript@npm:^6.0.0":
"@codemirror/lang-javascript@npm:6.1.1":
version: 6.1.1
resolution: "@codemirror/lang-javascript@npm:6.1.1"
dependencies:
@ -3265,6 +3265,21 @@ __metadata:
languageName: node
linkType: hard
"@codemirror/lang-javascript@npm:^6.0.0":
version: 6.1.2
resolution: "@codemirror/lang-javascript@npm:6.1.2"
dependencies:
"@codemirror/autocomplete": ^6.0.0
"@codemirror/language": ^6.0.0
"@codemirror/lint": ^6.0.0
"@codemirror/state": ^6.0.0
"@codemirror/view": ^6.0.0
"@lezer/common": ^1.0.0
"@lezer/javascript": ^1.0.0
checksum: f4336b7efd44e4158b9979f0c23918184c897d0fe3e40b5414bd9243a9899ecdba4dfe13970fe5024a1894579af80cb4c5dd574c6c2b7bd7ff06d8c8cb88616b
languageName: node
linkType: hard
"@codemirror/lang-markdown@npm:6.0.5":
version: 6.0.5
resolution: "@codemirror/lang-markdown@npm:6.0.5"
@ -3302,9 +3317,9 @@ __metadata:
languageName: node
linkType: hard
"@codemirror/language@npm:6.3.1, @codemirror/language@npm:^6.0.0, @codemirror/language@npm:^6.3.0":
version: 6.3.1
resolution: "@codemirror/language@npm:6.3.1"
"@codemirror/language@npm:6.3.2, @codemirror/language@npm:^6.0.0, @codemirror/language@npm:^6.3.0":
version: 6.3.2
resolution: "@codemirror/language@npm:6.3.2"
dependencies:
"@codemirror/state": ^6.0.0
"@codemirror/view": ^6.0.0
@ -3312,7 +3327,7 @@ __metadata:
"@lezer/highlight": ^1.0.0
"@lezer/lr": ^1.0.0
style-mod: ^4.0.0
checksum: 349b9806e1e2ce5d99ba1f5815cc4772e6032f68c95718594e8335196ef0686bc6378db7cdd5f0fda57ba068eebf0ee413bb336e32cc1ff958a743190a0266da
checksum: b70ed9b85d0bea79181c86e88a1f5c0bada30680ee1fe6a68efc01bc037c3d14f94a83602fc46cc4b4393589605ef7e986ed5174443502f3365dd61f883894fa
languageName: node
linkType: hard
@ -3354,14 +3369,14 @@ __metadata:
languageName: node
linkType: hard
"@codemirror/view@npm:6.6.0, @codemirror/view@npm:^6.0.0, @codemirror/view@npm:^6.2.2, @codemirror/view@npm:^6.6.0":
version: 6.6.0
resolution: "@codemirror/view@npm:6.6.0"
"@codemirror/view@npm:6.7.1, @codemirror/view@npm:^6.0.0, @codemirror/view@npm:^6.2.2, @codemirror/view@npm:^6.6.0":
version: 6.7.1
resolution: "@codemirror/view@npm:6.7.1"
dependencies:
"@codemirror/state": ^6.1.4
style-mod: ^4.0.0
w3c-keyname: ^2.2.4
checksum: 9b007eedcf13e94ec7d9c30ee302e1a1fcd382bef2481bd9afa3a116458652983e745b40494eb29d80df1dca8f99e91dcb1e4eba37670c2553ffc90bef0933e7
checksum: 75a5846d61e63027e9bf1dfd0b507932934cb7650b7959c1191e68b161eb1756e9773f964c4331970b51864aef8f7954bc5cc8fdb51b0f6533de6c20568833ed
languageName: node
linkType: hard
@ -4662,11 +4677,11 @@ __metadata:
"@codemirror/lang-markdown": 6.0.5
"@codemirror/lang-php": 6.0.1
"@codemirror/lang-rust": 6.0.1
"@codemirror/language": 6.3.1
"@codemirror/language": 6.3.2
"@codemirror/legacy-modes": 6.3.1
"@codemirror/search": 6.2.3
"@codemirror/state": 6.1.4
"@codemirror/view": 6.6.0
"@codemirror/view": 6.7.1
"@joplin/lib": ~2.10
"@joplin/react-native-saf-x": ~2.10
"@joplin/renderer": ~2.10
@ -6023,12 +6038,12 @@ __metadata:
linkType: hard
"@lezer/css@npm:^1.0.0, @lezer/css@npm:^1.1.0":
version: 1.1.0
resolution: "@lezer/css@npm:1.1.0"
version: 1.1.1
resolution: "@lezer/css@npm:1.1.1"
dependencies:
"@lezer/highlight": ^1.0.0
"@lezer/lr": ^1.0.0
checksum: 5d2a176d7f4cf5076d8841af9b7bcafcbad5dd1b8f46fa1ad56c0fbf76f4bd4cd4ee0b1c4f1f1c9f8dba4fffb88908e64b5d7919c8706b35f575ddff8512ef31
checksum: a7e4893aacaa7f26d5679c77a640f401b37d14155cb54863aa91b59dfd220b280360a341c0fedafc65d31101de13a5ae33cf3876c352f2da528344dafdc9b3d7
languageName: node
linkType: hard
@ -6042,13 +6057,13 @@ __metadata:
linkType: hard
"@lezer/html@npm:^1.1.0":
version: 1.2.0
resolution: "@lezer/html@npm:1.2.0"
version: 1.3.0
resolution: "@lezer/html@npm:1.3.0"
dependencies:
"@lezer/common": ^1.0.0
"@lezer/highlight": ^1.0.0
"@lezer/lr": ^1.0.0
checksum: 737f6884328845100575c3bb9b0add622d00233d9d75f6bd201d37e31b990af371984b7ab91681bfe258234b77d486bc97f61a8ebdb4bd70a942f06a22b1aac1
checksum: e6efde94614a5b7ebf2713b244a110ef9025369561c7bf42fe6dd8f5877d2ee0c71f894b8b43d1284d23bf429fd3688ec3b6b0c2b8702df366c2b5e5cedc4c19
languageName: node
linkType: hard
@ -6063,12 +6078,12 @@ __metadata:
linkType: hard
"@lezer/javascript@npm:^1.0.0":
version: 1.3.1
resolution: "@lezer/javascript@npm:1.3.1"
version: 1.4.0
resolution: "@lezer/javascript@npm:1.4.0"
dependencies:
"@lezer/highlight": ^1.0.0
"@lezer/lr": ^1.0.0
checksum: bcf1a2ac84198f7caedf320d5222b6f4d39ece62d939ebe02b461bb175027c9b66d7be7feba51d3d8317bdd972c4fbdde3d9edbd60fa342b9e97db8ca1b63922
checksum: 36c64e8530feef9b937cf75f8833aa8c0f5c8c0812c55c53a133d1af5deb491dd80084397d5773e873db90ff717aede25b45fa827eead66400cb81b097567c42
languageName: node
linkType: hard