From cb3178fd7b7d776ed9703db3c8099b2cc137821a Mon Sep 17 00:00:00 2001 From: umaranis Date: Tue, 7 May 2024 23:26:38 +1000 Subject: [PATCH] test: passing test increased from 137 to 287 --- .../src/__tests__/e2e/AutoLinks.spec.mjs | 146 +- .../src/__tests__/e2e/Autocomplete.spec.mjs | 2 +- .../e2e/BlockWithAlignableContents.spec.mjs | 12 +- .../__tests__/e2e/ClearFormatting.spec.mjs | 6 +- .../src/__tests__/e2e/CodeActionMenu.spec.mjs | 66 +- .../src/__tests__/e2e/CodeBlock.spec.mjs | 1402 +++---- .../src/__tests__/e2e/Composition.spec.mjs | 1218 ++++-- .../src/__tests__/e2e/CopyAndPaste.spec.mjs | 3306 ----------------- .../html/HTMLCopyAndPaste.spec.mjs | 278 ++ .../html/ImageHTMLCopyAndPaste.spec.mjs | 118 + .../html/LinksHTMLCopyAndPaste.spec.mjs | 440 +++ .../html/ListsHTMLCopyAndPaste.spec.mjs | 419 +++ .../html/TablesHTMLCopyAndPaste.spec.mjs | 470 +++ .../html/TextFormatHTMLCopyAndPaste.spec.mjs | 149 + .../lexical/CopyAndPaste.spec.mjs | 913 +++++ .../lexical/ListsCopyAndPaste.spec.mjs | 732 ++++ .../src/__tests__/e2e/DraggableBlock.spec.mjs | 37 + .../src/__tests__/e2e/ElementFormat.spec.mjs | 3 +- .../src/__tests__/e2e/Events.spec.mjs | 144 +- .../src/__tests__/e2e/Extensions.spec.mjs | 2 +- .../src/__tests__/e2e/Hashtags.spec.mjs | 43 + .../src/__tests__/e2e/Headings.spec.mjs | 153 - .../HeadingsBackspaceAtStart.spec.mjs | 58 + .../e2e/Headings/HeadingsEnterAtEnd.spec.mjs | 68 + .../Headings/HeadingsEnterInMiddle.spec.mjs | 56 + .../src/__tests__/e2e/History.spec.mjs | 320 +- .../src/__tests__/e2e/HorizontalRule.spec.mjs | 34 +- .../src/__tests__/e2e/Images.spec.mjs | 55 +- .../src/__tests__/e2e/Indentation.spec.mjs | 16 +- .../src/__tests__/e2e/Keywords.spec.mjs | 483 +-- .../src/__tests__/e2e/Links.spec.mjs | 651 +++- .../src/__tests__/e2e/List.spec.mjs | 448 +-- .../__tests__/e2e/ListMaxIndentLevel.spec.mjs | 1 - .../src/__tests__/e2e/Markdown.spec.mjs | 432 ++- .../src/__tests__/e2e/MaxLength.spec.mjs | 41 +- .../src/__tests__/e2e/Mentions.spec.mjs | 123 +- .../src/__tests__/e2e/Mutations.spec.mjs | 429 +-- .../src/__tests__/e2e/Navigation.spec.mjs | 1 + .../src/__tests__/e2e/Selection.spec.mjs | 381 +- .../playground/src/__tests__/e2e/Tab.spec.mjs | 111 + .../src/__tests__/e2e/Tables.spec.mjs | 724 +++- .../src/__tests__/e2e/TextEntry.spec.mjs | 100 +- .../src/__tests__/e2e/TextFormatting.spec.mjs | 368 +- .../src/__tests__/e2e/Toolbar.spec.mjs | 2 +- .../src/__tests__/keyboardShortcuts/index.mjs | 9 + 45 files changed, 8950 insertions(+), 6020 deletions(-) delete mode 100644 demos/playground/src/__tests__/e2e/CopyAndPaste.spec.mjs create mode 100644 demos/playground/src/__tests__/e2e/CopyAndPaste/html/HTMLCopyAndPaste.spec.mjs create mode 100644 demos/playground/src/__tests__/e2e/CopyAndPaste/html/ImageHTMLCopyAndPaste.spec.mjs create mode 100644 demos/playground/src/__tests__/e2e/CopyAndPaste/html/LinksHTMLCopyAndPaste.spec.mjs create mode 100644 demos/playground/src/__tests__/e2e/CopyAndPaste/html/ListsHTMLCopyAndPaste.spec.mjs create mode 100644 demos/playground/src/__tests__/e2e/CopyAndPaste/html/TablesHTMLCopyAndPaste.spec.mjs create mode 100644 demos/playground/src/__tests__/e2e/CopyAndPaste/html/TextFormatHTMLCopyAndPaste.spec.mjs create mode 100644 demos/playground/src/__tests__/e2e/CopyAndPaste/lexical/CopyAndPaste.spec.mjs create mode 100644 demos/playground/src/__tests__/e2e/CopyAndPaste/lexical/ListsCopyAndPaste.spec.mjs delete mode 100644 demos/playground/src/__tests__/e2e/Headings.spec.mjs create mode 100644 demos/playground/src/__tests__/e2e/Headings/HeadingsBackspaceAtStart.spec.mjs create mode 100644 demos/playground/src/__tests__/e2e/Headings/HeadingsEnterAtEnd.spec.mjs create mode 100644 demos/playground/src/__tests__/e2e/Headings/HeadingsEnterInMiddle.spec.mjs create mode 100644 demos/playground/src/__tests__/e2e/Tab.spec.mjs diff --git a/demos/playground/src/__tests__/e2e/AutoLinks.spec.mjs b/demos/playground/src/__tests__/e2e/AutoLinks.spec.mjs index 81b6e622..916b1bfc 100644 --- a/demos/playground/src/__tests__/e2e/AutoLinks.spec.mjs +++ b/demos/playground/src/__tests__/e2e/AutoLinks.spec.mjs @@ -12,6 +12,8 @@ import { moveToLineBeginning, moveToLineEnd, selectAll, + selectCharacters, + toggleBold, } from '../keyboardShortcuts/index.mjs'; import { assertHTML, @@ -156,45 +158,43 @@ test.describe('Auto Links', () => { ); }); - test.fixme( - 'Does not create redundant auto-link', - async ({page, isPlainText}) => { - test.skip(isPlainText); - await focusEditor(page); - await page.keyboard.type('hm'); + test('Does not create redundant auto-link', async ({page, isPlainText}) => { + test.skip(isPlainText); + await focusEditor(page); + await page.keyboard.type('hm'); - await selectAll(page); - await click(page, '.link'); + await selectAll(page); + await click(page, '.link'); + await click(page, '.link-confirm'); - await assertHTML( - page, - html` -

- - hm - -

- `, - undefined, - {ignoreClasses: true}, - ); - await moveLeft(page, 1); - await moveRight(page, 1); - await page.keyboard.type('ttps://facebook.co'); - await assertHTML( - page, - html` -

- - https://facebook.com - -

- `, - undefined, - {ignoreClasses: true}, - ); - }, - ); + await assertHTML( + page, + html` +

+ + hm + +

+ `, + undefined, + {ignoreClasses: true}, + ); + await moveLeft(page, 1); + await moveRight(page, 1); + await page.keyboard.type('ttps://facebook.co'); + await assertHTML( + page, + html` +

+ + https://facebook.com + +

+ `, + undefined, + {ignoreClasses: true}, + ); + }); test('Can create links when pasting text with multiple autolinks in a row separated by non-alphanumeric characters, but not whitespaces', async ({ page, @@ -259,4 +259,76 @@ test.describe('Auto Links', () => { {ignoreClasses: true}, ); }); + + test('Handles autolink following an invalid autolink', async ({ + page, + isPlainText, + }) => { + test.skip(isPlainText); + await focusEditor(page); + await page.keyboard.type('Hellohttps://example.com https://example.com'); + + await assertHTML( + page, + html` +

+ Hellohttps://example.com + + https://example.com + +

+ `, + undefined, + {ignoreClasses: true}, + ); + }); + + test('Can convert url-like text with formatting into links', async ({ + page, + isPlainText, + }) => { + test.skip(isPlainText); + await focusEditor(page); + await page.keyboard.type('Hellohttp://example.com and more'); + + // Add bold formatting to com + await moveToLineBeginning(page); + await moveRight(page, 20); + await selectCharacters(page, 'right', 3); + await toggleBold(page); + + await assertHTML( + page, + html` +

+ Hellohttp://example. + com + and more +

+ `, + undefined, + {ignoreClasses: true}, + ); + + // Add space before formatted link text + await moveToLineBeginning(page); + await moveRight(page, 5); + await page.keyboard.type(' '); + + await assertHTML( + page, + html` +

+ Hello + + http://example. + com + + and more +

+ `, + undefined, + {ignoreClasses: true}, + ); + }); }); diff --git a/demos/playground/src/__tests__/e2e/Autocomplete.spec.mjs b/demos/playground/src/__tests__/e2e/Autocomplete.spec.mjs index 58bd485a..70e58bbe 100644 --- a/demos/playground/src/__tests__/e2e/Autocomplete.spec.mjs +++ b/demos/playground/src/__tests__/e2e/Autocomplete.spec.mjs @@ -19,7 +19,7 @@ test.describe('Autocomplete', () => { test.beforeEach(({isCollab, page}) => initialize({isAutocomplete: true, isCollab, page}), ); - test.fixme('Can autocomplete a word', async ({page, isPlainText}) => { + test('Can autocomplete a word', async ({page, isPlainText}) => { await focusEditor(page); await page.keyboard.type('Sort by alpha'); await sleep(500); diff --git a/demos/playground/src/__tests__/e2e/BlockWithAlignableContents.spec.mjs b/demos/playground/src/__tests__/e2e/BlockWithAlignableContents.spec.mjs index 44927655..d25df507 100644 --- a/demos/playground/src/__tests__/e2e/BlockWithAlignableContents.spec.mjs +++ b/demos/playground/src/__tests__/e2e/BlockWithAlignableContents.spec.mjs @@ -15,9 +15,9 @@ import { insertYouTubeEmbed, selectFromAlignDropdown, test, + YOUTUBE_SAMPLE_URL, } from '../utils/index.mjs'; -const TEST_URL = 'https://www.youtube-nocookie.com/embed/jNQXAC9IVRw'; test.describe('BlockWithAlignableContents', () => { test.fixme(); test.beforeEach(({isCollab, page}) => initialize({isCollab, page})); @@ -39,7 +39,7 @@ test.describe('BlockWithAlignableContents', () => {

`, ); - await insertYouTubeEmbed(page, TEST_URL); + await insertYouTubeEmbed(page, YOUTUBE_SAMPLE_URL); await assertHTML( page, html` @@ -55,7 +55,7 @@ test.describe('BlockWithAlignableContents', () => { allowfullscreen="" frameborder="0" height="315" - src="${TEST_URL}" + src="${YOUTUBE_SAMPLE_URL}" title="YouTube video" width="560"> @@ -72,7 +72,7 @@ test.describe('BlockWithAlignableContents', () => { test.skip(isPlainText); await focusEditor(page); await page.keyboard.type('Hello world'); - await insertYouTubeEmbed(page, TEST_URL); + await insertYouTubeEmbed(page, YOUTUBE_SAMPLE_URL); await assertHTML( page, html` @@ -88,7 +88,7 @@ test.describe('BlockWithAlignableContents', () => { allowfullscreen="" frameborder="0" height="315" - src="${TEST_URL}" + src="${YOUTUBE_SAMPLE_URL}" title="YouTube video" width="560"> @@ -116,7 +116,7 @@ test.describe('BlockWithAlignableContents', () => { allowfullscreen="" frameborder="0" height="315" - src="${TEST_URL}" + src="${YOUTUBE_SAMPLE_URL}" title="YouTube video" width="560"> diff --git a/demos/playground/src/__tests__/e2e/ClearFormatting.spec.mjs b/demos/playground/src/__tests__/e2e/ClearFormatting.spec.mjs index 81f4a22a..cf49dba9 100644 --- a/demos/playground/src/__tests__/e2e/ClearFormatting.spec.mjs +++ b/demos/playground/src/__tests__/e2e/ClearFormatting.spec.mjs @@ -30,7 +30,7 @@ test.describe('Clear All Formatting', () => { test.fixme(); test.beforeEach(({isPlainText, isCollab, page}) => { test.skip(isPlainText); - initialize({isCollab, page}); + return initialize({isCollab, page}); }); test(`Can clear BIU formatting`, async ({page}) => { await focusEditor(page); @@ -134,7 +134,7 @@ test.describe('Clear All Formatting', () => { await clearEditor(page); - await page.keyboard.type('Luke'); + await page.keyboard.type('@Luke'); await waitForSelector(page, '#typeahead-menu ul li'); await assertHTML( @@ -143,7 +143,7 @@ test.describe('Clear All Formatting', () => {

- Luke + @Luke

`, ); diff --git a/demos/playground/src/__tests__/e2e/CodeActionMenu.spec.mjs b/demos/playground/src/__tests__/e2e/CodeActionMenu.spec.mjs index 8dd763bf..b5e09f49 100644 --- a/demos/playground/src/__tests__/e2e/CodeActionMenu.spec.mjs +++ b/demos/playground/src/__tests__/e2e/CodeActionMenu.spec.mjs @@ -6,6 +6,8 @@ * */ +/* eslint-disable no-useless-escape */ + import {paste} from '../keyboardShortcuts/index.mjs'; import { assertHTML, @@ -21,7 +23,7 @@ import { } from '../utils/index.mjs'; test.describe('CodeActionMenu', () => { - test.fixme(); + //test.fixme(); test.beforeEach(({isCollab, page}) => initialize({isCollab, page})); test('Can copy code, when click `Copy` button', async ({ page, @@ -29,7 +31,8 @@ test.describe('CodeActionMenu', () => { isPlainText, browserName, }) => { - test.skip(isPlainText); + test.skip(true); + await focusEditor(page); await page.keyboard.type('``` '); await page.keyboard.press('Space'); @@ -251,21 +254,19 @@ test.describe('CodeActionMenu', () => { ); }); - test('If the code syntax is incorrect, an error message should be displayed', async ({ - page, - isCollab, - isPlainText, - }) => { - test.skip(isCollab); - test.skip(isPlainText); - await focusEditor(page); - await page.keyboard.type('``` '); - await page.keyboard.press('Space'); - await page.keyboard.type(`cons luci = 'Hello World'`); + test.fixme( + 'If the code syntax is incorrect, an error message should be displayed', + async ({page, isCollab, isPlainText}) => { + test.skip(isCollab); + test.skip(isPlainText); + await focusEditor(page); + await page.keyboard.type('``` '); + await page.keyboard.press('Space'); + await page.keyboard.type(`cons luci = 'Hello World'`); - await assertHTML( - page, - ` + await assertHTML( + page, + ` { `, - ); + ); - await mouseMoveToSelector(page, 'code.PlaygroundEditorTheme__code'); - await click(page, 'button[aria-label=prettier]'); + await mouseMoveToSelector(page, 'code.PlaygroundEditorTheme__code'); + await click(page, 'button[aria-label=prettier]'); - await page.waitForTimeout(3000); + await page.waitForTimeout(3000); - expect(await page.$('i.format.prettier-error')).toBeTruthy(); + expect(await page.$('i.format.prettier-error')).toBeTruthy(); - const errorTips = await page.$('pre.code-error-tips'); + const errorTips = await page.$('pre.code-error-tips'); - expect(errorTips).toBeTruthy(); + expect(errorTips).toBeTruthy(); - const tips = await evaluate(page, () => { - return document.querySelector('pre.code-error-tips').innerText; - }); + const tips = await evaluate(page, () => { + return document.querySelector('pre.code-error-tips').innerText; + }); - expect(tips).toBe( - 'Missing semicolon. (1:6)\n' + - "> 1 | cons luci = 'Hello World'\n" + - ' | ^', - ); - }); + expect(tips).toBe( + 'Missing semicolon. (1:6)\n' + + "> 1 | cons luci = 'Hello World'\n" + + ' | ^', + ); + }, + ); }); diff --git a/demos/playground/src/__tests__/e2e/CodeBlock.spec.mjs b/demos/playground/src/__tests__/e2e/CodeBlock.spec.mjs index 5b6a566a..fdafb961 100644 --- a/demos/playground/src/__tests__/e2e/CodeBlock.spec.mjs +++ b/demos/playground/src/__tests__/e2e/CodeBlock.spec.mjs @@ -31,149 +31,146 @@ async function toggleCodeBlock(page) { test.describe('CodeBlock', () => { test.beforeEach(({isCollab, page}) => initialize({isCollab, page})); - test.fixme( - 'Can create code block with markdown', - async ({page, isRichText}) => { - await focusEditor(page); - await page.keyboard.type('``` alert(1);'); - if (isRichText) { - await assertSelection(page, { - anchorOffset: 1, - anchorPath: [0, 4, 0], - focusOffset: 1, - focusPath: [0, 4, 0], - }); - await assertHTML( - page, - html` - - - alert - - - ( - - - 1 - - - ) - - - ; - - - `, - ); - - // Remove code block (back to a normal paragraph) and check that highlights are converted into regular text - await moveToEditorBeginning(page); - await page.keyboard.press('Backspace'); - await assertHTML( - page, - html` -

- alert(1); -

- `, - ); - } else { - await assertHTML( - page, - html` -

- \`\`\` alert(1); -

- `, - ); - } - }, - ); + test('Can create code block with markdown', async ({page, isRichText}) => { + await focusEditor(page); + await page.keyboard.type('``` alert(1);'); + if (isRichText) { + await assertSelection(page, { + anchorOffset: 1, + anchorPath: [0, 4, 0], + focusOffset: 1, + focusPath: [0, 4, 0], + }); + await assertHTML( + page, + html` + + + alert + + + ( + + + 1 + + + ) + + + ; + + + `, + ); - test.fixme( - 'Can create code block with markdown and wrap existing text', - async ({page, isRichText}) => { - await focusEditor(page); - await page.keyboard.type('alert(1);'); + // Remove code block (back to a normal paragraph) and check that highlights are converted into regular text await moveToEditorBeginning(page); - await page.keyboard.type('``` '); - if (isRichText) { - await assertSelection(page, { - anchorOffset: 0, - anchorPath: [0, 0, 0], - focusOffset: 0, - focusPath: [0, 0, 0], - }); - await assertHTML( - page, - html` - - - alert - - - ( - - - 1 - - - ) - - - ; - - - `, - ); - } else { - await assertHTML( - page, - html` -

- \`\`\` alert(1); -

- `, - ); - } - }, - ); + await page.keyboard.press('Backspace'); + await assertHTML( + page, + html` +

+ alert(1); +

+ `, + ); + } else { + await assertHTML( + page, + html` +

+ \`\`\` alert(1); +

+ `, + ); + } + }); + + test('Can create code block with markdown and wrap existing text', async ({ + page, + isRichText, + }) => { + await focusEditor(page); + await page.keyboard.type('alert(1);'); + await moveToEditorBeginning(page); + await page.keyboard.type('``` '); + if (isRichText) { + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [0, 0, 0], + focusOffset: 0, + focusPath: [0, 0, 0], + }); + await assertHTML( + page, + html` + + + alert + + + ( + + + 1 + + + ) + + + ; + + + `, + ); + } else { + await assertHTML( + page, + html` +

+ \`\`\` alert(1); +

+ `, + ); + } + }); test('Can select multiple paragraphs and convert to code block', async ({ page, @@ -243,171 +240,13 @@ test.describe('CodeBlock', () => { ); }); - test.fixme( - 'Can switch highlighting language in a toolbar', - async ({page, isRichText}) => { - await focusEditor(page); - await page.keyboard.type('``` select * from users'); - if (isRichText) { - await assertHTML( - page, - html` - - select - - * - - from users - - `, - ); - await click(page, '.toolbar-item.code-language'); - await click(page, 'button:has-text("SQL")'); - await assertHTML( - page, - html` - - - select - - - - * - - - - from - - users - - `, - ); - } else { - await assertHTML( - page, - html` -

- \`\`\` select * from users -

- `, - ); - } - }, - ); - - test.fixme( - 'Can maintain indent when creating new lines', - async ({page, isRichText, isPlainText}) => { - test.skip(isPlainText); - await focusEditor(page); - await page.keyboard.type('``` alert(1);'); - await page.keyboard.press('Enter'); - await click(page, '.toolbar-item.alignment'); - await click(page, 'button:has-text("Indent")'); - await page.keyboard.type('alert(2);'); - await page.keyboard.press('Enter'); - await assertHTML( - page, - html` - - - alert - - - ( - - - 1 - - - ) - - - ; - -
- - - alert - - - ( - - - 2 - - - ) - - - ; - -
- -
- `, - ); - }, - ); - - test.fixme( - 'Can (un)indent multiple lines at once', - async ({page, isRichText, isPlainText}) => { - test.skip(isPlainText); - await focusEditor(page); - await page.keyboard.type('``` if (x) {'); - await page.keyboard.press('Enter'); - await click(page, '.toolbar-item.alignment'); - await click(page, 'button:has-text("Indent")'); - await page.keyboard.type('x();'); - await page.keyboard.press('Enter'); - await page.keyboard.press('Backspace'); - await page.keyboard.type('}'); + test('Can switch highlighting language in a toolbar', async ({ + page, + isRichText, + }) => { + await focusEditor(page); + await page.keyboard.type('``` select * from users'); + if (isRichText) { await assertHTML( page, html` @@ -415,70 +254,20 @@ test.describe('CodeBlock', () => { class="PlaygroundEditorTheme__code PlaygroundEditorTheme__ltr" spellcheck="false" dir="ltr" - data-gutter="123" + data-gutter="1" data-highlight-language="javascript"> + select - if - - - - ( - - x - - ) - - - - { - -
- - - x - - - ( - - - ) - - - ; - -
- - } + * + from users `, ); - await page.keyboard.down('Shift'); - await page.keyboard.press('ArrowUp'); - await page.keyboard.press('ArrowUp'); - await page.keyboard.up('Shift'); - await click(page, '.toolbar-item.alignment'); - await click(page, 'button:has-text("Indent")'); - await click(page, '.toolbar-item.alignment'); - await click(page, 'button:has-text("Indent")'); + await click(page, '.toolbar-item.code-language'); + await click(page, 'button:has-text("SQL")'); await assertHTML( page, html` @@ -486,207 +275,148 @@ test.describe('CodeBlock', () => { class="PlaygroundEditorTheme__code PlaygroundEditorTheme__ltr" spellcheck="false" dir="ltr" - data-gutter="123" - data-highlight-language="javascript"> - + data-gutter="1" + data-highlight-language="sql"> - if - - - - ( - - x - - ) - - - - { - -
- - - x - - - ( + select - - ) - - - ; - -
- } + * - - `, - ); - await page.keyboard.down('Shift'); - await click(page, '.toolbar-item.alignment'); - await click(page, 'button:has-text("Outdent")'); - await page.keyboard.up('Shift'); - await assertHTML( - page, - html` - - if - - - - ( - - x - - ) - - - - { - -
- - - x - - - ( - - - ) - - - ; - -
- - - } + from + users
`, ); - await click(page, '.toolbar-item.alignment'); - await click(page, 'button:has-text("Outdent")'); - await click(page, '.toolbar-item.alignment'); - await click(page, 'button:has-text("Outdent")'); + } else { await assertHTML( page, html` - - - if - - - - ( - - x - - ) - - - - { - -
- - x - - - ( - - - ) - - - ; - -
- - } - -
+

+ \`\`\` select * from users +

`, ); - }, - ); + } + }); + + test('Can maintain indent when creating new lines', async ({ + page, + isRichText, + isPlainText, + }) => { + test.skip(isPlainText); + await focusEditor(page); + await page.keyboard.type('``` alert(1);'); + await page.keyboard.press('Enter'); + await click(page, '.toolbar-item.alignment'); + await click(page, 'button:has-text("Indent")'); + await page.keyboard.type('alert(2);'); + await page.keyboard.press('Enter'); + await page.keyboard.type(';'); + await assertHTML( + page, + html` + + + alert + + + ( + + + 1 + + + ) + + + ; + +
+ + + alert + + + ( + + + 2 + + + ) + + + ; + +
+ + + ; + +
+ `, + ); + }); - test.fixme( - 'Can move around lines with option+arrow keys', - async ({page, isPlainText}) => { - test.skip(isPlainText); - const abcHTML = html` + test('Can (un)indent multiple lines at once', async ({ + page, + isRichText, + isPlainText, + }) => { + test.skip(isPlainText); + await focusEditor(page); + await page.keyboard.type('``` if (x) {'); + await page.keyboard.press('Enter'); + await click(page, '.toolbar-item.alignment'); + await click(page, 'button:has-text("Indent")'); + await page.keyboard.type('x();'); + await page.keyboard.press('Enter'); + await page.keyboard.press('Backspace'); + await page.keyboard.type('}'); + await assertHTML( + page, + html` { data-gutter="123" data-highlight-language="javascript"> - a + if + ( + x ) + - ; + {
+ - b + x { ;
+ + } + +
+ `, + ); + await page.keyboard.down('Shift'); + await page.keyboard.press('ArrowUp'); + await page.keyboard.press('ArrowUp'); + await page.keyboard.up('Shift'); + await click(page, '.toolbar-item.alignment'); + await click(page, 'button:has-text("Indent")'); + await click(page, '.toolbar-item.alignment'); + await click(page, 'button:has-text("Indent")'); + await assertHTML( + page, + html` + + + + + if + + + + ( + + x + + ) + + + + { + +
+ + + - c + x { data-lexical-text="true"> ; +
+ + + + } +
- `; - const bcaHTML = html` + `, + ); + await page.keyboard.down('Shift'); + await click(page, '.toolbar-item.alignment'); + await click(page, 'button:has-text("Outdent")'); + await page.keyboard.up('Shift'); + await assertHTML( + page, + html` + - b + if + ( + x ) + - ; + {
+ + - c + x { ;
+ + + } + +
+ `, + ); + await click(page, '.toolbar-item.alignment'); + await click(page, 'button:has-text("Outdent")'); + await click(page, '.toolbar-item.alignment'); + await click(page, 'button:has-text("Outdent")'); + await assertHTML( + page, + html` + + + if + + + + ( + + x + + ) + + + + { + +
- a + x { data-lexical-text="true"> ; +
+ + } +
- `; - const endOfFirstLine = { - anchorOffset: 1, - anchorPath: [0, 3, 0], - focusOffset: 1, - focusPath: [0, 3, 0], - }; - const endOfLastLine = { - anchorOffset: 1, - anchorPath: [0, 13, 0], - focusOffset: 1, - focusPath: [0, 13, 0], - }; - await focusEditor(page); - await page.keyboard.type('``` a();\nb();\nc();'); - await assertHTML(page, abcHTML); - await assertSelection(page, endOfLastLine); - await page.keyboard.press('ArrowUp'); - await page.keyboard.press('ArrowUp'); - // Workaround for #1173: just insert and remove a space to fix Firefox losing the selection - await page.keyboard.type(' '); - await page.keyboard.press('Backspace'); - await assertSelection(page, endOfFirstLine); - // End workaround - // Ensure attempting to move a line up at the top of a codeblock no-ops - await page.keyboard.down('Alt'); - await page.keyboard.press('ArrowUp'); - await assertSelection(page, endOfFirstLine); - await assertHTML(page, abcHTML); - await page.keyboard.press('ArrowDown'); - await page.keyboard.press('ArrowDown'); - await assertSelection(page, endOfLastLine); - // Can't move a line down and out of codeblock - await assertHTML(page, bcaHTML); - await page.keyboard.press('ArrowDown'); - await assertSelection(page, endOfLastLine); - await assertHTML(page, bcaHTML); - }, - ); + `, + ); + }); + + test('Can move around lines with option+arrow keys', async ({ + page, + isPlainText, + }) => { + test.skip(isPlainText); + const abcHTML = html` + + + a + + + ( + + + ) + + + ; + +
+ + b + + + ( + + + ) + + + ; + +
+ + c + + + ( + + + ) + + + ; + +
+ `; + const bcaHTML = html` + + + b + + + ( + + + ) + + + ; + +
+ + c + + + ( + + + ) + + + ; + +
+ + a + + + ( + + + ) + + + ; + +
+ `; + const endOfFirstLine = { + anchorOffset: 1, + anchorPath: [0, 3, 0], + focusOffset: 1, + focusPath: [0, 3, 0], + }; + const endOfLastLine = { + anchorOffset: 1, + anchorPath: [0, 13, 0], + focusOffset: 1, + focusPath: [0, 13, 0], + }; + await focusEditor(page); + await page.keyboard.type('``` a();\nb();\nc();'); + await assertHTML(page, abcHTML); + await assertSelection(page, endOfLastLine); + await page.keyboard.press('ArrowUp'); + await page.keyboard.press('ArrowUp'); + // Workaround for #1173: just insert and remove a space to fix Firefox losing the selection + await page.keyboard.type(' '); + await page.keyboard.press('Backspace'); + await assertSelection(page, endOfFirstLine); + // End workaround + // Ensure attempting to move a line up at the top of a codeblock no-ops + await page.keyboard.down('Alt'); + await page.keyboard.press('ArrowUp'); + await assertSelection(page, endOfFirstLine); + await assertHTML(page, abcHTML); + await page.keyboard.press('ArrowDown'); + await page.keyboard.press('ArrowDown'); + await assertSelection(page, endOfLastLine); + // Can't move a line down and out of codeblock + await assertHTML(page, bcaHTML); + await page.keyboard.press('ArrowDown'); + await assertSelection(page, endOfLastLine); + await assertHTML(page, bcaHTML); + }); /** * Code example for tests: @@ -970,6 +980,69 @@ test.describe('CodeBlock', () => { `; + const EXPECTED_HTML_GOOGLE_SPREADSHEET = html` + + + + + + + + + + + +
+

+ + Surface + +

+
+

+ + MWP_WORK_LS_COMPOSER + +

+
+

+ + 77349 + +

+
+

+ Lexical +

+
+

+ XDS_RICH_TEXT_AREA +

+
+ sdvd + + sdfvsfs + +
+ `; const CODE_PASTING_TESTS = [ { expectedHTML: EXPECTED_HTML, @@ -1022,14 +1095,14 @@ test.describe('CodeBlock', () => { 1 - +
@@ -1038,7 +1111,14 @@ test.describe('CodeBlock', () => {
`, name: 'Multiline ', - pastedHTML: `1\n2`, + // TODO This is not correct. This resembles how Lexical exports code right now but + // semantically it should be wrapped in a pre + pastedHTML: `1
2
`, + }, + { + expectedHTML: EXPECTED_HTML_GOOGLE_SPREADSHEET, + name: 'Google Spreadsheet', + pastedHTML: `
SurfaceMWP_WORK_LS_COMPOSER77349
LexicalXDS_RICH_TEXT_AREAsdvd sdfvsfs
`, }, ]; @@ -1057,85 +1137,85 @@ test.describe('CodeBlock', () => { }); }); - test.fixme( - 'When pressing CMD/Ctrl + Left, CMD/Ctrl + Right, the cursor should go to the start of the code', - async ({page, isPlainText}) => { - test.skip(isPlainText); - await focusEditor(page); - await page.keyboard.type('``` '); - await page.keyboard.press('Space'); - await click(page, '.toolbar-item.alignment'); - await click(page, 'button:has-text("Indent")'); - await page.keyboard.type('a b'); - await page.keyboard.press('Space'); - await page.keyboard.press('Enter'); - await page.keyboard.type('c d'); - await page.keyboard.press('Space'); - await assertHTML( - page, - ` + test('When pressing CMD/Ctrl + Left, CMD/Ctrl + Right, the cursor should go to the start of the code', async ({ + page, + isPlainText, + }) => { + test.skip(isPlainText); + await focusEditor(page); + await page.keyboard.type('``` '); + await page.keyboard.press('Space'); + await click(page, '.toolbar-item.alignment'); + await click(page, 'button:has-text("Indent")'); + await page.keyboard.type('a b'); + await page.keyboard.press('Space'); + await page.keyboard.press('Enter'); + await page.keyboard.type('c d'); + await page.keyboard.press('Space'); + await assertHTML( + page, + ` + a b
+ c d
`, - ); - - await selectCharacters(page, 'left', 13); - await assertSelection(page, { - anchorOffset: 6, - anchorPath: [0, 2, 0], - focusOffset: 0, - focusPath: [0, 0, 0], - }); + ); - await moveToStart(page); - await assertSelection(page, { - anchorOffset: 2, - anchorPath: [0, 0, 0], - focusOffset: 2, - focusPath: [0, 0, 0], - }); + await selectCharacters(page, 'left', 11); + await assertSelection(page, { + anchorOffset: 5, + anchorPath: [0, 4, 0], + focusOffset: 1, + focusPath: [0, 1, 0], + }); - await moveToEnd(page); - await assertSelection(page, { - anchorOffset: 5, - anchorPath: [0, 0, 0], - focusOffset: 5, - focusPath: [0, 0, 0], - }); + await moveToStart(page); + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [0, 0, 0], + focusOffset: 0, + focusPath: [0, 0, 0], + }); - await moveToStart(page); - await assertSelection(page, { - anchorOffset: 2, - anchorPath: [0, 0, 0], - focusOffset: 2, - focusPath: [0, 0, 0], - }); + await moveToEnd(page); + await assertSelection(page, { + anchorOffset: 5, + anchorPath: [0, 1, 0], + focusOffset: 5, + focusPath: [0, 1, 0], + }); - await selectCharacters(page, 'right', 11); - await assertSelection(page, { - anchorOffset: 2, - anchorPath: [0, 0, 0], - focusOffset: 6, - focusPath: [0, 2, 0], - }); + await moveToStart(page); + await assertSelection(page, { + anchorOffset: 1, + anchorPath: [0, 1, 0], + focusOffset: 1, + focusPath: [0, 1, 0], + }); - await moveToEnd(page); - await assertSelection(page, { - anchorOffset: 5, - anchorPath: [0, 2, 0], - focusOffset: 5, - focusPath: [0, 2, 0], - }); + await selectCharacters(page, 'right', 11); + await assertSelection(page, { + anchorOffset: 1, + anchorPath: [0, 1, 0], + focusOffset: 5, + focusPath: [0, 4, 0], + }); - await page.pause(); - }, - ); + await moveToEnd(page); + await assertSelection(page, { + anchorOffset: 5, + anchorPath: [0, 4, 0], + focusOffset: 5, + focusPath: [0, 4, 0], + }); + }); }); diff --git a/demos/playground/src/__tests__/e2e/Composition.spec.mjs b/demos/playground/src/__tests__/e2e/Composition.spec.mjs index aa0d86e9..a48a7f8e 100644 --- a/demos/playground/src/__tests__/e2e/Composition.spec.mjs +++ b/demos/playground/src/__tests__/e2e/Composition.spec.mjs @@ -181,27 +181,73 @@ test.describe('Composition', () => { }); test.describe('IME', () => { - test.fixme(); test('Can type Hiragana via IME', async ({page, browserName}) => { // We don't yet support FF. - test.skip(browserName === 'firefox'); - + test.skip(browserName !== 'chromium'); await focusEditor(page); await enableCompositionKeyEvents(page); - await page.keyboard.imeSetComposition('s', 1, 1); - await page.keyboard.imeSetComposition('す', 1, 1); - await page.keyboard.imeSetComposition('すs', 2, 2); - await page.keyboard.imeSetComposition('すsh', 3, 3); - await page.keyboard.imeSetComposition('すし', 2, 2); - await page.keyboard.insertText('すし'); - await page.keyboard.type(' '); - await page.keyboard.imeSetComposition('m', 1, 1); - await page.keyboard.imeSetComposition('も', 1, 1); - await page.keyboard.imeSetComposition('もj', 2, 2); - await page.keyboard.imeSetComposition('もじ', 2, 2); - await page.keyboard.imeSetComposition('もじあ', 3, 3); - await page.keyboard.insertText('もじあ'); + const client = await page.context().newCDPSession(page); + + await client.send('Input.imeSetComposition', { + selectionStart: 1, + selectionEnd: 1, + text: 's', + }); + await client.send('Input.imeSetComposition', { + selectionStart: 1, + selectionEnd: 1, + text: 'す', + }); + await client.send('Input.imeSetComposition', { + selectionStart: 2, + selectionEnd: 2, + text: 'すs', + }); + await client.send('Input.imeSetComposition', { + selectionStart: 3, + selectionEnd: 3, + text: 'すsh', + }); + await client.send('Input.imeSetComposition', { + selectionStart: 2, + selectionEnd: 2, + text: 'すし', + }); + await client.send('Input.insertText', { + text: 'すし', + }); + await client.send('Input.insertText', { + text: ' ', + }); + await client.send('Input.imeSetComposition', { + selectionStart: 1, + selectionEnd: 1, + text: 'm', + }); + await client.send('Input.imeSetComposition', { + selectionStart: 1, + selectionEnd: 1, + text: 'も', + }); + await client.send('Input.imeSetComposition', { + selectionStart: 2, + selectionEnd: 2, + text: 'もj', + }); + await client.send('Input.imeSetComposition', { + selectionStart: 2, + selectionEnd: 2, + text: 'もじ', + }); + await client.send('Input.imeSetComposition', { + selectionStart: 3, + selectionEnd: 3, + text: 'もじあ', + }); + await client.send('Input.insertText', { + text: 'もじあ', + }); await assertHTML( page, @@ -226,7 +272,7 @@ test.describe('Composition', () => { browserName, }) => { // We don't yet support FF. - test.skip(browserName === 'firefox'); + test.skip(browserName !== 'chromium'); await focusEditor(page); await enableCompositionKeyEvents(page); @@ -241,19 +287,79 @@ test.describe('Composition', () => { await page.keyboard.press('ArrowLeft'); - await page.keyboard.imeSetComposition('s', 1, 1); - await page.keyboard.imeSetComposition('す', 1, 1); - await page.keyboard.imeSetComposition('すs', 2, 2); - await page.keyboard.imeSetComposition('すsh', 3, 3); - await page.keyboard.imeSetComposition('すし', 2, 2); - await page.keyboard.insertText('すし'); - await page.keyboard.type(' '); - await page.keyboard.imeSetComposition('m', 1, 1); - await page.keyboard.imeSetComposition('も', 1, 1); - await page.keyboard.imeSetComposition('もj', 2, 2); - await page.keyboard.imeSetComposition('もじ', 2, 2); - await page.keyboard.imeSetComposition('もじあ', 3, 3); - await page.keyboard.insertText('もじあ'); + const client = await page.context().newCDPSession(page); + // await page.keyboard.imeSetComposition('s', 1, 1); + await client.send('Input.imeSetComposition', { + selectionStart: 1, + selectionEnd: 1, + text: 's', + }); + // await page.keyboard.imeSetComposition('す', 1, 1); + await client.send('Input.imeSetComposition', { + selectionStart: 1, + selectionEnd: 1, + text: 'す', + }); + // await page.keyboard.imeSetComposition('すs', 2, 2); + await client.send('Input.imeSetComposition', { + selectionStart: 2, + selectionEnd: 2, + text: 'すs', + }); + // await page.keyboard.imeSetComposition('すsh', 3, 3); + await client.send('Input.imeSetComposition', { + selectionStart: 3, + selectionEnd: 3, + text: 'すsh', + }); + // await page.keyboard.imeSetComposition('すし', 2, 2); + await client.send('Input.imeSetComposition', { + selectionStart: 2, + selectionEnd: 2, + text: 'すし', + }); + // await page.keyboard.insertText('すし'); + await client.send('Input.insertText', { + text: 'すし', + }); + // await page.keyboard.type(' '); + await client.send('Input.insertText', { + text: ' ', + }); + // await page.keyboard.imeSetComposition('m', 1, 1); + await client.send('Input.imeSetComposition', { + selectionStart: 1, + selectionEnd: 1, + text: 'm', + }); + // await page.keyboard.imeSetComposition('も', 1, 1); + await client.send('Input.imeSetComposition', { + selectionStart: 1, + selectionEnd: 1, + text: 'も', + }); + // await page.keyboard.imeSetComposition('もj', 2, 2); + await client.send('Input.imeSetComposition', { + selectionStart: 2, + selectionEnd: 2, + text: 'もj', + }); + // await page.keyboard.imeSetComposition('もじ', 2, 2); + await client.send('Input.imeSetComposition', { + selectionStart: 2, + selectionEnd: 2, + text: 'もじ', + }); + // await page.keyboard.imeSetComposition('もじあ', 3, 3); + await client.send('Input.imeSetComposition', { + selectionStart: 3, + selectionEnd: 3, + text: 'もじあ', + }); + // await page.keyboard.insertText('もじあ'); + await client.send('Input.insertText', { + text: 'もじあ', + }); await assertHTML( page, @@ -282,7 +388,7 @@ test.describe('Composition', () => { isPlainText, }) => { // We don't yet support FF. - test.skip(browserName === 'firefox' || isPlainText); + test.skip(browserName !== 'chromium' || isPlainText); await focusEditor(page); await enableCompositionKeyEvents(page); @@ -293,12 +399,41 @@ test.describe('Composition', () => { await page.keyboard.press('b'); await keyUpCtrlOrMeta(page); - await page.keyboard.imeSetComposition('s', 1, 1); - await page.keyboard.imeSetComposition('す', 1, 1); - await page.keyboard.imeSetComposition('すs', 2, 2); - await page.keyboard.imeSetComposition('すsh', 3, 3); - await page.keyboard.imeSetComposition('すし', 2, 2); - await page.keyboard.insertText('すし'); + const client = await page.context().newCDPSession(page); + // await page.keyboard.imeSetComposition('s', 1, 1); + await client.send('Input.imeSetComposition', { + selectionStart: 1, + selectionEnd: 1, + text: 's', + }); + // await page.keyboard.imeSetComposition('す', 1, 1); + await client.send('Input.imeSetComposition', { + selectionStart: 1, + selectionEnd: 1, + text: 'す', + }); + // await page.keyboard.imeSetComposition('すs', 2, 2); + await client.send('Input.imeSetComposition', { + selectionStart: 2, + selectionEnd: 2, + text: 'すs', + }); + // await page.keyboard.imeSetComposition('すsh', 3, 3); + await client.send('Input.imeSetComposition', { + selectionStart: 3, + selectionEnd: 3, + text: 'すsh', + }); + // await page.keyboard.imeSetComposition('すし', 2, 2); + await client.send('Input.imeSetComposition', { + selectionStart: 2, + selectionEnd: 2, + text: 'すし', + }); + // await page.keyboard.insertText('すし'); + await client.send('Input.insertText', { + text: 'すし', + }); await assertHTML( page, @@ -323,230 +458,506 @@ test.describe('Composition', () => { }); }); - test('Can type Hiragana via IME between emojis', async ({ - page, - browserName, - }) => { - test.skip(browserName === 'firefox'); - await focusEditor(page); - await enableCompositionKeyEvents(page); - - await page.keyboard.type(':):)'); - - await page.keyboard.press('ArrowLeft'); - - await page.keyboard.imeSetComposition('s', 1, 1); - await page.keyboard.imeSetComposition('す', 1, 1); - await page.keyboard.imeSetComposition('すs', 2, 2); - await page.keyboard.imeSetComposition('すsh', 3, 3); - await page.keyboard.imeSetComposition('すし', 2, 2); - await page.keyboard.insertText('すし'); - await page.keyboard.type(' '); - await page.keyboard.imeSetComposition('m', 1, 1); - await page.keyboard.imeSetComposition('も', 1, 1); - await page.keyboard.imeSetComposition('もj', 2, 2); - await page.keyboard.imeSetComposition('もじ', 2, 2); - await page.keyboard.imeSetComposition('もじあ', 3, 3); - await page.keyboard.insertText('もじあ'); - - await assertHTML( - page, - html` -

- - 🙂 - - すし もじあ - - 🙂 - -

- `, - ); - await assertSelection(page, { - anchorOffset: 6, - anchorPath: [0, 1, 0], - focusOffset: 6, - focusPath: [0, 1, 0], - }); - - await pressBackspace(page, 6); - await assertHTML( - page, - html` -

- - 🙂 - - - 🙂 - -

- `, - ); - await assertSelection(page, { - anchorOffset: 2, - anchorPath: [0, 0, 0, 0], - focusOffset: 2, - focusPath: [0, 0, 0, 0], - }); + test.fixme( + 'Can type Hiragana via IME between emojis', + async ({page, browserName}) => { + test.skip(browserName !== 'chromium'); + await focusEditor(page); + await enableCompositionKeyEvents(page); - await page.keyboard.imeSetComposition('s', 1, 1); - await page.keyboard.imeSetComposition('す', 1, 1); - await page.keyboard.imeSetComposition('すs', 2, 2); - await page.keyboard.imeSetComposition('すsh', 3, 3); - await page.keyboard.imeSetComposition('すし', 2, 2); - await page.keyboard.imeSetComposition('す', 1, 1); - await page.keyboard.imeSetComposition('', 0, 0); - // Escape would fire here - await page.keyboard.insertText(''); + await page.keyboard.type(':):)'); - await assertHTML( - page, - html` -

- - 🙂 - - - 🙂 - -

- `, - ); - await assertSelection(page, { - anchorOffset: 2, - anchorPath: [0, 0, 0, 0], - focusOffset: 2, - focusPath: [0, 0, 0, 0], - }); - }); - - test('Can type Hiragana via IME at the end of a mention', async ({ - page, - browserName, - }) => { - // We don't yet support FF. - test.skip(browserName === 'firefox'); + await page.keyboard.press('ArrowLeft'); - await focusEditor(page); - await enableCompositionKeyEvents(page); + const client = await page.context().newCDPSession(page); + // await page.keyboard.imeSetComposition('s', 1, 1); + await client.send('Input.imeSetComposition', { + selectionStart: 1, + selectionEnd: 1, + text: 's', + }); + // await page.keyboard.imeSetComposition('す', 1, 1); + await client.send('Input.imeSetComposition', { + selectionStart: 1, + selectionEnd: 1, + text: 'す', + }); + // await page.keyboard.imeSetComposition('すs', 2, 2); + await client.send('Input.imeSetComposition', { + selectionStart: 2, + selectionEnd: 2, + text: 'すs', + }); + // await page.keyboard.imeSetComposition('すsh', 3, 3); + await client.send('Input.imeSetComposition', { + selectionStart: 3, + selectionEnd: 3, + text: 'すsh', + }); + // await page.keyboard.imeSetComposition('すし', 2, 2); + await client.send('Input.imeSetComposition', { + selectionStart: 2, + selectionEnd: 2, + text: 'すし', + }); + // await page.keyboard.insertText('すし'); + await client.send('Input.insertText', { + text: 'すし', + }); + await page.keyboard.type(' '); + // await page.keyboard.imeSetComposition('m', 1, 1); + await client.send('Input.imeSetComposition', { + selectionStart: 1, + selectionEnd: 1, + text: 'm', + }); + // await page.keyboard.imeSetComposition('も', 1, 1); + await client.send('Input.imeSetComposition', { + selectionStart: 1, + selectionEnd: 1, + text: 'も', + }); + // await page.keyboard.imeSetComposition('もj', 2, 2); + await client.send('Input.imeSetComposition', { + selectionStart: 2, + selectionEnd: 2, + text: 'もj', + }); + // await page.keyboard.imeSetComposition('もじ', 2, 2); + await client.send('Input.imeSetComposition', { + selectionStart: 2, + selectionEnd: 2, + text: 'もじ', + }); + // await page.keyboard.imeSetComposition('もじあ', 3, 3); + await client.send('Input.imeSetComposition', { + selectionStart: 3, + selectionEnd: 3, + text: 'もじあ', + }); + // await page.keyboard.insertText('もじあ'); + await client.send('Input.insertText', { + text: 'もじあ', + }); - await page.keyboard.type('Luke'); - await waitForSelector(page, '#typeahead-menu ul li'); - await page.keyboard.press('Enter'); + await assertHTML( + page, + html` +

+ + 🙂 + + すし もじあ + + 🙂 + +

+ `, + ); + await assertSelection(page, { + anchorOffset: 6, + anchorPath: [0, 1, 0], + focusOffset: 6, + focusPath: [0, 1, 0], + }); - await waitForSelector(page, '.mention'); + await pressBackspace(page, 6); + await assertHTML( + page, + html` +

+ + 🙂 + + + 🙂 + +

+ `, + ); + await assertSelection(page, { + anchorOffset: 2, + anchorPath: [0, 0, 0, 0], + focusOffset: 2, + focusPath: [0, 0, 0, 0], + }); - await page.keyboard.imeSetComposition('s', 1, 1); - await page.keyboard.imeSetComposition('す', 1, 1); - await page.keyboard.imeSetComposition('すs', 2, 2); - await page.keyboard.imeSetComposition('すsh', 3, 3); - await page.keyboard.imeSetComposition('すし', 2, 2); - await page.keyboard.insertText('すし'); - await page.keyboard.type(' '); - await page.keyboard.imeSetComposition('m', 1, 1); - await page.keyboard.imeSetComposition('も', 1, 1); - await page.keyboard.imeSetComposition('もj', 2, 2); - await page.keyboard.imeSetComposition('もじ', 2, 2); - await page.keyboard.imeSetComposition('もじあ', 3, 3); - await page.keyboard.insertText('もじあ'); + // await page.keyboard.imeSetComposition('s', 1, 1); + await client.send('Input.imeSetComposition', { + selectionStart: 1, + selectionEnd: 1, + text: 's', + }); + // await page.keyboard.imeSetComposition('す', 1, 1); + await client.send('Input.imeSetComposition', { + selectionStart: 1, + selectionEnd: 1, + text: 'す', + }); + // await page.keyboard.imeSetComposition('すs', 2, 2); + await client.send('Input.imeSetComposition', { + selectionStart: 2, + selectionEnd: 2, + text: 'すs', + }); + // await page.keyboard.imeSetComposition('すsh', 3, 3); + await client.send('Input.imeSetComposition', { + selectionStart: 3, + selectionEnd: 3, + text: 'すsh', + }); + // await page.keyboard.imeSetComposition('すし', 2, 2); + await client.send('Input.imeSetComposition', { + selectionStart: 2, + selectionEnd: 2, + text: 'すし', + }); + // await page.keyboard.imeSetComposition('す', 1, 1); + await client.send('Input.imeSetComposition', { + selectionStart: 1, + selectionEnd: 1, + text: 'す', + }); + // await page.keyboard.imeSetComposition('', 0, 0); + await client.send('Input.imeSetComposition', { + selectionStart: 0, + selectionEnd: 0, + text: '', + }); + // Escape would fire here + await page.keyboard.insertText(''); - await assertHTML( - page, - html` -

- - Luke Skywalker - - すし もじあ -

- `, - ); - await assertSelection(page, { - anchorOffset: 6, - anchorPath: [0, 1, 0], - focusOffset: 6, - focusPath: [0, 1, 0], - }); - }); + await assertHTML( + page, + html` +

+ + 🙂 + + + 🙂 + +

+ `, + ); + await assertSelection(page, { + anchorOffset: 2, + anchorPath: [0, 0, 0, 0], + focusOffset: 2, + focusPath: [0, 0, 0, 0], + }); + }, + ); - test('Can type Hiragana via IME part way through a mention', async ({ - page, - browserName, - }) => { - // We don't yet support FF. - test.skip(browserName === 'firefox'); + test.fixme( + 'Can type Hiragana via IME at the end of a mention', + async ({page, browserName}) => { + // We don't yet support FF. + test.skip(browserName !== 'chromium'); - await focusEditor(page); - await enableCompositionKeyEvents(page); + await focusEditor(page); + await enableCompositionKeyEvents(page); - await page.keyboard.type('Luke'); - await waitForSelector(page, '#typeahead-menu ul li'); - await page.keyboard.press('Enter'); + await page.keyboard.type('@Luke'); + await waitForSelector(page, '#typeahead-menu ul li'); + await page.keyboard.press('Enter'); - await waitForSelector(page, '.mention'); + await waitForSelector(page, '.mention'); - await moveLeft(page, 9); + const client = await page.context().newCDPSession(page); + // await page.keyboard.imeSetComposition('s', 1, 1); + await client.send('Input.imeSetComposition', { + selectionStart: 1, + selectionEnd: 1, + text: 's', + }); + // await page.keyboard.imeSetComposition('す', 1, 1); + await client.send('Input.imeSetComposition', { + selectionStart: 1, + selectionEnd: 1, + text: 'す', + }); + // await page.keyboard.imeSetComposition('すs', 2, 2); + await client.send('Input.imeSetComposition', { + selectionStart: 2, + selectionEnd: 2, + text: 'すs', + }); + // await page.keyboard.imeSetComposition('すsh', 3, 3); + await client.send('Input.imeSetComposition', { + selectionStart: 3, + selectionEnd: 3, + text: 'すsh', + }); + // await page.keyboard.imeSetComposition('すし', 2, 2); + await client.send('Input.imeSetComposition', { + selectionStart: 2, + selectionEnd: 2, + text: 'すし', + }); + // await page.keyboard.insertText('すし'); + await client.send('Input.insertText', { + text: 'すし', + }); + await page.keyboard.type(' '); + // await page.keyboard.imeSetComposition('m', 1, 1); + await client.send('Input.imeSetComposition', { + selectionStart: 1, + selectionEnd: 1, + text: 'm', + }); + // await page.keyboard.imeSetComposition('も', 1, 1); + await client.send('Input.imeSetComposition', { + selectionStart: 1, + selectionEnd: 1, + text: 'も', + }); + // await page.keyboard.imeSetComposition('もj', 2, 2); + await client.send('Input.imeSetComposition', { + selectionStart: 2, + selectionEnd: 2, + text: 'もj', + }); + // await page.keyboard.imeSetComposition('もじ', 2, 2); + await client.send('Input.imeSetComposition', { + selectionStart: 2, + selectionEnd: 2, + text: 'もじ', + }); + // await page.keyboard.imeSetComposition('もじあ', 3, 3); + await client.send('Input.imeSetComposition', { + selectionStart: 3, + selectionEnd: 3, + text: 'もじあ', + }); + // await page.keyboard.insertText('もじあ'); + await client.send('Input.insertText', { + text: 'もじあ', + }); - await page.keyboard.imeSetComposition('s', 1, 1); - await page.keyboard.imeSetComposition('す', 1, 1); - await page.keyboard.imeSetComposition('すs', 2, 2); - await page.keyboard.imeSetComposition('すsh', 3, 3); - await page.keyboard.imeSetComposition('すし', 2, 2); - await page.keyboard.insertText('すし'); - await page.keyboard.type(' '); - await page.keyboard.imeSetComposition('m', 1, 1); - await page.keyboard.imeSetComposition('も', 1, 1); - await page.keyboard.imeSetComposition('もj', 2, 2); - await page.keyboard.imeSetComposition('もじ', 2, 2); - await page.keyboard.imeSetComposition('もじあ', 3, 3); - await page.keyboard.insertText('もじあ'); - - if (browserName === 'webkit') await assertHTML( page, html`

- - Luke  すし もじあSkywalker + + Luke Skywalker + すし もじあ

`, ); - /* eslint-disable no-irregular-whitespace */ - if (browserName === 'chromium') + await assertSelection(page, { + anchorOffset: 6, + anchorPath: [0, 1, 0], + focusOffset: 6, + focusPath: [0, 1, 0], + }); + }, + ); + + test.fixme( + 'Can type Hiragana via IME part way through a mention', + async ({page, browserName}) => { + // We don't yet support FF. + test.skip(browserName !== 'chromium'); + + await focusEditor(page); + await enableCompositionKeyEvents(page); + + await page.keyboard.type('@Luke'); + await waitForSelector(page, '#typeahead-menu ul li'); + await page.keyboard.press('Enter'); + + await waitForSelector(page, '.mention'); + + await moveLeft(page, 9); + + const client = await page.context().newCDPSession(page); + // await page.keyboard.imeSetComposition('s', 1, 1); + await client.send('Input.imeSetComposition', { + selectionStart: 1, + selectionEnd: 1, + text: 's', + }); + // await page.keyboard.imeSetComposition('す', 1, 1); + await client.send('Input.imeSetComposition', { + selectionStart: 1, + selectionEnd: 1, + text: 'す', + }); + // await page.keyboard.imeSetComposition('すs', 2, 2); + await client.send('Input.imeSetComposition', { + selectionStart: 2, + selectionEnd: 2, + text: 'すs', + }); + // await page.keyboard.imeSetComposition('すsh', 3, 3); + await client.send('Input.imeSetComposition', { + selectionStart: 3, + selectionEnd: 3, + text: 'すsh', + }); + // await page.keyboard.imeSetComposition('すし', 2, 2); + await client.send('Input.imeSetComposition', { + selectionStart: 2, + selectionEnd: 2, + text: 'すし', + }); + // await page.keyboard.insertText('すし'); + await client.send('Input.insertText', { + text: 'すし', + }); + await page.keyboard.type(' '); + // await page.keyboard.imeSetComposition('m', 1, 1); + await client.send('Input.imeSetComposition', { + selectionStart: 1, + selectionEnd: 1, + text: 'm', + }); + // await page.keyboard.imeSetComposition('も', 1, 1); + await client.send('Input.imeSetComposition', { + selectionStart: 1, + selectionEnd: 1, + text: 'も', + }); + // await page.keyboard.imeSetComposition('もj', 2, 2); + await client.send('Input.imeSetComposition', { + selectionStart: 2, + selectionEnd: 2, + text: 'もj', + }); + // await page.keyboard.imeSetComposition('もじ', 2, 2); + await client.send('Input.imeSetComposition', { + selectionStart: 2, + selectionEnd: 2, + text: 'もじ', + }); + // await page.keyboard.imeSetComposition('もじあ', 3, 3); + await client.send('Input.imeSetComposition', { + selectionStart: 3, + selectionEnd: 3, + text: 'もじあ', + }); + // await page.keyboard.insertText('もじあ'); + await client.send('Input.insertText', { + text: 'もじあ', + }); + + if (browserName === 'webkit') { + await assertHTML( + page, + html` +

+ + Luke  すし もじあSkywalker + +

+ `, + ); + } + /* eslint-disable no-irregular-whitespace */ + if (browserName === 'chromium') { + await assertHTML( + page, + html` +

+ Luke ​すし もじあSkywalker +

+ `, + ); + } + + await assertSelection(page, { + anchorOffset: 12, + anchorPath: [0, 0, 0], + focusOffset: 12, + focusPath: [0, 0, 0], + }); + }, + ); + + test.fixme( + 'Typing after mention with IME should not break it', + async ({page, browserName, isPlainText}) => { + // We don't yet support FF. + test.skip(browserName !== 'chromium'); + + await focusEditor(page); + await enableCompositionKeyEvents(page); + + await page.keyboard.type('@Luke'); + await waitForSelector(page, '#typeahead-menu ul li'); + await page.keyboard.press('Enter'); + + const client = await page.context().newCDPSession(page); + // await page.keyboard.imeSetComposition('s', 1, 1); + await client.send('Input.imeSetComposition', { + selectionStart: 1, + selectionEnd: 1, + text: 's', + }); + // await page.keyboard.imeSetComposition('す', 1, 1); + await client.send('Input.imeSetComposition', { + selectionStart: 1, + selectionEnd: 1, + text: 'す', + }); + // await page.keyboard.imeSetComposition('すs', 2, 2); + await client.send('Input.imeSetComposition', { + selectionStart: 2, + selectionEnd: 2, + text: 'すs', + }); + // await page.keyboard.imeSetComposition('すsh', 3, 3); + await client.send('Input.imeSetComposition', { + selectionStart: 3, + selectionEnd: 3, + text: 'すsh', + }); + // await page.keyboard.imeSetComposition('すし', 2, 2); + await client.send('Input.imeSetComposition', { + selectionStart: 2, + selectionEnd: 2, + text: 'すし', + }); + // await page.keyboard.insertText('すし'); + await client.send('Input.insertText', { + text: 'すし', + }); + await assertHTML( page, html`

- Luke ​すし もじあSkywalker + + Luke Skywalker + + すし

`, ); - - await assertSelection(page, { - anchorOffset: 12, - anchorPath: [0, 0, 0], - focusOffset: 12, - focusPath: [0, 0, 0], - }); - }); + }, + ); test('Can type Hiragana via IME with hashtags', async ({ page, @@ -554,27 +965,84 @@ test.describe('Composition', () => { isCollab, }) => { // We don't yet support FF. - test.skip(browserName === 'firefox'); + test.skip(browserName !== 'chromium'); await focusEditor(page); await enableCompositionKeyEvents(page); await page.keyboard.type('#'); - await page.keyboard.imeSetComposition('s', 1, 1); - await page.keyboard.imeSetComposition('す', 1, 1); - await page.keyboard.imeSetComposition('すs', 2, 2); - await page.keyboard.imeSetComposition('すsh', 3, 3); - await page.keyboard.imeSetComposition('すし', 2, 2); - await page.keyboard.insertText('すし'); + const client = await page.context().newCDPSession(page); + // await page.keyboard.imeSetComposition('s', 1, 1); + await client.send('Input.imeSetComposition', { + selectionStart: 1, + selectionEnd: 1, + text: 's', + }); + // await page.keyboard.imeSetComposition('す', 1, 1); + await client.send('Input.imeSetComposition', { + selectionStart: 1, + selectionEnd: 1, + text: 'す', + }); + // await page.keyboard.imeSetComposition('すs', 2, 2); + await client.send('Input.imeSetComposition', { + selectionStart: 2, + selectionEnd: 2, + text: 'すs', + }); + // await page.keyboard.imeSetComposition('すsh', 3, 3); + await client.send('Input.imeSetComposition', { + selectionStart: 3, + selectionEnd: 3, + text: 'すsh', + }); + // await page.keyboard.imeSetComposition('すし', 2, 2); + await client.send('Input.imeSetComposition', { + selectionStart: 2, + selectionEnd: 2, + text: 'すし', + }); + // await page.keyboard.insertText('すし'); + await client.send('Input.insertText', { + text: 'すし', + }); await page.keyboard.type(' '); - await page.keyboard.imeSetComposition('m', 1, 1); - await page.keyboard.imeSetComposition('も', 1, 1); - await page.keyboard.imeSetComposition('もj', 2, 2); - await page.keyboard.imeSetComposition('もじ', 2, 2); - await page.keyboard.imeSetComposition('もじあ', 3, 3); - await page.keyboard.insertText('もじあ'); + // await page.keyboard.imeSetComposition('m', 1, 1); + await client.send('Input.imeSetComposition', { + selectionStart: 1, + selectionEnd: 1, + text: 'm', + }); + // await page.keyboard.imeSetComposition('も', 1, 1); + await client.send('Input.imeSetComposition', { + selectionStart: 1, + selectionEnd: 1, + text: 'も', + }); + // await page.keyboard.imeSetComposition('もj', 2, 2); + await client.send('Input.imeSetComposition', { + selectionStart: 2, + selectionEnd: 2, + text: 'もj', + }); + // await page.keyboard.imeSetComposition('もじ', 2, 2); + await client.send('Input.imeSetComposition', { + selectionStart: 2, + selectionEnd: 2, + text: 'もじ', + }); + // await page.keyboard.imeSetComposition('もじあ', 3, 3); + await client.send('Input.imeSetComposition', { + selectionStart: 3, + selectionEnd: 3, + text: 'もじあ', + }); + // await page.keyboard.insertText('もじあ'); + await client.send('Input.insertText', { + text: 'もじあ', + }); await assertHTML( page, @@ -600,12 +1068,40 @@ test.describe('Composition', () => { await moveToLineBeginning(page); - await page.keyboard.imeSetComposition('s', 1, 1); - await page.keyboard.imeSetComposition('す', 1, 1); - await page.keyboard.imeSetComposition('すs', 2, 2); - await page.keyboard.imeSetComposition('すsh', 3, 3); - await page.keyboard.imeSetComposition('すし', 2, 2); - await page.keyboard.insertText('すし'); + // await page.keyboard.imeSetComposition('s', 1, 1); + await client.send('Input.imeSetComposition', { + selectionStart: 1, + selectionEnd: 1, + text: 's', + }); + // await page.keyboard.imeSetComposition('す', 1, 1); + await client.send('Input.imeSetComposition', { + selectionStart: 1, + selectionEnd: 1, + text: 'す', + }); + // await page.keyboard.imeSetComposition('すs', 2, 2); + await client.send('Input.imeSetComposition', { + selectionStart: 2, + selectionEnd: 2, + text: 'すs', + }); + // await page.keyboard.imeSetComposition('すsh', 3, 3); + await client.send('Input.imeSetComposition', { + selectionStart: 3, + selectionEnd: 3, + text: 'すsh', + }); + // await page.keyboard.imeSetComposition('すし', 2, 2); + await client.send('Input.imeSetComposition', { + selectionStart: 2, + selectionEnd: 2, + text: 'すし', + }); + // await page.keyboard.insertText('すし'); + await client.send('Input.insertText', { + text: 'すし', + }); await assertHTML( page, @@ -625,83 +1121,180 @@ test.describe('Composition', () => { }); }); - test.fixme( - 'Can type, delete and cancel Hiragana via IME', - async ({page, browserName}) => { - // We don't yet support FF. - test.skip(browserName === 'firefox'); + test('Can type, delete and cancel Hiragana via IME', async ({ + page, + browserName, + }) => { + // We don't yet support FF. + test.skip(browserName !== 'chromium'); - await focusEditor(page); - await enableCompositionKeyEvents(page); + await focusEditor(page); + await enableCompositionKeyEvents(page); - await page.keyboard.imeSetComposition('s', 1, 1); - await page.keyboard.imeSetComposition('す', 1, 1); - await page.keyboard.imeSetComposition('すs', 2, 2); - await page.keyboard.imeSetComposition('すsh', 3, 3); - await page.keyboard.imeSetComposition('すし', 2, 2); - await page.keyboard.imeSetComposition('す', 1, 1); - await page.keyboard.imeSetComposition('', 0, 0); - // Escape would fire here - await page.keyboard.insertText(''); + const client = await page.context().newCDPSession(page); + // await page.keyboard.imeSetComposition('s', 1, 1); + await client.send('Input.imeSetComposition', { + selectionStart: 1, + selectionEnd: 1, + text: 's', + }); + // await page.keyboard.imeSetComposition('す', 1, 1); + await client.send('Input.imeSetComposition', { + selectionStart: 1, + selectionEnd: 1, + text: 'す', + }); + // await page.keyboard.imeSetComposition('すs', 2, 2); + await client.send('Input.imeSetComposition', { + selectionStart: 2, + selectionEnd: 2, + text: 'すs', + }); + // await page.keyboard.imeSetComposition('すsh', 3, 3); + await client.send('Input.imeSetComposition', { + selectionStart: 3, + selectionEnd: 3, + text: 'すsh', + }); + // await page.keyboard.imeSetComposition('すし', 2, 2); + await client.send('Input.imeSetComposition', { + selectionStart: 2, + selectionEnd: 2, + text: 'すし', + }); + // await page.keyboard.imeSetComposition('す', 1, 1); + await client.send('Input.imeSetComposition', { + selectionStart: 1, + selectionEnd: 1, + text: 'す', + }); + // await page.keyboard.imeSetComposition('', 0, 0); + await client.send('Input.imeSetComposition', { + selectionStart: 0, + selectionEnd: 0, + text: '', + }); + // Escape would fire here + await page.keyboard.insertText(''); - await assertHTML( - page, - html` -


- `, - ); - await assertSelection(page, { - anchorOffset: 0, - anchorPath: [0], - focusOffset: 0, - focusPath: [0], - }); + await assertHTML( + page, + html` +


+ `, + ); + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [0], + focusOffset: 0, + focusPath: [0], + }); - await page.keyboard.type(' '); - await page.keyboard.press('ArrowLeft'); + await page.keyboard.type(' '); + await page.keyboard.press('ArrowLeft'); - await page.keyboard.imeSetComposition('s', 1, 1); - await page.keyboard.imeSetComposition('す', 1, 1); - await page.keyboard.imeSetComposition('すs', 2, 2); - await page.keyboard.imeSetComposition('すsh', 3, 3); - await page.keyboard.imeSetComposition('すし', 2, 2); - await page.keyboard.imeSetComposition('す', 1, 1); - await page.keyboard.imeSetComposition('', 0, 0); - // Escape would fire here - await page.keyboard.insertText(''); + // await page.keyboard.imeSetComposition('s', 1, 1); + await client.send('Input.imeSetComposition', { + selectionStart: 1, + selectionEnd: 1, + text: 's', + }); + // await page.keyboard.imeSetComposition('す', 1, 1); + await client.send('Input.imeSetComposition', { + selectionStart: 1, + selectionEnd: 1, + text: 'す', + }); + // await page.keyboard.imeSetComposition('すs', 2, 2); + await client.send('Input.imeSetComposition', { + selectionStart: 2, + selectionEnd: 2, + text: 'すs', + }); + // await page.keyboard.imeSetComposition('すsh', 3, 3); + await client.send('Input.imeSetComposition', { + selectionStart: 3, + selectionEnd: 3, + text: 'すsh', + }); + // await page.keyboard.imeSetComposition('すし', 2, 2); + await client.send('Input.imeSetComposition', { + selectionStart: 2, + selectionEnd: 2, + text: 'すし', + }); + // await page.keyboard.imeSetComposition('す', 1, 1); + await client.send('Input.imeSetComposition', { + selectionStart: 1, + selectionEnd: 1, + text: 'す', + }); + // await page.keyboard.imeSetComposition('', 0, 0); + await client.send('Input.imeSetComposition', { + selectionStart: 0, + selectionEnd: 0, + text: '', + }); + // Escape would fire here + await page.keyboard.insertText(''); - await assertHTML( - page, - html` -

- -

- `, - ); - await assertSelection(page, { - anchorOffset: 0, - anchorPath: [0, 0, 0], - focusOffset: 0, - focusPath: [0, 0, 0], - }); - }, - ); + await assertHTML( + page, + html` +

+ +

+ `, + ); + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [0, 0, 0], + focusOffset: 0, + focusPath: [0, 0, 0], + }); + }); test.fixme( 'Floating toolbar should not be displayed when using IME', async ({page, browserName, isPlainText}) => { - test.skip(isPlainText); // We don't yet support FF. - test.skip(browserName === 'firefox'); + test.skip(browserName !== 'chromium' || isPlainText); await focusEditor(page); await enableCompositionKeyEvents(page); - await page.keyboard.imeSetComposition('s', 0, 1); - await page.keyboard.imeSetComposition('す', 0, 1); - await page.keyboard.imeSetComposition('すs', 0, 2); - await page.keyboard.imeSetComposition('すsh', 0, 3); - await page.keyboard.imeSetComposition('すsh', 0, 4); + const client = await page.context().newCDPSession(page); + + // await page.keyboard.imeSetComposition('s', 0, 1); + await client.send('Input.imeSetComposition', { + selectionStart: 0, + selectionEnd: 1, + text: 's', + }); + // await page.keyboard.imeSetComposition('す', 0, 1); + await client.send('Input.imeSetComposition', { + selectionStart: 0, + selectionEnd: 1, + text: 'す', + }); + // await page.keyboard.imeSetComposition('すs', 0, 2); + await client.send('Input.imeSetComposition', { + selectionStart: 0, + selectionEnd: 2, + text: 'すs', + }); + // await page.keyboard.imeSetComposition('すsh', 0, 3); + await client.send('Input.imeSetComposition', { + selectionStart: 0, + selectionEnd: 3, + text: 'すsh', + }); + // await page.keyboard.imeSetComposition('すsh', 0, 4); + await client.send('Input.imeSetComposition', { + selectionStart: 0, + selectionEnd: 4, + text: 'すsh', + }); const isFloatingToolbarDisplayedWhenUseIME = await evaluate( page, @@ -712,7 +1305,10 @@ test.describe('Composition', () => { expect(isFloatingToolbarDisplayedWhenUseIME).toEqual(false); - await page.keyboard.insertText('すsh'); + // await page.keyboard.insertText('すsh'); + await client.send('Input.insertText', { + text: 'すsh', + }); await selectCharacters(page, 'left', 3); const isFloatingToolbarDisplayed = await evaluate(page, () => { diff --git a/demos/playground/src/__tests__/e2e/CopyAndPaste.spec.mjs b/demos/playground/src/__tests__/e2e/CopyAndPaste.spec.mjs deleted file mode 100644 index 4cc7a1e7..00000000 --- a/demos/playground/src/__tests__/e2e/CopyAndPaste.spec.mjs +++ /dev/null @@ -1,3306 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ -import {expect} from '@playwright/test'; - -import { - extendToNextWord, - moveLeft, - moveToEditorBeginning, - moveToEditorEnd, - moveToLineBeginning, - moveToLineEnd, - moveToNextWord, - moveToPrevWord, - selectAll, - selectCharacters, - undo, -} from '../keyboardShortcuts/index.mjs'; -import { - assertHTML, - assertSelection, - clearEditor, - click, - copyToClipboard, - focus, - focusEditor, - html, - initialize, - insertTable, - IS_LINUX, - IS_WINDOWS, - LEXICAL_IMAGE_BASE64, - pasteFromClipboard, - selectCellsFromTableCords, - selectFromAlignDropdown, - sleepInsertImage, - test, -} from '../utils/index.mjs'; - -test.describe('CopyAndPaste', () => { - test.beforeEach(({isCollab, page}) => initialize({isCollab, page})); - test('Basic copy + paste', async ({isRichText, page, browserName}) => { - await focusEditor(page); - - // Add paragraph - await page.keyboard.type('Copy + pasting?'); - await page.keyboard.press('Enter'); - await page.keyboard.press('Enter'); - await page.keyboard.type('Sounds good!'); - if (isRichText) { - await assertHTML( - page, - html` -

- Copy + pasting? -

-


-

- Sounds good! -

- `, - ); - await assertSelection(page, { - anchorOffset: 12, - anchorPath: [2, 0, 0], - focusOffset: 12, - focusPath: [2, 0, 0], - }); - } else { - await assertHTML( - page, - html` -

- Copy + pasting? -
-
- Sounds good! -

- `, - ); - await assertSelection(page, { - anchorOffset: 12, - anchorPath: [0, 3, 0], - focusOffset: 12, - focusPath: [0, 3, 0], - }); - } - - // Select all the text - await selectAll(page); - if (isRichText) { - await assertHTML( - page, - html` -

- Copy + pasting? -

-


-

- Sounds good! -

- `, - ); - if (browserName === 'firefox') { - await assertSelection(page, { - anchorOffset: 0, - anchorPath: [], - focusOffset: 3, - focusPath: [], - }); - } else { - await assertSelection(page, { - anchorOffset: 0, - anchorPath: [0, 0, 0], - focusOffset: 12, - focusPath: [2, 0, 0], - }); - } - } else { - await assertHTML( - page, - html` -

- Copy + pasting? -
-
- Sounds good! -

- `, - ); - if (browserName === 'firefox') { - await assertSelection(page, { - anchorOffset: 0, - anchorPath: [], - focusOffset: 1, - focusPath: [], - }); - } else { - await assertSelection(page, { - anchorOffset: 0, - anchorPath: [0, 0, 0], - focusOffset: 12, - focusPath: [0, 3, 0], - }); - } - } - - // Copy all the text - const clipboard = await copyToClipboard(page); - if (isRichText) { - await assertHTML( - page, - html` -

- Copy + pasting? -

-


-

- Sounds good! -

- `, - ); - } else { - await assertHTML( - page, - html` -

- Copy + pasting? -
-
- Sounds good! -

- `, - ); - } - - // Paste after - await page.keyboard.press('ArrowRight'); - await pasteFromClipboard(page, clipboard); - if (isRichText) { - await assertHTML( - page, - html` -

- Copy + pasting? -

-


-

- Sounds good!Copy + pasting? -

-


-

- Sounds good! -

- `, - ); - await assertSelection(page, { - anchorOffset: 12, - anchorPath: [4, 0, 0], - focusOffset: 12, - focusPath: [4, 0, 0], - }); - } else { - await assertHTML( - page, - html` -

- Copy + pasting? -
-
- Sounds good!Copy + pasting? -
-
- Sounds good! -

- `, - ); - await assertSelection(page, { - anchorOffset: 12, - anchorPath: [0, 6, 0], - focusOffset: 12, - focusPath: [0, 6, 0], - }); - } - }); - - test.fixme( - `Copy and paste heading`, - async ({isPlainText, page, browserName}) => { - test.skip(isPlainText); - - await focusEditor(page); - await page.keyboard.type('# Heading'); - await page.keyboard.press('Enter'); - await page.keyboard.type('Some text'); - - await moveToEditorBeginning(page); - await page.keyboard.down('Shift'); - await moveToLineEnd(page); - await page.keyboard.up('Shift'); - - const clipboard = await copyToClipboard(page); - - await moveToEditorEnd(page); - await page.keyboard.press('Enter'); - - // Paste the content - await pasteFromClipboard(page, clipboard); - - await assertHTML( - page, - html` -

- Heading -

-

- Some text -

-

- Heading -

- `, - ); - - await assertSelection(page, { - anchorOffset: 7, - anchorPath: [2, 0, 0], - focusOffset: 7, - focusPath: [2, 0, 0], - }); - }, - ); - - test.fixme( - `Copy and paste between sections`, - async ({isRichText, page, browserName}) => { - await focusEditor(page); - await page.keyboard.type('Hello world #foobar test #foobar2 when #not'); - - await page.keyboard.press('Enter'); - await page.keyboard.type('Next #line of #text test #foo'); - - if (isRichText) { - await assertHTML( - page, - html` -

- Hello world - - #foobar - - test - - #foobar2 - - when - - #not - -

-

- Next - - #line - - of - - #text - - test - - #foo - -

- `, - ); - - await assertSelection(page, { - anchorOffset: 4, - anchorPath: [1, 5, 0], - focusOffset: 4, - focusPath: [1, 5, 0], - }); - } else { - await assertHTML( - page, - html` -

- Hello world - - #foobar - - test - - #foobar2 - - when - - #not - -
- Next - - #line - - of - - #text - - test - - #foo - -

- `, - ); - await assertSelection(page, { - anchorOffset: 4, - anchorPath: [0, 12, 0], - focusOffset: 4, - focusPath: [0, 12, 0], - }); - } - - // Select all the content - await selectAll(page); - - if (isRichText) { - if (browserName === 'firefox') { - await assertSelection(page, { - anchorOffset: 0, - anchorPath: [], - focusOffset: 2, - focusPath: [], - }); - } else { - await assertSelection(page, { - anchorOffset: 0, - anchorPath: [0, 0, 0], - focusOffset: 4, - focusPath: [1, 5, 0], - }); - } - } else { - if (browserName === 'firefox') { - await assertSelection(page, { - anchorOffset: 0, - anchorPath: [], - focusOffset: 1, - focusPath: [], - }); - } else { - await assertSelection(page, { - anchorOffset: 0, - anchorPath: [0, 0, 0], - focusOffset: 4, - focusPath: [0, 12, 0], - }); - } - } - - // Copy all the text - let clipboard = await copyToClipboard(page); - await page.keyboard.press('Delete'); - // Paste the content - await pasteFromClipboard(page, clipboard); - - if (isRichText) { - await assertHTML( - page, - html` -

- Hello world - - #foobar - - test - - #foobar2 - - when - - #not - -

-

- Next - - #line - - of - - #text - - test - - #foo - -

- `, - ); - await assertSelection(page, { - anchorOffset: 4, - anchorPath: [1, 5, 0], - focusOffset: 4, - focusPath: [1, 5, 0], - }); - } else { - await assertHTML( - page, - html` -

- Hello world - - #foobar - - test - - #foobar2 - - when - - #not - -
- Next - - #line - - of - - #text - - test - - #foo - -

- `, - ); - await assertSelection(page, { - anchorOffset: 4, - anchorPath: [0, 12, 0], - focusOffset: 4, - focusPath: [0, 12, 0], - }); - } - - await moveToPrevWord(page); - await page.keyboard.down('Shift'); - await page.keyboard.press('ArrowUp'); - await moveToPrevWord(page); - // Once more for linux on Chromium - if (IS_LINUX && browserName === 'chromium') { - await moveToPrevWord(page); - } - await page.keyboard.up('Shift'); - - if (isRichText) { - await assertSelection(page, { - anchorOffset: 1, - anchorPath: [1, 5, 0], - focusOffset: 1, - focusPath: [0, 2, 0], - }); - } else { - await assertSelection(page, { - anchorOffset: 1, - anchorPath: [0, 12, 0], - focusOffset: 1, - focusPath: [0, 2, 0], - }); - } - - // Copy selected text - clipboard = await copyToClipboard(page); - await page.keyboard.press('Delete'); - // Paste the content - await pasteFromClipboard(page, clipboard); - - if (isRichText) { - await assertHTML( - page, - html` -

- Hello world - - #foobar - - test - - #foobar2 - - when - - #not - -

-

- Next - - #line - - of - - #text - - test - - #foo - -

- `, - ); - await assertSelection(page, { - anchorOffset: 1, - anchorPath: [1, 5, 0], - focusOffset: 1, - focusPath: [1, 5, 0], - }); - } else { - await assertHTML( - page, - html` -

- Hello world - - #foobar - - test - - #foobar2 - - when - - #not - -
- Next - - #line - - of - - #text - - test - - #foo - -

- `, - ); - await assertSelection(page, { - anchorOffset: 1, - anchorPath: [0, 12, 0], - focusOffset: 1, - focusPath: [0, 12, 0], - }); - } - - // Select all the content - await selectAll(page); - - if (isRichText) { - if (browserName === 'firefox') { - await assertSelection(page, { - anchorOffset: 0, - anchorPath: [], - focusOffset: 2, - focusPath: [], - }); - } else { - if (browserName === 'firefox') { - await assertSelection(page, { - anchorOffset: 0, - anchorPath: [0, 0, 0], - focusOffset: 3, - focusPath: [1, 5, 0], - }); - } else { - await assertSelection(page, { - anchorOffset: 0, - anchorPath: [0, 0, 0], - focusOffset: 4, - focusPath: [1, 5, 0], - }); - } - } - } else { - if (browserName === 'firefox') { - await assertSelection(page, { - anchorOffset: 0, - anchorPath: [], - focusOffset: 1, - focusPath: [], - }); - } else { - await assertSelection(page, { - anchorOffset: 0, - anchorPath: [0, 0, 0], - focusOffset: 4, - focusPath: [0, 12, 0], - }); - } - } - - await page.keyboard.press('Delete'); - await assertHTML( - page, - html` -


- `, - ); - await assertSelection(page, { - anchorOffset: 0, - anchorPath: [0], - focusOffset: 0, - focusPath: [0], - }); - }, - ); - - test.fixme( - 'Copy and paste of partial list items into an empty editor', - async ({page, isPlainText}) => { - test.skip(isPlainText); - await focusEditor(page); - - // Add three list items - await page.keyboard.type('- one'); - await page.keyboard.press('Enter'); - await page.keyboard.type('two'); - await page.keyboard.press('Enter'); - await page.keyboard.type('three'); - - await page.keyboard.press('Enter'); - await page.keyboard.press('Enter'); - - // Add a paragraph - await page.keyboard.type('Some text.'); - - await assertHTML( - page, - '
  • one
  • two
  • three

Some text.

', - ); - await assertSelection(page, { - anchorOffset: 10, - anchorPath: [1, 0, 0], - focusOffset: 10, - focusPath: [1, 0, 0], - }); - - await page.keyboard.down('Shift'); - await moveToLineBeginning(page); - await moveLeft(page, 3); - await page.keyboard.up('Shift'); - - await assertSelection(page, { - anchorOffset: 10, - anchorPath: [1, 0, 0], - focusOffset: 3, - focusPath: [0, 2, 0, 0], - }); - - // Copy the partial list item and paragraph - const clipboard = await copyToClipboard(page); - - // Select all and remove content - await selectAll(page); - await page.keyboard.press('Backspace'); - await page.keyboard.press('Backspace'); - - await assertHTML( - page, - html` -


- `, - ); - await assertSelection(page, { - anchorOffset: 0, - anchorPath: [0], - focusOffset: 0, - focusPath: [0], - }); - - // Paste - - await pasteFromClipboard(page, clipboard); - - await assertHTML( - page, - '
  • ee

Some text.

', - ); - await assertSelection(page, { - anchorOffset: 10, - anchorPath: [1, 0, 0], - focusOffset: 10, - focusPath: [1, 0, 0], - }); - }, - ); - - test.fixme( - 'Copy and paste of partial list items into the list', - async ({page, isPlainText, isCollab, browserName}) => { - test.skip(isPlainText); - - await focusEditor(page); - - // Add three list items - await page.keyboard.type('- one'); - await page.keyboard.press('Enter'); - await page.keyboard.type('two'); - await page.keyboard.press('Enter'); - await page.keyboard.type('three'); - - await page.keyboard.press('Enter'); - await page.keyboard.press('Enter'); - - // Add a paragraph - await page.keyboard.type('Some text.'); - - await assertHTML( - page, - html` -
    -
  • - one -
  • -
  • - two -
  • -
  • - three -
  • -
-

- Some text. -

- `, - ); - await assertSelection(page, { - anchorOffset: 10, - anchorPath: [1, 0, 0], - focusOffset: 10, - focusPath: [1, 0, 0], - }); - - await page.keyboard.down('Shift'); - await moveToLineBeginning(page); - await moveLeft(page, 3); - await page.keyboard.up('Shift'); - - await assertSelection(page, { - anchorOffset: 10, - anchorPath: [1, 0, 0], - focusOffset: 3, - focusPath: [0, 2, 0, 0], - }); - - // Copy the partial list item and paragraph - const clipboard = await copyToClipboard(page); - - // Select all and remove content - await page.keyboard.press('ArrowUp'); - await page.keyboard.press('ArrowUp'); - if (!IS_WINDOWS && browserName === 'firefox') { - await page.keyboard.press('ArrowUp'); - } - await moveToLineEnd(page); - - await page.keyboard.down('Enter'); - - await assertHTML( - page, - html` -
    -
  • - one -
  • -
  • -
    -
  • -
  • - two -
  • -
  • - three -
  • -
-

- Some text. -

- `, - ); - await assertSelection(page, { - anchorOffset: 0, - anchorPath: [0, 1], - focusOffset: 0, - focusPath: [0, 1], - }); - - await pasteFromClipboard(page, clipboard); - - await assertHTML( - page, - html` -
    -
  • - one -
  • -
  • - ee -
  • -
-

- Some text. -

-
    -
  • - three -
  • -
  • - two -
  • -
-

- Some text. -

- `, - ); - await assertSelection(page, { - anchorOffset: 10, - anchorPath: [1, 0, 0], - focusOffset: 10, - focusPath: [1, 0, 0], - }); - }, - ); - - test.fixme( - 'Copy list items and paste back into list', - async ({page, isPlainText, isCollab}) => { - test.skip(isPlainText); - - await focusEditor(page); - - await page.keyboard.type('- one'); - await page.keyboard.press('Enter'); - await page.keyboard.type('two'); - await page.keyboard.press('Enter'); - await page.keyboard.type('three'); - await page.keyboard.press('Enter'); - await page.keyboard.type('four'); - await page.keyboard.press('Enter'); - await page.keyboard.type('five'); - - await page.keyboard.press('ArrowUp'); - await page.keyboard.press('ArrowUp'); - - await moveToLineBeginning(page); - await page.keyboard.down('Shift'); - await page.keyboard.press('ArrowDown'); - await moveToLineEnd(page); - await page.keyboard.up('Shift'); - - await assertHTML( - page, - '
  • one
  • two
  • three
  • four
  • five
', - ); - await assertSelection(page, { - anchorOffset: 0, - anchorPath: [0, 2, 0, 0], - focusOffset: 4, - focusPath: [0, 3, 0, 0], - }); - - const clipboard = await copyToClipboard(page); - - await page.keyboard.press('Backspace'); - - await assertHTML( - page, - '
  • one
  • two

  • five
', - ); - await assertSelection(page, { - anchorOffset: 0, - anchorPath: [0, 2], - focusOffset: 0, - focusPath: [0, 2], - }); - - await pasteFromClipboard(page, clipboard); - - await assertHTML( - page, - '
  • one
  • two
  • three
  • four
  • five
', - ); - await assertSelection(page, { - anchorOffset: 4, - anchorPath: [0, 3, 0, 0], - focusOffset: 4, - focusPath: [0, 3, 0, 0], - }); - }, - ); - - test.fixme( - 'Copy list items and paste into list', - async ({page, isPlainText, isCollab}) => { - test.skip(isPlainText); - - await focusEditor(page); - - await page.keyboard.type('- one'); - await page.keyboard.press('Enter'); - await page.keyboard.type('two'); - await page.keyboard.press('Enter'); - await page.keyboard.type('three'); - await page.keyboard.press('Enter'); - await page.keyboard.type('four'); - await page.keyboard.press('Enter'); - await page.keyboard.type('five'); - - await selectAll(page); - - await assertHTML( - page, - html` -
    -
  • - one -
  • -
  • - two -
  • -
  • - three -
  • -
  • - four -
  • -
  • - five -
  • -
- `, - ); - - const clipboard = await copyToClipboard(page); - - await page.keyboard.press('ArrowDown'); - await page.keyboard.press('Enter'); - await page.keyboard.press('Enter'); - - await page.keyboard.type('12345'); - - await assertHTML( - page, - html` -
    -
  • - one -
  • -
  • - two -
  • -
  • - three -
  • -
  • - four -
  • -
  • - five -
  • -
-

- 12345 -

- `, - ); - - await page.keyboard.press('ArrowLeft'); - await page.keyboard.press('ArrowLeft'); - await selectCharacters(page, 'left', 1); - - await pasteFromClipboard(page, clipboard); - - await assertHTML( - page, - html` -
    -
  • - one -
  • -
  • - two -
  • -
  • - three -
  • -
  • - four -
  • -
  • - five -
  • -
-

- 12one -

-
    -
  • - two -
  • -
  • - three -
  • -
  • - four -
  • -
  • - five -
  • -
  • - 45 -
  • -
- `, - ); - }, - ); - - test.fixme( - 'Copy and paste of list items and paste back into list on an existing item', - async ({page, isPlainText, isCollab}) => { - test.skip(isPlainText); - - await focusEditor(page); - - await page.keyboard.type('- one'); - await page.keyboard.press('Enter'); - await page.keyboard.type('two'); - await page.keyboard.press('Enter'); - await page.keyboard.type('three'); - await page.keyboard.press('Enter'); - await page.keyboard.type('four'); - await page.keyboard.press('Enter'); - await page.keyboard.type('five'); - - await page.keyboard.press('ArrowUp'); - await page.keyboard.press('ArrowUp'); - - await moveToLineBeginning(page); - await page.keyboard.down('Shift'); - await page.keyboard.press('ArrowDown'); - await moveToLineEnd(page); - await page.keyboard.up('Shift'); - - await assertHTML( - page, - '
  • one
  • two
  • three
  • four
  • five
', - ); - await assertSelection(page, { - anchorOffset: 0, - anchorPath: [0, 2, 0, 0], - focusOffset: 4, - focusPath: [0, 3, 0, 0], - }); - - const clipboard = await copyToClipboard(page); - - await page.keyboard.press('ArrowRight'); - - await assertHTML( - page, - '
  • one
  • two
  • three
  • four
  • five
', - ); - await assertSelection(page, { - anchorOffset: 4, - anchorPath: [0, 3, 0, 0], - focusOffset: 4, - focusPath: [0, 3, 0, 0], - }); - - await pasteFromClipboard(page, clipboard); - - await assertHTML( - page, - '
  • one
  • two
  • three
  • fourthree
  • four
  • five
', - ); - await assertSelection(page, { - anchorOffset: 4, - anchorPath: [0, 4, 0, 0], - focusOffset: 4, - focusPath: [0, 4, 0, 0], - }); - }, - ); - - test.fixme( - 'Copy list of a different type and paste into list on an existing item - should merge the lists.', - async ({page, isPlainText, isCollab}) => { - test.skip(isPlainText); - - await focusEditor(page); - - await page.keyboard.type('- one'); - await page.keyboard.press('Enter'); - await page.keyboard.type('two'); - await page.keyboard.press('Tab'); - await page.keyboard.press('Enter'); - await page.keyboard.type('a'); - - await page.keyboard.press('Enter'); - await page.keyboard.press('Enter'); - await page.keyboard.press('Enter'); - await page.keyboard.press('Enter'); - - await page.keyboard.type('1. four'); - await page.keyboard.press('Enter'); - await page.keyboard.type('five'); - await page.keyboard.press('Tab'); - - await page.keyboard.press('ArrowUp'); - - await moveToLineBeginning(page); - await page.keyboard.down('Shift'); - await page.keyboard.press('ArrowDown'); - await page.keyboard.press('ArrowDown'); - await page.keyboard.up('Shift'); - - await assertHTML( - page, - html` -
    -
  • - one -
  • -
  • -
      -
    • - two -
    • -
    • - a -
    • -
    -
  • -
-


-
    -
  1. - four -
  2. -
  3. -
      -
    1. - five -
    2. -
    -
  4. -
- `, - ); - - const clipboard = await copyToClipboard(page); - - await page.keyboard.press('ArrowLeft'); - await page.keyboard.press('ArrowLeft'); - await page.keyboard.press('ArrowLeft'); - await page.keyboard.press('Backspace'); - - await pasteFromClipboard(page, clipboard); - - await assertHTML( - page, - html` -
    -
  • - one -
  • -
  • -
      -
    • - two -
    • -
    • - four -
    • -
    • -
        -
      1. - five -
      2. -
      -
    • -
    -
  • -
-


-
    -
  1. - four -
  2. -
  3. -
      -
    1. - five -
    2. -
    -
  4. -
- `, - ); - }, - ); - - test.fixme( - 'Copy and paste two paragraphs into list on an existing item', - async ({page, isPlainText}) => { - test.skip(isPlainText); - await focusEditor(page); - - await page.keyboard.type('Hello'); - await page.keyboard.press('Enter'); - await page.keyboard.type('World'); - - await selectAll(page); - - const clipboard = await copyToClipboard(page); - - await page.keyboard.press('Backspace'); - - await page.keyboard.type('- one'); - await page.keyboard.press('Enter'); - await page.keyboard.type('two'); - await page.keyboard.press('Enter'); - await page.keyboard.type('three'); - await page.keyboard.press('Enter'); - await page.keyboard.type('four'); - await page.keyboard.press('Enter'); - await page.keyboard.type('five'); - - await page.keyboard.press('ArrowUp'); - await page.keyboard.press('ArrowUp'); - - await moveToLineBeginning(page); - await page.keyboard.press('ArrowDown'); - await moveToLineEnd(page); - await moveLeft(page, 2); - - await assertHTML( - page, - '
  • one
  • two
  • three
  • four
  • five
', - ); - await assertSelection(page, { - anchorOffset: 2, - anchorPath: [0, 3, 0, 0], - focusOffset: 2, - focusPath: [0, 3, 0, 0], - }); - - await pasteFromClipboard(page, clipboard); - - await assertHTML( - page, - '
  • one
  • two
  • three
  • foHello

Worldur

  • five
', - ); - await assertSelection(page, { - anchorOffset: 5, - anchorPath: [1, 0, 0], - focusOffset: 5, - focusPath: [1, 0, 0], - }); - }, - ); - - test.fixme( - 'Copy and paste two paragraphs at the end of a list', - async ({page, isPlainText}) => { - test.skip(isPlainText); - - await focusEditor(page); - - await page.keyboard.type('Hello'); - await page.keyboard.press('Enter'); - await page.keyboard.type('World'); - - await selectAll(page); - - const clipboard = await copyToClipboard(page); - - await page.keyboard.press('Backspace'); - - await page.keyboard.type('- one'); - await page.keyboard.press('Enter'); - await page.keyboard.type('two'); - await page.keyboard.press('Enter'); - await page.keyboard.type('three'); - await page.keyboard.press('Enter'); - await page.keyboard.type('four'); - await page.keyboard.press('Enter'); - await page.keyboard.type('five'); - await page.keyboard.press('Enter'); - - await pasteFromClipboard(page, clipboard); - - await assertHTML( - page, - '
  • one
  • two
  • three
  • four
  • five
  • Hello

World

', - ); - await assertSelection(page, { - anchorOffset: 5, - anchorPath: [1, 0, 0], - focusOffset: 5, - focusPath: [1, 0, 0], - }); - - await pasteFromClipboard(page, clipboard); - - await assertHTML( - page, - '
  • one
  • two
  • three
  • four
  • five
  • Hello

WorldHello

World

', - ); - await assertSelection(page, { - anchorOffset: 5, - anchorPath: [2, 0, 0], - focusOffset: 5, - focusPath: [2, 0, 0], - }); - }, - ); - - test.fixme( - 'Copy and paste an inline element into a leaf node', - async ({page, isPlainText}) => { - test.skip(isPlainText); - await focusEditor(page); - - // Root - // |- Paragraph - // |- Link - // |- Text "Hello" - // |- Text "World" - await page.keyboard.type('Hello'); - await selectAll(page); - await click(page, '.link'); - await page.keyboard.press('ArrowRight'); - await page.keyboard.press('Space'); - await page.keyboard.type('World'); - - await selectAll(page); - - const clipboard = await copyToClipboard(page); - - await page.keyboard.press('ArrowRight'); - - await pasteFromClipboard(page, clipboard); - - await assertHTML( - page, - html` -

- - Hello - - World - - Hello - - World -

- `, - ); - }, - ); - - test('HTML Copy + paste a plain DOM text node', async ({ - page, - isPlainText, - }) => { - test.skip(isPlainText); - - await focusEditor(page); - - const clipboard = {'text/html': 'Hello!'}; - - await pasteFromClipboard(page, clipboard); - - await assertHTML( - page, - html` -

- Hello! -

- `, - ); - await assertSelection(page, { - anchorOffset: 6, - anchorPath: [0, 0, 0], - focusOffset: 6, - focusPath: [0, 0, 0], - }); - }); - - test('HTML Copy + paste a paragraph element', async ({page, isPlainText}) => { - test.skip(isPlainText); - - await focusEditor(page); - - const clipboard = {'text/html': '

Hello!

'}; - - await pasteFromClipboard(page, clipboard); - - await assertHTML( - page, - html` -

- Hello! -

-


- `, - ); - - await assertSelection(page, { - anchorOffset: 0, - anchorPath: [1], - focusOffset: 0, - focusPath: [1], - }); - }); - - test('HTML Copy + paste an anchor element', async ({page, isPlainText}) => { - test.skip(isPlainText); - - await focusEditor(page); - - const clipboard = { - 'text/html': 'Facebook!', - }; - - await pasteFromClipboard(page, clipboard); - - await assertHTML( - page, - html` -

- - Facebook! - -

- `, - ); - - await assertSelection(page, { - anchorOffset: 9, - anchorPath: [0, 0, 0, 0], - focusOffset: 9, - focusPath: [0, 0, 0, 0], - }); - - await selectAll(page); - - await click(page, '.link'); - - await assertHTML( - page, - html` -

- Facebook! -

- `, - ); - - await click(page, '.link'); - //await click(page, '.link-edit'); // link editor opens in edit mode by default so we don't need to click edit button - await focus(page, '.link-input'); - await page.keyboard.type('facebook.com'); - await page.keyboard.press('Enter'); - - await assertHTML( - page, - html` -

- - Facebook! - -

- `, - ); - }); - - test('HTML Copy + paste a list element', async ({page, isPlainText}) => { - test.skip(isPlainText); - - await focusEditor(page); - - const clipboard = {'text/html': '
  • Hello
  • world!
'}; - - await pasteFromClipboard(page, clipboard); - - await assertHTML( - page, - '
  • Hello
  • world!
', - ); - - await assertSelection(page, { - anchorOffset: 6, - anchorPath: [0, 1, 0, 0], - focusOffset: 6, - focusPath: [0, 1, 0, 0], - }); - - await selectFromAlignDropdown(page, '.indent'); - - await assertHTML( - page, - '
  • Hello
    • world!
', - ); - - await selectFromAlignDropdown(page, '.outdent'); - - await assertHTML( - page, - '
  • Hello
  • world!
', - ); - }); - - test('HTML Copy + paste a Lexical nested list', async ({ - page, - isPlainText, - }) => { - test.skip(isPlainText); - - await focusEditor(page); - - const clipboard = { - 'text/html': - '
  • Hello
    • awesome
  • world!
', - }; - - await pasteFromClipboard(page, clipboard); - - await assertHTML( - page, - '
  • Hello
    • awesome
  • world!
', - ); - }); - - test.fixme( - 'HTML Copy + paste (Nested List - directly nested ul)', - async ({page, isPlainText}) => { - test.skip(isPlainText); - - await focusEditor(page); - - const clipboard = { - 'text/html': '
    • Hello
  • world!
', - }; - - await pasteFromClipboard(page, clipboard); - - await assertHTML( - page, - html` -
    -
  • -
      -
    • - Hello -
    • -
    -
  • -
  • - world! -
  • -
- `, - ); - - await assertSelection(page, { - anchorOffset: 6, - anchorPath: [0, 1, 0, 0], - focusOffset: 6, - focusPath: [0, 1, 0, 0], - }); - - await selectFromAlignDropdown(page, '.indent'); - - await assertHTML( - page, - html` -
    -
  • -
      -
    • - Hello -
    • -
    • - world! -
    • -
    -
  • -
- `, - ); - - await page.keyboard.press('ArrowUp'); - - await selectFromAlignDropdown(page, '.outdent'); - - await assertHTML( - page, - html` -
    -
  • - Hello -
  • -
  • -
      -
    • - world! -
    • -
    -
  • -
- `, - ); - }, - ); - - test.fixme( - 'HTML Copy + paste (Nested List - li with non-list content plus ul child)', - async ({page, isPlainText}) => { - test.skip(isPlainText); - - await focusEditor(page); - - const clipboard = { - 'text/html': '
  • Hello
    • world!
', - }; - - await pasteFromClipboard(page, clipboard); - - await assertHTML( - page, - html` -
    -
  • - Hello -
  • -
  • -
      -
    • - world! -
    • -
    -
  • -
- `, - ); - - await assertSelection(page, { - anchorOffset: 6, - anchorPath: [0, 1, 0, 0, 0, 0], - focusOffset: 6, - focusPath: [0, 1, 0, 0, 0, 0], - }); - - await selectFromAlignDropdown(page, '.outdent'); - - await assertHTML( - page, - html` -
    -
  • - Hello -
  • -
  • - world! -
  • -
- `, - ); - - await page.keyboard.press('ArrowUp'); - - await selectFromAlignDropdown(page, '.indent'); - - await assertHTML( - page, - html` -
    -
  • -
      -
    • - Hello -
    • -
    -
  • -
  • - world! -
  • -
- `, - ); - }, - ); - - test.fixme( - 'HTML Copy + paste (Table - Google Docs)', - async ({page, isPlainText, isCollab}) => { - test.skip(isPlainText); - - test.fixme( - isCollab, - 'Table selection styles are not properly synced to the right hand frame', - ); - - await focusEditor(page); - - const clipboard = { - 'text/html': `

a

b

b

c

d

e

f

`, - }; - - await pasteFromClipboard(page, clipboard); - - await assertHTML( - page, - html` - - - - - - - - - - - -
-

- a -

-
-

- b -

-

- b -

-
-

- c -

-
-

- d -

-
-

- e -

-
-

- f -

-
- `, - ); - }, - ); - - test.fixme( - 'HTML Copy + paste (Table - Quip)', - async ({page, isPlainText}) => { - test.skip(isPlainText); - - await focusEditor(page); - - const clipboard = { - 'text/html': `
ab
b
c
def
`, - }; - - await pasteFromClipboard(page, clipboard); - - await assertHTML( - page, - html` - - - - - - - - - - - -
-

- a -

-
-

- b -

-

- b -

-
-

- c -

-
-

- d -

-
-

- e -

-
-

- f -

-
- `, - ); - }, - ); - - test.fixme( - 'HTML Copy + paste (Table - Google Sheets)', - async ({page, isPlainText}) => { - test.skip(isPlainText); - - await focusEditor(page); - - const clipboard = { - 'text/html': `
ab
b
c
def
`, - }; - - await pasteFromClipboard(page, clipboard); - - await assertHTML( - page, - html` - - - - - - - - - - - -
-

- a -

-
-

- b -

-

- b -

-
-

- c -

-
-

- d -

-
-

- e -

-
-

- f -

-
- `, - ); - }, - ); - - test.fixme( - 'Merge Grids on Copy + paste', - async ({page, isPlainText, isCollab}) => { - test.skip(isPlainText); - - await focusEditor(page); - await insertTable(page, 4, 4); - - const clipboard = { - 'text/html': `

a

b

c

d

`, - }; - - await selectCellsFromTableCords( - page, - {x: 0, y: 0}, - {x: 3, y: 3}, - true, - false, - ); - - await pasteFromClipboard(page, clipboard); - - await assertHTML( - page, - html` -


- - - - - - - - - - - - - - - - - - - - - - - - - -
-

- a -

-
-

- b -

-
-


-
-


-
-

- c -

-
-

- d -

-
-


-
-


-
-


-
-


-
-


-
-


-
-


-
-


-
-


-
-


-
-


- `, - html` -


- - - - - - - - - - - - - - - - - - - - - - - - - -
-

- a -

-
-

- b -

-
-


-
-


-
-

- c -

-
-

- d -

-
-


-
-


-
-


-
-


-
-


-
-


-
-


-
-


-
-


-
-


-
-


- `, - ); - }, - ); - - test('HTML Copy + paste multi line html with extra newlines', async ({ - page, - isPlainText, - isCollab, - }) => { - test.skip(isPlainText || isCollab); - - await focusEditor(page); - await pasteFromClipboard(page, { - 'text/html': - '

Hello\n

\n\n

\n\nWorld\n\n

\n\n

Hello\n\n World \n\n!\n\n

Hello World !

', - }); - - const paragraphs = page.locator('div[contenteditable="true"] > p'); - await expect(paragraphs).toHaveCount(4); - - // Explicitly checking inner text, since regular assertHTML will prettify it and strip all - // extra newlines, which makes this test less acurate - await expect(paragraphs.nth(0)).toHaveText('Hello', {useInnerText: true}); - await expect(paragraphs.nth(1)).toHaveText('World', {useInnerText: true}); - await expect(paragraphs.nth(2)).toHaveText('Hello World !', { - useInnerText: true, - }); - await expect(paragraphs.nth(3)).toHaveText('Hello World !', { - useInnerText: true, - }); - }); - - test.fixme( - 'HTML Copy + paste in front of or after a link', - async ({page, isPlainText}) => { - test.skip(isPlainText); - await focusEditor(page); - await pasteFromClipboard(page, { - 'text/html': `textlinktext`, - }); - await moveToEditorBeginning(page); - await pasteFromClipboard(page, { - 'text/html': 'before', - }); - await moveToEditorEnd(page); - await pasteFromClipboard(page, { - 'text/html': 'after', - }); - await assertHTML( - page, - html` -

- beforetext - - link - - textafter -

- `, - ); - }, - ); - - test.fixme( - 'HTML Copy + paste link by selecting its (partial) content', - async ({page, isPlainText}) => { - test.skip(isPlainText); - await focusEditor(page); - await pasteFromClipboard(page, { - 'text/html': `textlinktext`, - }); - await moveLeft(page, 5); - await page.keyboard.down('Shift'); - await moveLeft(page, 2); - await page.keyboard.up('Shift'); - const clipboard = await copyToClipboard(page); - await moveToEditorEnd(page); - await pasteFromClipboard(page, clipboard); - - await assertHTML( - page, - html` -

- text - - link - - text - - in - -

- `, - ); - }, - ); - - test.fixme( - 'Copy + paste multi-line plain text into rich text produces separate paragraphs', - async ({page, isPlainText}) => { - test.skip(isPlainText); - await focusEditor(page); - await page.keyboard.type('# Hello '); - await pasteFromClipboard(page, { - 'text/plain': 'world\nAnd text below', - }); - await assertHTML( - page, - html` -

- Hello world -

-

- And text below -

- `, - ); - }, - ); - - test('HTML Copy + paste html with BIU formatting', async ({ - page, - isPlainText, - }) => { - test.skip(isPlainText); - await focusEditor(page); - const clipboardData = { - 'text/html': `

Bold

Italic

underline

Bold Italic Underline


`, - }; - await pasteFromClipboard(page, clipboardData); - await assertHTML( - page, - html` -

- - Bold - -

-

- - Italic - -

-

- - underline - -

-

- - Bold Italic Underline - -

-

-
-

- `, - ); - }); - - test('HTML Copy + paste text with subscript and superscript', async ({ - page, - isPlainText, - }) => { - test.skip(isPlainText); - await focusEditor(page); - const clipboardData = { - 'text/html': - 'subscript and superscript', - }; - await pasteFromClipboard(page, clipboardData); - await assertHTML( - page, - html` -

- - subscript - - and - - - superscript - - -

- `, - ); - }); - - test('HTML Copy + paste a Title from Google Docs', async ({ - page, - isPlainText, - }) => { - test.skip(isPlainText); - - await focusEditor(page); - - const clipboard = { - 'text/html': `My document`, - }; - - await pasteFromClipboard(page, clipboard); - - await assertHTML( - page, - html` -

- My document -

- `, - ); - - await clearEditor(page); - await focusEditor(page); - - // These can sometimes be put onto the clipboard wrapped in a paragraph element - clipboard['text/html'] = - `

My document

`; - - await pasteFromClipboard(page, clipboard); - - await assertHTML( - page, - html` -

- My document -

- `, - ); - }); - - test('HTML Copy + paste a checklist', async ({page, isPlainText}) => { - test.skip(isPlainText); - - await focusEditor(page); - - const clipboard = { - 'text/html': `
`, - }; - - await pasteFromClipboard(page, clipboard); - - await assertHTML( - page, - html` -
    - - -
- `, - ); - - await clearEditor(page); - await focusEditor(page); - - // Ensure we preserve checked status. - clipboard['text/html'] = - `
`; - - await pasteFromClipboard(page, clipboard); - - await assertHTML( - page, - html` -
    - - -
- `, - ); - }); - - test.fixme( - 'HTML Copy + paste a code block with BR', - async ({page, isPlainText}) => { - test.skip(isPlainText); - - await focusEditor(page); - - const clipboard = { - 'text/html': `

Code block

function foo() {
return 'Hey there';
}

--end--

`, - }; - - await pasteFromClipboard(page, clipboard); - - await assertHTML( - page, - html` -

- Code block -

- - - function - - - - foo - - - ( - - - ) - - - - { - -
- - - return - - - - 'Hey there' - - - ; - -
- - } - -
-

- --end-- -

- `, - ); - }, - ); - - test.fixme( - 'HTML Copy + paste empty link #3193', - async ({page, isPlainText}) => { - test.skip(isPlainText); - - await focusEditor(page); - - const clipboard = { - // eslint-disable-next-line no-irregular-whitespace - 'text/html': `
Line 0
  • â..ï¸. Line 1 Some link.
  • â..ï¸. Line 2.
`, - }; - - await pasteFromClipboard(page, clipboard); - - await assertHTML( - page, - html` -

- Line 0 -

-
    -
  • - â..ï¸. Line 1  - - Some link - - . -
  • -
  • - â..ï¸. Line 2. -
  • -
- `, - ); - }, - ); - - test.fixme('HTML Paste a link into text', async ({page, isPlainText}) => { - test.skip(isPlainText); - - await focusEditor(page); - - await page.keyboard.type('A Lexical in the wild'); - await page.pause(); - await moveToLineBeginning(page); - await moveToNextWord(page); - await extendToNextWord(page); - - const clipboard = { - text: `https://lexical.dev`, - }; - - await pasteFromClipboard(page, clipboard); - - await assertHTML( - page, - html` -

- A - - Lexical - - in the wild -

- `, - ); - }); - - test.fixme('HTML Copy + paste an image', async ({page, isPlainText}) => { - test.skip(isPlainText); - - await focusEditor(page); - - const clipboard = { - 'playwright/base64': [LEXICAL_IMAGE_BASE64, 'image/png'], - }; - - await page.keyboard.type('An image'); - await moveLeft(page, 'image'.length); - await pasteFromClipboard(page, clipboard); - await page.keyboard.type(' inline '); - await sleepInsertImage(); - - await assertHTML( - page, - html` -

- An - -

- file -
- - inline image -

- `, - ); - }); - - test.fixme( - 'HTML Copy + paste + undo multiple image', - async ({page, isPlainText}) => { - test.skip(isPlainText); - - await focusEditor(page); - - const clipboard = { - 'playwright/base64_1': [LEXICAL_IMAGE_BASE64, 'image/png'], - 'playwright/base64_2': [LEXICAL_IMAGE_BASE64, 'image/png'], - }; - - await pasteFromClipboard(page, clipboard); - await sleepInsertImage(2); - - await assertHTML( - page, - html` -

- -

- file -
- - -
- file -
-
-
-

- `, - ); - - await undo(page); - await assertHTML( - page, - html` -


- `, - ); - }, - ); - - test.fixme( - 'HTML Copy + paste a paragraph element between horizontal rules', - async ({page, isPlainText, isCollab}) => { - test.skip(isPlainText); - - await focusEditor(page); - - let clipboard = {'text/html': '

'}; - - await pasteFromClipboard(page, clipboard); - // Collab doesn't process the cursor correctly - if (!isCollab) { - await assertHTML( - page, - html` -


-
-
-
- `, - ); - } - await click(page, 'hr:first-of-type'); - - // sets focus between HRs - await page.keyboard.press('ArrowRight'); - - clipboard = {'text/html': '

Text between HRs

'}; - - await pasteFromClipboard(page, clipboard); - await assertHTML( - page, - html` -


-
-

- Text between HRs -

-
- `, - ); - await assertSelection(page, { - anchorOffset: 16, - anchorPath: [2, 0, 0], - focusOffset: 16, - focusPath: [2, 0, 0], - }); - }, - ); - - test.fixme( - 'Paste top level element in the middle of paragraph', - async ({page, isPlainText, isCollab}) => { - test.skip(isPlainText || isCollab); - await focusEditor(page); - await page.keyboard.type('Hello world'); - await moveToPrevWord(page); - await pasteFromClipboard(page, { - 'text/html': `
`, - }); - - await assertHTML( - page, - html` -

- Hello -

-
-

- world -

- `, - ); - }, - ); - - test.fixme( - 'Paste top level element in the middle of list', - async ({page, isPlainText, isCollab}) => { - test.skip(isPlainText || isCollab); - await focusEditor(page); - // Add three list items - await page.keyboard.type('- one'); - await page.keyboard.press('Enter'); - await page.keyboard.type('two'); - await page.keyboard.press('Enter'); - await page.keyboard.type('three'); - await page.keyboard.press('Enter'); - await page.keyboard.type('four'); - - await page.keyboard.press('Enter'); - await page.keyboard.press('Enter'); - await page.keyboard.press('ArrowUp'); - await moveLeft(page, 4); - await page.keyboard.press('ArrowUp'); - await page.keyboard.press('ArrowUp'); - await pasteFromClipboard(page, { - 'text/html': `
`, - }); - - await assertHTML( - page, - html` -
    -
  • - one -
  • -
  • - two -
  • -
-
-
-
    -
  • - three -
  • -
  • - four -
  • -
-


- `, - ); - }, - ); -}); diff --git a/demos/playground/src/__tests__/e2e/CopyAndPaste/html/HTMLCopyAndPaste.spec.mjs b/demos/playground/src/__tests__/e2e/CopyAndPaste/html/HTMLCopyAndPaste.spec.mjs new file mode 100644 index 00000000..3b832edd --- /dev/null +++ b/demos/playground/src/__tests__/e2e/CopyAndPaste/html/HTMLCopyAndPaste.spec.mjs @@ -0,0 +1,278 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ +import {expect} from '@playwright/test'; + +import {moveToPrevWord} from '../../../keyboardShortcuts/index.mjs'; +import { + assertHTML, + assertSelection, + click, + focusEditor, + html, + initialize, + pasteFromClipboard, + test, +} from '../../../utils/index.mjs'; + +test.describe('HTML CopyAndPaste', () => { + test.beforeEach(({isCollab, page}) => initialize({isCollab, page})); + + test('Copy + paste a plain DOM text node', async ({page, isPlainText}) => { + test.skip(isPlainText); + + await focusEditor(page); + + const clipboard = {'text/html': 'Hello!'}; + + await pasteFromClipboard(page, clipboard); + + await assertHTML( + page, + html` +

+ Hello! +

+ `, + ); + await assertSelection(page, { + anchorOffset: 6, + anchorPath: [0, 0, 0], + focusOffset: 6, + focusPath: [0, 0, 0], + }); + }); + + test('Copy + paste a paragraph element', async ({page, isPlainText}) => { + test.skip(isPlainText); + + await focusEditor(page); + + const clipboard = {'text/html': '

Hello!

'}; + + await pasteFromClipboard(page, clipboard); + + await assertHTML( + page, + html` +

+ Hello! +

+


+ `, + ); + + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [1], + focusOffset: 0, + focusPath: [1], + }); + }); + + test('Copy + paste multi line html with extra newlines', async ({ + page, + isPlainText, + isCollab, + }) => { + test.skip(isPlainText || isCollab); + + await focusEditor(page); + await pasteFromClipboard(page, { + 'text/html': + '

Hello\n

\n\n

\n\nWorld\n\n

\n\n

Hello\n\n World \n\n!\n\n

Hello World !

', + }); + + const paragraphs = page.locator('div[contenteditable="true"] > p'); + await expect(paragraphs).toHaveCount(4); + + // Explicitly checking inner text, since regular assertHTML will prettify it and strip all + // extra newlines, which makes this test less acurate + await expect(paragraphs.nth(0)).toHaveText('Hello', {useInnerText: true}); + await expect(paragraphs.nth(1)).toHaveText('World', {useInnerText: true}); + await expect(paragraphs.nth(2)).toHaveText('Hello World !', { + useInnerText: true, + }); + await expect(paragraphs.nth(3)).toHaveText('Hello World !', { + useInnerText: true, + }); + }); + + test('Copy + paste a code block with BR', async ({page, isPlainText}) => { + test.skip(isPlainText); + + await focusEditor(page); + + const clipboard = { + 'text/html': `

Code block

function foo() {
return 'Hey there';
}

--end--

`, + }; + + await pasteFromClipboard(page, clipboard); + + await assertHTML( + page, + html` +

+ Code block +

+ + + function + + + + foo + + + ( + + + ) + + + + { + +
+ + return + + + + 'Hey there' + + + ; + +
+ + } + +
+

+ --end-- +

+ `, + ); + }); + + test('Copy + paste a paragraph element between horizontal rules', async ({ + page, + isPlainText, + isCollab, + }) => { + test.skip(isPlainText); + + await focusEditor(page); + + let clipboard = {'text/html': '

'}; + + await pasteFromClipboard(page, clipboard); + // Collab doesn't process the cursor correctly + if (!isCollab) { + await assertHTML( + page, + html` +
+
+
+ `, + ); + } + await click(page, 'hr:first-of-type'); + + // sets focus between HRs + await page.keyboard.press('ArrowRight'); + + clipboard = {'text/html': '

Text between HRs

'}; + + await pasteFromClipboard(page, clipboard); + await assertHTML( + page, + html` +
+

+ Text between HRs +

+
+ `, + ); + await assertSelection(page, { + anchorOffset: 16, + anchorPath: [1, 0, 0], + focusOffset: 16, + focusPath: [1, 0, 0], + }); + }); + + test('Paste top level element in the middle of paragraph', async ({ + page, + isPlainText, + isCollab, + }) => { + test.skip(isPlainText || isCollab); + await focusEditor(page); + await page.keyboard.type('Hello world'); + await moveToPrevWord(page); + await pasteFromClipboard(page, { + 'text/html': `
`, + }); + + await assertHTML( + page, + html` +

+ Hello +

+
+

+ world +

+ `, + ); + }); +}); diff --git a/demos/playground/src/__tests__/e2e/CopyAndPaste/html/ImageHTMLCopyAndPaste.spec.mjs b/demos/playground/src/__tests__/e2e/CopyAndPaste/html/ImageHTMLCopyAndPaste.spec.mjs new file mode 100644 index 00000000..dd120ef1 --- /dev/null +++ b/demos/playground/src/__tests__/e2e/CopyAndPaste/html/ImageHTMLCopyAndPaste.spec.mjs @@ -0,0 +1,118 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import {moveLeft, undo} from '../../../keyboardShortcuts/index.mjs'; +import { + assertHTML, + focusEditor, + html, + initialize, + LEXICAL_IMAGE_BASE64, + pasteFromClipboard, + sleepInsertImage, + test, +} from '../../../utils/index.mjs'; + +test.describe('HTML Image CopyAndPaste', () => { + test.beforeEach(({isCollab, page}) => initialize({isCollab, page})); + + test('Copy + paste an image', async ({page, isPlainText}) => { + test.skip(isPlainText); + + await focusEditor(page); + + const clipboard = { + 'playwright/base64': [LEXICAL_IMAGE_BASE64, 'image/png'], + }; + + await page.keyboard.type('An image'); + await moveLeft(page, 'image'.length); + await pasteFromClipboard(page, clipboard); + await sleepInsertImage(); + await page.keyboard.type(' inline '); + + await assertHTML( + page, + html` +

+ An + +

+ file +
+ + inline image +

+ `, + ); + }); + + test('Copy + paste + undo multiple image', async ({page, isPlainText}) => { + test.skip(isPlainText); + + await focusEditor(page); + + const clipboard = { + 'playwright/base64_1': [LEXICAL_IMAGE_BASE64, 'image/png'], + 'playwright/base64_2': [LEXICAL_IMAGE_BASE64, 'image/png'], + }; + + await pasteFromClipboard(page, clipboard); + await sleepInsertImage(2); + + await assertHTML( + page, + html` +

+ +

+ file +
+ + +
+ file +
+
+
+

+ `, + ); + + await undo(page); + await assertHTML( + page, + html` +


+ `, + ); + }); +}); diff --git a/demos/playground/src/__tests__/e2e/CopyAndPaste/html/LinksHTMLCopyAndPaste.spec.mjs b/demos/playground/src/__tests__/e2e/CopyAndPaste/html/LinksHTMLCopyAndPaste.spec.mjs new file mode 100644 index 00000000..2489924c --- /dev/null +++ b/demos/playground/src/__tests__/e2e/CopyAndPaste/html/LinksHTMLCopyAndPaste.spec.mjs @@ -0,0 +1,440 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import { + extendToNextWord, + moveLeft, + moveRight, + moveToEditorBeginning, + moveToEditorEnd, + moveToLineBeginning, + moveToNextWord, + selectAll, +} from '../../../keyboardShortcuts/index.mjs'; +import { + assertHTML, + assertSelection, + click, + copyToClipboard, + focusEditor, + html, + initialize, + pasteFromClipboard, + test, +} from '../../../utils/index.mjs'; + +test.describe('HTML Links CopyAndPaste', () => { + test.beforeEach(({isCollab, page}) => initialize({isCollab, page})); + + test('Copy + paste an anchor element', async ({page, isPlainText}) => { + test.skip(isPlainText); + + await focusEditor(page); + + const clipboard = { + 'text/html': 'Facebook!', + }; + + await pasteFromClipboard(page, clipboard); + await assertHTML( + page, + html` +

+ + Facebook! + +

+ `, + ); + + await assertSelection(page, { + anchorOffset: 9, + anchorPath: [0, 0, 0, 0], + focusOffset: 9, + focusPath: [0, 0, 0, 0], + }); + + await selectAll(page); + + // unlink + await click(page, '.link'); + + await assertHTML( + page, + html` +

+ Facebook! +

+ `, + ); + + await click(page, '.link'); + await page.keyboard.type('facebook.com'); + await click(page, '.link-confirm'); + + await assertHTML( + page, + html` +

+ + Facebook! + +

+ `, + ); + }); + + test('Copy + paste in front of or after a link', async ({ + page, + isPlainText, + }) => { + test.skip(isPlainText); + await focusEditor(page); + await pasteFromClipboard(page, { + 'text/html': `textlinktext`, + }); + await moveToEditorBeginning(page); + await pasteFromClipboard(page, { + 'text/html': 'before', + }); + await moveToEditorEnd(page); + await pasteFromClipboard(page, { + 'text/html': 'after', + }); + await assertHTML( + page, + html` +

+ beforetext + + link + + textafter +

+ `, + ); + }); + + test('Copy + paste link by selecting its (partial) content', async ({ + page, + isPlainText, + }) => { + test.skip(isPlainText); + await focusEditor(page); + await pasteFromClipboard(page, { + 'text/html': `textlinktext`, + }); + await moveLeft(page, 5); + await page.keyboard.down('Shift'); + await moveLeft(page, 2); + await page.keyboard.up('Shift'); + const clipboard = await copyToClipboard(page); + await moveToEditorEnd(page); + await pasteFromClipboard(page, clipboard); + + await assertHTML( + page, + html` +

+ text + + link + + text + + in + +

+ `, + ); + }); + + test('Copy + paste empty link #3193', async ({page, isPlainText}) => { + test.skip(isPlainText); + + await focusEditor(page); + + const clipboard = { + // eslint-disable-next-line no-irregular-whitespace + 'text/html': `
Line 0
  • â..ï¸. Line 1 Some link.
  • â..ï¸. Line 2.
`, + }; + + await pasteFromClipboard(page, clipboard); + + await assertHTML( + page, + html` +

+ Line 0 +

+
    +
  • + â..ï¸. Line 1  + + Some link + + . +
  • +
  • + â..ï¸. Line 2. +
  • +
+ `, + ); + }); + + test('Paste a link into text', async ({page, isPlainText}) => { + test.skip(isPlainText); + + await focusEditor(page); + + await page.keyboard.type('A Lexical in the wild'); + await page.pause(); + await moveToLineBeginning(page); + await moveToNextWord(page); + await extendToNextWord(page); + + const clipboard = { + text: `https://lexical.dev`, + }; + + await pasteFromClipboard(page, clipboard); + + await assertHTML( + page, + html` +

+ A + + Lexical + + in the wild +

+ `, + ); + }); + + test('Paste text into a link', async ({page, isPlainText}) => { + test.skip(isPlainText); + await focusEditor(page); + + await page.keyboard.type('Link text'); + await selectAll(page); + await click(page, '.link'); + await click(page, '.link-confirm'); + await moveRight(page, 1); + await moveLeft(page, 4); + + await pasteFromClipboard(page, { + 'text/html': 'normal text', + }); + + await assertHTML( + page, + html` +

+ + Link + + normal text + + text + +

+ `, + ); + }); + + test('Paste formatted text into a link', async ({page, isPlainText}) => { + test.skip(isPlainText); + await focusEditor(page); + + await page.keyboard.type('Link text'); + await selectAll(page); + await click(page, '.link'); + await click(page, '.link-confirm'); + await moveRight(page, 1); + await moveLeft(page, 4); + + await pasteFromClipboard(page, { + 'text/html': 'bold text', + }); + + await assertHTML( + page, + html` +

+ + Link + + + bold + + text + + text + +

+ `, + ); + }); + + test('Paste a link into a link', async ({page, isPlainText}) => { + test.skip(isPlainText); + await focusEditor(page); + + await page.keyboard.type('Link text'); + await selectAll(page); + await click(page, '.link'); + await click(page, '.link-confirm'); + await moveRight(page, 1); + await moveLeft(page, 4); + + await pasteFromClipboard(page, { + 'text/html': 'text with link', + }); + + await assertHTML( + page, + html` +

+ + Link + + text with + + link + + + text + +

+ `, + ); + }); + + test('Paste multiple blocks into a link', async ({page, isPlainText}) => { + test.skip(isPlainText); + await focusEditor(page); + + await page.keyboard.type('Link text'); + await selectAll(page); + await click(page, '.link'); + await click(page, '.link-confirm'); + await moveRight(page, 1); + await moveLeft(page, 4); + + await pasteFromClipboard(page, { + 'text/html': '

para 1

para 2

', + }); + + await assertHTML( + page, + html` +

+ + Link + + para 1 +

+

+ para 2 + + text + +

+ `, + ); + }); +}); diff --git a/demos/playground/src/__tests__/e2e/CopyAndPaste/html/ListsHTMLCopyAndPaste.spec.mjs b/demos/playground/src/__tests__/e2e/CopyAndPaste/html/ListsHTMLCopyAndPaste.spec.mjs new file mode 100644 index 00000000..33bf100a --- /dev/null +++ b/demos/playground/src/__tests__/e2e/CopyAndPaste/html/ListsHTMLCopyAndPaste.spec.mjs @@ -0,0 +1,419 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import { + assertHTML, + assertSelection, + clearEditor, + focusEditor, + html, + initialize, + pasteFromClipboard, + selectFromAlignDropdown, + test, +} from '../../../utils/index.mjs'; + +test.describe('HTML Lists CopyAndPaste', () => { + test.beforeEach(({isCollab, page}) => initialize({isCollab, page})); + + test('Copy + paste a list element', async ({page, isPlainText}) => { + test.skip(isPlainText); + + await focusEditor(page); + + const clipboard = {'text/html': '
  • Hello
  • world!
'}; + + await pasteFromClipboard(page, clipboard); + + await assertHTML( + page, + '
  • Hello
  • world!
', + ); + + await assertSelection(page, { + anchorOffset: 6, + anchorPath: [0, 1, 0, 0], + focusOffset: 6, + focusPath: [0, 1, 0, 0], + }); + + await selectFromAlignDropdown(page, '.indent'); + + await assertHTML( + page, + '
  • Hello
    • world!
', + ); + + await selectFromAlignDropdown(page, '.outdent'); + + await assertHTML( + page, + '
  • Hello
  • world!
', + ); + }); + + test('Copy + paste a Lexical nested list', async ({page, isPlainText}) => { + test.skip(isPlainText); + + await focusEditor(page); + + const clipboard = { + 'text/html': + '
  • Hello
    • awesome
  • world!
', + }; + + await pasteFromClipboard(page, clipboard); + + await assertHTML( + page, + '
  • Hello
    • awesome
  • world!
', + ); + }); + + test('Copy + paste (Nested List - directly nested ul)', async ({ + page, + isPlainText, + }) => { + test.skip(isPlainText); + + await focusEditor(page); + + const clipboard = { + 'text/html': '
    • Hello
  • world!
', + }; + + await pasteFromClipboard(page, clipboard); + + await assertHTML( + page, + html` +
    +
  • +
      +
    • + Hello +
    • +
    +
  • +
  • + world! +
  • +
+ `, + ); + + await assertSelection(page, { + anchorOffset: 6, + anchorPath: [0, 1, 0, 0], + focusOffset: 6, + focusPath: [0, 1, 0, 0], + }); + + await selectFromAlignDropdown(page, '.indent'); + + await assertHTML( + page, + html` +
    +
  • +
      +
    • + Hello +
    • +
    • + world! +
    • +
    +
  • +
+ `, + ); + + await page.keyboard.press('ArrowUp'); + + await selectFromAlignDropdown(page, '.outdent'); + + await assertHTML( + page, + html` +
    +
  • + Hello +
  • +
  • +
      +
    • + world! +
    • +
    +
  • +
+ `, + ); + }); + + test('Copy + paste (Nested List - li with non-list content plus ul child)', async ({ + page, + isPlainText, + }) => { + test.skip(isPlainText); + + await focusEditor(page); + + const clipboard = { + 'text/html': '
  • Hello
    • world!
', + }; + + await pasteFromClipboard(page, clipboard); + + await assertHTML( + page, + html` +
    +
  • + Hello +
  • +
  • +
      +
    • + world! +
    • +
    +
  • +
+ `, + ); + + await assertSelection(page, { + anchorOffset: 6, + anchorPath: [0, 1, 0, 0, 0, 0], + focusOffset: 6, + focusPath: [0, 1, 0, 0, 0, 0], + }); + + await selectFromAlignDropdown(page, '.outdent'); + + await assertHTML( + page, + html` +
    +
  • + Hello +
  • +
  • + world! +
  • +
+ `, + ); + + await page.keyboard.press('ArrowUp'); + + await selectFromAlignDropdown(page, '.indent'); + + await assertHTML( + page, + html` +
    +
  • +
      +
    • + Hello +
    • +
    +
  • +
  • + world! +
  • +
+ `, + ); + }); + + test('Copy + paste a checklist', async ({page, isPlainText}) => { + test.skip(isPlainText); + + await focusEditor(page); + + const clipboard = { + 'text/html': `
`, + }; + + await pasteFromClipboard(page, clipboard); + + await assertHTML( + page, + html` +
    + + +
+ `, + ); + + await clearEditor(page); + await focusEditor(page); + + // Ensure we preserve checked status. + clipboard[ + 'text/html' + ] = `
`; + + await pasteFromClipboard(page, clipboard); + + await assertHTML( + page, + html` +
    + + +
+ `, + ); + }); + + test('Paste top level element in the middle of list', async ({ + page, + isPlainText, + isCollab, + }) => { + test.skip(isPlainText || isCollab); + await focusEditor(page); + // Add three list items + await page.keyboard.type('- one'); + await page.keyboard.press('Enter'); + await page.keyboard.type('two'); + await page.keyboard.press('Enter'); + await page.keyboard.type('three'); + await page.keyboard.press('Enter'); + await page.keyboard.type('four'); + + await page.keyboard.press('Enter'); + await page.keyboard.press('Enter'); + await page.keyboard.press('ArrowUp'); + await page.keyboard.press('ArrowUp'); + await page.keyboard.press('ArrowUp'); + await pasteFromClipboard(page, { + 'text/html': `
`, + }); + + await assertHTML( + page, + html` +
    +
  • + one +
  • +
+
+
    +
  • + two +
  • +
  • + three +
  • +
  • + four +
  • +
+


+ `, + ); + }); +}); diff --git a/demos/playground/src/__tests__/e2e/CopyAndPaste/html/TablesHTMLCopyAndPaste.spec.mjs b/demos/playground/src/__tests__/e2e/CopyAndPaste/html/TablesHTMLCopyAndPaste.spec.mjs new file mode 100644 index 00000000..6ef1a126 --- /dev/null +++ b/demos/playground/src/__tests__/e2e/CopyAndPaste/html/TablesHTMLCopyAndPaste.spec.mjs @@ -0,0 +1,470 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ +import { + assertHTML, + focusEditor, + html, + initialize, + insertTable, + pasteFromClipboard, + selectCellsFromTableCords, + test, +} from '../../../utils/index.mjs'; + +test.describe('HTML Tables CopyAndPaste', () => { + test.beforeEach(({isCollab, page}) => initialize({isCollab, page})); + + test('Copy + paste (Table - Google Docs)', async ({ + page, + isPlainText, + isCollab, + }) => { + test.skip(isPlainText); + + test.fixme( + isCollab, + 'Table selection styles are not properly synced to the right hand frame', + ); + + await focusEditor(page); + + const clipboard = { + 'text/html': `

a

b

b

c

d

e

f

`, + }; + + await pasteFromClipboard(page, clipboard); + + await assertHTML( + page, + html` + + + + + + + + + + + +
+

+ a +

+
+

+ b +

+

+ b +

+
+

+ c +

+
+

+ d +

+
+

+ e +

+
+

+ f +

+
+ `, + ); + }); + + test('Copy + paste (Table - Quip)', async ({page, isPlainText}) => { + test.skip(isPlainText); + + await focusEditor(page); + + const clipboard = { + 'text/html': `
ab
b
c
def
`, + }; + await pasteFromClipboard(page, clipboard); + + await assertHTML( + page, + html` + + + + + + + + + + + +
+

+ a +

+
+

+ b +

+

+ b +

+
+

+ c +

+
+

+ d +

+
+

+ e +

+
+

+ f +

+
+ `, + ); + }); + + test('Copy + paste (Table - Google Sheets)', async ({page, isPlainText}) => { + test.skip(isPlainText); + + await focusEditor(page); + + const clipboard = { + 'text/html': `
ab
b
c
def
`, + }; + + await pasteFromClipboard(page, clipboard); + + await assertHTML( + page, + html` + + + + + + + + + + + +
+

+ a +

+
+

+ b +

+

+ b +

+
+

+ c +

+
+

+ d +

+
+

+ e +

+
+

+ f +

+
+ `, + ); + }); + + test.fixme( + 'Copy + paste - Merge Grids', + async ({page, isPlainText, isCollab}) => { + test.skip(isPlainText); + test.fixme( + isCollab, + 'Table selection styles are not properly selected/deselected', + ); + + await focusEditor(page); + await insertTable(page, 4, 4); + + const clipboard = { + 'text/html': `

a

b

c

d

`, + }; + + await selectCellsFromTableCords( + page, + {x: 0, y: 0}, + {x: 3, y: 3}, + true, + false, + ); + + await pasteFromClipboard(page, clipboard); + + await assertHTML( + page, + html` +


+ + + + + + + + + + + + + + + + + + + + + + + + + +
+

+ a +

+
+

+ b +

+
+


+
+


+
+

+ c +

+
+

+ d +

+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+ `, + html` +


+ + + + + + + + + + + + + + + + + + + + + + + + + +
+

+ a +

+
+

+ b +

+
+


+
+


+
+

+ c +

+
+

+ d +

+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+ `, + ); + }, + ); +}); diff --git a/demos/playground/src/__tests__/e2e/CopyAndPaste/html/TextFormatHTMLCopyAndPaste.spec.mjs b/demos/playground/src/__tests__/e2e/CopyAndPaste/html/TextFormatHTMLCopyAndPaste.spec.mjs new file mode 100644 index 00000000..a53b7144 --- /dev/null +++ b/demos/playground/src/__tests__/e2e/CopyAndPaste/html/TextFormatHTMLCopyAndPaste.spec.mjs @@ -0,0 +1,149 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ +import { + assertHTML, + clearEditor, + focusEditor, + html, + initialize, + pasteFromClipboard, + test, +} from '../../../utils/index.mjs'; + +test.describe('HTML CopyAndPaste', () => { + test.beforeEach(({isCollab, page}) => initialize({isCollab, page})); + + test('Copy + paste html with BIU formatting', async ({page, isPlainText}) => { + test.skip(isPlainText); + await focusEditor(page); + const clipboardData = { + 'text/html': `

Bold

Italic

underline

Bold Italic Underline


`, + }; + await pasteFromClipboard(page, clipboardData); + await assertHTML( + page, + html` +

+ + Bold + +

+

+ + Italic + +

+

+ + underline + +

+

+ + Bold Italic Underline + +

+

+
+

+ `, + ); + }); + + test('Copy + paste text with subscript and superscript', async ({ + page, + isPlainText, + }) => { + test.skip(isPlainText); + await focusEditor(page); + const clipboardData = { + 'text/html': + 'subscript and superscript', + }; + await pasteFromClipboard(page, clipboardData); + await assertHTML( + page, + html` +

+ + subscript + + and + + + superscript + + +

+ `, + ); + }); + + test('Copy + paste a Title from Google Docs', async ({page, isPlainText}) => { + test.skip(isPlainText); + + await focusEditor(page); + + const clipboard = { + 'text/html': `My document`, + }; + + await pasteFromClipboard(page, clipboard); + + await assertHTML( + page, + html` +

+ My document +

+ `, + ); + + await clearEditor(page); + await focusEditor(page); + + // These can sometimes be put onto the clipboard wrapped in a paragraph element + clipboard[ + 'text/html' + ] = `

My document

`; + + await pasteFromClipboard(page, clipboard); + + await assertHTML( + page, + html` +

+ My document +

+ `, + ); + }); +}); diff --git a/demos/playground/src/__tests__/e2e/CopyAndPaste/lexical/CopyAndPaste.spec.mjs b/demos/playground/src/__tests__/e2e/CopyAndPaste/lexical/CopyAndPaste.spec.mjs new file mode 100644 index 00000000..bbda6085 --- /dev/null +++ b/demos/playground/src/__tests__/e2e/CopyAndPaste/lexical/CopyAndPaste.spec.mjs @@ -0,0 +1,913 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ +import { + moveToEditorBeginning, + moveToEditorEnd, + moveToLineEnd, + moveToPrevWord, + selectAll, +} from '../../../keyboardShortcuts/index.mjs'; +import { + assertHTML, + assertSelection, + click, + copyToClipboard, + focusEditor, + html, + initialize, + insertYouTubeEmbed, + IS_LINUX, + pasteFromClipboard, + test, + YOUTUBE_SAMPLE_URL, +} from '../../../utils/index.mjs'; + +test.describe('CopyAndPaste', () => { + test.beforeEach(({isCollab, page}) => initialize({isCollab, page})); + test('Basic copy + paste', async ({isRichText, page, browserName}) => { + await focusEditor(page); + + // Add paragraph + await page.keyboard.type('Copy + pasting?'); + await page.keyboard.press('Enter'); + await page.keyboard.press('Enter'); + await page.keyboard.type('Sounds good!'); + if (isRichText) { + await assertHTML( + page, + html` +

+ Copy + pasting? +

+


+

+ Sounds good! +

+ `, + ); + await assertSelection(page, { + anchorOffset: 12, + anchorPath: [2, 0, 0], + focusOffset: 12, + focusPath: [2, 0, 0], + }); + } else { + await assertHTML( + page, + html` +

+ Copy + pasting? +
+
+ Sounds good! +

+ `, + ); + await assertSelection(page, { + anchorOffset: 12, + anchorPath: [0, 3, 0], + focusOffset: 12, + focusPath: [0, 3, 0], + }); + } + + // Select all the text + await selectAll(page); + if (isRichText) { + await assertHTML( + page, + html` +

+ Copy + pasting? +

+


+

+ Sounds good! +

+ `, + ); + if (browserName === 'firefox') { + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [], + focusOffset: 3, + focusPath: [], + }); + } else { + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [0, 0, 0], + focusOffset: 12, + focusPath: [2, 0, 0], + }); + } + } else { + await assertHTML( + page, + html` +

+ Copy + pasting? +
+
+ Sounds good! +

+ `, + ); + if (browserName === 'firefox') { + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [], + focusOffset: 1, + focusPath: [], + }); + } else { + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [0, 0, 0], + focusOffset: 12, + focusPath: [0, 3, 0], + }); + } + } + + // Copy all the text + const clipboard = await copyToClipboard(page); + if (isRichText) { + await assertHTML( + page, + html` +

+ Copy + pasting? +

+


+

+ Sounds good! +

+ `, + ); + } else { + await assertHTML( + page, + html` +

+ Copy + pasting? +
+
+ Sounds good! +

+ `, + ); + } + + // Paste after + await page.keyboard.press('ArrowRight'); + await pasteFromClipboard(page, clipboard); + if (isRichText) { + await assertHTML( + page, + html` +

+ Copy + pasting? +

+


+

+ Sounds good!Copy + pasting? +

+


+

+ Sounds good! +

+ `, + ); + await assertSelection(page, { + anchorOffset: 12, + anchorPath: [4, 0, 0], + focusOffset: 12, + focusPath: [4, 0, 0], + }); + } else { + await assertHTML( + page, + html` +

+ Copy + pasting? +
+
+ Sounds good!Copy + pasting? +
+
+ Sounds good! +

+ `, + ); + await assertSelection(page, { + anchorOffset: 12, + anchorPath: [0, 6, 0], + focusOffset: 12, + focusPath: [0, 6, 0], + }); + } + }); + + test(`Copy and paste heading`, async ({ + isPlainText, + isCollab, + page, + browserName, + }) => { + test.fixme(isCollab && IS_LINUX, 'Flaky on Linux + Collab'); + test.skip(isPlainText); + + await focusEditor(page); + await page.keyboard.type('# Heading'); + await page.keyboard.press('Enter'); + await page.keyboard.type('Some text'); + + await moveToEditorBeginning(page); + await page.keyboard.down('Shift'); + await moveToLineEnd(page); + await page.keyboard.up('Shift'); + + const clipboard = await copyToClipboard(page); + + await moveToEditorEnd(page); + await page.keyboard.press('Enter'); + + // Paste the content + await pasteFromClipboard(page, clipboard); + + await assertHTML( + page, + html` +

+ Heading +

+

+ Some text +

+

+ Heading +

+ `, + ); + + await assertSelection(page, { + anchorOffset: 7, + anchorPath: [2, 0, 0], + focusOffset: 7, + focusPath: [2, 0, 0], + }); + }); + + test(`Copy and paste between sections`, async ({ + isRichText, + page, + browserName, + }) => { + await focusEditor(page); + await page.keyboard.type('Hello world #foobar test #foobar2 when #not'); + + await page.keyboard.press('Enter'); + await page.keyboard.type('Next #line of #text test #foo'); + + if (isRichText) { + await assertHTML( + page, + html` +

+ Hello world + + #foobar + + test + + #foobar2 + + when + + #not + +

+

+ Next + + #line + + of + + #text + + test + + #foo + +

+ `, + ); + + await assertSelection(page, { + anchorOffset: 4, + anchorPath: [1, 5, 0], + focusOffset: 4, + focusPath: [1, 5, 0], + }); + } else { + await assertHTML( + page, + html` +

+ Hello world + + #foobar + + test + + #foobar2 + + when + + #not + +
+ Next + + #line + + of + + #text + + test + + #foo + +

+ `, + ); + await assertSelection(page, { + anchorOffset: 4, + anchorPath: [0, 12, 0], + focusOffset: 4, + focusPath: [0, 12, 0], + }); + } + + // Select all the content + await selectAll(page); + + if (isRichText) { + if (browserName === 'firefox') { + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [], + focusOffset: 2, + focusPath: [], + }); + } else { + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [0, 0, 0], + focusOffset: 4, + focusPath: [1, 5, 0], + }); + } + } else { + if (browserName === 'firefox') { + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [], + focusOffset: 1, + focusPath: [], + }); + } else { + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [0, 0, 0], + focusOffset: 4, + focusPath: [0, 12, 0], + }); + } + } + + // Copy all the text + let clipboard = await copyToClipboard(page); + await page.keyboard.press('Delete'); + // Paste the content + await pasteFromClipboard(page, clipboard); + + if (isRichText) { + await assertHTML( + page, + html` +

+ Hello world + + #foobar + + test + + #foobar2 + + when + + #not + +

+

+ Next + + #line + + of + + #text + + test + + #foo + +

+ `, + ); + await assertSelection(page, { + anchorOffset: 4, + anchorPath: [1, 5, 0], + focusOffset: 4, + focusPath: [1, 5, 0], + }); + } else { + await assertHTML( + page, + html` +

+ Hello world + + #foobar + + test + + #foobar2 + + when + + #not + +
+ Next + + #line + + of + + #text + + test + + #foo + +

+ `, + ); + await assertSelection(page, { + anchorOffset: 4, + anchorPath: [0, 12, 0], + focusOffset: 4, + focusPath: [0, 12, 0], + }); + } + + await moveToPrevWord(page); + await page.keyboard.down('Shift'); + await page.keyboard.press('ArrowUp'); + await moveToPrevWord(page); + // Once more for linux on Chromium + if (IS_LINUX && browserName === 'chromium') { + await moveToPrevWord(page); + } + await page.keyboard.up('Shift'); + + if (isRichText) { + await assertSelection(page, { + anchorOffset: 1, + anchorPath: [1, 5, 0], + focusOffset: 1, + focusPath: [0, 2, 0], + }); + } else { + await assertSelection(page, { + anchorOffset: 1, + anchorPath: [0, 12, 0], + focusOffset: 1, + focusPath: [0, 2, 0], + }); + } + + // Copy selected text + clipboard = await copyToClipboard(page); + await page.keyboard.press('Delete'); + // Paste the content + await pasteFromClipboard(page, clipboard); + + if (isRichText) { + await assertHTML( + page, + html` +

+ Hello world + + #foobar + + test + + #foobar2 + + when + + #not + +

+

+ Next + + #line + + of + + #text + + test + + #foo + +

+ `, + ); + await assertSelection(page, { + anchorOffset: 1, + anchorPath: [1, 5, 0], + focusOffset: 1, + focusPath: [1, 5, 0], + }); + } else { + await assertHTML( + page, + html` +

+ Hello world + + #foobar + + test + + #foobar2 + + when + + #not + +
+ Next + + #line + + of + + #text + + test + + #foo + +

+ `, + ); + await assertSelection(page, { + anchorOffset: 1, + anchorPath: [0, 12, 0], + focusOffset: 1, + focusPath: [0, 12, 0], + }); + } + + // Select all the content + await selectAll(page); + + if (isRichText) { + if (browserName === 'firefox') { + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [], + focusOffset: 2, + focusPath: [], + }); + } else { + if (browserName === 'firefox') { + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [0, 0, 0], + focusOffset: 3, + focusPath: [1, 5, 0], + }); + } else { + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [0, 0, 0], + focusOffset: 4, + focusPath: [1, 5, 0], + }); + } + } + } else { + if (browserName === 'firefox') { + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [], + focusOffset: 1, + focusPath: [], + }); + } else { + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [0, 0, 0], + focusOffset: 4, + focusPath: [0, 12, 0], + }); + } + } + + await page.keyboard.press('Delete'); + await assertHTML( + page, + html` +


+ `, + ); + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [0], + focusOffset: 0, + focusPath: [0], + }); + }); + + test('Copy and paste an inline element into a leaf node', async ({ + page, + isPlainText, + }) => { + test.skip(isPlainText); + await focusEditor(page); + + // Root + // |- Paragraph + // |- Link + // |- Text "Hello" + // |- Text "World" + await page.keyboard.type('Hello'); + await selectAll(page); + await click(page, '.link'); + await click(page, '.link-confirm'); + await page.keyboard.press('ArrowRight'); + await page.keyboard.press('Space'); + await page.keyboard.type('World'); + + await selectAll(page); + + const clipboard = await copyToClipboard(page); + + await page.keyboard.press('ArrowRight'); + + await pasteFromClipboard(page, clipboard); + + await assertHTML( + page, + html` +

+ + Hello + + World + + Hello + + World +

+ `, + ); + }); + + test('Copy + paste multi-line plain text into rich text produces separate paragraphs', async ({ + page, + isPlainText, + }) => { + test.skip(isPlainText); + await focusEditor(page); + await page.keyboard.type('# Hello '); + await pasteFromClipboard(page, { + 'text/plain': 'world\nAnd text below', + }); + await assertHTML( + page, + html` +

+ Hello world +

+

+ And text below +

+ `, + ); + }); + + test('Pasting a decorator node on a blank line inserts before the line', async ({ + page, + isCollab, + isPlainText, + }) => { + test.fixme(); // TODO: flaky + test.skip(isPlainText); + + // copying and pasting the node is easier than creating the clipboard data + await focusEditor(page); + await insertYouTubeEmbed(page, YOUTUBE_SAMPLE_URL); + await page.keyboard.press('ArrowLeft'); // this selects the node + const clipboard = await copyToClipboard(page); + await page.keyboard.press('ArrowRight'); // this moves to a new line (empty paragraph node) + await pasteFromClipboard(page, clipboard); + + await assertHTML( + page, + html` +


+
+
+ +
+
+
+
+ +
+
+
+ `, + ); + }); +}); diff --git a/demos/playground/src/__tests__/e2e/CopyAndPaste/lexical/ListsCopyAndPaste.spec.mjs b/demos/playground/src/__tests__/e2e/CopyAndPaste/lexical/ListsCopyAndPaste.spec.mjs new file mode 100644 index 00000000..1b2dcd6e --- /dev/null +++ b/demos/playground/src/__tests__/e2e/CopyAndPaste/lexical/ListsCopyAndPaste.spec.mjs @@ -0,0 +1,732 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ +import { + moveLeft, + moveToLineBeginning, + moveToLineEnd, + selectAll, + selectCharacters, +} from '../../../keyboardShortcuts/index.mjs'; +import { + assertHTML, + assertSelection, + copyToClipboard, + focusEditor, + html, + initialize, + IS_LINUX, + IS_WINDOWS, + pasteFromClipboard, + test, +} from '../../../utils/index.mjs'; + +test.describe('Lists CopyAndPaste', () => { + test.beforeEach(({isCollab, page}) => initialize({isCollab, page})); + + test('Copy and paste of partial list items into an empty editor', async ({ + page, + isPlainText, + }) => { + test.skip(isPlainText); + await focusEditor(page); + + // Add three list items + await page.keyboard.type('- one'); + await page.keyboard.press('Enter'); + await page.keyboard.type('two'); + await page.keyboard.press('Enter'); + await page.keyboard.type('three'); + + await page.keyboard.press('Enter'); + await page.keyboard.press('Enter'); + + // Add a paragraph + await page.keyboard.type('Some text.'); + + await assertHTML( + page, + '
  • one
  • two
  • three

Some text.

', + ); + await assertSelection(page, { + anchorOffset: 10, + anchorPath: [1, 0, 0], + focusOffset: 10, + focusPath: [1, 0, 0], + }); + + await page.keyboard.down('Shift'); + await moveToLineBeginning(page); + await moveLeft(page, 3); + await page.keyboard.up('Shift'); + + await assertSelection(page, { + anchorOffset: 10, + anchorPath: [1, 0, 0], + focusOffset: 3, + focusPath: [0, 2, 0, 0], + }); + + // Copy the partial list item and paragraph + const clipboard = await copyToClipboard(page); + + // Select all and remove content + await selectAll(page); + await page.keyboard.press('Backspace'); + await page.keyboard.press('Backspace'); + + await assertHTML( + page, + html` +


+ `, + ); + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [0], + focusOffset: 0, + focusPath: [0], + }); + + // Paste + + await pasteFromClipboard(page, clipboard); + + await assertHTML( + page, + '
  • ee

Some text.

', + ); + await assertSelection(page, { + anchorOffset: 10, + anchorPath: [1, 0, 0], + focusOffset: 10, + focusPath: [1, 0, 0], + }); + }); + + test('Copy and paste of partial list items into the list', async ({ + page, + isPlainText, + isCollab, + browserName, + }) => { + test.skip(isPlainText); + + await focusEditor(page); + + // Add three list items + await page.keyboard.type('- one'); + await page.keyboard.press('Enter'); + await page.keyboard.type('two'); + await page.keyboard.press('Enter'); + await page.keyboard.type('three'); + + await page.keyboard.press('Enter'); + await page.keyboard.press('Enter'); + + // Add a paragraph + await page.keyboard.type('Some text.'); + + await assertHTML( + page, + html` +
    +
  • + one +
  • +
  • + two +
  • +
  • + three +
  • +
+

+ Some text. +

+ `, + ); + await assertSelection(page, { + anchorOffset: 10, + anchorPath: [1, 0, 0], + focusOffset: 10, + focusPath: [1, 0, 0], + }); + + await page.keyboard.down('Shift'); + await moveToLineBeginning(page); + await moveLeft(page, 3); + await page.keyboard.up('Shift'); + + await assertSelection(page, { + anchorOffset: 10, + anchorPath: [1, 0, 0], + focusOffset: 3, + focusPath: [0, 2, 0, 0], + }); + + // Copy the partial list item and paragraph + const clipboard = await copyToClipboard(page); + + // Select all and remove content + await page.keyboard.press('ArrowUp'); + await page.keyboard.press('ArrowUp'); + if (!IS_WINDOWS && browserName === 'firefox') { + await page.keyboard.press('ArrowUp'); + } + await moveToLineEnd(page); + + await page.keyboard.down('Enter'); + + await assertHTML( + page, + html` +
    +
  • + one +
  • +
  • +
    +
  • +
  • + two +
  • +
  • + three +
  • +
+

+ Some text. +

+ `, + ); + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [0, 1], + focusOffset: 0, + focusPath: [0, 1], + }); + + await pasteFromClipboard(page, clipboard); + + await assertHTML( + page, + html` +
    +
  • + one +
  • +
  • + ee +
  • +
+

+ Some text. +

+
    +
  • + two +
  • +
  • + three +
  • +
+

+ Some text. +

+ `, + ); + await assertSelection(page, { + anchorOffset: 10, + anchorPath: [1, 0, 0], + focusOffset: 10, + focusPath: [1, 0, 0], + }); + }); + + test('Copy list items and paste back into list', async ({ + page, + isPlainText, + isCollab, + }) => { + test.skip(isPlainText); + + await focusEditor(page); + + await page.keyboard.type('- one'); + await page.keyboard.press('Enter'); + await page.keyboard.type('two'); + await page.keyboard.press('Enter'); + await page.keyboard.type('three'); + await page.keyboard.press('Enter'); + await page.keyboard.type('four'); + await page.keyboard.press('Enter'); + await page.keyboard.type('five'); + + await page.keyboard.press('ArrowUp'); + await page.keyboard.press('ArrowUp'); + + await moveToLineBeginning(page); + await page.keyboard.down('Shift'); + await page.keyboard.press('ArrowDown'); + await moveToLineEnd(page); + await page.keyboard.up('Shift'); + + await assertHTML( + page, + '
  • one
  • two
  • three
  • four
  • five
', + ); + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [0, 2, 0, 0], + focusOffset: 4, + focusPath: [0, 3, 0, 0], + }); + + const clipboard = await copyToClipboard(page); + + await page.keyboard.press('Backspace'); + + await assertHTML( + page, + '
  • one
  • two

  • five
', + ); + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [0, 2], + focusOffset: 0, + focusPath: [0, 2], + }); + + await pasteFromClipboard(page, clipboard); + + await assertHTML( + page, + '
  • one
  • two
  • three
  • four
  • five
', + ); + await assertSelection(page, { + anchorOffset: 4, + anchorPath: [0, 3, 0, 0], + focusOffset: 4, + focusPath: [0, 3, 0, 0], + }); + }); + + test('Copy list items and paste into list', async ({ + page, + isPlainText, + isCollab, + }) => { + test.fixme(isCollab && IS_LINUX, 'Flaky on Linux + Collab'); + test.skip(isPlainText); + + await focusEditor(page); + + await page.keyboard.type('- one'); + await page.keyboard.press('Enter'); + await page.keyboard.type('two'); + await page.keyboard.press('Enter'); + await page.keyboard.type('three'); + await page.keyboard.press('Enter'); + await page.keyboard.type('four'); + await page.keyboard.press('Enter'); + await page.keyboard.type('five'); + + await selectAll(page); + + await assertHTML( + page, + html` +
    +
  • + one +
  • +
  • + two +
  • +
  • + three +
  • +
  • + four +
  • +
  • + five +
  • +
+ `, + ); + + const clipboard = await copyToClipboard(page); + + await page.keyboard.press('ArrowDown'); + await page.keyboard.press('Enter'); + await page.keyboard.press('Enter'); + + await page.keyboard.type('12345'); + + await assertHTML( + page, + html` +
    +
  • + one +
  • +
  • + two +
  • +
  • + three +
  • +
  • + four +
  • +
  • + five +
  • +
+

+ 12345 +

+ `, + ); + + await page.keyboard.press('ArrowLeft'); + await page.keyboard.press('ArrowLeft'); + await selectCharacters(page, 'left', 1); + + await pasteFromClipboard(page, clipboard); + + await assertHTML( + page, + html` +
    +
  • + one +
  • +
  • + two +
  • +
  • + three +
  • +
  • + four +
  • +
  • + five +
  • +
+

+ 12 +

+
    +
  • + one +
  • +
  • + two +
  • +
  • + three +
  • +
  • + four +
  • +
  • + five +
  • +
+

+ 45 +

+ `, + ); + }); + + test('Copy and paste of list items and paste back into list on an existing item', async ({ + page, + isPlainText, + isCollab, + }) => { + test.skip(isPlainText); + + await focusEditor(page); + + await page.keyboard.type('- one'); + await page.keyboard.press('Enter'); + await page.keyboard.type('two'); + await page.keyboard.press('Enter'); + await page.keyboard.type('three'); + await page.keyboard.press('Enter'); + await page.keyboard.type('four'); + await page.keyboard.press('Enter'); + await page.keyboard.type('five'); + + await page.keyboard.press('ArrowUp'); + await page.keyboard.press('ArrowUp'); + + await moveToLineBeginning(page); + await page.keyboard.down('Shift'); + await page.keyboard.press('ArrowDown'); + await moveToLineEnd(page); + await page.keyboard.up('Shift'); + + await assertHTML( + page, + '
  • one
  • two
  • three
  • four
  • five
', + ); + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [0, 2, 0, 0], + focusOffset: 4, + focusPath: [0, 3, 0, 0], + }); + + const clipboard = await copyToClipboard(page); + + await page.keyboard.press('ArrowRight'); + + await assertHTML( + page, + '
  • one
  • two
  • three
  • four
  • five
', + ); + await assertSelection(page, { + anchorOffset: 4, + anchorPath: [0, 3, 0, 0], + focusOffset: 4, + focusPath: [0, 3, 0, 0], + }); + + await pasteFromClipboard(page, clipboard); + + await assertHTML( + page, + '
  • one
  • two
  • three
  • four
  • three
  • four
  • five
', + ); + await assertSelection(page, { + anchorOffset: 4, + anchorPath: [0, 5, 0, 0], + focusOffset: 4, + focusPath: [0, 5, 0, 0], + }); + }); + + test('Copy and paste two paragraphs into list on an existing item', async ({ + page, + isPlainText, + }) => { + test.skip(isPlainText); + await focusEditor(page); + + await page.keyboard.type('Hello'); + await page.keyboard.press('Enter'); + await page.keyboard.type('World'); + + await selectAll(page); + + const clipboard = await copyToClipboard(page); + + await page.keyboard.press('Backspace'); + + await page.keyboard.type('- one'); + await page.keyboard.press('Enter'); + await page.keyboard.type('two'); + await page.keyboard.press('Enter'); + await page.keyboard.type('three'); + await page.keyboard.press('Enter'); + await page.keyboard.type('four'); + await page.keyboard.press('Enter'); + await page.keyboard.type('five'); + + await page.keyboard.press('ArrowUp'); + await page.keyboard.press('ArrowUp'); + + await moveToLineBeginning(page); + await page.keyboard.press('ArrowDown'); + await moveToLineEnd(page); + await moveLeft(page, 2); + + await assertHTML( + page, + '
  • one
  • two
  • three
  • four
  • five
', + ); + await assertSelection(page, { + anchorOffset: 2, + anchorPath: [0, 3, 0, 0], + focusOffset: 2, + focusPath: [0, 3, 0, 0], + }); + + await pasteFromClipboard(page, clipboard); + + await assertHTML( + page, + '
  • one
  • two
  • three
  • foHello

Worldur

  • five
', + ); + await assertSelection(page, { + anchorOffset: 5, + anchorPath: [1, 0, 0], + focusOffset: 5, + focusPath: [1, 0, 0], + }); + }); + + test('Copy and paste two paragraphs at the end of a list', async ({ + page, + isPlainText, + }) => { + test.skip(isPlainText); + + await focusEditor(page); + + await page.keyboard.type('Hello'); + await page.keyboard.press('Enter'); + await page.keyboard.type('World'); + + await selectAll(page); + + const clipboard = await copyToClipboard(page); + + await page.keyboard.press('Backspace'); + + await page.keyboard.type('- one'); + await page.keyboard.press('Enter'); + await page.keyboard.type('two'); + await page.keyboard.press('Enter'); + await page.keyboard.type('three'); + await page.keyboard.press('Enter'); + await page.keyboard.type('four'); + await page.keyboard.press('Enter'); + await page.keyboard.type('five'); + await page.keyboard.press('Enter'); + + await pasteFromClipboard(page, clipboard); + + await assertHTML( + page, + '
  • one
  • two
  • three
  • four
  • five
  • Hello

World

', + ); + await assertSelection(page, { + anchorOffset: 5, + anchorPath: [1, 0, 0], + focusOffset: 5, + focusPath: [1, 0, 0], + }); + + await pasteFromClipboard(page, clipboard); + + await assertHTML( + page, + '
  • one
  • two
  • three
  • four
  • five
  • Hello

WorldHello

World

', + ); + await assertSelection(page, { + anchorOffset: 5, + anchorPath: [2, 0, 0], + focusOffset: 5, + focusPath: [2, 0, 0], + }); + }); +}); diff --git a/demos/playground/src/__tests__/e2e/DraggableBlock.spec.mjs b/demos/playground/src/__tests__/e2e/DraggableBlock.spec.mjs index 3623927a..a442f58c 100644 --- a/demos/playground/src/__tests__/e2e/DraggableBlock.spec.mjs +++ b/demos/playground/src/__tests__/e2e/DraggableBlock.spec.mjs @@ -151,4 +151,41 @@ test.describe('DraggableBlock', () => { `, ); }); + + test('Dragging the first paragraph to an empty space in the middle of the editor works correctly', async ({ + page, + isPlainText, + browserName, + isCollab, + }) => { + test.skip(isCollab); + test.skip(isPlainText); + test.skip(browserName === 'firefox'); + + await focusEditor(page); + await page.keyboard.type('Paragraph 1'); + await page.keyboard.press('Enter'); + await page.keyboard.type('Paragraph 2'); + + await mouseMoveToSelector(page, 'p:has-text("Paragraph 1")'); + await page.pause(); + await dragDraggableMenuTo(page, '.ContentEditable__root'); + + await assertHTML( + page, + ` +

+ Paragraph 2 +

+

+ Paragraph 1 +

+ `, + ); + }); }); diff --git a/demos/playground/src/__tests__/e2e/ElementFormat.spec.mjs b/demos/playground/src/__tests__/e2e/ElementFormat.spec.mjs index cd861f30..47d3685a 100644 --- a/demos/playground/src/__tests__/e2e/ElementFormat.spec.mjs +++ b/demos/playground/src/__tests__/e2e/ElementFormat.spec.mjs @@ -18,10 +18,9 @@ import { } from '../utils/index.mjs'; test.describe('Element format', () => { - test.fixme(); test.beforeEach(({isCollab, isPlainText, page}) => { test.skip(isPlainText); - initialize({isCollab, page}); + return initialize({isCollab, page}); }); test('Can indent/align paragraph when caret is within link', async ({ diff --git a/demos/playground/src/__tests__/e2e/Events.spec.mjs b/demos/playground/src/__tests__/e2e/Events.spec.mjs index 0bed45df..11fbca86 100644 --- a/demos/playground/src/__tests__/e2e/Events.spec.mjs +++ b/demos/playground/src/__tests__/e2e/Events.spec.mjs @@ -99,79 +99,79 @@ test.describe('Events', () => { ); }); - test.fixme( - 'Add period with double-space after emoji (MacOS specific) #3953', - async ({page, isPlainText}) => { - if (LEGACY_EVENTS) { - return; - } - await focusEditor(page); - await page.keyboard.type(':)'); - await assertHTML( - page, - html` -

- - 🙂 - -

- `, - ); - await page.keyboard.type(' '); + test('Add period with double-space after emoji (MacOS specific) #3953', async ({ + page, + isPlainText, + }) => { + if (LEGACY_EVENTS) { + return; + } + await focusEditor(page); + await page.keyboard.type(':)'); + await assertHTML( + page, + html` +

+ + 🙂 + +

+ `, + ); + await page.keyboard.type(' '); - await evaluate(page, () => { - const editable = document.querySelector('[contenteditable="true"]'); - const spans = editable.querySelectorAll('span'); - const lastSpan = spans[spans.length - 1]; - const lastSpanTextNode = lastSpan.firstChild; - function singleRangeFn( - startContainer, - startOffset, - endContainer, - endOffset, - ) { - return () => [ - new StaticRange({ - endContainer, - endOffset, - startContainer, - startOffset, - }), - ]; - } - const characterBeforeInputEvent = new InputEvent('beforeinput', { - bubbles: true, - cancelable: true, - data: '. ', - inputType: 'insertText', - }); - characterBeforeInputEvent.getTargetRanges = singleRangeFn( - lastSpanTextNode, - 0, - lastSpanTextNode, - 1, - ); - // We don't do textNode.textContent += character; intentionally; if the code prevents default - // Lexical should add it via controlled mode. - editable.dispatchEvent(characterBeforeInputEvent); + await evaluate(page, () => { + const editable = document.querySelector('[contenteditable="true"]'); + const spans = editable.querySelectorAll('span'); + const lastSpan = spans[spans.length - 1]; + const lastSpanTextNode = lastSpan.firstChild; + function singleRangeFn( + startContainer, + startOffset, + endContainer, + endOffset, + ) { + return () => [ + new StaticRange({ + endContainer, + endOffset, + startContainer, + startOffset, + }), + ]; + } + const characterBeforeInputEvent = new InputEvent('beforeinput', { + bubbles: true, + cancelable: true, + data: '. ', + inputType: 'insertText', }); - await page.pause(); - - await assertHTML( - page, - html` -

- - 🙂 - - . -

- `, + characterBeforeInputEvent.getTargetRanges = singleRangeFn( + lastSpanTextNode, + 0, + lastSpanTextNode, + 1, ); - }, - ); + // We don't do textNode.textContent += character; intentionally; if the code prevents default + // Lexical should add it via controlled mode. + editable.dispatchEvent(characterBeforeInputEvent); + }); + await page.pause(); + + await assertHTML( + page, + html` +

+ + 🙂 + + . +

+ `, + ); + }); }); diff --git a/demos/playground/src/__tests__/e2e/Extensions.spec.mjs b/demos/playground/src/__tests__/e2e/Extensions.spec.mjs index 838511ea..e054da71 100644 --- a/demos/playground/src/__tests__/e2e/Extensions.spec.mjs +++ b/demos/playground/src/__tests__/e2e/Extensions.spec.mjs @@ -200,7 +200,7 @@ test.describe('Extensions', () => { isPlainText, }) => { // This test is flaky in collab #3915 - test.fixme(isCollab); // lexical + test.fixme(isCollab); test.skip(isPlainText); await focusEditor(page); diff --git a/demos/playground/src/__tests__/e2e/Hashtags.spec.mjs b/demos/playground/src/__tests__/e2e/Hashtags.spec.mjs index d85e9066..924eda6f 100644 --- a/demos/playground/src/__tests__/e2e/Hashtags.spec.mjs +++ b/demos/playground/src/__tests__/e2e/Hashtags.spec.mjs @@ -302,4 +302,47 @@ test.describe('Hashtags', () => { `, ); }); + + test('Should not break with multiple leading "#" #5636', async ({page}) => { + await focusEditor(page); + await page.keyboard.type('#hello'); + + await waitForSelector(page, '.PlaygroundEditorTheme__hashtag'); + + await assertHTML( + page, + html` +

+ + #hello + +

+ `, + ); + await assertSelection(page, { + anchorOffset: 6, + anchorPath: [0, 0, 0], + focusOffset: 6, + focusPath: [0, 0, 0], + }); + + await moveToEditorBeginning(page); + await page.keyboard.type('#'); + + await assertHTML( + page, + html` +

+ # + + #hello + +

+ `, + ); + }); }); diff --git a/demos/playground/src/__tests__/e2e/Headings.spec.mjs b/demos/playground/src/__tests__/e2e/Headings.spec.mjs deleted file mode 100644 index 00aa0cbf..00000000 --- a/demos/playground/src/__tests__/e2e/Headings.spec.mjs +++ /dev/null @@ -1,153 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ - -import {moveRight, moveToEditorBeginning} from '../keyboardShortcuts/index.mjs'; -import { - assertHTML, - click, - focusEditor, - html, - initialize, - test, - IS_WINDOWS, -} from '../utils/index.mjs'; - -test.describe('Headings', () => { - test.beforeEach(({isPlainText, isCollab, browserName, page}) => { - test.fixme(IS_WINDOWS && browserName === 'firefox' && isCollab); - test.skip(isPlainText); - initialize({isCollab, page}); - }); - - test('Stays as a heading when you backspace at the start of a heading with no previous sibling nodes present', async ({ - page, - isCollab, - browserName, - }) => { - if (IS_WINDOWS && browserName === 'firefox' && isCollab) { - test.fixme(); - } - await focusEditor(page); - - await click(page, '.block-controls'); - await click(page, '.dropdown .icon.h1'); - - await page.keyboard.type('Welcome to the playground'); - - await assertHTML( - page, - html` -

- Welcome to the playground -

- `, - ); - - await moveToEditorBeginning(page); - - await page.keyboard.press('Backspace'); - - await assertHTML( - page, - html` -

- Welcome to the playground -

- `, - ); - }); - - test('Stays as a heading when you press enter in the middle of a heading', async ({ - page, - }) => { - await focusEditor(page); - - await click(page, '.block-controls'); - await click(page, '.dropdown .icon.h1'); - - await page.keyboard.type('Welcome to the playground'); - - await assertHTML( - page, - html` -

- Welcome to the playground -

- `, - ); - - await moveToEditorBeginning(page); - - await moveRight(page, 5); - - await page.keyboard.press('Enter'); - - await assertHTML( - page, - html` -

- Welco -

-

- me to the playground -

- `, - ); - }); - - test('Changes to a paragraph when you press enter at the end of a heading', async ({ - page, - browserName, - isCollab, - }) => { - if (IS_WINDOWS && browserName === 'firefox' && isCollab) { - test.fixme(); - } - await focusEditor(page); - - await click(page, '.block-controls'); - await click(page, '.dropdown .icon.h1'); - - await page.keyboard.type('Welcome to the playground'); - - await assertHTML( - page, - html` -

- Welcome to the playground -

- `, - ); - - await page.keyboard.press('Enter'); - - await assertHTML( - page, - html` -

- Welcome to the playground -

-


- `, - ); - }); -}); diff --git a/demos/playground/src/__tests__/e2e/Headings/HeadingsBackspaceAtStart.spec.mjs b/demos/playground/src/__tests__/e2e/Headings/HeadingsBackspaceAtStart.spec.mjs new file mode 100644 index 00000000..703bf182 --- /dev/null +++ b/demos/playground/src/__tests__/e2e/Headings/HeadingsBackspaceAtStart.spec.mjs @@ -0,0 +1,58 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import {moveToEditorBeginning} from '../../keyboardShortcuts/index.mjs'; +import { + assertHTML, + click, + focusEditor, + html, + initialize, + test, +} from '../../utils/index.mjs'; + +test('Headings - stays as a heading when you backspace at the start of a heading with no previous sibling nodes present', async ({ + page, + isPlainText, + isCollab, +}) => { + test.skip(isPlainText); + await initialize({isCollab, page}); + await focusEditor(page); + + await click(page, '.block-controls'); + await click(page, '.dropdown .icon.h1'); + + await page.keyboard.type('Welcome to the playground'); + + await assertHTML( + page, + html` +

+ Welcome to the playground +

+ `, + ); + + await moveToEditorBeginning(page); + + await page.keyboard.press('Backspace'); + + await assertHTML( + page, + html` +

+ Welcome to the playground +

+ `, + ); +}); diff --git a/demos/playground/src/__tests__/e2e/Headings/HeadingsEnterAtEnd.spec.mjs b/demos/playground/src/__tests__/e2e/Headings/HeadingsEnterAtEnd.spec.mjs new file mode 100644 index 00000000..37239396 --- /dev/null +++ b/demos/playground/src/__tests__/e2e/Headings/HeadingsEnterAtEnd.spec.mjs @@ -0,0 +1,68 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import { + moveRight, + moveToEditorBeginning, +} from '../../keyboardShortcuts/index.mjs'; +import { + assertHTML, + click, + focusEditor, + html, + initialize, + test, +} from '../../utils/index.mjs'; + +test(`Headings - stays as a heading when you press enter in the middle of a heading`, async ({ + page, + isCollab, + isPlainText, +}) => { + test.skip(isPlainText); + await initialize({isCollab, page}); + await focusEditor(page); + + await click(page, '.block-controls'); + await click(page, '.dropdown .icon.h1'); + + await page.keyboard.type('Welcome to the playground'); + + await assertHTML( + page, + html` +

+ Welcome to the playground +

+ `, + ); + + await moveToEditorBeginning(page); + + await moveRight(page, 5); + + await page.keyboard.press('Enter'); + + await assertHTML( + page, + html` +

+ Welco +

+

+ me to the playground +

+ `, + ); +}); diff --git a/demos/playground/src/__tests__/e2e/Headings/HeadingsEnterInMiddle.spec.mjs b/demos/playground/src/__tests__/e2e/Headings/HeadingsEnterInMiddle.spec.mjs new file mode 100644 index 00000000..5a15096e --- /dev/null +++ b/demos/playground/src/__tests__/e2e/Headings/HeadingsEnterInMiddle.spec.mjs @@ -0,0 +1,56 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import { + assertHTML, + click, + focusEditor, + html, + initialize, + test, +} from '../../utils/index.mjs'; + +test('Headings - changes to a paragraph when you press enter at the end of a heading', async ({ + page, + isPlainText, + isCollab, +}) => { + test.skip(isPlainText); + await initialize({isCollab, page}); + await focusEditor(page); + + await click(page, '.block-controls'); + await click(page, '.dropdown .icon.h1'); + + await page.keyboard.type('Welcome to the playground'); + + await assertHTML( + page, + html` +

+ Welcome to the playground +

+ `, + ); + + await page.keyboard.press('Enter'); + + await assertHTML( + page, + html` +

+ Welcome to the playground +

+


+ `, + ); +}); diff --git a/demos/playground/src/__tests__/e2e/History.spec.mjs b/demos/playground/src/__tests__/e2e/History.spec.mjs index 9978ad21..c601e10b 100644 --- a/demos/playground/src/__tests__/e2e/History.spec.mjs +++ b/demos/playground/src/__tests__/e2e/History.spec.mjs @@ -564,77 +564,168 @@ test.describe('History', () => { test.describe('History - IME', () => { test.beforeEach(({isCollab, page}) => initialize({isCollab, page})); - test.fixme( - 'Can undo composed Hirigana via IME after composition ends (#2479)', - async ({page, browserName, isCollab, isPlainText, legacyEvents}) => { - // We don't yet support FF. - test.skip(isCollab || isPlainText || browserName === 'firefox'); + test('Can undo composed Hirigana via IME after composition ends (#2479)', async ({ + page, + browserName, + isCollab, + isPlainText, + legacyEvents, + }) => { + // We don't yet support FF. + test.skip(isCollab || isPlainText || browserName !== 'chromium'); - await focusEditor(page); - await enableCompositionKeyEvents(page); + await focusEditor(page); + await enableCompositionKeyEvents(page); + + const client = await page.context().newCDPSession(page); + // await page.keyboard.imeSetComposition('s', 1, 1); + await client.send('Input.imeSetComposition', { + selectionStart: 1, + selectionEnd: 1, + text: 's', + }); + // await page.keyboard.imeSetComposition('す', 1, 1); + await client.send('Input.imeSetComposition', { + selectionStart: 1, + selectionEnd: 1, + text: 'す', + }); + // await page.keyboard.imeSetComposition('すs', 2, 2); + await client.send('Input.imeSetComposition', { + selectionStart: 2, + selectionEnd: 2, + text: 'すs', + }); + // await page.keyboard.imeSetComposition('すsh', 3, 3); + await client.send('Input.imeSetComposition', { + selectionStart: 3, + selectionEnd: 3, + text: 'すsh', + }); + // await page.keyboard.imeSetComposition('すし', 2, 2); + await client.send('Input.imeSetComposition', { + selectionStart: 2, + selectionEnd: 2, + text: 'すし', + }); + // await page.keyboard.insertText('すし'); + await client.send('Input.insertText', { + text: 'すし', + }); - await page.keyboard.imeSetComposition('s', 1, 1); - await page.keyboard.imeSetComposition('す', 1, 1); - await page.keyboard.imeSetComposition('すs', 2, 2); - await page.keyboard.imeSetComposition('すsh', 3, 3); - await page.keyboard.imeSetComposition('すし', 2, 2); - await page.keyboard.insertText('すし'); + await sleep(1050); // default merge interval is 1000, add 50ms as overhead due to CI latency. - await sleep(1050); // default merge interval is 1000, add 50ms as overhead due to CI latency. + await page.keyboard.type(' '); - await page.keyboard.type(' '); + await sleep(1050); - await sleep(1050); + // await page.keyboard.imeSetComposition('m', 1, 1); + await client.send('Input.imeSetComposition', { + selectionStart: 1, + selectionEnd: 1, + text: 'm', + }); + // await page.keyboard.imeSetComposition('も', 1, 1); + await client.send('Input.imeSetComposition', { + selectionStart: 1, + selectionEnd: 1, + text: 'も', + }); + // await page.keyboard.imeSetComposition('もj', 2, 2); + await client.send('Input.imeSetComposition', { + selectionStart: 2, + selectionEnd: 2, + text: 'もj', + }); + // await page.keyboard.imeSetComposition('もじ', 2, 2); + await client.send('Input.imeSetComposition', { + selectionStart: 2, + selectionEnd: 2, + text: 'もじ', + }); + // await page.keyboard.imeSetComposition('もじあ', 3, 3); + await client.send('Input.imeSetComposition', { + selectionStart: 3, + selectionEnd: 3, + text: 'もじあ', + }); + // await page.keyboard.insertText('もじあ'); + await client.send('Input.insertText', { + text: 'もじあ', + }); - await page.keyboard.imeSetComposition('m', 1, 1); - await page.keyboard.imeSetComposition('も', 1, 1); - await page.keyboard.imeSetComposition('もj', 2, 2); - await page.keyboard.imeSetComposition('もじ', 2, 2); - await page.keyboard.imeSetComposition('もじあ', 3, 3); - await page.keyboard.insertText('もじあ'); + await assertHTML( + page, + html` +

+ すし もじあ +

+ `, + ); - await assertHTML( - page, - html` -

- すし もじあ -

- `, - ); + await assertSelection(page, { + anchorOffset: 6, + anchorPath: [0, 0, 0], + focusOffset: 6, + focusPath: [0, 0, 0], + }); - await assertSelection(page, { - anchorOffset: 6, - anchorPath: [0, 0, 0], - focusOffset: 6, - focusPath: [0, 0, 0], - }); + await undo(page); - await undo(page); + const WHITESPACE_TOKEN = ' '; - const WHITESPACE_TOKEN = ' '; + await assertHTML( + page, + html` +

+ すし${WHITESPACE_TOKEN} +

+ `, + ); - await assertHTML( - page, - html` -

- すし${WHITESPACE_TOKEN} -

- `, - ); + await assertSelection(page, { + anchorOffset: 3, + anchorPath: [0, 0, 0], + focusOffset: 3, + focusPath: [0, 0, 0], + }); + + await undo(page); + + await assertHTML( + page, + html` +

+ すし +

+ `, + ); + if (browserName === 'webkit' && !legacyEvents) { await assertSelection(page, { anchorOffset: 3, anchorPath: [0, 0, 0], focusOffset: 3, focusPath: [0, 0, 0], }); + } else { + await assertSelection(page, { + anchorOffset: 2, + anchorPath: [0, 0, 0], + focusOffset: 2, + focusPath: [0, 0, 0], + }); + } - await undo(page); + await undo(page); + if (browserName === 'webkit' && !legacyEvents) { await assertHTML( page, html` @@ -645,89 +736,58 @@ test.describe('History - IME', () => {

`, ); - - if (browserName === 'webkit' && !legacyEvents) { - await assertSelection(page, { - anchorOffset: 3, - anchorPath: [0, 0, 0], - focusOffset: 3, - focusPath: [0, 0, 0], - }); - } else { - await assertSelection(page, { - anchorOffset: 2, - anchorPath: [0, 0, 0], - focusOffset: 2, - focusPath: [0, 0, 0], - }); - } - - await undo(page); - - if (browserName === 'webkit' && !legacyEvents) { - await assertHTML( - page, - html` -

- すし -

- `, - ); - } else { - await assertHTML( - page, - html` -


- `, - ); - } - - if (browserName === 'webkit' && !legacyEvents) { - await assertSelection(page, { - anchorOffset: 2, - anchorPath: [0, 0, 0], - focusOffset: 2, - focusPath: [0, 0, 0], - }); - } else { - await assertSelection(page, { - anchorOffset: 0, - anchorPath: [0], - focusOffset: 0, - focusPath: [0], - }); - } - - await redo(page); - + } else { await assertHTML( page, html` -

- すし -

+


`, ); + } + + if (browserName === 'webkit' && !legacyEvents) { + await assertSelection(page, { + anchorOffset: 2, + anchorPath: [0, 0, 0], + focusOffset: 2, + focusPath: [0, 0, 0], + }); + } else { + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [0], + focusOffset: 0, + focusPath: [0], + }); + } + + await redo(page); - if (browserName === 'webkit' && !legacyEvents) { - await assertSelection(page, { - anchorOffset: 3, - anchorPath: [0, 0, 0], - focusOffset: 3, - focusPath: [0, 0, 0], - }); - } else { - await assertSelection(page, { - anchorOffset: 2, - anchorPath: [0, 0, 0], - focusOffset: 2, - focusPath: [0, 0, 0], - }); - } - }, - ); + await assertHTML( + page, + html` +

+ すし +

+ `, + ); + + if (browserName === 'webkit' && !legacyEvents) { + await assertSelection(page, { + anchorOffset: 3, + anchorPath: [0, 0, 0], + focusOffset: 3, + focusPath: [0, 0, 0], + }); + } else { + await assertSelection(page, { + anchorOffset: 2, + anchorPath: [0, 0, 0], + focusOffset: 2, + focusPath: [0, 0, 0], + }); + } + }); }); diff --git a/demos/playground/src/__tests__/e2e/HorizontalRule.spec.mjs b/demos/playground/src/__tests__/e2e/HorizontalRule.spec.mjs index a9ea3492..5ef60940 100644 --- a/demos/playground/src/__tests__/e2e/HorizontalRule.spec.mjs +++ b/demos/playground/src/__tests__/e2e/HorizontalRule.spec.mjs @@ -23,7 +23,6 @@ import { selectFromInsertDropdown, test, waitForSelector, - IS_WINDOWS, } from '../utils/index.mjs'; test.describe('HorizontalRule', () => { @@ -34,9 +33,6 @@ test.describe('HorizontalRule', () => { isPlainText, browserName, }) => { - if (IS_WINDOWS && browserName === 'firefox' && isCollab) { - test.fixme(); - } test.skip(isPlainText); await focusEditor(page); @@ -48,7 +44,7 @@ test.describe('HorizontalRule', () => { page, html`


-
+


`, ); @@ -111,7 +107,7 @@ test.describe('HorizontalRule', () => { dir="ltr"> Some text

-
+

@@ -139,7 +135,7 @@ test.describe('HorizontalRule', () => { if (!isCollab) { await assertHTML( page, - '


Some more text

', + '

Some more text

', ); } @@ -191,7 +187,7 @@ test.describe('HorizontalRule', () => { dir="ltr"> Test

-
+


`, ); @@ -252,7 +248,7 @@ test.describe('HorizontalRule', () => { dir="ltr"> Te

-
+

@@ -269,15 +265,7 @@ test.describe('HorizontalRule', () => { }); }); - test('Can copy and paste a horizontal rule', async ({ - page, - isPlainText, - browserName, - isCollab, - }) => { - if (IS_WINDOWS && browserName === 'firefox' && isCollab) { - test.fixme(); - } + test('Can copy and paste a horizontal rule', async ({page, isPlainText}) => { test.skip(isPlainText); await focusEditor(page); @@ -290,7 +278,7 @@ test.describe('HorizontalRule', () => { page, html`


-
+


`, ); @@ -317,7 +305,7 @@ test.describe('HorizontalRule', () => { page, html`


-
+


`, ); @@ -338,9 +326,9 @@ test.describe('HorizontalRule', () => { page, html`


-
+


-
+


`, ); @@ -371,7 +359,7 @@ test.describe('HorizontalRule', () => { page, html`


-
+


`, ); diff --git a/demos/playground/src/__tests__/e2e/Images.spec.mjs b/demos/playground/src/__tests__/e2e/Images.spec.mjs index 3b5ec433..d2f1cf93 100644 --- a/demos/playground/src/__tests__/e2e/Images.spec.mjs +++ b/demos/playground/src/__tests__/e2e/Images.spec.mjs @@ -23,6 +23,7 @@ import { insertUploadImage, insertUrlImage, IS_WINDOWS, + LEGACY_EVENTS, SAMPLE_IMAGE_URL, SAMPLE_LANDSCAPE_IMAGE_URL, selectorBoundingBox, @@ -404,8 +405,6 @@ test.describe('Images', () => { 'a pretty yellow flower :)', ); - await page.waitForTimeout(3000); - await assertHTML( page, html` @@ -588,7 +587,7 @@ test.describe('Images', () => { isCollab, }) => { // This test is flaky in collab #3915 - test.fixme(isCollab); // lexical + test.fixme(isCollab); test.skip(isPlainText); await page.setViewportSize({ @@ -619,7 +618,10 @@ test.describe('Images', () => { test('Node selection: can select multiple image nodes and replace them with a new image', async ({ page, isPlainText, + browserName, }) => { + // It doesn't work in legacy events mode in WebKit #5673 + test.fixme(LEGACY_EVENTS && browserName === 'webkit'); test.skip(isPlainText); await focusEditor(page); @@ -738,4 +740,51 @@ test.describe('Images', () => { `, ); }); + + test('Can resolve selection correctly when the image is clicked and dragged right', async ({ + page, + isPlainText, + browserName, + isCollab, + }) => { + test.skip(isPlainText); + let leftFrame = page; + if (isCollab) { + leftFrame = await page.frame('left'); + } + await focusEditor(page); + + await page.keyboard.type('HelloWorld'); + await insertSampleImage(page); + await click(page, '.editor-image img'); + + await leftFrame.locator('.editor-image img').hover(); + await page.mouse.down(); + await leftFrame.locator('.PlaygroundEditorTheme__paragraph').hover(); + await page.mouse.up(); + await waitForSelector(page, '.editor-image img'); + await assertHTML( + page, + html` +

+ HelloWorld + +

+ Yellow flower in tilt shift lens +
+ +
+

+ `, + ); + }); }); diff --git a/demos/playground/src/__tests__/e2e/Indentation.spec.mjs b/demos/playground/src/__tests__/e2e/Indentation.spec.mjs index feeaf2c7..e65846a6 100644 --- a/demos/playground/src/__tests__/e2e/Indentation.spec.mjs +++ b/demos/playground/src/__tests__/e2e/Indentation.spec.mjs @@ -19,6 +19,7 @@ import { test.describe('Identation', () => { test.beforeEach(({isCollab, page}) => initialize({isCollab, page})); + test.fixme( `Can create content and indent and outdent it all`, async ({page, browserName, isPlainText, isCollab}) => { @@ -99,7 +100,8 @@ test.describe('Identation', () => { code


- +
{ style="padding-inline-start: calc(40px)">

- +
{ style="padding-inline-start: calc(80px)">

- +
{ style="padding-inline-start: calc(40px)">

- +
{ code


- +
{ isPlainText, legacyEvents, }) => { - test.fixme(browserName === 'webkit', 'This test has to be fixed'); test.skip(isPlainText); await focusEditor(page); await page.keyboard.type('congrats'); @@ -321,292 +320,310 @@ test.describe('Keywords', () => {

`, ); - await assertSelection(page, { - anchorOffset: 0, - anchorPath: [0, 1, 0], - focusOffset: 0, - focusPath: [0, 1, 0], - }); await page.keyboard.press('Space'); - await assertHTML( - page, - html` -

- - congrats - - - - Bob! - -

- `, - ); - if (browserName === 'firefox' && legacyEvents) { - await assertSelection(page, { - anchorOffset: 1, - anchorPath: [0, 2, 0], - focusOffset: 1, - focusPath: [0, 2, 0], - }); - } else { - await assertSelection(page, { - anchorOffset: 1, - anchorPath: [0, 1, 0], - focusOffset: 1, - focusPath: [0, 1, 0], - }); - } - }); - - test.fixme( - 'Can type "Everyone congrats!" where "Everyone " and "!" are bold', - async ({page, isPlainText}) => { - test.skip(isPlainText); - await focusEditor(page); - - await toggleBold(page); - await page.keyboard.type('Everyone '); - - await assertHTML( - page, - html` -

- - Everyone - -

- `, - ); - await assertSelection(page, { - anchorOffset: 9, - anchorPath: [0, 0, 0], - focusOffset: 9, - focusPath: [0, 0, 0], - }); - - await toggleBold(page); - - await page.keyboard.type('congrats'); - + if (browserName === 'webkit') { await assertHTML( page, html`

- - Everyone - congrats -

- `, - ); - await assertSelection(page, { - anchorOffset: 8, - anchorPath: [0, 1, 0], - focusOffset: 8, - focusPath: [0, 1, 0], - }); - - await page.keyboard.type('!'); - - await assertHTML( - page, - html` -

- Everyone + Bob! - - congrats - - !

`, ); - await assertSelection(page, { - anchorOffset: 1, - anchorPath: [0, 2, 0], - focusOffset: 1, - focusPath: [0, 2, 0], - }); - - await page.keyboard.press('Backspace'); - - await assertHTML( - page, - html` -

- - Everyone - - - congrats - -

- `, - ); - await assertSelection(page, { - anchorOffset: 8, - anchorPath: [0, 1, 0], - focusOffset: 8, - focusPath: [0, 1, 0], - }); - - await toggleBold(page); - - await page.keyboard.type('!'); - + } else { await assertHTML( page, html`

- - Everyone - congrats + - ! + Bob!

`, ); + } + + if (browserName === 'firefox' && legacyEvents) { await assertSelection(page, { anchorOffset: 1, anchorPath: [0, 2, 0], focusOffset: 1, focusPath: [0, 2, 0], }); - - await page.keyboard.press('Backspace'); - - await assertHTML( - page, - html` -

- - Everyone - - - congrats - -

- `, - ); + } else { await assertSelection(page, { - anchorOffset: 8, + anchorOffset: 1, anchorPath: [0, 1, 0], - focusOffset: 8, + focusOffset: 1, focusPath: [0, 1, 0], }); + } + }); - await moveToPrevWord(page); + test('Can type "Everyone congrats!" where "Everyone " and "!" are bold', async ({ + page, + isPlainText, + }) => { + test.skip(isPlainText); + await focusEditor(page); - await page.keyboard.press('Backspace'); + await toggleBold(page); + await page.keyboard.type('Everyone '); - await assertHTML( - page, - html` -

- - Everyone - - congrats -

- `, - ); - await assertSelection(page, { - anchorOffset: 8, - anchorPath: [0, 0, 0], - focusOffset: 8, - focusPath: [0, 0, 0], - }); + await assertHTML( + page, + html` +

+ + Everyone + +

+ `, + ); + await assertSelection(page, { + anchorOffset: 9, + anchorPath: [0, 0, 0], + focusOffset: 9, + focusPath: [0, 0, 0], + }); - await page.keyboard.press('Space'); + await toggleBold(page); - await assertHTML( - page, - html` -

- - Everyone - - - congrats - -

- `, - ); - await assertSelection(page, { - anchorOffset: 9, - anchorPath: [0, 0, 0], - focusOffset: 9, - focusPath: [0, 0, 0], - }); - }, - ); + await page.keyboard.type('congrats'); + + await assertHTML( + page, + html` +

+ + Everyone + + + congrats + +

+ `, + ); + await assertSelection(page, { + anchorOffset: 8, + anchorPath: [0, 1, 0], + focusOffset: 8, + focusPath: [0, 1, 0], + }); + + await page.keyboard.type('!'); + + await assertHTML( + page, + html` +

+ + Everyone + + + congrats + + ! +

+ `, + ); + await assertSelection(page, { + anchorOffset: 1, + anchorPath: [0, 2, 0], + focusOffset: 1, + focusPath: [0, 2, 0], + }); + + await page.keyboard.press('Backspace'); + + await assertHTML( + page, + html` +

+ + Everyone + + + congrats + +

+ `, + ); + await assertSelection(page, { + anchorOffset: 8, + anchorPath: [0, 1, 0], + focusOffset: 8, + focusPath: [0, 1, 0], + }); + + await toggleBold(page); + + await page.keyboard.type('!'); + + await assertHTML( + page, + html` +

+ + Everyone + + + congrats + + + ! + +

+ `, + ); + await assertSelection(page, { + anchorOffset: 1, + anchorPath: [0, 2, 0], + focusOffset: 1, + focusPath: [0, 2, 0], + }); + + await page.keyboard.press('Backspace'); + + await assertHTML( + page, + html` +

+ + Everyone + + + congrats + +

+ `, + ); + await assertSelection(page, { + anchorOffset: 8, + anchorPath: [0, 1, 0], + focusOffset: 8, + focusPath: [0, 1, 0], + }); + + await moveToPrevWord(page); + + await page.keyboard.press('Backspace'); + + await assertHTML( + page, + html` +

+ + Everyone + + congrats +

+ `, + ); + await assertSelection(page, { + anchorOffset: 8, + anchorPath: [0, 0, 0], + focusOffset: 8, + focusPath: [0, 0, 0], + }); + + await page.keyboard.press('Space'); + + await assertHTML( + page, + html` +

+ + Everyone + + + congrats + +

+ `, + ); + await assertSelection(page, { + anchorOffset: 9, + anchorPath: [0, 0, 0], + focusOffset: 9, + focusPath: [0, 0, 0], + }); + }); }); diff --git a/demos/playground/src/__tests__/e2e/Links.spec.mjs b/demos/playground/src/__tests__/e2e/Links.spec.mjs index f9548b8a..22e633ff 100644 --- a/demos/playground/src/__tests__/e2e/Links.spec.mjs +++ b/demos/playground/src/__tests__/e2e/Links.spec.mjs @@ -6,6 +6,8 @@ * */ +/* eslint-disable no-useless-escape */ + import { deleteBackward, moveLeft, @@ -24,8 +26,10 @@ import { focusEditor, html, initialize, + IS_LINUX, keyDownCtrlOrMeta, keyUpCtrlOrMeta, + pasteFromClipboard, test, } from '../utils/index.mjs'; @@ -34,7 +38,6 @@ test.beforeEach(({isPlainText}) => { }); test.describe('Links', () => { - test.fixme(); test.beforeEach(({isCollab, page}) => initialize({isCollab, page})); test(`Can convert a text node into a link`, async ({page}) => { await focusEditor(page); @@ -54,6 +57,7 @@ test.describe('Links', () => { // link await click(page, '.link'); + await click(page, '.link-confirm'); await assertHTML( page, @@ -63,7 +67,7 @@ test.describe('Links', () => { dir="ltr"> Hello @@ -90,7 +94,7 @@ test.describe('Links', () => { dir="ltr"> Hello @@ -190,6 +194,7 @@ test.describe('Links', () => { // link await click(page, '.link'); + await click(page, '.link-confirm'); await assertHTML( page, @@ -200,7 +205,7 @@ test.describe('Links', () => { class="PlaygroundEditorTheme__link PlaygroundEditorTheme__ltr" dir="ltr" href="https://" - rel="noopener"> + rel="noreferrer"> abc { class="PlaygroundEditorTheme__link PlaygroundEditorTheme__ltr" dir="ltr" href="https://facebook.com" - rel="noopener"> + rel="noreferrer"> abc { // link await click(page, '.link'); + await click(page, '.link-confirm'); await assertHTML( page, @@ -320,7 +326,7 @@ test.describe('Links', () => { class="PlaygroundEditorTheme__link PlaygroundEditorTheme__ltr" dir="ltr" href="https://" - rel="noopener"> + rel="noreferrer"> abc { class="PlaygroundEditorTheme__link PlaygroundEditorTheme__ltr" dir="ltr" href="https://facebook.com" - rel="noopener"> + rel="noreferrer"> abc { // link await click(page, '.link'); + await click(page, '.link-confirm'); await moveLeft(page, 1); @@ -392,7 +399,7 @@ test.describe('Links', () => { class="PlaygroundEditorTheme__link PlaygroundEditorTheme__ltr" dir="ltr" href="https://" - rel="noopener"> + rel="noreferrer"> hello @@ -412,7 +419,7 @@ test.describe('Links', () => { class="PlaygroundEditorTheme__link PlaygroundEditorTheme__ltr" dir="ltr" href="https://" - rel="noopener"> + rel="noreferrer"> hello @@ -431,6 +438,7 @@ test.describe('Links', () => { // link await click(page, '.link'); + await click(page, '.link-confirm'); await assertHTML( page, @@ -443,7 +451,7 @@ test.describe('Links', () => { class="PlaygroundEditorTheme__link PlaygroundEditorTheme__ltr" dir="ltr" href="https://" - rel="noopener"> + rel="noreferrer"> abc def @@ -464,7 +472,7 @@ test.describe('Links', () => { class="PlaygroundEditorTheme__link PlaygroundEditorTheme__ltr" dir="ltr" href="https://" - rel="noopener"> + rel="noreferrer"> ab

@@ -475,7 +483,7 @@ test.describe('Links', () => { class="PlaygroundEditorTheme__link PlaygroundEditorTheme__ltr" dir="ltr" href="https://" - rel="noopener"> + rel="noreferrer"> c def @@ -496,14 +504,14 @@ test.describe('Links', () => { class="PlaygroundEditorTheme__link PlaygroundEditorTheme__ltr" dir="ltr" href="https://" - rel="noopener"> + rel="noreferrer"> ab + rel="noreferrer"> c def @@ -521,6 +529,7 @@ test.describe('Links', () => { // link await click(page, '.link'); + await click(page, '.link-confirm'); await assertHTML( page, @@ -531,7 +540,7 @@ test.describe('Links', () => { class="PlaygroundEditorTheme__link PlaygroundEditorTheme__ltr" dir="ltr" href="https://" - rel="noopener"> + rel="noreferrer"> abc @@ -564,6 +573,7 @@ test.describe('Links', () => { // link await click(page, '.link'); + await click(page, '.link-confirm'); await selectCharacters(page, 'left', 1); await page.keyboard.type('a'); @@ -591,6 +601,7 @@ test.describe('Links', () => { // link await click(page, '.link'); + await click(page, '.link-confirm'); await selectCharacters(page, 'right', 1); await page.keyboard.type('a'); @@ -606,7 +617,7 @@ test.describe('Links', () => { class="PlaygroundEditorTheme__link PlaygroundEditorTheme__ltr" dir="ltr" href="https://" - rel="noopener"> + rel="noreferrer"> a a @@ -615,6 +626,491 @@ test.describe('Links', () => { ); }); + test.describe('Inserting text either side of links', () => { + // In each of the pasting tests, we'll paste the letter 'x' in a different + // clipboard data format. + const clipboardData = { + html: {'text/html': 'x'}, + lexical: { + 'application/x-lexical-editor': JSON.stringify({ + namespace: 'Playground', + nodes: [ + { + detail: 0, + format: 0, + mode: 'normal', + style: '', + text: 'x', + type: 'text', + version: 1, + }, + ], + }), + }, + plain: {'text/plain': 'x'}, + }; + + test.describe('Inserting text before links', () => { + test.describe('Start-of-paragraph links', () => { + /** + * @param {import('@playwright/test').Page} page + * @param {'type' | 'paste:plain' | 'paste:html' | 'paste:lexical'} insertMethod + */ + const setup = async (page, insertMethod) => { + await focusEditor(page); + await page.keyboard.type('ab'); + + // Turn 'a' into a link + await moveLeft(page, 'b'.length); + await selectCharacters(page, 'left', 1); + await click(page, '.link'); + await click(page, '.link-confirm'); + + // Insert a character directly before the link + await moveLeft(page, 1); + if (insertMethod === 'type') { + await page.keyboard.type('x'); + } else { + const data = + insertMethod === 'paste:plain' + ? clipboardData.plain + : insertMethod === 'paste:html' + ? clipboardData.html + : clipboardData.lexical; + await pasteFromClipboard(page, data); + } + + // The character should be inserted before the link + await assertHTML( + page, + html` +

+ x + + a + + b +

+ `, + ); + }; + + test(`Can insert text before a start-of-paragraph link, via typing`, async ({ + page, + }) => { + await setup(page, 'type'); + }); + + test(`Can insert text before a start-of-paragraph link, via pasting plain text`, async ({ + page, + }) => { + await setup(page, 'paste:plain'); + }); + + // TODO: https://github.com/facebook/lexical/issues/4295 + test.skip(`Can insert text before a start-of-paragraph link, via pasting HTML`, async ({ + page, + }) => { + await setup(page, 'paste:html'); + }); + + // TODO: https://github.com/facebook/lexical/issues/4295 + test.skip(`Can insert text before a start-of-paragraph link, via pasting Lexical text`, async ({ + page, + }) => { + await setup(page, 'paste:lexical'); + }); + }); + + test.describe('Mid-paragraph links', () => { + /** + * @param {import('@playwright/test').Page} page + * @param {'type' | 'paste:plain' | 'paste:html' | 'paste:lexical'} insertMethod + */ + const setup = async (page, insertMethod) => { + await focusEditor(page); + await page.keyboard.type('abc'); + + // Turn 'b' into a link + await moveLeft(page, 1); + await selectCharacters(page, 'left', 1); + await click(page, '.link'); + await click(page, '.link-confirm'); + + // Insert a character directly before the link + await moveLeft(page, 1); + if (insertMethod === 'type') { + await page.keyboard.type('x'); + } else { + const data = + insertMethod === 'paste:plain' + ? clipboardData.plain + : insertMethod === 'paste:html' + ? clipboardData.html + : clipboardData.lexical; + await pasteFromClipboard(page, data); + } + + // The character should be inserted before the link + await assertHTML( + page, + html` +

+ ax + + b + + c +

+ `, + ); + }; + + test(`Can insert text before a mid-paragraph link, via typing`, async ({ + page, + }) => { + await setup(page, 'type'); + }); + + test(`Can insert text before a mid-paragraph link, via pasting plain text`, async ({ + page, + }) => { + await setup(page, 'paste:plain'); + }); + + test(`Can insert text before a mid-paragraph link, via pasting HTML`, async ({ + page, + }) => { + await setup(page, 'paste:html'); + }); + + test(`Can insert text before a mid-paragraph link, via pasting Lexical text`, async ({ + page, + }) => { + await setup(page, 'paste:lexical'); + }); + }); + + test.describe('End-of-paragraph links', () => { + /** + * @param {import('@playwright/test').Page} page + * @param {'type' | 'paste:plain' | 'paste:html' | 'paste:lexical'} insertMethod + */ + const setup = async (page, insertMethod) => { + await focusEditor(page); + await page.keyboard.type('ab'); + + // Turn 'b' into a link + await selectCharacters(page, 'left', 1); + await click(page, '.link'); + await click(page, '.link-confirm'); + + // Insert a character directly before the link + await moveLeft(page, 1); + if (insertMethod === 'type') { + await page.keyboard.type('x'); + } else { + const data = + insertMethod === 'paste:plain' + ? clipboardData.plain + : insertMethod === 'paste:html' + ? clipboardData.html + : clipboardData.lexical; + await pasteFromClipboard(page, data); + } + + // The character should be inserted before the link + await assertHTML( + page, + html` +

+ ax + + b + +

+ `, + ); + }; + + test(`Can insert text before an end-of-paragraph link, via typing`, async ({ + page, + }) => { + await setup(page, 'type'); + }); + + test(`Can insert text before an end-of-paragraph link, via pasting plain text`, async ({ + page, + }) => { + await setup(page, 'paste:plain'); + }); + + // TODO: https://github.com/facebook/lexical/issues/4295 + test.skip(`Can insert text before an end-of-paragraph link, via pasting HTML`, async ({ + page, + }) => { + await setup(page, 'paste:html'); + }); + + // TODO: https://github.com/facebook/lexical/issues/4295 + test.skip(`Can insert text before an end-of-paragraph link, via pasting Lexical text`, async ({ + page, + }) => { + await setup(page, 'paste:lexical'); + }); + }); + }); + + test.describe('Inserting text after links', () => { + test.describe('Start-of-paragraph links', () => { + /** + * @param {import('@playwright/test').Page} page + * @param {'type' | 'paste:plain' | 'paste:html' | 'paste:lexical'} insertMethod + */ + const setup = async (page, insertMethod) => { + await focusEditor(page); + await page.keyboard.type('ab'); + + // Turn 'a' into a link + await moveLeft(page, 'b'.length); + await selectCharacters(page, 'left', 1); + await click(page, '.link'); + await click(page, '.link-confirm'); + + // Insert a character directly after the link + await moveRight(page, 1); + if (insertMethod === 'type') { + await page.keyboard.type('x'); + } else { + const data = + insertMethod === 'paste:plain' + ? clipboardData.plain + : insertMethod === 'paste:html' + ? clipboardData.html + : clipboardData.lexical; + await pasteFromClipboard(page, data); + } + + // The character should be inserted after the link + await assertHTML( + page, + html` +

+ + a + + xb +

+ `, + ); + }; + + test(`Can insert text after a start-of-paragraph link, via typing`, async ({ + page, + }) => { + await setup(page, 'type'); + }); + + test(`Can insert text after a start-of-paragraph link, via pasting plain text`, async ({ + page, + }) => { + await setup(page, 'paste:plain'); + }); + + // TODO: https://github.com/facebook/lexical/issues/4295 + test.skip(`Can insert text after a start-of-paragraph link, via pasting HTML`, async ({ + page, + }) => { + await setup(page, 'paste:html'); + }); + + // TODO: https://github.com/facebook/lexical/issues/4295 + test.skip(`Can insert text after a start-of-paragraph link, via pasting Lexical text`, async ({ + page, + }) => { + await setup(page, 'paste:lexical'); + }); + }); + + test.describe('Mid-paragraph links', () => { + /** + * @param {import('@playwright/test').Page} page + * @param {'type' | 'paste:plain' | 'paste:html' | 'paste:lexical'} insertMethod + */ + const setup = async (page, insertMethod) => { + await focusEditor(page); + await page.keyboard.type('abc'); + + // Turn 'b' into a link + await moveLeft(page, 1); + await selectCharacters(page, 'left', 1); + await click(page, '.link'); + await click(page, '.link-confirm'); + + // Insert a character directly after the link + await moveRight(page, 1); + if (insertMethod === 'type') { + await page.keyboard.type('x'); + } else { + const data = + insertMethod === 'paste:plain' + ? clipboardData.plain + : insertMethod === 'paste:html' + ? clipboardData.html + : clipboardData.lexical; + await pasteFromClipboard(page, data); + } + + // The character should be inserted after the link + await assertHTML( + page, + html` +

+ a + + b + + xc +

+ `, + ); + }; + + test(`Can insert text after a mid-paragraph link, via typing`, async ({ + page, + }) => { + await setup(page, 'type'); + }); + + test(`Can insert text after a mid-paragraph link, via pasting plain text`, async ({ + page, + }) => { + await setup(page, 'paste:plain'); + }); + + // TODO: https://github.com/facebook/lexical/issues/4295 + test.skip(`Can insert text after a mid-paragraph link, via pasting HTML`, async ({ + page, + }) => { + await setup(page, 'paste:html'); + }); + + // TODO: https://github.com/facebook/lexical/issues/4295 + test.skip(`Can insert text after a mid-paragraph link, via pasting Lexical text`, async ({ + page, + }) => { + await setup(page, 'paste:lexical'); + }); + }); + + test.describe('End-of-paragraph links', () => { + /** + * @param {import('@playwright/test').Page} page + * @param {'type' | 'paste:plain' | 'paste:html' | 'paste:lexical'} insertMethod + */ + const setup = async (page, insertMethod) => { + await focusEditor(page); + await page.keyboard.type('ab'); + + // Turn 'b' into a link + await selectCharacters(page, 'left', 1); + await click(page, '.link'); + await click(page, '.link-confirm'); + + // Insert a character directly after the link + await moveRight(page, 1); + if (insertMethod === 'type') { + await page.keyboard.type('x'); + } else { + const data = + insertMethod === 'paste:plain' + ? clipboardData.plain + : insertMethod === 'paste:html' + ? clipboardData.html + : clipboardData.lexical; + await pasteFromClipboard(page, data); + } + + // The character should be inserted after the link + await assertHTML( + page, + html` +

+ a + + b + + x +

+ `, + ); + }; + + test(`Can insert text after an end-of-paragraph link, via typing`, async ({ + page, + }) => { + await setup(page, 'type'); + }); + + test(`Can insert text after an end-of-paragraph link, via pasting plain text`, async ({ + page, + }) => { + await setup(page, 'paste:plain'); + }); + + // TODO: https://github.com/facebook/lexical/issues/4295 + test.skip(`Can insert text after an end-of-paragraph link, via pasting HTML`, async ({ + page, + }) => { + await setup(page, 'paste:html'); + }); + + // TODO: https://github.com/facebook/lexical/issues/4295 + test.skip(`Can insert text after an end-of-paragraph link, via pasting Lexical text`, async ({ + page, + }) => { + await setup(page, 'paste:lexical'); + }); + }); + }); + }); + test(`Can convert multi-formatted text into a link and then modify text after`, async ({ page, }) => { @@ -677,6 +1173,7 @@ test.describe('Links', () => { // link await click(page, '.link'); + await click(page, '.link-confirm'); await assertHTML( page, @@ -687,7 +1184,7 @@ test.describe('Links', () => { class="PlaygroundEditorTheme__link PlaygroundEditorTheme__ltr" dir="ltr" href="https://" - rel="noopener"> + rel="noreferrer"> abc { class="PlaygroundEditorTheme__link PlaygroundEditorTheme__ltr" dir="ltr" href="https://" - rel="noopener"> + rel="noreferrer"> abc {

`, ); + await assertSelection(page, { anchorOffset: 1, anchorPath: [0, 2, 0], @@ -756,6 +1254,7 @@ test.describe('Links', () => { // Make it a link await click(page, '.link'); + await click(page, '.link-confirm'); await assertHTML( page, @@ -767,7 +1266,7 @@ test.describe('Links', () => { class="PlaygroundEditorTheme__link PlaygroundEditorTheme__ltr" dir="ltr" href="https://" - rel="noopener"> + rel="noreferrer"> ${linkText}

@@ -794,7 +1293,7 @@ test.describe('Links', () => { class="PlaygroundEditorTheme__link PlaygroundEditorTheme__ltr" dir="ltr" href="https://" - rel="noopener"> + rel="noreferrer"> This is the { class="PlaygroundEditorTheme__link PlaygroundEditorTheme__ltr" dir="ltr" href="https://" - rel="noopener"> + rel="noreferrer"> This is the { // Make it a link await click(page, '.link'); + await click(page, '.link-confirm'); await assertHTML( page, @@ -860,7 +1360,7 @@ test.describe('Links', () => { class="PlaygroundEditorTheme__link PlaygroundEditorTheme__ltr" dir="ltr" href="https://" - rel="noopener"> + rel="noreferrer"> ${linkText}

@@ -888,7 +1388,7 @@ test.describe('Links', () => { class="PlaygroundEditorTheme__link PlaygroundEditorTheme__ltr" dir="ltr" href="https://" - rel="noopener"> + rel="noreferrer"> This is a { class="PlaygroundEditorTheme__link PlaygroundEditorTheme__ltr" dir="ltr" href="https://" - rel="noopener"> + rel="noreferrer"> This is the { await selectAll(page); await click(page, '.link'); + await click(page, '.link-confirm'); + await assertHTML( page, `

{ class=\"PlaygroundEditorTheme__link PlaygroundEditorTheme__ltr\" dir=\"ltr\" href=\"https://\" - rel=\"noopener\"> + rel=\"noreferrer\"> A link

`, @@ -966,7 +1468,7 @@ test.describe('Links', () => { class=\"PlaygroundEditorTheme__link PlaygroundEditorTheme__ltr\" dir=\"ltr\" href=\"https://facebook.com\" - rel=\"noopener\"> + rel=\"noreferrer\"> A link

`, @@ -989,6 +1491,8 @@ test.describe('Links', () => { ); await click(page, '.link'); + await click(page, '.link-confirm'); + await assertHTML( page, html` @@ -997,7 +1501,7 @@ test.describe('Links', () => { dir="ltr"> An Awesome Website @@ -1020,7 +1524,7 @@ test.describe('Links', () => { Hey, check this out: An Awesome Website @@ -1053,6 +1557,8 @@ test.describe('Links', () => { await selectCharacters(page, 'left', 'Awesome Website'.length); await click(page, '.link'); + await click(page, '.link-confirm'); + await assertHTML( page, ` @@ -1062,7 +1568,7 @@ test.describe('Links', () => { This is an Awesome Website @@ -1087,7 +1593,7 @@ test.describe('Links', () => { This is an Awesome Website @@ -1121,6 +1627,7 @@ test.describe('Links', () => { // link await click(page, '.link'); + await click(page, '.link-confirm'); await assertHTML( page, @@ -1131,7 +1638,7 @@ test.describe('Links', () => { Hello world @@ -1139,21 +1646,12 @@ test.describe('Links', () => {

`, ); - if (browserName === 'webkit') { - await assertSelection(page, { - anchorOffset: 0, - anchorPath: [0, 1, 0, 0], - focusOffset: 5, - focusPath: [0, 1, 0, 0], - }); - } else { - await assertSelection(page, { - anchorOffset: 6, - anchorPath: [0, 0, 0], - focusOffset: 5, - focusPath: [0, 1, 0, 0], - }); - } + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [0, 1], + focusOffset: 5, + focusPath: [0, 1, 0, 0], + }); await setURL(page, 'facebook.com'); @@ -1166,7 +1664,7 @@ test.describe('Links', () => { Hello
world @@ -1185,7 +1683,7 @@ test.describe('Links', () => { } else { await assertSelection(page, { anchorOffset: 0, - anchorPath: [0, 1], + anchorPath: [0, 1, 0, 0], focusOffset: 5, focusPath: [0, 1, 0, 0], }); @@ -1235,6 +1733,7 @@ test.describe('Links', () => { // link await click(page, '.link'); + await click(page, '.link-confirm'); await assertHTML( page, @@ -1245,7 +1744,7 @@ test.describe('Links', () => { Hello world @@ -1254,19 +1753,19 @@ test.describe('Links', () => { `, ); - if (browserName === 'webkit') { + if (browserName === 'chromium') { await assertSelection(page, { anchorOffset: 5, anchorPath: [0, 1, 0, 0], focusOffset: 0, - focusPath: [0, 1, 0, 0], + focusPath: [0, 1], }); } else { await assertSelection(page, { anchorOffset: 5, anchorPath: [0, 1, 0, 0], - focusOffset: 6, - focusPath: [0, 0, 0], + focusOffset: 0, + focusPath: [0, 1], }); } @@ -1281,7 +1780,7 @@ test.describe('Links', () => { Hello world @@ -1297,12 +1796,19 @@ test.describe('Links', () => { focusOffset: 0, focusPath: [0, 1, 0, 0], }); + } else if (browserName === 'chromium') { + await assertSelection(page, { + anchorOffset: 5, + anchorPath: [0, 1, 0, 0], + focusOffset: 0, + focusPath: [0, 1, 0, 0], + }); } else { await assertSelection(page, { anchorOffset: 5, anchorPath: [0, 1, 0, 0], focusOffset: 0, - focusPath: [0, 1], + focusPath: [0, 1, 0, 0], }); } @@ -1338,6 +1844,7 @@ test.describe('Links', () => { await selectCharacters(page, 'left', 5); await click(page, '.link'); + await click(page, '.link-confirm'); await assertHTML( page, @@ -1348,7 +1855,7 @@ test.describe('Links', () => { Hello world @@ -1371,7 +1878,7 @@ test.describe('Links', () => { Hello world @@ -1397,17 +1904,17 @@ test.describe('Links', () => { page, html`

- + Hello world

- + Hello world

- + Hello world

@@ -1422,6 +1929,7 @@ test.describe('Links', () => { await page.keyboard.type('Hello awesome'); await selectAll(page); await click(page, '.link'); + await click(page, '.link-confirm'); await page.keyboard.press('ArrowRight'); await page.keyboard.type('world'); @@ -1434,12 +1942,12 @@ test.describe('Links', () => { page, html`

- + Hello

- + awesome world @@ -1452,7 +1960,9 @@ test.describe('Links', () => { test('Can handle pressing Enter inside a Link containing multiple TextNodes', async ({ page, + isCollab, }) => { + test.fixme(isCollab && IS_LINUX, 'Flaky on Linux + Collab'); await focusEditor(page); await page.keyboard.type('Hello '); await toggleBold(page); @@ -1461,6 +1971,7 @@ test.describe('Links', () => { await page.keyboard.type('some'); await selectAll(page); await click(page, '.link'); + await click(page, '.link-confirm'); await page.keyboard.press('ArrowRight'); await page.keyboard.type(' world'); @@ -1473,12 +1984,12 @@ test.describe('Links', () => { page, html`

- + Hello

- + awe some @@ -1497,6 +2008,7 @@ test.describe('Links', () => { await page.keyboard.type('Hello awesome'); await selectAll(page); await click(page, '.link'); + await click(page, '.link-confirm'); await page.keyboard.press('ArrowRight'); await page.keyboard.type(' world'); @@ -1508,7 +2020,7 @@ test.describe('Links', () => { html`


- + Hello awesome world @@ -1519,11 +2031,16 @@ test.describe('Links', () => { ); }); - test('Can handle pressing Enter at the end of a Link', async ({page}) => { + test('Can handle pressing Enter at the end of a Link', async ({ + isCollab, + page, + }) => { + test.fixme(true, 'Flaky'); await focusEditor(page); await page.keyboard.type('Hello awesome'); await selectAll(page); await click(page, '.link'); + await click(page, '.link-confirm'); await page.keyboard.press('ArrowRight'); await page.keyboard.type(' world'); @@ -1535,7 +2052,7 @@ test.describe('Links', () => { page, html`

- + Hello awesome

diff --git a/demos/playground/src/__tests__/e2e/List.spec.mjs b/demos/playground/src/__tests__/e2e/List.spec.mjs index 77ced461..7978fb67 100644 --- a/demos/playground/src/__tests__/e2e/List.spec.mjs +++ b/demos/playground/src/__tests__/e2e/List.spec.mjs @@ -28,13 +28,13 @@ import { focusEditor, html, initialize, + IS_LINUX, pasteFromClipboard, repeat, selectFromAlignDropdown, selectFromFormatDropdown, test, waitForSelector, - IS_WINDOWS, } from '../utils/index.mjs'; async function toggleBulletList(page) { @@ -70,93 +70,12 @@ test.beforeEach(({isPlainText}) => { test.describe('Nested List', () => { test.beforeEach(({isCollab, page}) => initialize({isCollab, page})); - test(`Can toggle an empty list on/off`, async ({page}) => { - await focusEditor(page); - - await assertHTML( - page, - html` -


- `, - ); - - await toggleBulletList(page); - - await assertHTML( - page, - '

', - ); - - await toggleBulletList(page); - - await assertHTML( - page, - html` -


- `, - ); - }); - - test.fixme(`Can create a list and indent/outdent it`, async ({page}) => { - await focusEditor(page); - await toggleBulletList(page); - await assertHTML( - page, - '

', - ); - - // Should allow indenting an empty list item - await clickIndentButton(page, 2); - - await assertHTML( - page, - '

', - ); - - // Backspace should "unindent" the first list item. - await page.keyboard.press('Backspace'); - await page.keyboard.press('Backspace'); - - await assertHTML( - page, - '

', - ); - - await page.keyboard.type('Hello'); - await page.keyboard.press('Enter'); - await page.keyboard.type('from'); - await page.keyboard.press('Enter'); - await page.keyboard.type('the'); - await page.keyboard.press('Enter'); - await page.keyboard.type('other'); - await page.keyboard.press('Enter'); - await page.keyboard.type('side'); - - await assertHTML( - page, - '
  • Hello
  • from
  • the
  • other
  • side
', - ); - - await selectAll(page); - - await clickIndentButton(page, 3); - - await assertHTML( - page, - '
        • Hello
        • from
        • the
        • other
        • side
', - ); - - await clickOutdentButton(page, 3); - - await assertHTML( - page, - '
  • Hello
  • from
  • the
  • other
  • side
', - ); - }); test(`Can create a list and partially copy some content out of it`, async ({ page, + isCollab, }) => { + test.fixme(isCollab && IS_LINUX, 'Flaky on Linux + Collab'); await focusEditor(page); await page.keyboard.type( 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam venenatis risus ac cursus efficitur. Cras efficitur magna odio, lacinia posuere mauris placerat in. Etiam eu congue nisl. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nulla vulputate justo id eros convallis, vel pellentesque orci hendrerit. Pellentesque accumsan molestie eros, vitae tempor nisl semper sit amet. Sed vulputate leo dolor, et bibendum quam feugiat eget. Praesent vestibulum libero sed enim ornare, in consequat dui posuere. Maecenas ornare vestibulum felis, non elementum urna imperdiet sit amet.', @@ -207,12 +126,7 @@ test.describe('Nested List', () => { test('Should outdent if indented when the backspace key is pressed', async ({ page, - browserName, - isCollab, }) => { - if (IS_WINDOWS && browserName === 'firefox' && isCollab) { - test.fixme(); - } await focusEditor(page); await toggleBulletList(page); @@ -243,12 +157,7 @@ test.describe('Nested List', () => { test(`Can indent/outdent mutliple list nodes in a list with multiple levels of indentation`, async ({ page, - browserName, - isCollab, }) => { - if (IS_WINDOWS && browserName === 'firefox' && isCollab) { - test.fixme(); - } await focusEditor(page); await toggleBulletList(page); @@ -327,12 +236,7 @@ test.describe('Nested List', () => { test(`Can indent a list with a list item in between nested lists`, async ({ page, - browserName, - isCollab, }) => { - if (IS_WINDOWS && browserName === 'firefox' && isCollab) { - test.fixme(); - } await focusEditor(page); await toggleBulletList(page); await page.keyboard.type('foo'); @@ -511,116 +415,120 @@ test.describe('Nested List', () => { page, html`

+ class="PlaygroundEditorTheme__paragraph PlaygroundEditorTheme__indent PlaygroundEditorTheme__ltr" + dir="ltr" + style="padding-inline-start: calc(120px)"> Hello

+ class="PlaygroundEditorTheme__paragraph PlaygroundEditorTheme__indent PlaygroundEditorTheme__ltr" + dir="ltr" + style="padding-inline-start: calc(120px)"> from

+ class="PlaygroundEditorTheme__paragraph PlaygroundEditorTheme__indent PlaygroundEditorTheme__ltr" + dir="ltr" + style="padding-inline-start: calc(120px)"> the

+ class="PlaygroundEditorTheme__paragraph PlaygroundEditorTheme__indent PlaygroundEditorTheme__ltr" + dir="ltr" + style="padding-inline-start: calc(120px)"> other

+ class="PlaygroundEditorTheme__paragraph PlaygroundEditorTheme__indent PlaygroundEditorTheme__ltr" + dir="ltr" + style="padding-inline-start: calc(120px)"> side

`, ); }); - test.fixme( - `Can create a list containing inline blocks and then toggle it back to original state.`, - async ({page}) => { - await focusEditor(page); - - await assertHTML( - page, - html` -


- `, - ); - - await page.keyboard.type('One two three'); - - await assertHTML( - page, - html` -

- One two three -

- `, - ); - - await moveLeft(page, 6); - await selectCharacters(page, 'left', 3); - - // link - await click(page, '.link'); - - await assertHTML( - page, - html` -

{ + await focusEditor(page); + + await assertHTML( + page, + html` +


+ `, + ); + + await page.keyboard.type('One two three'); + + await assertHTML( + page, + html` +

+ One two three +

+ `, + ); + + await moveLeft(page, 6); + await selectCharacters(page, 'left', 3); + + // link + await click(page, '.link'); + + await assertHTML( + page, + html` +

+ One + - One - - two - - three -

- `, - ); - - // move to end of paragraph to close the floating link bar - await moveToParagraphEnd(page); - - await toggleBulletList(page); - - await assertHTML( - page, - '', - ); - - await toggleBulletList(page); - - await assertHTML( - page, - html` -

two + + three +

+ `, + ); + + // move to end of paragraph to close the floating link bar + await moveToParagraphEnd(page); + + await toggleBulletList(page); + + await assertHTML( + page, + '', + ); + + await toggleBulletList(page); + + await assertHTML( + page, + html` +

+ One + - One - - two - - three -

- `, - ); - }, - ); + two + + three +

+ `, + ); + }); test(`Can create mutliple bullet lists and then toggle off the list.`, async ({ page, @@ -709,12 +617,7 @@ test.describe('Nested List', () => { test(`Can create an unordered list and convert it to an ordered list `, async ({ page, - browserName, - isCollab, }) => { - if (IS_WINDOWS && browserName === 'firefox' && isCollab) { - test.fixme(); - } await focusEditor(page); await waitForSelector(page, '.block-controls'); @@ -743,12 +646,7 @@ test.describe('Nested List', () => { test(`Can create a single item unordered list with text and convert it to an ordered list `, async ({ page, - browserName, - isCollab, }) => { - if (IS_WINDOWS && browserName === 'firefox' && isCollab) { - test.fixme(); - } await focusEditor(page); await toggleBulletList(page); @@ -772,12 +670,7 @@ test.describe('Nested List', () => { test(`Can create a multi-line unordered list and convert it to an ordered list `, async ({ page, - browserName, - isCollab, }) => { - if (IS_WINDOWS && browserName === 'firefox' && isCollab) { - test.fixme(); - } await focusEditor(page); await toggleBulletList(page); @@ -814,12 +707,7 @@ test.describe('Nested List', () => { test(`Can create a multi-line unordered list and convert it to an ordered list when no nodes are in the selection`, async ({ page, - browserName, - isCollab, }) => { - if (IS_WINDOWS && browserName === 'firefox' && isCollab) { - test.fixme(); - } await focusEditor(page); await toggleBulletList(page); @@ -855,12 +743,7 @@ test.describe('Nested List', () => { test(`Can create an indented multi-line unordered list and convert it to an ordered list `, async ({ page, - browserName, - isCollab, }) => { - if (IS_WINDOWS && browserName === 'firefox' && isCollab) { - test.fixme(); - } await focusEditor(page); await toggleBulletList(page); @@ -903,12 +786,7 @@ test.describe('Nested List', () => { test(`Can create an indented multi-line unordered list and convert individual lists in the nested structure to a numbered list. `, async ({ page, - browserName, - isCollab, }) => { - if (IS_WINDOWS && browserName === 'firefox' && isCollab) { - test.fixme(); - } await focusEditor(page); await toggleBulletList(page); @@ -1056,11 +934,7 @@ test.describe('Nested List', () => { test(`Should NOT merge selected nodes into existing list siblings of a different type when formatting to a list`, async ({ page, isCollab, - browserName, }) => { - if (IS_WINDOWS && browserName === 'firefox' && isCollab) { - test.fixme(); - } await focusEditor(page); // - Hello @@ -1107,45 +981,38 @@ test.describe('Nested List', () => { ); }); - test.fixme( - `Should create list with start number markdown`, - async ({page, isCollab}) => { - await focusEditor(page); - // Trigger markdown using 321 digits followed by "." and a trigger of " ". - await page.keyboard.type('321. '); - - // forward case is the normal case. - // undo case is when the user presses undo. - - const forwardHTML = - '

'; - - const undoHTML = html` -

- 321. -

- `; - - await assertHTML(page, forwardHTML); - if (isCollab) { - // Collab uses its own undo/redo - return; - } - await undo(page); - await assertHTML(page, undoHTML); - await redo(page); - await assertHTML(page, forwardHTML); - }, - ); - - test(`Should not process paragraph markdown inside list.`, async ({ + test(`Should create list with start number markdown`, async ({ page, - browserName, isCollab, }) => { - if (IS_WINDOWS && browserName === 'firefox' && isCollab) { - test.fixme(); + await focusEditor(page); + // Trigger markdown using 321 digits followed by "." and a trigger of " ". + await page.keyboard.type('321. '); + + // forward case is the normal case. + // undo case is when the user presses undo. + + const forwardHTML = + '

'; + + const undoHTML = html` +

+ 321. +

+ `; + + await assertHTML(page, forwardHTML); + if (isCollab) { + // Collab uses its own undo/redo + return; } + await undo(page); + await assertHTML(page, undoHTML); + await redo(page); + await assertHTML(page, forwardHTML); + }); + + test(`Should not process paragraph markdown inside list.`, async ({page}) => { await focusEditor(page); await toggleBulletList(page); @@ -1158,12 +1025,7 @@ test.describe('Nested List', () => { test(`Un-indents list empty list items when the user presses enter`, async ({ page, - browserName, - isCollab, }) => { - if (IS_WINDOWS && browserName === 'firefox' && isCollab) { - test.fixme(); - } await focusEditor(page); await toggleBulletList(page); await page.keyboard.type('a'); @@ -1189,12 +1051,7 @@ test.describe('Nested List', () => { test(`Converts a List with one ListItem to a Paragraph when Normal is selected in the format menu`, async ({ page, - browserName, - isCollab, }) => { - if (IS_WINDOWS && browserName === 'firefox' && isCollab) { - test.fixme(); - } await focusEditor(page); await toggleBulletList(page); await page.keyboard.type('a'); @@ -1223,12 +1080,7 @@ test.describe('Nested List', () => { test(`Converts the last ListItem in a List with multiple ListItem to a Paragraph when Normal is selected in the format menu`, async ({ page, - browserName, - isCollab, }) => { - if (IS_WINDOWS && browserName === 'firefox' && isCollab) { - test.fixme(); - } await focusEditor(page); await toggleBulletList(page); await page.keyboard.type('a'); @@ -1267,12 +1119,7 @@ test.describe('Nested List', () => { test(`Converts the middle ListItem in a List with multiple ListItem to a Paragraph when Normal is selected in the format menu`, async ({ page, - browserName, - isCollab, }) => { - if (IS_WINDOWS && browserName === 'firefox' && isCollab) { - test.fixme(); - } await focusEditor(page); await toggleBulletList(page); await page.keyboard.type('a'); @@ -1322,12 +1169,7 @@ test.describe('Nested List', () => { test('Can create check list, toggle it to bullet-list and back', async ({ page, - browserName, - isCollab, }) => { - if (IS_WINDOWS && browserName === 'firefox' && isCollab) { - test.fixme(); - } await focusEditor(page); await toggleCheckList(page); await page.keyboard.type('a'); @@ -1465,11 +1307,7 @@ test.describe('Nested List', () => { test('can navigate and check/uncheck with keyboard', async ({ page, isCollab, - browserName, }) => { - if (IS_WINDOWS && browserName === 'firefox' && isCollab) { - test.fixme(); - } await focusEditor(page); await toggleCheckList(page); // @@ -1529,7 +1367,7 @@ test.describe('Nested List', () => { await assertCheckCount(3, 3); }); - test.fixme('replaces existing element node', async ({page}) => { + test('replaces existing element node', async ({page}) => { // Create two quote blocks, select it and format to a list // should replace quotes (instead of moving quotes into the list items) await focusEditor(page); @@ -1577,7 +1415,11 @@ test.describe('Nested List', () => { dir="ltr"> Hello World

-


+

+
+

`, ); await page.pause(); @@ -1612,17 +1454,27 @@ test.describe('Nested List', () => { dir="ltr"> Hello World

+
    +
  • + a +
  • +

- a -

-


-

- b + class="PlaygroundEditorTheme__paragraph PlaygroundEditorTheme__indent" + style="padding-inline-start: calc(40px)"> +

+
    +
  • + b +
  • +
`, ); await page.pause(); diff --git a/demos/playground/src/__tests__/e2e/ListMaxIndentLevel.spec.mjs b/demos/playground/src/__tests__/e2e/ListMaxIndentLevel.spec.mjs index 85a2a049..fe37a84b 100644 --- a/demos/playground/src/__tests__/e2e/ListMaxIndentLevel.spec.mjs +++ b/demos/playground/src/__tests__/e2e/ListMaxIndentLevel.spec.mjs @@ -30,7 +30,6 @@ async function clickIndentButton(page, times = 1) { const MAX_INDENT_LEVEL = 6; test.describe('Nested List', () => { - test.fixme(); test.beforeEach(({isCollab, page}) => initialize({isCollab, page})); test(`Can only indent until the max depth when list is empty`, async ({ page, diff --git a/demos/playground/src/__tests__/e2e/Markdown.spec.mjs b/demos/playground/src/__tests__/e2e/Markdown.spec.mjs index 051a690e..d69c3eef 100644 --- a/demos/playground/src/__tests__/e2e/Markdown.spec.mjs +++ b/demos/playground/src/__tests__/e2e/Markdown.spec.mjs @@ -19,7 +19,6 @@ import { assertSelection, clearEditor, click, - E2E_BROWSER, focusEditor, getHTML, html, @@ -51,7 +50,6 @@ async function checkHTMLExpectationsIncludingUndoRedo( } test.describe('Markdown', () => { - test.fixme(); test.beforeEach(({isCollab, page}) => initialize({isCollab, page})); const triggersAndExpectations = [ { @@ -329,28 +327,27 @@ test.describe('Markdown', () => { } if (triggersAndExpectations[i].markdownImport.length > 0) { - test(`Should test importing markdown (${markdownText}) trigger.`, async ({ - page, - isPlainText, - isCollab, - }) => { - test.skip(isPlainText); - await focusEditor(page); - - await page.keyboard.type( - '```markdown ' + triggersAndExpectations[i].markdownImport, - ); - await click(page, 'i.markdown'); + test.fixme( + `Should test importing markdown (${markdownText}) trigger.`, + async ({page, isPlainText, isCollab}) => { + test.skip(isPlainText); + await focusEditor(page); + + await page.keyboard.type( + '```markdown ' + triggersAndExpectations[i].markdownImport, + ); + await click(page, 'i.markdown'); - const htmlInner = triggersAndExpectations[i].importExpectation; - await assertHTML(page, htmlInner); + const htmlInner = triggersAndExpectations[i].importExpectation; + await assertHTML(page, htmlInner); - // Click on markdow toggle twice to run import -> export loop and then - // validate that it's the same rich text after full cycle - await click(page, 'i.markdown'); - await click(page, 'i.markdown'); - await assertHTML(page, htmlInner); - }); + // Click on markdow toggle twice to run import -> export loop and then + // validate that it's the same rich text after full cycle + await click(page, 'i.markdown'); + await click(page, 'i.markdown'); + await assertHTML(page, htmlInner); + }, + ); } } }); @@ -379,7 +376,7 @@ async function assertMarkdownImportExport( test.describe('Markdown', () => { test.beforeEach(({isCollab, isPlainText, page}) => { test.skip(isPlainText); - initialize({isCollab, page}); + return initialize({isCollab, page}); }); const BASE_BLOCK_SHORTCUTS = [ @@ -657,29 +654,27 @@ test.describe('Markdown', () => { ]; BASE_BLOCK_SHORTCUTS.forEach((testCase) => { - test.fixme( - `can convert "${testCase.text}" shortcut`, - async ({page, isCollab}) => { - await focusEditor(page); - await page.keyboard.type(testCase.text); - await assertHTML(page, testCase.html, undefined, {ignoreClasses: true}); + test(`can convert "${testCase.text}" shortcut`, async ({ + page, + isCollab, + }) => { + await focusEditor(page); + await page.keyboard.type(testCase.text); + await assertHTML(page, testCase.html, undefined, {ignoreClasses: true}); - if (!isCollab) { - const escapedText = testCase.text.replace('>', '>'); - await undo(page); - await assertHTML( - page, - `

${escapedText}

`, - undefined, - {ignoreClasses: true}, - ); - await redo(page); - await assertHTML(page, testCase.html, undefined, { - ignoreClasses: true, - }); - } - }, - ); + if (!isCollab) { + const escapedText = testCase.text.replace('>', '>'); + await undo(page); + await assertHTML( + page, + `

${escapedText}

`, + undefined, + {ignoreClasses: true}, + ); + await redo(page); + await assertHTML(page, testCase.html, undefined, {ignoreClasses: true}); + } + }); }); SIMPLE_TEXT_FORMAT_SHORTCUTS.forEach((testCase) => { @@ -709,131 +704,127 @@ test.describe('Markdown', () => { }); }); - test.fixme( - 'can undo/redo nested transformations', - async ({page, isCollab}) => { - await focusEditor(page); - await page.keyboard.type('~~_**hello world**_~~'); + test('can undo/redo nested transformations', async ({page, isCollab}) => { + await focusEditor(page); + await page.keyboard.type('~~_**hello world**_~~'); + + const BOLD_ITALIC_STRIKETHROUGH = html` +

+ + hello world + +

+ `; + const BOLD_ITALIC = html` +

+ ~~ + + hello world + + ~~ +

+ `; + const BOLD = html` +

+ ~~_ + + hello world + + _ +

+ `; + const PLAIN = html` +

+ ~~_**hello world** +

+ `; + + await assertHTML(page, BOLD_ITALIC_STRIKETHROUGH); + + if (isCollab) { + return; + } + + await undo(page); // Undo last transformation + await assertHTML(page, BOLD_ITALIC); + await undo(page); // Undo transformation & its text typing + await undo(page); + await assertHTML(page, BOLD); + await undo(page); // Undo transformation & its text typing + await undo(page); + await assertHTML(page, PLAIN); + await redo(page); // Redo transformation & its text typing + await redo(page); + await assertHTML(page, BOLD); + await redo(page); // Redo transformation & its text typing + await redo(page); + await assertHTML(page, BOLD_ITALIC); + await redo(page); // Redo transformation + await assertHTML(page, BOLD_ITALIC_STRIKETHROUGH); + }); - const BOLD_ITALIC_STRIKETHROUGH = html` + test('can convert already styled text (overlapping ranges)', async ({ + page, + }) => { + // type partially bold/underlined text, add opening markdown tag within bold/underline part + // and add closing within plain text + await focusEditor(page); + await pressToggleBold(page); + await pressToggleUnderline(page); + await page.keyboard.type('h*e~~llo'); + await pressToggleBold(page); + await pressToggleUnderline(page); + await page.keyboard.type(' wo~~r*ld'); + await assertHTML( + page, + html`

- hello world + h -

- `; - const BOLD_ITALIC = html` -

- ~~ - hello world + e - ~~ -

- `; - const BOLD = html` -

- ~~_ - hello world + llo - _ -

- `; - const PLAIN = html` -

- ~~_**hello world** + + wo + + + r + + ld

- `; - - await assertHTML(page, BOLD_ITALIC_STRIKETHROUGH); - - if (isCollab) { - return; - } - - await undo(page); // Undo last transformation - await assertHTML(page, BOLD_ITALIC); - await undo(page); // Undo transformation & its text typing - await undo(page); - await assertHTML(page, BOLD); - await undo(page); // Undo transformation & its text typing - await undo(page); - await assertHTML(page, PLAIN); - await redo(page); // Redo transformation & its text typing - await redo(page); - await assertHTML(page, BOLD); - await redo(page); // Redo transformation & its text typing - await redo(page); - await assertHTML(page, BOLD_ITALIC); - await redo(page); // Redo transformation - await assertHTML(page, BOLD_ITALIC_STRIKETHROUGH); - }, - ); - - test.fixme( - 'can convert already styled text (overlapping ranges)', - async ({page}) => { - // type partially bold/underlined text, add opening markdown tag within bold/underline part - // and add closing within plain text - await focusEditor(page); - await pressToggleBold(page); - await pressToggleUnderline(page); - await page.keyboard.type('h*e~~llo'); - await pressToggleBold(page); - await pressToggleUnderline(page); - await page.keyboard.type(' wo~~r*ld'); - await assertHTML( - page, - html` -

- - h - - - e - - - llo - - - wo - - - r - - ld -

- `, - ); - }, - ); + `, + ); + }); test.fixme( 'can convert markdown text into rich text', @@ -867,13 +858,13 @@ test.describe('Markdown', () => { }, ); - test.fixme('can type text with markdown', async ({page}) => { + test('can type text with markdown', async ({page}) => { await focusEditor(page); await page.keyboard.type(TYPED_MARKDOWN); await assertHTML(page, TYPED_MARKDOWN_HTML); }); - test.fixme('itraword text format', async ({page}) => { + test('intraword text format', async ({page}) => { await focusEditor(page); await page.keyboard.type('he_llo_ world'); await assertHTML( @@ -970,52 +961,103 @@ test.describe('Markdown', () => { }); test.fixme( - 'can adjust selection after text match transformer', + 'can import several text match transformers in a same line (#5385)', async ({page}) => { await focusEditor(page); - await page.keyboard.type('Hello world'); - await moveLeft(page, 6); - await page.keyboard.type('[link](https://lexical.dev)'); + await page.keyboard.type( + '```markdown [link](https://lexical.dev)[link](https://lexical.dev)![Yellow flower in tilt shift lens](' + + SAMPLE_IMAGE_URL + + ')just text in between$1$', + ); + await click(page, '.action-button .markdown'); + await waitForSelector(page, '.editor-image img'); + await waitForSelector(page, '.editor-equation'); await assertHTML( page, html`

- Hello + dir="ltr" + href="https://lexical.dev"> link - world + + link + + +

+ Yellow flower in tilt shift lens +
+ + just text in between + + + + + + + + + +

`, ); - // Selection starts after newly created link element - - if (E2E_BROWSER === 'webkit') { - // TODO: safari keeps dom selection on newly inserted link although Lexical's selection - // is correctly adjusted to start on [ world] text node. #updateDomSelection calls - // selection.setBaseAndExtent correctly, but safari does not seem to sync dom selection - // to newly passed values of anchor/focus/offset - await assertSelection(page, { - anchorOffset: 4, - anchorPath: [0, 1, 0, 0], - focusOffset: 4, - focusPath: [0, 1, 0, 0], - }); - } else { - await assertSelection(page, { - anchorOffset: 0, - anchorPath: [0, 2, 0], - focusOffset: 0, - focusPath: [0, 2, 0], - }); - } }, ); + + test('can adjust selection after text match transformer', async ({page}) => { + await focusEditor(page); + await page.keyboard.type('Hello world'); + await moveLeft(page, 6); + await page.keyboard.type('[link](https://lexical.dev)'); + await assertHTML( + page, + html` +

+ Hello + + link + + world +

+ `, + ); + // Selection starts after newly created link element + + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [0, 2, 0], + focusOffset: 0, + focusPath: [0, 2, 0], + }); + }); }); const TYPED_MARKDOWN = `# Markdown Shortcuts @@ -1207,6 +1249,7 @@ line after 1. And can be nested and multiline as well +. 31. Have any starting number ### Inline code Inline \`code\` format which also \`preserves **_~~any markdown-like~~_** text\` within @@ -1374,15 +1417,15 @@ const IMPORTED_MARKDOWN_HTML = html` class="PlaygroundEditorTheme__listItem PlaygroundEditorTheme__ltr" dir="ltr"> Create a list with - + + , - + - , or - + * @@ -1424,7 +1467,7 @@ const IMPORTED_MARKDOWN_HTML = html` Oredered lists started with numbers as - + 1. @@ -1443,6 +1486,9 @@ const IMPORTED_MARKDOWN_HTML = html` +

+ . +

  1. Inline - + code format which also - + preserves **_~~any markdown-like~~_** text diff --git a/demos/playground/src/__tests__/e2e/MaxLength.spec.mjs b/demos/playground/src/__tests__/e2e/MaxLength.spec.mjs index ee7ef529..c2e52b48 100644 --- a/demos/playground/src/__tests__/e2e/MaxLength.spec.mjs +++ b/demos/playground/src/__tests__/e2e/MaxLength.spec.mjs @@ -18,12 +18,11 @@ import { } from '../utils/index.mjs'; test.describe('MaxLength', () => { - test.fixme(); test.use({isMaxLength: true}); test.beforeEach(({isCollab, isMaxLength, page}) => initialize({isCollab, isMaxLength, page}), ); - test(`can restrict the text to specified length`, async ({page}) => { + test.fixme(`can restrict the text to specified length`, async ({page}) => { await focusEditor(page); await page.keyboard.type( @@ -130,4 +129,42 @@ test.describe('MaxLength', () => { `, ); }); + + test(`paste with empty paragraph in between #3773`, async ({page}) => { + await focusEditor(page); + await pasteFromClipboard(page, { + 'text/plain': + 'lorem ipsum dolor sit amet, consectetuer \n\nadipiscing elit\n\n', + }); + + await assertHTML( + page, + html` +

    + lorem ipsum dolor sit amet, co +

    + `, + ); + }); + + test(`paste with empty paragraph at end #3773`, async ({page}) => { + await focusEditor(page); + await pasteFromClipboard(page, { + 'text/plain': + 'lorem ipsum dolor sit amet, consectetuer adipiscing elit\n\n', + }); + + await assertHTML( + page, + html` +

    + lorem ipsum dolor sit amet, co +

    + `, + ); + }); }); diff --git a/demos/playground/src/__tests__/e2e/Mentions.spec.mjs b/demos/playground/src/__tests__/e2e/Mentions.spec.mjs index c6891b96..a4cbc517 100644 --- a/demos/playground/src/__tests__/e2e/Mentions.spec.mjs +++ b/demos/playground/src/__tests__/e2e/Mentions.spec.mjs @@ -28,22 +28,26 @@ test.describe('Mentions', () => { test(`Can enter the Luke Skywalker mention`, async ({page}) => { await focusEditor(page); - await page.keyboard.type('Luke'); + await page.keyboard.type('@Luke'); await assertSelection(page, { - anchorOffset: 4, + anchorOffset: 5, anchorPath: [0, 0, 0], - focusOffset: 4, + focusOffset: 5, focusPath: [0, 0, 0], }); - await waitForSelector(page, '#typeahead-menu ul li'); + await waitForSelector( + page, + '#typeahead-menu ul li:has-text("Luke Skywalker")', + ); + await assertHTML( page, html`

    - Luke + @Luke

    `, ); @@ -100,22 +104,26 @@ test.describe('Mentions', () => { page, }) => { await focusEditor(page); - await page.keyboard.type('Luke'); + await page.keyboard.type('@Luke'); await assertSelection(page, { - anchorOffset: 4, + anchorOffset: 5, anchorPath: [0, 0, 0], - focusOffset: 4, + focusOffset: 5, focusPath: [0, 0, 0], }); - await waitForSelector(page, '#typeahead-menu ul li'); + await waitForSelector( + page, + '#typeahead-menu ul li:has-text("Luke Skywalker")', + ); + await assertHTML( page, html`

    - Luke + @Luke

    `, ); @@ -191,22 +199,26 @@ test.describe('Mentions', () => { page, }) => { await focusEditor(page); - await page.keyboard.type('Luke'); + await page.keyboard.type('@Luke'); await assertSelection(page, { - anchorOffset: 4, + anchorOffset: 5, anchorPath: [0, 0, 0], - focusOffset: 4, + focusOffset: 5, focusPath: [0, 0, 0], }); - await waitForSelector(page, '#typeahead-menu ul li'); + await waitForSelector( + page, + '#typeahead-menu ul li:has-text("Luke Skywalker")', + ); + await assertHTML( page, html`

    - Luke + @Luke

    `, ); @@ -263,22 +275,26 @@ test.describe('Mentions', () => { page, }) => { await focusEditor(page); - await page.keyboard.type('Luke'); + await page.keyboard.type('@Luke'); await assertSelection(page, { - anchorOffset: 4, + anchorOffset: 5, anchorPath: [0, 0, 0], - focusOffset: 4, + focusOffset: 5, focusPath: [0, 0, 0], }); - await waitForSelector(page, '#typeahead-menu ul li'); + await waitForSelector( + page, + '#typeahead-menu ul li:has-text("Luke Skywalker")', + ); + await assertHTML( page, html`

    - Luke + @Luke

    `, ); @@ -335,22 +351,26 @@ test.describe('Mentions', () => { page, }) => { await focusEditor(page); - await page.keyboard.type('Luke'); + await page.keyboard.type('@Luke'); await assertSelection(page, { - anchorOffset: 4, + anchorOffset: 5, anchorPath: [0, 0, 0], - focusOffset: 4, + focusOffset: 5, focusPath: [0, 0, 0], }); - await waitForSelector(page, '#typeahead-menu ul li'); + await waitForSelector( + page, + '#typeahead-menu ul li:has-text("Luke Skywalker")', + ); + await assertHTML( page, html`

    - Luke + @Luke

    `, ); @@ -436,16 +456,20 @@ test.describe('Mentions', () => { focusPath: [0, 0, 0], }); - await page.keyboard.type('Luke'); + await page.keyboard.type('@Luke'); + + await waitForSelector( + page, + '#typeahead-menu ul li:has-text("Luke Skywalker")', + ); - await waitForSelector(page, '#typeahead-menu ul li'); await assertHTML( page, html`

    - abc Luke def + abc @Luke def

    `, ); @@ -506,42 +530,54 @@ test.describe('Mentions', () => { browserName, }) => { await focusEditor(page); - await page.keyboard.type('Luke'); + await page.keyboard.type('@Luke'); await assertSelection(page, { - anchorOffset: 4, + anchorOffset: 5, anchorPath: [0, 0, 0], - focusOffset: 4, + focusOffset: 5, focusPath: [0, 0, 0], }); - await waitForSelector(page, '#typeahead-menu ul li'); + await waitForSelector( + page, + '#typeahead-menu ul li:has-text("Luke Skywalker")', + ); await page.keyboard.press('Enter'); await waitForSelector(page, '.mention'); await page.keyboard.type(' '); - await page.keyboard.type('Luke'); + await page.keyboard.type('@Luke'); - await waitForSelector(page, '#typeahead-menu ul li'); + await waitForSelector( + page, + '#typeahead-menu ul li:has-text("Luke Skywalker")', + ); await page.keyboard.press('Enter'); await waitForSelector(page, '.mention:nth-child(1)'); await page.keyboard.type(' '); - await page.keyboard.type('Luke'); + await page.keyboard.type('@Luke'); - await waitForSelector(page, '#typeahead-menu ul li'); + await waitForSelector( + page, + '#typeahead-menu ul li:has-text("Luke Skywalker")', + ); await page.keyboard.press('Enter'); await waitForSelector(page, '.mention:nth-child(3)'); await page.keyboard.type(' '); - await page.keyboard.type('Luke'); + await page.keyboard.type('@Luke'); - await waitForSelector(page, '#typeahead-menu ul li'); + await waitForSelector( + page, + '#typeahead-menu ul li:has-text("Luke Skywalker")', + ); await page.keyboard.press('Enter'); await waitForSelector(page, '.mention:nth-child(5)'); @@ -815,15 +851,18 @@ test.describe('Mentions', () => { page, }) => { await focusEditor(page); - await page.keyboard.type('Luke'); + await page.keyboard.type('@Luke'); await assertSelection(page, { - anchorOffset: 4, + anchorOffset: 5, anchorPath: [0, 0, 0], - focusOffset: 4, + focusOffset: 5, focusPath: [0, 0, 0], }); - await waitForSelector(page, '#typeahead-menu ul li'); + await waitForSelector( + page, + '#typeahead-menu ul li:has-text("Luke Skywalker")', + ); await page.keyboard.press('Enter'); await waitForSelector(page, '.mention'); diff --git a/demos/playground/src/__tests__/e2e/Mutations.spec.mjs b/demos/playground/src/__tests__/e2e/Mutations.spec.mjs index caa7dd30..362ca155 100644 --- a/demos/playground/src/__tests__/e2e/Mutations.spec.mjs +++ b/demos/playground/src/__tests__/e2e/Mutations.spec.mjs @@ -49,238 +49,199 @@ async function validateContent(page) { test.describe('Mutations', () => { test.beforeEach(({isCollab, page}) => initialize({isCollab, page})); - test.fixme( - `Can restore the DOM to the editor state state`, - async ({page}) => { - await focusEditor(page); - await page.keyboard.type( - 'Hello #world. This content #should remain #intact.', - ); - - await validateContent(page); - - // Remove the paragraph - await await evaluate(page, () => { - const rootElement = document.querySelector( - 'div[contenteditable="true"]', - ); - const paragraph = rootElement.firstChild; - - paragraph.remove(); - }); - await validateContent(page); - - // Remove the paragraph content - await await evaluate(page, () => { - const rootElement = document.querySelector( - 'div[contenteditable="true"]', - ); - const paragraph = rootElement.firstChild; - - paragraph.textContent = ''; - }); - await validateContent(page); - - // Remove the first text - await await evaluate(page, () => { - const rootElement = document.querySelector( - 'div[contenteditable="true"]', - ); - const firstTextNode = rootElement.firstChild.firstChild; - - firstTextNode.remove(); - }); - await validateContent(page); - - // Remove the first text contents - await await evaluate(page, () => { - const rootElement = document.querySelector( - 'div[contenteditable="true"]', - ); - const firstTextNode = rootElement.firstChild.firstChild; - - firstTextNode.textContent = ''; - }); - await validateContent(page); - - // Remove the second text - await await evaluate(page, () => { - const rootElement = document.querySelector( - 'div[contenteditable="true"]', - ); - const secondTextNode = rootElement.firstChild.firstChild.nextSibling; - - secondTextNode.remove(); - }); - await validateContent(page); - - // Remove the third text - await await evaluate(page, () => { - const rootElement = document.querySelector( - 'div[contenteditable="true"]', - ); - const thirdTextNode = - rootElement.firstChild.firstChild.nextSibling.nextSibling; - - thirdTextNode.remove(); - }); - await validateContent(page); - - // Remove the forth text - await await evaluate(page, () => { - const rootElement = document.querySelector( - 'div[contenteditable="true"]', - ); - const forthTextNode = - rootElement.firstChild.firstChild.nextSibling.nextSibling.nextSibling; - - forthTextNode.remove(); - }); - await validateContent(page); - - // Move last to first - await await evaluate(page, () => { - const rootElement = document.querySelector( - 'div[contenteditable="true"]', - ); - const paragraph = rootElement.firstChild; - const firstTextNode = paragraph.firstChild; - const forthTextNode = - paragraph.firstChild.nextSibling.nextSibling.nextSibling; - - paragraph.insertBefore(forthTextNode, firstTextNode); - }); - await validateContent(page); - - // Reverse sort all the children - await await evaluate(page, () => { - const rootElement = document.querySelector( - 'div[contenteditable="true"]', - ); - const paragraph = rootElement.firstChild; - const firstTextNode = paragraph.firstChild; - const secondTextNode = paragraph.firstChild.nextSibling; - const thirdTextNode = paragraph.firstChild.nextSibling.nextSibling; - const forthTextNode = - paragraph.firstChild.nextSibling.nextSibling.nextSibling; - - paragraph.insertBefore(forthTextNode, firstTextNode); - paragraph.insertBefore(thirdTextNode, firstTextNode); - paragraph.insertBefore(secondTextNode, firstTextNode); - }); - await validateContent(page); - - // Adding additional nodes to root - await await evaluate(page, () => { - const rootElement = document.querySelector( - 'div[contenteditable="true"]', - ); - const span = document.createElement('span'); - const span2 = document.createElement('span'); - const text = document.createTextNode('123'); - rootElement.appendChild(span); - rootElement.appendChild(span2); - rootElement.appendChild(text); - }); - await validateContent(page); - - // Adding additional nodes to paragraph - await await evaluate(page, () => { - const rootElement = document.querySelector( - 'div[contenteditable="true"]', - ); - const paragraph = rootElement.firstChild; - const firstTextNode = paragraph.firstChild; - const span = document.createElement('span'); - const span2 = document.createElement('span'); - const text = document.createTextNode('123'); - paragraph.appendChild(span); - paragraph.appendChild(text); - paragraph.insertBefore(span2, firstTextNode); - }); - await validateContent(page); - - // Adding additional nodes to text nodes - await await evaluate(page, () => { - const rootElement = document.querySelector( - 'div[contenteditable="true"]', - ); - const paragraph = rootElement.firstChild; - const firstTextNode = paragraph.firstChild; - const span = document.createElement('span'); - const text = document.createTextNode('123'); - firstTextNode.appendChild(span); - firstTextNode.appendChild(text); - }); - await validateContent(page); - - // Replace text nodes on text nodes #1 - await await evaluate(page, () => { - const rootElement = document.querySelector( - 'div[contenteditable="true"]', - ); - const paragraph = rootElement.firstChild; - const firstTextNode = paragraph.firstChild; - const text = document.createTextNode('123'); - firstTextNode.firstChild.replaceWith(text); - }); - await validateContent(page); - - // Replace text nodes on line break #2 - await await evaluate(page, () => { - const rootElement = document.querySelector( - 'div[contenteditable="true"]', - ); - const paragraph = rootElement.firstChild; - const firstTextNode = paragraph.firstChild; - const br = document.createElement('br'); - firstTextNode.firstChild.replaceWith(br); - }); - await validateContent(page); - - // Update text content, this should work :) - await await evaluate(page, () => { - const rootElement = document.querySelector( - 'div[contenteditable="true"]', - ); - const paragraph = rootElement.firstChild; - const firstTextNode = paragraph.firstChild; - firstTextNode.firstChild.nodeValue = 'Bonjour '; - }); - await assertHTML( - page, - html` -

    - Bonjour - - #world - - . This content - - #should - - remain - - #intact - - . -

    - `, - ); - await assertSelection(page, { - anchorOffset: 1, - anchorPath: [0, 6, 0], - focusOffset: 1, - focusPath: [0, 6, 0], - }); - }, - ); + test(`Can restore the DOM to the editor state state`, async ({page}) => { + await focusEditor(page); + await page.keyboard.type( + 'Hello #world. This content #should remain #intact.', + ); + + await validateContent(page); + + // Remove the paragraph + await await evaluate(page, () => { + const rootElement = document.querySelector('div[contenteditable="true"]'); + const paragraph = rootElement.firstChild; + + paragraph.remove(); + }); + await validateContent(page); + + // Remove the paragraph content + await await evaluate(page, () => { + const rootElement = document.querySelector('div[contenteditable="true"]'); + const paragraph = rootElement.firstChild; + + paragraph.textContent = ''; + }); + await validateContent(page); + + // Remove the first text + await await evaluate(page, () => { + const rootElement = document.querySelector('div[contenteditable="true"]'); + const firstTextNode = rootElement.firstChild.firstChild; + + firstTextNode.remove(); + }); + await validateContent(page); + + // Remove the first text contents + await await evaluate(page, () => { + const rootElement = document.querySelector('div[contenteditable="true"]'); + const firstTextNode = rootElement.firstChild.firstChild; + + firstTextNode.textContent = ''; + }); + await validateContent(page); + + // Remove the second text + await await evaluate(page, () => { + const rootElement = document.querySelector('div[contenteditable="true"]'); + const secondTextNode = rootElement.firstChild.firstChild.nextSibling; + + secondTextNode.remove(); + }); + await validateContent(page); + + // Remove the third text + await await evaluate(page, () => { + const rootElement = document.querySelector('div[contenteditable="true"]'); + const thirdTextNode = + rootElement.firstChild.firstChild.nextSibling.nextSibling; + + thirdTextNode.remove(); + }); + await validateContent(page); + + // Remove the forth text + await await evaluate(page, () => { + const rootElement = document.querySelector('div[contenteditable="true"]'); + const forthTextNode = + rootElement.firstChild.firstChild.nextSibling.nextSibling.nextSibling; + + forthTextNode.remove(); + }); + await validateContent(page); + + // Move last to first + await await evaluate(page, () => { + const rootElement = document.querySelector('div[contenteditable="true"]'); + const paragraph = rootElement.firstChild; + const firstTextNode = paragraph.firstChild; + const forthTextNode = + paragraph.firstChild.nextSibling.nextSibling.nextSibling; + + paragraph.insertBefore(forthTextNode, firstTextNode); + }); + await validateContent(page); + + // Reverse sort all the children + await await evaluate(page, () => { + const rootElement = document.querySelector('div[contenteditable="true"]'); + const paragraph = rootElement.firstChild; + const firstTextNode = paragraph.firstChild; + const secondTextNode = paragraph.firstChild.nextSibling; + const thirdTextNode = paragraph.firstChild.nextSibling.nextSibling; + const forthTextNode = + paragraph.firstChild.nextSibling.nextSibling.nextSibling; + + paragraph.insertBefore(forthTextNode, firstTextNode); + paragraph.insertBefore(thirdTextNode, firstTextNode); + paragraph.insertBefore(secondTextNode, firstTextNode); + }); + await validateContent(page); + + // Adding additional nodes to root + await await evaluate(page, () => { + const rootElement = document.querySelector('div[contenteditable="true"]'); + const span = document.createElement('span'); + const span2 = document.createElement('span'); + const text = document.createTextNode('123'); + rootElement.appendChild(span); + rootElement.appendChild(span2); + rootElement.appendChild(text); + }); + await validateContent(page); + + // Adding additional nodes to paragraph + await await evaluate(page, () => { + const rootElement = document.querySelector('div[contenteditable="true"]'); + const paragraph = rootElement.firstChild; + const firstTextNode = paragraph.firstChild; + const span = document.createElement('span'); + const span2 = document.createElement('span'); + const text = document.createTextNode('123'); + paragraph.appendChild(span); + paragraph.appendChild(text); + paragraph.insertBefore(span2, firstTextNode); + }); + await validateContent(page); + + // Adding additional nodes to text nodes + await await evaluate(page, () => { + const rootElement = document.querySelector('div[contenteditable="true"]'); + const paragraph = rootElement.firstChild; + const firstTextNode = paragraph.firstChild; + const span = document.createElement('span'); + const text = document.createTextNode('123'); + firstTextNode.appendChild(span); + firstTextNode.appendChild(text); + }); + await validateContent(page); + + // Replace text nodes on text nodes #1 + await await evaluate(page, () => { + const rootElement = document.querySelector('div[contenteditable="true"]'); + const paragraph = rootElement.firstChild; + const firstTextNode = paragraph.firstChild; + const text = document.createTextNode('123'); + firstTextNode.firstChild.replaceWith(text); + }); + await validateContent(page); + + // Replace text nodes on line break #2 + await await evaluate(page, () => { + const rootElement = document.querySelector('div[contenteditable="true"]'); + const paragraph = rootElement.firstChild; + const firstTextNode = paragraph.firstChild; + const br = document.createElement('br'); + firstTextNode.firstChild.replaceWith(br); + }); + await validateContent(page); + + // Update text content, this should work :) + await await evaluate(page, () => { + const rootElement = document.querySelector('div[contenteditable="true"]'); + const paragraph = rootElement.firstChild; + const firstTextNode = paragraph.firstChild; + firstTextNode.firstChild.nodeValue = 'Bonjour '; + }); + await assertHTML( + page, + html` +

    + Bonjour + + #world + + . This content + + #should + + remain + + #intact + + . +

    + `, + ); + await assertSelection(page, { + anchorOffset: 1, + anchorPath: [0, 6, 0], + focusOffset: 1, + focusPath: [0, 6, 0], + }); + }); }); diff --git a/demos/playground/src/__tests__/e2e/Navigation.spec.mjs b/demos/playground/src/__tests__/e2e/Navigation.spec.mjs index e0268d6a..b83b0b80 100644 --- a/demos/playground/src/__tests__/e2e/Navigation.spec.mjs +++ b/demos/playground/src/__tests__/e2e/Navigation.spec.mjs @@ -764,6 +764,7 @@ test.describe('Keyboard Navigation', () => { focusPath: [0, 4, 0], }); } + // eslint-disable-next-line no-dupe-else-if } else if (!IS_WINDOWS) { await assertSelection(page, { anchorOffset: 1, diff --git a/demos/playground/src/__tests__/e2e/Selection.spec.mjs b/demos/playground/src/__tests__/e2e/Selection.spec.mjs index 336ca0a0..31f13d32 100644 --- a/demos/playground/src/__tests__/e2e/Selection.spec.mjs +++ b/demos/playground/src/__tests__/e2e/Selection.spec.mjs @@ -16,6 +16,7 @@ import { moveToPrevWord, pressShiftEnter, selectAll, + selectPrevWord, } from '../keyboardShortcuts/index.mjs'; import { assertHTML, @@ -27,16 +28,22 @@ import { html, initialize, insertCollapsible, + insertHorizontalRule, insertImageCaption, insertSampleImage, insertTable, + insertYouTubeEmbed, + IS_LINUX, IS_MAC, keyDownCtrlOrMeta, keyUpCtrlOrMeta, pasteFromClipboard, + pressToggleBold, + pressToggleItalic, selectFromFormatDropdown, sleep, test, + YOUTUBE_SAMPLE_URL, } from '../utils/index.mjs'; test.describe('Selection', () => { @@ -115,38 +122,38 @@ test.describe('Selection', () => { }, ); - test.fixme( - 'can wrap post-linebreak nodes into new element', - async ({page, isPlainText}) => { - test.skip(isPlainText); - await focusEditor(page); - await page.keyboard.type('Line1'); - await pressShiftEnter(page); - await page.keyboard.type('Line2'); - await page.keyboard.down('Shift'); - await moveToLineBeginning(page); - await page.keyboard.up('Shift'); - await selectFromFormatDropdown(page, '.code'); - await assertHTML( - page, - html` -

    - Line1 -

    - - Line2 - - `, - ); - }, - ); + test('can wrap post-linebreak nodes into new element', async ({ + page, + isPlainText, + }) => { + test.skip(isPlainText); + await focusEditor(page); + await page.keyboard.type('Line1'); + await pressShiftEnter(page); + await page.keyboard.type('Line2'); + await page.keyboard.down('Shift'); + await moveToLineBeginning(page); + await page.keyboard.up('Shift'); + await selectFromFormatDropdown(page, '.code'); + await assertHTML( + page, + html` +

    + Line1 +

    + + Line2 + + `, + ); + }); test('can delete text by line with CMD+delete', async ({ page, @@ -209,25 +216,25 @@ test.describe('Selection', () => { ); }); - test.fixme( - 'Can insert inline element within text and put selection after it', - async ({page, isPlainText}) => { - test.skip(isPlainText); - await focusEditor(page); - await page.keyboard.type('Hello world'); - await moveToPrevWord(page); - await pasteFromClipboard(page, { - 'text/html': `link`, - }); - await sleep(3000); - await assertSelection(page, { - anchorOffset: 4, - anchorPath: [0, 1, 0, 0], - focusOffset: 4, - focusPath: [0, 1, 0, 0], - }); - }, - ); + test('Can insert inline element within text and put selection after it', async ({ + page, + isPlainText, + }) => { + test.skip(isPlainText); + await focusEditor(page); + await page.keyboard.type('Hello world'); + await moveToPrevWord(page); + await pasteFromClipboard(page, { + 'text/html': `link`, + }); + await sleep(3000); + await assertSelection(page, { + anchorOffset: 4, + anchorPath: [0, 1, 0, 0], + focusOffset: 4, + focusPath: [0, 1, 0, 0], + }); + }); test('Can delete at boundary #4221', async ({page, isPlainText}) => { test.skip(!isPlainText); @@ -358,7 +365,6 @@ test.describe('Selection', () => { }); test('Can delete block elements', async ({page, isPlainText}) => { - test.fixme(true, 'requires markdown plugin'); test.skip(isPlainText); await focusEditor(page); await page.keyboard.type('# A'); @@ -425,7 +431,6 @@ test.describe('Selection', () => { }); test('Can delete sibling elements forward', async ({page, isPlainText}) => { - test.fixme(true, 'requires markdown plugin'); test.skip(isPlainText); await focusEditor(page); @@ -444,4 +449,274 @@ test.describe('Selection', () => { `, ); }); + + test('Can adjust tripple click selection', async ({ + page, + isPlainText, + isCollab, + }) => { + test.skip(isPlainText || isCollab); + + await page.keyboard.type('Paragraph 1'); + await page.keyboard.press('Enter'); + await page.keyboard.type('Paragraph 2'); + await page + .locator('div[contenteditable="true"] > p') + .first() + .click({clickCount: 3}); + + await click(page, '.block-controls'); + await click(page, '.dropdown .item:has(.icon.h1)'); + + await assertHTML( + page, + html` +

    + Paragraph 1 +

    +

    + Paragraph 2 +

    + `, + ); + }); + + test.fixme( + 'Select all from Node selection #4658', + async ({page, isPlainText}) => { + // TODO selectAll is bad for Linux #4665 + test.skip(isPlainText || IS_LINUX); + + await insertYouTubeEmbed(page, YOUTUBE_SAMPLE_URL); + await page.keyboard.type('abcdefg'); + await moveLeft(page, 'abcdefg'.length + 1); + + await selectAll(page); + await page.keyboard.press('Backspace'); + + await assertHTML( + page, + html` +


    + `, + ); + }, + ); + + test.fixme( + 'Select all (DecoratorNode at start) #4670', + async ({page, isPlainText}) => { + // TODO selectAll is bad for Linux #4665 + test.skip(isPlainText || IS_LINUX); + + await insertYouTubeEmbed(page, YOUTUBE_SAMPLE_URL); + // Delete empty paragraph in front + await moveLeft(page, 2); + await page.keyboard.press('Backspace'); + await moveRight(page, 2); + await page.keyboard.type('abcdefg'); + + await selectAll(page); + await page.keyboard.press('Backspace'); + await assertHTML( + page, + html` +


    + `, + ); + }, + ); + + test('Can use block controls on selections including decorator nodes #5371', async ({ + page, + isPlainText, + isCollab, + }) => { + test.skip(isPlainText || isCollab); + + await page.keyboard.type('Some text'); + await insertHorizontalRule(page); + await page.keyboard.type('More text'); + await selectAll(page); + + await click(page, '.block-controls'); + await click(page, '.dropdown .icon.h1'); + + await assertHTML( + page, + html` +

    + Some text +

    +
    +

    + More text +

    + `, + ); + }); + + test.fixme( + 'Can delete table node present at the end #5543', + async ({page, isPlainText, isCollab}) => { + test.skip(isPlainText); + + await focusEditor(page); + await insertTable(page, 1, 2); + await page.keyboard.press('ArrowDown'); + await page.keyboard.down('Shift'); + await page.keyboard.press('ArrowUp'); + await page.keyboard.up('Shift'); + await page.keyboard.press('Backspace'); + await assertHTML( + page, + html` +


    +


    + `, + ); + }, + ); + + test('Can persist the text format from the paragraph', async ({ + page, + isPlainText, + }) => { + test.skip(isPlainText); + await focusEditor(page); + await pressToggleBold(page); + await page.keyboard.type('Line1'); + await page.keyboard.press('Enter'); + await page.keyboard.press('Enter'); + await page.keyboard.press('Enter'); + await page.keyboard.type('Line2'); + await page.keyboard.press('ArrowUp'); + await page.keyboard.type('Line3'); + await assertHTML( + page, + html` +

    + + Line1 + +

    +


    +

    + + Line3 + +

    +

    + + Line2 + +

    + `, + ); + }); + + test('toggle format at the start of paragraph to a different format persists the format', async ({ + page, + isPlainText, + }) => { + test.skip(isPlainText); + await focusEditor(page); + await pressToggleBold(page); + await page.keyboard.type('Line1'); + await page.keyboard.press('Enter'); + await page.keyboard.press('Enter'); + await page.keyboard.press('Enter'); + await pressToggleItalic(page); + await page.keyboard.type('Line2'); + await page.keyboard.press('ArrowUp'); + await pressToggleBold(page); + await page.keyboard.type('Line3'); + await assertHTML( + page, + html` +

    + + Line1 + +

    +


    +

    + Line3 +

    +

    + + Line2 + +

    + `, + ); + }); + + test('formatting is persisted after deleting all nodes from the paragraph node', async ({ + page, + isPlainText, + }) => { + test.skip(isPlainText); + await focusEditor(page); + await pressToggleBold(page); + await page.keyboard.type('Line1'); + await page.keyboard.press('Enter'); + await pressToggleBold(page); + await page.keyboard.type('Line2'); + await selectPrevWord(page); + await page.keyboard.press('Backspace'); + await page.keyboard.type('Line3'); + await assertHTML( + page, + html` +

    + + Line1 + +

    +

    + Line3 +

    + `, + ); + }); }); diff --git a/demos/playground/src/__tests__/e2e/Tab.spec.mjs b/demos/playground/src/__tests__/e2e/Tab.spec.mjs new file mode 100644 index 00000000..6d97235d --- /dev/null +++ b/demos/playground/src/__tests__/e2e/Tab.spec.mjs @@ -0,0 +1,111 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import { + assertHTML, + focusEditor, + html, + initialize, + test, +} from '../utils/index.mjs'; + +test.describe('Tab', () => { + test.beforeEach(({isCollab, page}) => initialize({isCollab, page})); + test(`can tab + IME`, async ({page, isPlainText, browserName}) => { + // CDP session is only available in Chromium + test.skip( + isPlainText || browserName === 'firefox' || browserName === 'webkit', + ); + + const client = await page.context().newCDPSession(page); + async function imeType() { + // await page.keyboard.imeSetComposition('s', 1, 1); + await client.send('Input.imeSetComposition', { + selectionStart: 1, + selectionEnd: 1, + text: 's', + }); + // await page.keyboard.imeSetComposition('す', 1, 1); + await client.send('Input.imeSetComposition', { + selectionStart: 1, + selectionEnd: 1, + text: 'す', + }); + // await page.keyboard.imeSetComposition('すs', 2, 2); + await client.send('Input.imeSetComposition', { + selectionStart: 2, + selectionEnd: 2, + text: 'すs', + }); + // await page.keyboard.imeSetComposition('すsh', 3, 3); + await client.send('Input.imeSetComposition', { + selectionStart: 3, + selectionEnd: 3, + text: 'すsh', + }); + // await page.keyboard.imeSetComposition('すし', 2, 2); + await client.send('Input.imeSetComposition', { + selectionStart: 2, + selectionEnd: 2, + text: 'すし', + }); + // await page.keyboard.insertText('すし'); + await client.send('Input.insertText', { + text: 'すし', + }); + await page.keyboard.type(' '); + } + await focusEditor(page); + // Indent + await page.keyboard.press('Tab'); + await imeType(); + await page.keyboard.press('Tab'); + await imeType(); + + await assertHTML( + page, + html` +

    + すし + + すし +

    + `, + ); + }); + + test('can tab inside code block #4399', async ({page, isPlainText}) => { + test.skip(isPlainText); + await focusEditor(page); + + await page.keyboard.type('``` '); + await page.keyboard.press('Tab'); + await page.keyboard.type('function'); + await assertHTML( + page, + html` + + + + function + + + `, + ); + }); +}); diff --git a/demos/playground/src/__tests__/e2e/Tables.spec.mjs b/demos/playground/src/__tests__/e2e/Tables.spec.mjs index 1ce6f5ae..1b9960f4 100644 --- a/demos/playground/src/__tests__/e2e/Tables.spec.mjs +++ b/demos/playground/src/__tests__/e2e/Tables.spec.mjs @@ -7,6 +7,7 @@ */ import { + deleteBackward, moveDown, moveLeft, moveRight, @@ -25,19 +26,23 @@ import { focusEditor, html, initialize, + insertCollapsible, insertHorizontalRule, insertSampleImage, insertTable, insertTableColumnBefore, insertTableRowBelow, IS_COLLAB, + LEGACY_EVENTS, mergeTableCells, pasteFromClipboard, SAMPLE_IMAGE_URL, selectCellsFromTableCords, selectFromAdditionalStylesDropdown, + selectFromAlignDropdown, setBackgroundColor, test, + toggleColumnHeader, unmergeTableCell, } from '../utils/index.mjs'; @@ -146,6 +151,342 @@ test.describe('Tables', () => { ); }); + test.describe(`Can exit tables with the horizontal arrow keys`, () => { + test(`Can exit the first cell of a non-nested table`, async ({ + page, + isPlainText, + isCollab, + }) => { + await initialize({isCollab, page}); + test.skip(isPlainText); + + await focusEditor(page); + await insertTable(page, 2, 2); + + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [1, 0, 0, 0], + focusOffset: 0, + focusPath: [1, 0, 0, 0], + }); + + await moveLeft(page, 1); + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [0], + focusOffset: 0, + focusPath: [0], + }); + + await moveRight(page, 1); + await page.keyboard.type('ab'); + await assertSelection(page, { + anchorOffset: 2, + anchorPath: [1, 0, 0, 0, 0, 0], + focusOffset: 2, + focusPath: [1, 0, 0, 0, 0, 0], + }); + + await moveRight(page, 3); + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [1, 1, 1, 0], + focusOffset: 0, + focusPath: [1, 1, 1, 0], + }); + }); + + test(`Can exit the last cell of a non-nested table`, async ({ + page, + isPlainText, + isCollab, + }) => { + await initialize({isCollab, page}); + test.skip(isPlainText); + + await focusEditor(page); + await insertTable(page, 2, 2); + + await moveRight(page, 3); + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [1, 1, 1, 0], + focusOffset: 0, + focusPath: [1, 1, 1, 0], + }); + + await moveRight(page, 1); + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [2], + focusOffset: 0, + focusPath: [2], + }); + + await moveLeft(page, 1); + await page.keyboard.type('ab'); + await assertSelection(page, { + anchorOffset: 2, + anchorPath: [1, 1, 1, 0, 0, 0], + focusOffset: 2, + focusPath: [1, 1, 1, 0, 0, 0], + }); + + await moveRight(page, 3); + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [2], + focusOffset: 0, + focusPath: [2], + }); + }); + + test(`Can exit the first cell of a nested table into the parent table cell`, async ({ + page, + isPlainText, + isCollab, + }) => { + await initialize({isCollab, page}); + test.skip(isPlainText); + + await focusEditor(page); + await insertTable(page, 2, 2); + await insertTable(page, 2, 2); + + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [1, 0, 0, 1, 0, 0, 0], + focusOffset: 0, + focusPath: [1, 0, 0, 1, 0, 0, 0], + }); + + await moveLeft(page, 1); + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [1, 0, 0, 0], + focusOffset: 0, + focusPath: [1, 0, 0, 0], + }); + }); + + test(`Can exit the last cell of a nested table into the parent table cell`, async ({ + page, + isPlainText, + isCollab, + }) => { + await initialize({isCollab, page}); + test.skip(isPlainText); + + await focusEditor(page); + await insertTable(page, 2, 2); + await insertTable(page, 2, 2); + + await moveRight(page, 3); + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [1, 0, 0, 1, 1, 1, 0], + focusOffset: 0, + focusPath: [1, 0, 0, 1, 1, 1, 0], + }); + + await moveRight(page, 1); + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [1, 0, 0, 2], + focusOffset: 0, + focusPath: [1, 0, 0, 2], + }); + }); + }); + + test(`Can insert a paragraph after a table, that is the last node, with the "Enter" key`, async ({ + page, + isPlainText, + isCollab, + browserName, + }) => { + await initialize({isCollab, page}); + test.skip(isPlainText); + // Table edge cursor doesn't show up in Firefox. + test.fixme(browserName === 'firefox'); + + await focusEditor(page); + await insertTable(page, 2, 2); + + await moveDown(page, 2); + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [2], + focusOffset: 0, + focusPath: [2], + }); + + await deleteBackward(page); + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [1, 1, 1, 0], + focusOffset: 0, + focusPath: [1, 1, 1, 0], + }); + await assertHTML( + page, + html` +


    + + + + + + + + + +
    +


    +
    +


    +
    +


    +
    +


    +
    + `, + undefined, + {ignoreClasses: true}, + ); + + await moveRight(page, 1); + // The native window selection should be on the root, whereas + // the editor selection should be on the last cell of the table. + await assertSelection(page, { + anchorOffset: 2, + anchorPath: [], + focusOffset: 2, + focusPath: [], + }); + + await page.keyboard.press('Enter'); + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [2], + focusOffset: 0, + focusPath: [2], + }); + + await assertHTML( + page, + html` +


    + + + + + + + + + +
    +


    +
    +


    +
    +


    +
    +


    +
    +


    + `, + undefined, + {ignoreClasses: true}, + ); + }); + + test(`Can type text after a table that is the last node`, async ({ + page, + isPlainText, + isCollab, + browserName, + }) => { + await initialize({isCollab, page}); + test.skip(isPlainText); + // Table edge cursor doesn't show up in Firefox. + test.fixme(browserName === 'firefox'); + // After typing, the dom selection gets set back to the internal previous selection during the update. + test.fixme(LEGACY_EVENTS); + + await focusEditor(page); + await insertTable(page, 2, 2); + + await moveDown(page, 2); + await deleteBackward(page); + + await moveRight(page, 1); + await page.keyboard.type('a'); + await assertSelection(page, { + anchorOffset: 1, + anchorPath: [2, 0, 0], + focusOffset: 1, + focusPath: [2, 0, 0], + }); + + await assertHTML( + page, + html` +


    + + + + + + + + + +
    +


    +
    +


    +
    +


    +
    +


    +
    +

    a

    + `, + undefined, + {ignoreClasses: true}, + ); + }); + + test(`Can enter a table from a paragraph underneath via the left arrow key`, async ({ + page, + isPlainText, + isCollab, + }) => { + await initialize({isCollab, page}); + test.skip(isPlainText); + + await focusEditor(page); + await insertTable(page, 2, 2); + + await moveDown(page, 2); + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [2], + focusOffset: 0, + focusPath: [2], + }); + + await moveLeft(page, 1); + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [1, 1, 1, 0], + focusOffset: 0, + focusPath: [1, 1, 1, 0], + }); + }); + test(`Can navigate table with keyboard`, async ({ page, isPlainText, @@ -284,6 +625,7 @@ test.describe('Tables', () => { page, isPlainText, isCollab, + browserName, }) => { await initialize({isCollab, page}); test.skip(isPlainText); @@ -309,6 +651,10 @@ test.describe('Tables', () => { await page.keyboard.down('Shift'); await page.keyboard.press('ArrowRight'); + // Firefox range selection spans across cells after two arrow key press + if (browserName === 'firefox') { + await page.keyboard.press('ArrowRight'); + } await page.keyboard.press('ArrowDown'); await page.keyboard.up('Shift'); @@ -396,7 +742,7 @@ test.describe('Tables', () => {


`, - {ignoreClasses: true}, + {ignoreClasses: true, ignoreInlineStyles: true}, ); }); @@ -802,6 +1148,7 @@ test.describe('Tables', () => { page, isPlainText, isCollab, + browserName, }) => { await initialize({isCollab, page}); test.skip(isPlainText); @@ -815,6 +1162,10 @@ test.describe('Tables', () => { await page.keyboard.down('Shift'); await page.keyboard.press('ArrowRight'); + // Firefox range selection spans across cells after two arrow key press + if (browserName === 'firefox') { + await page.keyboard.press('ArrowRight'); + } await page.keyboard.press('ArrowDown'); await page.keyboard.up('Shift'); @@ -952,6 +1303,95 @@ test.describe('Tables', () => { ); }); + test('Can remove new lines in a collapsible section inside of a table', async ({ + page, + isPlainText, + isCollab, + }) => { + await initialize({isCollab, page}); + test.skip(isPlainText); + + await focusEditor(page); + + await insertTable(page, 1, 2); + await insertCollapsible(page); + + await page.keyboard.type('123'); + await page.keyboard.press('ArrowDown'); + await page.keyboard.type('123'); + await page.keyboard.press('Enter'); + await page.keyboard.press('Enter'); + await page.keyboard.press('Enter'); + + await assertHTML( + page, + html` +


+ + + + + +
+


+
+ +

+ 123 +

+
+
+

+ 123 +

+


+


+


+
+
+


+
+


+
+


+ `, + ); + + await pressBackspace(page, 10); + await assertHTML( + page, + html` +


+ + + + + +
+


+
+ +

+ 123 +

+
+
+


+
+
+


+
+


+
+


+ `, + ); + }); + test('Merge/unmerge cells (1)', async ({page, isPlainText, isCollab}) => { await initialize({isCollab, page}); test.skip(isPlainText); @@ -1302,7 +1742,8 @@ test.describe('Tables', () => { page, html`


- +
- +
{

+


+
{


- + + + +
+


- +


+


+
+


+ `, + ); + }); + + test('Insert column before (with 1+ selected cells in a row)', async ({ + page, + isPlainText, + isCollab, + }) => { + await initialize({isCollab, page}); + test.skip(isPlainText); + if (IS_COLLAB) { + // The contextual menu positioning needs fixing (it's hardcoded to show on the right side) + page.setViewportSize({height: 1000, width: 3000}); + } + + await focusEditor(page); + + await insertTable(page, 2, 2); + + await click(page, '.PlaygroundEditorTheme__tableCell'); + await selectCellsFromTableCords( + page, + {x: 0, y: 0}, + {x: 1, y: 0}, + true, + true, + ); + await insertTableColumnBefore(page); + + await assertHTML( + page, + html` +


+ + + + + + + + + +
+


+
+


+
+


+
+


+


+


+
+


+
+


+


@@ -1883,4 +2399,202 @@ test.describe('Tables', () => { `, ); }); + + test('Add column header after merging cells #4378', async ({ + page, + isPlainText, + isCollab, + }) => { + await initialize({isCollab, page}); + test.skip(isPlainText); + if (IS_COLLAB) { + // The contextual menu positioning needs fixing (it's hardcoded to show on the right side) + page.setViewportSize({height: 1000, width: 3000}); + } + + await focusEditor(page); + + await insertTable(page, 4, 4); + await selectCellsFromTableCords( + page, + {x: 1, y: 2}, + {x: 3, y: 3}, + false, + false, + ); + await mergeTableCells(page); + await selectCellsFromTableCords( + page, + {x: 3, y: 1}, + {x: 3, y: 1}, + false, + false, + ); + await toggleColumnHeader(page); + await assertHTML( + page, + html` +


+ + + + + + + + + + + + + + + + + + + + +
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+
+


+ `, + ); + }); + + test('Can align text using Table selection', async ({ + page, + isPlainText, + isCollab, + }) => { + await initialize({isCollab, page}); + test.skip(isPlainText); + + await focusEditor(page); + await insertTable(page, 2, 3); + + await fillTablePartiallyWithText(page); + await selectCellsFromTableCords( + page, + {x: 0, y: 0}, + {x: 1, y: 1}, + true, + false, + ); + + await selectFromAlignDropdown(page, '.center-align'); + + await assertHTML( + page, + html` +


+ + + + + + + + + + + +
+

+ a +

+
+

+ bb +

+
+

cc

+
+

+ d +

+
+

+ e +

+
+

f

+
+


+ `, + html` +


+ + + + + + + + + + + +
+

+ a +

+
+

+ bb +

+
+

cc

+
+

+ d +

+
+

+ e +

+
+

f

+
+


+ `, + {ignoreClasses: true}, + ); + }); }); diff --git a/demos/playground/src/__tests__/e2e/TextEntry.spec.mjs b/demos/playground/src/__tests__/e2e/TextEntry.spec.mjs index 0eaf4b42..2b39fb8d 100644 --- a/demos/playground/src/__tests__/e2e/TextEntry.spec.mjs +++ b/demos/playground/src/__tests__/e2e/TextEntry.spec.mjs @@ -22,7 +22,6 @@ import { keyDownCtrlOrAlt, keyUpCtrlOrAlt, test, - IS_WINDOWS, } from '../utils/index.mjs'; test.describe('TextEntry', () => { @@ -49,13 +48,7 @@ test.describe('TextEntry', () => { }); }); - test(`Can insert text and replace it`, async ({ - isCollab, - page, - browserName, - }) => { - test.fixme(browserName === 'webkit'); - + test(`Can insert text and replace it`, async ({isCollab, page}) => { test.skip(isCollab); await page.locator('[data-lexical-editor]').fill('Front'); await page.locator('[data-lexical-editor]').fill('Front updated'); @@ -77,53 +70,53 @@ test.describe('TextEntry', () => { }); }); - test.fixme( - `Can type 'Hello' as a header and insert a paragraph before`, - async ({page, isPlainText}) => { - test.skip(isPlainText); - await focusEditor(page); - await page.keyboard.type('# Hello'); + test(`Can type 'Hello' as a header and insert a paragraph before`, async ({ + page, + isPlainText, + }) => { + test.skip(isPlainText); + await focusEditor(page); + await page.keyboard.type('# Hello'); - await moveToLineBeginning(page); + await moveToLineBeginning(page); - await assertHTML( - page, - html` -

- Hello -

- `, - ); - await assertSelection(page, { - anchorOffset: 0, - anchorPath: [0, 0, 0], - focusOffset: 0, - focusPath: [0, 0, 0], - }); + await assertHTML( + page, + html` +

+ Hello +

+ `, + ); + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [0, 0, 0], + focusOffset: 0, + focusPath: [0, 0, 0], + }); - await page.keyboard.press('Enter'); + await page.keyboard.press('Enter'); - await assertHTML( - page, - html` -


-

- Hello -

- `, - ); - await assertSelection(page, { - anchorOffset: 0, - anchorPath: [1, 0, 0], - focusOffset: 0, - focusPath: [1, 0, 0], - }); - }, - ); + await assertHTML( + page, + html` +


+

+ Hello +

+ `, + ); + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [1, 0, 0], + focusOffset: 0, + focusPath: [1, 0, 0], + }); + }); test(`Can insert a paragraph between two text nodes`, async ({ page, @@ -638,12 +631,7 @@ test.describe('TextEntry', () => { test('Empty paragraph and new line node selection', async ({ isRichText, page, - browserName, - isCollab, }) => { - if (IS_WINDOWS && browserName === 'firefox' && isCollab) { - test.fixme(); - } await focusEditor(page); // Add paragraph diff --git a/demos/playground/src/__tests__/e2e/TextFormatting.spec.mjs b/demos/playground/src/__tests__/e2e/TextFormatting.spec.mjs index e99de875..3da07617 100644 --- a/demos/playground/src/__tests__/e2e/TextFormatting.spec.mjs +++ b/demos/playground/src/__tests__/e2e/TextFormatting.spec.mjs @@ -22,6 +22,7 @@ import { click, evaluate, expect, + fill, focusEditor, html, initialize, @@ -428,7 +429,7 @@ test.describe('TextFormatting', () => { }); test.fixme( - `Can select text and change the font-size`, + `Can select text and increase the font-size`, async ({page, isPlainText}) => { test.skip(isPlainText); @@ -444,8 +445,7 @@ test.describe('TextFormatting', () => { focusPath: [0, 0, 0], }); - await click(page, '.font-size'); - await click(page, 'button:has-text("10px")'); + await click(page, '.font-increment'); await assertHTML( page, @@ -454,7 +454,7 @@ test.describe('TextFormatting', () => { class="PlaygroundEditorTheme__paragraph PlaygroundEditorTheme__ltr" dir="ltr"> Hello - world + world !

`, @@ -469,6 +469,105 @@ test.describe('TextFormatting', () => { }, ); + test.fixme( + `Can select text with different size and increase the font-size relatively`, + async ({page, isPlainText}) => { + test.skip(isPlainText); + + await focusEditor(page); + await page.keyboard.type('Hello world!'); + await selectCharacters(page, 'left', 6); + await click(page, '.font-increment'); + await moveRight(page, 6); + await selectCharacters(page, 'left', 12); + await click(page, '.font-increment'); + + await assertHTML( + page, + html` +

+ Hello + + world! + +

+ `, + ); + }, + ); + + test.fixme( + `Can select text and decrease the font-size`, + async ({page, isPlainText}) => { + test.skip(isPlainText); + + await focusEditor(page); + await page.keyboard.type('Hello world!'); + await moveLeft(page); + await selectCharacters(page, 'left', 5); + + await assertSelection(page, { + anchorOffset: 11, + anchorPath: [0, 0, 0], + focusOffset: 6, + focusPath: [0, 0, 0], + }); + + await click(page, '.font-decrement'); + + await assertHTML( + page, + html` +

+ Hello + world + ! +

+ `, + ); + + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [0, 1, 0], + focusOffset: 5, + focusPath: [0, 1, 0], + }); + }, + ); + + test.fixme( + `Can select text with different size and decrease the font-size relatively`, + async ({page, isPlainText}) => { + test.skip(isPlainText); + + await focusEditor(page); + await page.keyboard.type('Hello world!'); + await selectCharacters(page, 'left', 6); + await click(page, '.font-decrement'); + await moveRight(page, 6); + await selectCharacters(page, 'left', 12); + await click(page, '.font-decrement'); + + await assertHTML( + page, + html` +

+ Hello + + world! + +

+ `, + ); + }, + ); + test.fixme( `Can select text and change the font-size and font-family`, async ({page, isPlainText}) => { @@ -487,8 +586,7 @@ test.describe('TextFormatting', () => { focusPath: [0, 0, 0], }); - await click(page, '.font-size'); - await click(page, 'button:has-text("10px")'); + await click(page, '.font-increment'); await assertHTML( page, @@ -497,7 +595,7 @@ test.describe('TextFormatting', () => { class="PlaygroundEditorTheme__paragraph PlaygroundEditorTheme__ltr" dir="ltr"> Hello - world + world !

`, @@ -521,7 +619,7 @@ test.describe('TextFormatting', () => { dir="ltr"> Hello world @@ -537,8 +635,7 @@ test.describe('TextFormatting', () => { focusPath: [0, 1, 0], }); - await click(page, '.font-size'); - await click(page, 'button:has-text("20px")'); + await click(page, '.font-decrement'); await assertHTML( page, @@ -548,7 +645,7 @@ test.describe('TextFormatting', () => { dir="ltr"> Hello world @@ -566,12 +663,82 @@ test.describe('TextFormatting', () => { }, ); + test.fixme( + `Can select text and update font size by entering the value`, + async ({page, isPlainText}) => { + test.skip(isPlainText); + + await focusEditor(page); + await page.keyboard.type('Hello world!'); + await moveLeft(page); + await selectCharacters(page, 'left', 5); + + await assertSelection(page, { + anchorOffset: 11, + anchorPath: [0, 0, 0], + focusOffset: 6, + focusPath: [0, 0, 0], + }); + + await fill(page, '.font-size-input', '20'); + await page.keyboard.press('Enter'); + + await assertHTML( + page, + html` +

+ Hello + world + ! +

+ `, + ); + + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [0, 1, 0], + focusOffset: 5, + focusPath: [0, 1, 0], + }); + }, + ); + + test.fixme( + `Can select text with different size and update font size by entering the value`, + async ({page, isPlainText}) => { + test.skip(isPlainText); + + await focusEditor(page); + await page.keyboard.type('Hello world!'); + await selectCharacters(page, 'left', 6); + await click(page, '.font-decrement'); + await moveRight(page, 6); + await selectCharacters(page, 'left', 12); + await fill(page, '.font-size-input', '20'); + await page.keyboard.press('Enter'); + + await assertHTML( + page, + html` +

+ + Hello world! + +

+ `, + ); + }, + ); + test(`Can select multiple text parts and format them with shortcuts`, async ({ page, isPlainText, browserName, }) => { - test.fixme(browserName === 'webkit'); test.skip(isPlainText); await focusEditor(page); @@ -689,12 +856,6 @@ test.describe('TextFormatting', () => { await moveLeft(page, 2); await selectCharacters(page, 'right', 5); - await assertSelection(page, { - anchorOffset: 0, - anchorPath: [0, 1, 0], - focusOffset: 2, - focusPath: [0, 3, 0], - }); await toggleBold(page); await assertHTML( @@ -957,76 +1118,23 @@ test.describe('TextFormatting', () => { }, ); - test.fixme( - 'Regression #2523: can toggle format when selecting a TextNode edge followed by a non TextNode; ', - async ({page, isCollab, isPlainText}) => { - test.skip(isPlainText); - await focusEditor(page); + test('Regression #2523: can toggle format when selecting a TextNode edge followed by a non TextNode; ', async ({ + page, + isCollab, + isPlainText, + }) => { + test.skip(isPlainText); + await focusEditor(page); - await page.keyboard.type('A'); - await insertSampleImage(page); - await page.keyboard.type('BC'); - - await moveLeft(page, 1); - await selectCharacters(page, 'left', 2); - - if (!isCollab) { - await waitForSelector(page, '.editor-image img'); - await assertHTML( - page, - html` -

- A - -

- Yellow flower in tilt shift lens -
- - BC -

- `, - ); - } - await toggleBold(page); - await assertHTML( - page, - html` -

- A - -

- Yellow flower in tilt shift lens -
- - - B - - C -

- `, - ); - await toggleBold(page); + await page.keyboard.type('A'); + await insertSampleImage(page); + await page.keyboard.type('BC'); + + await moveLeft(page, 1); + await selectCharacters(page, 'left', 2); + + if (!isCollab) { + await waitForSelector(page, '.editor-image img'); await assertHTML( page, html` @@ -1041,6 +1149,7 @@ test.describe('TextFormatting', () => {
Yellow flower in tilt shift lens @@ -1050,6 +1159,87 @@ test.describe('TextFormatting', () => {

`, ); - }, - ); + } + await toggleBold(page); + await assertHTML( + page, + html` +

+ A + +

+ Yellow flower in tilt shift lens +
+ + + B + + C +

+ `, + ); + await toggleBold(page); + await assertHTML( + page, + html` +

+ A + +

+ Yellow flower in tilt shift lens +
+ + BC +

+ `, + ); + }); + + test('Multiline selection format ignores new lines', async ({ + page, + isPlainText, + isCollab, + }) => { + test.skip(isPlainText); + let leftFrame = page; + if (isCollab) { + leftFrame = await page.frame('left'); + } + await focusEditor(page); + + await page.keyboard.type('Fist'); + await page.keyboard.press('Enter'); + await toggleUnderline(page); + await page.keyboard.type('Second'); + await page.pause(); + + await moveLeft(page, 'Second'.length + 1); + await page.pause(); + await selectCharacters(page, 'right', 'Second'.length + 1); + await page.pause(); + + await expect( + leftFrame.locator('.toolbar-item[title^="Underline"]'), + ).toHaveClass(/active/); + }); }); diff --git a/demos/playground/src/__tests__/e2e/Toolbar.spec.mjs b/demos/playground/src/__tests__/e2e/Toolbar.spec.mjs index 688c7732..6e943fb1 100644 --- a/demos/playground/src/__tests__/e2e/Toolbar.spec.mjs +++ b/demos/playground/src/__tests__/e2e/Toolbar.spec.mjs @@ -206,7 +206,7 @@ test.describe('Toolbar', () => { ); }); - test.fixme('Center align image', async ({page, isPlainText, isCollab}) => { + test('Center align image', async ({page, isPlainText, isCollab}) => { // Image selection can't be synced in collab test.skip(isPlainText || isCollab); await focusEditor(page); diff --git a/demos/playground/src/__tests__/keyboardShortcuts/index.mjs b/demos/playground/src/__tests__/keyboardShortcuts/index.mjs index fe67eda5..10c15c10 100644 --- a/demos/playground/src/__tests__/keyboardShortcuts/index.mjs +++ b/demos/playground/src/__tests__/keyboardShortcuts/index.mjs @@ -70,6 +70,14 @@ export async function moveToPrevWord(page) { await keyUpCtrlOrAlt(page); } +export async function selectPrevWord(page) { + await keyDownCtrlOrAlt(page); + await page.keyboard.down('Shift'); + await page.keyboard.press('ArrowLeft'); + await keyUpCtrlOrAlt(page); + await page.keyboard.up('Shift'); +} + export async function moveToNextWord(page) { await keyDownCtrlOrAlt(page); await page.keyboard.press('ArrowRight'); @@ -131,6 +139,7 @@ export async function moveToParagraphEnd(page) { } export async function selectAll(page) { + // TODO Normalize #4665 if (E2E_BROWSER === 'firefox' && IS_LINUX) { await evaluate(page, () => { const rootElement = document.querySelector('div[contenteditable="true"]');