diff --git a/packages/elements/src/components/ino-markdown-editor/ino-markdown-editor.e2e.ts b/packages/elements/src/components/ino-markdown-editor/ino-markdown-editor.e2e.ts deleted file mode 100644 index 5fd06b218e..0000000000 --- a/packages/elements/src/components/ino-markdown-editor/ino-markdown-editor.e2e.ts +++ /dev/null @@ -1,147 +0,0 @@ -import { E2EElement, E2EPage } from '@stencil/core/testing'; -import { setupPageWithContent } from '../../util/e2etests-setup'; -import { ViewMode } from '../types'; - -const MARKDOWN_TEXT = [ - '# Headline 1\n', - '## Headline 2\n', - [ - '_italic_', - '**bold**', - '~~strikethrough~~', - '[elements](https://github.com/inovex/elements)', - '`inline code`', - ].join(' '), - '', - '> Blockquote\n', - '```\nthis should be a code block\n```\n', - '* [x] ToDo 1 checked', - '* [ ] ToDo 2', - '* [ ] ToDo 3', -].join('\n'); - -const HTML_TEXT_TAGS = [ - '<h1>Headline 1</h1>', - '<h2>Headline 2</h2>', - '<em>italic</em>', - '<strong>bold</strong>', - '<s>strikethrough</s>', - '<a target="_blank" rel="noopener noreferrer nofollow" href="https://github.com/inovex/elements">elements</a>', - '<code>inline code</code>', - '<blockquote><p>Blockquote</p></blockquote>', - `<pre><code>this should be a code block -<br class="ProseMirror-trailingBreak"></code></pre>`, -]; - -const INO_MARKDOWN_EDITOR_SELECTOR = 'ino-markdown-editor'; -const INO_MARKDOWN_EDITOR = (initialValue: string, viewMode: ViewMode) => - initialValue - ? `<ino-markdown-editor initial-value="${initialValue}" view-mode="${viewMode}"></ino-markdown-editor>` - : `<ino-markdown-editor view-mode="${viewMode}"></ino-markdown-editor>`; - -describe('InoMarkdownEditor', () => { - let page: E2EPage; - let inoMarkdownEditor: E2EElement; - let textArea: E2EElement; - let tipTapEditor: E2EElement; - let textFormatToolbar: E2EElement; - - async function setUpTest(initialValue: string, viewMode: ViewMode) { - page = await setupPageWithContent( - INO_MARKDOWN_EDITOR(initialValue, viewMode), - ); - inoMarkdownEditor = await page.find(INO_MARKDOWN_EDITOR_SELECTOR); - textArea = await inoMarkdownEditor.find('textarea'); - tipTapEditor = await inoMarkdownEditor.find('.ProseMirror'); - textFormatToolbar = await inoMarkdownEditor.find('.toolbar__text-format'); - } - - it('should show preview mode correctly', async () => { - await setUpTest('', ViewMode.PREVIEW); - const isTextareaVisible = await textArea.isIntersectingViewport(); - expect(isTextareaVisible).toBeFalsy(); - - const isEditorVisible = await tipTapEditor.isIntersectingViewport(); - expect(isEditorVisible).toBeTruthy(); - - const isTextFormatToolbarVisible = await textFormatToolbar.isVisible(); - expect(isTextFormatToolbarVisible).toBeTruthy(); - }); - - it('should show markdown mode correctly', async () => { - await setUpTest('', ViewMode.MARKDOWN); - const isTextareaVisible = await textArea.isIntersectingViewport(); - expect(isTextareaVisible).toBeTruthy(); - - const isEditorVisible = await tipTapEditor.isIntersectingViewport(); - expect(isEditorVisible).toBeFalsy(); - - const isTextFormatToolbarVisible = await textFormatToolbar.isVisible(); - expect(isTextFormatToolbarVisible).toBeFalsy(); - }); - - it('should emit view mode change', async () => { - await setUpTest('', ViewMode.PREVIEW); - const viewModeChangeBtn = await page.findAll('.toolbar__view-mode'); - expect(viewModeChangeBtn).toHaveLength(2); - const spy = await page.spyOnEvent('viewModeChange'); - - await viewModeChangeBtn[0].click(); - expect(spy).toHaveReceivedEvent(); - expect(spy).toHaveReceivedEventDetail(ViewMode.PREVIEW); - - await viewModeChangeBtn[1].click(); - expect(spy).toHaveReceivedEvent(); - expect(spy).toHaveReceivedEventDetail(ViewMode.MARKDOWN); - }); - - it('should display all text format buttons', async () => { - await setUpTest('', ViewMode.PREVIEW); - const buttons = await textFormatToolbar.findAll('.toolbar__action-button'); - expect(buttons).toHaveLength(11); - }); - - it('should show preview as html when set initial value property', async () => { - await setUpTest(MARKDOWN_TEXT, ViewMode.MARKDOWN); - const htmlValue = tipTapEditor.innerHTML; - - expect(htmlValue.includes('<ul data-type="taskList">')).toBeTruthy(); - expect(htmlValue.match(/input/gm)).toHaveLength(3); - HTML_TEXT_TAGS.forEach((tag) => expect(htmlValue).toContain(tag)); - }); - - it('should emit a valueChange on textarea blur', async () => { - await setUpTest('', ViewMode.MARKDOWN); - const dummyText = '# Hallo Welt'; - await textArea.type(dummyText); - - const spy = await page.spyOnEvent('valueChange'); - await page.$eval('textarea', (el: HTMLElement) => el.blur()); - - expect(spy).toHaveReceivedEvent(); - expect(spy).toHaveReceivedEventDetail(dummyText); - }); - - it('should emit a valueChange on editor blur', async () => { - await setUpTest('', ViewMode.PREVIEW); - const dummyText = '# Hallo Welt'; - const spy = await page.spyOnEvent('valueChange'); - - await tipTapEditor.type(dummyText); - await page.$eval('.ProseMirror', (el: HTMLElement) => el.blur()); - - expect(spy).toHaveReceivedEvent(); - expect(spy).toHaveReceivedEventDetail(dummyText); - }); - - it('should change initial value', async () => { - const initialText = '# Hello World'; - await setUpTest(initialText, ViewMode.MARKDOWN); - expect(await textArea.getProperty('value')).toBe(initialText); - - const newInitialText = '# Hallo Welt'; - inoMarkdownEditor.setProperty('initialValue', newInitialText); - await page.waitForChanges(); - expect(await textArea.getProperty('value')).toBe(newInitialText); - }); -}); diff --git a/packages/elements/src/components/ino-markdown-editor/markdown-serializer.ts b/packages/elements/src/components/ino-markdown-editor/markdown-serializer.ts index 97ae8f9ba2..825e1c8886 100644 --- a/packages/elements/src/components/ino-markdown-editor/markdown-serializer.ts +++ b/packages/elements/src/components/ino-markdown-editor/markdown-serializer.ts @@ -14,6 +14,7 @@ import Blockquote from '@tiptap/extension-blockquote'; import Heading from '@tiptap/extension-heading'; import HorizontalRule from '@tiptap/extension-horizontal-rule'; import Paragraph from '@tiptap/extension-paragraph'; +import Text from '@tiptap/extension-text'; import OrderedList from '@tiptap/extension-ordered-list'; import BulletList from './extensions/bullet_list'; import ListItem from '@tiptap/extension-list-item'; diff --git a/packages/storybook/src/stories/ino-markdown-editor/ino-markdown-editor.spec.ts b/packages/storybook/src/stories/ino-markdown-editor/ino-markdown-editor.spec.ts new file mode 100644 index 0000000000..f92e9cbcad --- /dev/null +++ b/packages/storybook/src/stories/ino-markdown-editor/ino-markdown-editor.spec.ts @@ -0,0 +1,151 @@ +import { expect, Locator, Page, test } from '@playwright/test'; +import { goToStory } from '../test-utils'; + +enum ViewMode { + MARKDOWN = 'markdown', + PREVIEW = 'preview', + READONLY = 'readonly', +} + +const MARKDOWN_TEXT = [ + '# Headline 1\n', + '## Headline 2\n', + [ + '_italic_', + '**bold**', + '~~strikethrough~~', + '[elements](https://github.com/inovex/elements)', + '`inline code`', + ].join(' '), + '', + '> Blockquote\n', + '```\nthis should be a code block\n```\n', + '* [x] ToDo 1 checked', + '* [ ] ToDo 2', + '* [ ] ToDo 3', +].join('\n'); + +const HTML_TEXT_TAGS = [ + '<h1>Headline 1</h1>', + '<h2>Headline 2</h2>', + '<em>italic</em>', + '<strong>bold</strong>', + '<s>strikethrough</s>', + '<a target="_blank" rel="noopener noreferrer nofollow" href="https://github.com/inovex/elements">elements</a>', + '<code>inline code</code>', + '<blockquote><p>Blockquote</p></blockquote>', + `<pre><code>this should be a code block +<br class="ProseMirror-trailingBreak"></code></pre>`, +]; + +test.describe('ino-markdown-editor', () => { + const setupEditor = ( + inoMarkdownEditor: Locator, + viewMode: ViewMode, + text?: string, + ) => + inoMarkdownEditor.evaluate( + (editor, { viewMode, text }) => { + editor.setAttribute('view-mode', viewMode); + if (text) { + editor.setAttribute('initial-value', text); + } + }, + { viewMode, text }, + ); + + const getDefaultLocators = (page: Page) => { + const inoMarkdownEditor = page.locator('ino-markdown-editor'); + return { + inoMarkdownEditor, + textArea: inoMarkdownEditor.locator('textarea'), + tipTapEditor: inoMarkdownEditor.locator('.ProseMirror'), + textFormatToolbar: inoMarkdownEditor.locator('.toolbar__text-format'), + }; + }; + + test.beforeEach(async ({ page }) => { + await goToStory(page, ['Input', 'ino-markdown-editor', 'default']); + }); + + test('should show preview mode correctly', async ({ page }) => { + const { inoMarkdownEditor, textArea, tipTapEditor, textFormatToolbar } = + getDefaultLocators(page); + await setupEditor(inoMarkdownEditor, ViewMode.PREVIEW); + await expect(textArea).not.toBeInViewport(); + await expect(tipTapEditor).toBeInViewport(); + await expect(textFormatToolbar).toBeInViewport(); + }); + + test('should show markdown mode correctly', async ({ page }) => { + const { inoMarkdownEditor, textArea, tipTapEditor, textFormatToolbar } = + getDefaultLocators(page); + await setupEditor(inoMarkdownEditor, ViewMode.MARKDOWN); + await expect(textArea).toBeInViewport(); + await expect(tipTapEditor).not.toBeInViewport(); + await expect(textFormatToolbar).not.toBeInViewport(); + }); + + test('should change view mode', async ({ page }) => { + const { inoMarkdownEditor, textArea, tipTapEditor, textFormatToolbar } = + getDefaultLocators(page); + await setupEditor(inoMarkdownEditor, ViewMode.MARKDOWN); + const viewModeToolbar = inoMarkdownEditor.locator('.toolbar__view-mode'); + await expect(viewModeToolbar).toHaveCount(2); + + await viewModeToolbar.nth(0).click(); + await expect(textArea).not.toBeInViewport(); + await expect(tipTapEditor).toBeInViewport(); + await expect(textFormatToolbar).toBeInViewport(); + + await viewModeToolbar.nth(1).click(); + await expect(textArea).toBeInViewport(); + await expect(tipTapEditor).not.toBeInViewport(); + await expect(textFormatToolbar).not.toBeInViewport(); + }); + + test('should display all text format buttons', async ({ page }) => { + const { inoMarkdownEditor, textFormatToolbar } = getDefaultLocators(page); + await setupEditor(inoMarkdownEditor, ViewMode.PREVIEW); + const buttons = textFormatToolbar.locator('.toolbar__action-button'); + await expect(buttons).toHaveCount(11); + }); + + test('should show preview as html when set initial value property', async ({ + page, + }) => { + const { inoMarkdownEditor, tipTapEditor } = getDefaultLocators(page); + await setupEditor(inoMarkdownEditor, ViewMode.MARKDOWN, MARKDOWN_TEXT); + const htmlValue = await tipTapEditor.innerHTML(); + + expect(htmlValue.includes('<ul data-type="taskList">')).toBeTruthy(); + expect(htmlValue.match(/input/gm)).toHaveLength(3); + HTML_TEXT_TAGS.forEach((tag) => expect(htmlValue).toContain(tag)); + }); + + test('should enter text in markdown mode', async ({ page }) => { + const { inoMarkdownEditor, textArea, tipTapEditor } = + getDefaultLocators(page); + const viewModeToolbar = inoMarkdownEditor.locator('.toolbar__view-mode'); + await setupEditor(inoMarkdownEditor, ViewMode.MARKDOWN); + const dummyText = '# Hallo Welt'; + + await textArea.fill(dummyText); + await textArea.blur(); + await viewModeToolbar.nth(0).click(); + await expect(tipTapEditor).toHaveText('Hallo Welt'); + }); + + test('should change initial value', async ({ page }) => { + const initialText = '# Hello World'; + const { inoMarkdownEditor, textArea } = getDefaultLocators(page); + await setupEditor(inoMarkdownEditor, ViewMode.MARKDOWN, initialText); + await expect(textArea).toHaveValue(initialText); + + const newInitialText = '# Hello New Welt'; + await inoMarkdownEditor.evaluate((editor, text) => { + editor.setAttribute('initial-value', text); + }, newInitialText); + await expect(textArea).toHaveValue(newInitialText); + }); +});