diff options
author | Azul <azul@riseup.net> | 2021-11-30 14:03:38 +0300 |
---|---|---|
committer | Max <max@nextcloud.com> | 2022-02-09 11:42:41 +0300 |
commit | edfbb70f1578dad76ff869b9f2ca0f3c027232d2 (patch) | |
tree | a4458fed13123910d3ab7a5024e08edc33993f0c /src/tests | |
parent | 3c9f8e7b2c1d7019e79e855014ade08f79e86f2d (diff) |
upgrade: tiptap v2
Migrate the entire editor to tiptap v2.
Some changes were introduces that go beyond just using the new tiptap API:
*Collaboration*
Port tiptap1 collaboration.
We still want to use our session and sync mechanism.
*Serialization*
Add Markdown extension to handle serialization.
Tiptap config extensions are not automatically added
to the prosemirror schema anymore.
The extension adds the `toMarkdown` config value to the prosemirror schema.
With the new naming scheme tiptap nodes for a number of elements
do not match the prosemirror names.
Camelcase the marks and nodes from `defaultMarkdownSerializer`
so they match the tiptap names.
*Menubar*
* Specify args for isActive function directly rather than setting a function.
* Make use of the editor instance inside the MenuBar component.
* Use the editor rather than slots for command, focused etc.
* disable icons based on editor.can
* Show menubar as long as submenus are open.
When opening a submenu of the menubar
keep track of the open menu even for the image and the remaining action menu.
Also refocus the editor whenever a submenu is closed.
*MenuBubble*
Let tippy handle the positioning
Tippy is really good at positioning the menu bubble.
Remove all our workarounds and let it do its thing.
In order for this to work the content of the MenuBubble
actually needs to live inside the tippy-content.
Tippy bases its calculations on the width of tippy-content.
So if we have the content hanging in a separate div
with absolute positioning
tippy-content will be 0x0 px and not represent the actual width
of the menu bubble.
*Upgrade image node and ImageView.*
Quite a bit of the syntax changed.
We now need a wrapping `<node-view-wrapper>` element.
Pretty good docs at https://tiptap.dev/guide/node-views/vue#render-a-vue-component
We also need to handle the async action.
It will run the action on it's own.
So in `clickIcon()` we need to test if the action returned anything.
Tiptap v1 had inline images.
v2 keeps them outside of paragraphs by default.
Configure Image node to use inline images as markdownit creates inline images right now.
*Trailing Node*
Tiptap v2 does not ship the trailing node extension anymore.
Included the one from the demos and turned it from typescript into javascript.
*Tests*
In order to isolate some problems tests were added.
The tests in Undeline.spec.js were green right from the beginning.
They are not related to the fix and only helped isolate the problem.
Also introduced a cypress test for Lists
that tests the editor
without rendering the page and logging in.
It is very fast and fairly easy to write.
*Refactorings*
* Split marks into separate files.
Signed-off-by: Max <max@nextcloud.com>
Diffstat (limited to 'src/tests')
-rw-r--r-- | src/tests/createEditor.js | 16 | ||||
-rw-r--r-- | src/tests/extensions/Markdown.spec.js | 59 | ||||
-rw-r--r-- | src/tests/markdown.spec.js | 8 | ||||
-rw-r--r-- | src/tests/marks/Underline.spec.js | 35 | ||||
-rw-r--r-- | src/tests/nodes/ImageView.spec.js | 4 | ||||
-rw-r--r-- | src/tests/nodes/ListItem.spec.js | 20 |
6 files changed, 137 insertions, 5 deletions
diff --git a/src/tests/createEditor.js b/src/tests/createEditor.js new file mode 100644 index 000000000..5047d4b7c --- /dev/null +++ b/src/tests/createEditor.js @@ -0,0 +1,16 @@ +import { Editor } from '@tiptap/core' +import Document from '@tiptap/extension-document' +import Paragraph from '@tiptap/extension-paragraph' +import Text from '@tiptap/extension-text' + +export default function({ content, extensions }) { + return new Editor({ + content, + extensions: [ + Document, + Paragraph, + Text, + ...extensions, + ] + }) +} diff --git a/src/tests/extensions/Markdown.spec.js b/src/tests/extensions/Markdown.spec.js new file mode 100644 index 000000000..d4a2b80d1 --- /dev/null +++ b/src/tests/extensions/Markdown.spec.js @@ -0,0 +1,59 @@ +import { Markdown } from './../../extensions'; +import { createMarkdownSerializer } from './../../extensions/Markdown'; +import Underline from './../../marks/Underline'; +import { BulletList, ListItem } from './../../nodes' +import Image from '@tiptap/extension-image' +import { getExtensionField } from '@tiptap/core' +import createEditor from './../createEditor' + +describe('Markdown extension unit', () => { + it('has a config', () => { + expect(Markdown.config.name).toBe('markdown') + }) + + it('exposes toMarkdown function in Prosemirror', () => { + const extend = getExtensionField(Markdown, 'extendMarkSchema', Markdown) + expect(extend(Underline).toMarkdown).toBeDefined() + }) + + it('makes toMarkdown available in prose mirror schema', () => { + const editor = createEditor({ + extensions: [Markdown, Underline] + }) + const serializer = createMarkdownSerializer(editor.schema) + const underline = serializer.serializer.marks.underline + expect(underline).toEqual(Underline.config.toMarkdown) + const listItem = serializer.serializer.nodes.listItem + expect(typeof listItem).toBe('function') + }) +}) + +describe('Markdown extension integrated in the editor', () => { + it('serializes marks according to their spec', () => { + const editor = createEditor({ + content: '<p><u>Test</u></p>', + extensions: [Markdown, Underline], + }) + const serializer = createMarkdownSerializer(editor.schema) + expect(serializer.serialize(editor.state.doc)).toBe('__Test__') + }) + + it('serializes nodes according to their spec', () => { + const editor = createEditor({ + content: '<p><ul><li>Hello</li></ul></p>', + extensions: [Markdown, BulletList, ListItem], + }) + const serializer = createMarkdownSerializer(editor.schema) + expect(serializer.serialize(editor.state.doc)).toBe('\n* Hello') + }) + + it('serializes nodes with the default prosemirror way', () => { + const editor = createEditor({ + content: `<p><img alt="Hello" src="test" /></p>`, + extensions: [Markdown, Image.configure({inline: true})], + }) + const serializer = createMarkdownSerializer(editor.schema) + expect(serializer.serialize(editor.state.doc)).toBe('![Hello](test)') + }) + +}) diff --git a/src/tests/markdown.spec.js b/src/tests/markdown.spec.js index 6a229e7b3..1be46087a 100644 --- a/src/tests/markdown.spec.js +++ b/src/tests/markdown.spec.js @@ -1,4 +1,5 @@ -import { createEditor, createMarkdownSerializer } from './../EditorFactory'; +import { createEditor } from './../EditorFactory'; +import { createMarkdownSerializer } from './../extensions/Markdown' import spec from "./fixtures/spec" import markdownit from './../markdownit' @@ -7,7 +8,7 @@ const markdownThroughEditor = (markdown) => { content: markdownit.render(markdown), enableRichEditing: true }) - const serializer = createMarkdownSerializer(tiptap.nodes, tiptap.marks) + const serializer = createMarkdownSerializer(tiptap.schema) return serializer.serialize(tiptap.state.doc) } @@ -16,7 +17,7 @@ const markdownThroughEditorHtml = (html) => { content: html, enableRichEditing: true }) - const serializer = createMarkdownSerializer(tiptap.nodes, tiptap.marks) + const serializer = createMarkdownSerializer(tiptap.schema) return serializer.serialize(tiptap.state.doc) } @@ -123,6 +124,7 @@ describe('Markdown serializer from html', () => { }) test('images', () => { expect(markdownThroughEditorHtml('<img src="image" alt="description" />')).toBe('![description](image)') + expect(markdownThroughEditorHtml('<p><img src="image" alt="description" /></p>')).toBe('![description](image)') }) test('checkboxes', () => { expect(markdownThroughEditorHtml('<ul><li><input type="checkbox" checked /><label>foo</label></li></ul>')).toBe('* [x] foo') diff --git a/src/tests/marks/Underline.spec.js b/src/tests/marks/Underline.spec.js new file mode 100644 index 000000000..1146398b6 --- /dev/null +++ b/src/tests/marks/Underline.spec.js @@ -0,0 +1,35 @@ +import Underline from './../../marks/Underline'; +import { getExtensionField } from '@tiptap/core' +import createEditor from './../createEditor' + +describe('Underline extension unit', () => { + it('exposes toMarkdown function', () => { + const toMarkdown = getExtensionField(Underline, 'toMarkdown', Underline) + expect(toMarkdown).toEqual({ + open: '__', + close: '__', + mixable: true, + expelEnclosingWhitespace: true, + }) + }) +}) + +describe('Underline extension integrated in the editor', () => { + + it('is not active by default', () => { + const editor = createEditor({ + content: '<p>Test</p>', + extensions: [Underline], + }) + expect(editor.isActive('underline')).toBe(false) + }) + + it('is active within <u> tags', () => { + const editor = createEditor({ + content: '<p><u>Test</u></p>', + extensions: [Underline], + }) + expect(editor.isActive('underline')).toBe(true) + }) + +}) diff --git a/src/tests/nodes/ImageView.spec.js b/src/tests/nodes/ImageView.spec.js index db1ac3e00..fcb4b0c4a 100644 --- a/src/tests/nodes/ImageView.spec.js +++ b/src/tests/nodes/ImageView.spec.js @@ -12,7 +12,7 @@ describe('Image View src attribute based on markdown', () => { const factory = attrs => { const propsData = { - options: {currentDirectory: '/current'}, + extension: { options: {currentDirectory: '/current'} }, node: {attrs} } const data = () => ({ @@ -46,7 +46,7 @@ describe('Image View src attribute based on markdown', () => { test('use dav paths for gifs so they are animated', () => { const src = '/Media/giffy.gif?fileId=7#mimetype=image%2Fgif&hasPreview=true' const wrapper = factory({src}) - expect(wrapper.vm.options.currentDirectory).toBe('/current') + expect(wrapper.vm.extension.options.currentDirectory).toBe('/current') expect(wrapper.find('.image__main').attributes('src')) .toContain("remote.php/dav/files/user1/current/Media/giffy.gif") }) diff --git a/src/tests/nodes/ListItem.spec.js b/src/tests/nodes/ListItem.spec.js new file mode 100644 index 000000000..f9ef09136 --- /dev/null +++ b/src/tests/nodes/ListItem.spec.js @@ -0,0 +1,20 @@ +import { BulletList, ListItem } from './../../nodes' +import Markdown from './../../extensions/Markdown' +import { getExtensionField } from '@tiptap/core' +import createEditor from './../createEditor' + +describe('ListItem extension', () => { + it('exposes toMarkdown function', () => { + const toMarkdown = getExtensionField(ListItem, 'toMarkdown', ListItem) + expect(typeof toMarkdown).toEqual('function') + }) + + it('exposes the toMarkdown function in the prosemirror schema', () => { + const editor = createEditor({ + extensions: [Markdown, BulletList, ListItem] + }) + const listItem = editor.schema.nodes.listItem + expect(listItem.spec.toMarkdown).toBeDefined() + }) + +}) |