diff --git a/.github/workflows/call-e2e-test.yml b/.github/workflows/call-e2e-test.yml index 2b5db8bbdf2..156f4ba382d 100644 --- a/.github/workflows/call-e2e-test.yml +++ b/.github/workflows/call-e2e-test.yml @@ -40,14 +40,14 @@ jobs: fail-on-cache-miss: ${{ inputs.fail-on-cache-miss }} - name: Run tests if: inputs.editor-mode != 'rich-text-with-collab' - run: pnpm run test-e2e-${{ inputs.prod && 'prod-' || '' }}${{ inputs.browser }} ${{ inputs.flaky && '--grep' || '--grep-invert' }} "@flaky" + run: pnpm run test-e2e-${{ inputs.prod && 'prod-' || '' }}${{ inputs.browser }} ${{ inputs.flaky && '--grep' || '--grep-invert' }} "@flaky"${{ inputs.flaky && ' --pass-with-no-tests' || '' }} - name: Run collab tests if: inputs.editor-mode == 'rich-text-with-collab' shell: bash run: | pnpm exec concurrently -k -s first \ "pnpm run collab" \ - "pnpm run test-e2e-collab-${{ inputs.prod && 'prod-' || '' }}${{ inputs.browser }} ${{ inputs.flaky && '--grep' || '--grep-invert' }} @flaky" + "pnpm run test-e2e-collab-${{ inputs.prod && 'prod-' || '' }}${{ inputs.browser }} ${{ inputs.flaky && '--grep' || '--grep-invert' }} @flaky${{ inputs.flaky && ' --pass-with-no-tests' || '' }}" - name: Upload Artifacts if: failure() uses: actions/upload-artifact@v7 diff --git a/packages/lexical-code-core/src/FlatStructureUtils.ts b/packages/lexical-code-core/src/FlatStructureUtils.ts index 447373fa51a..30543567433 100644 --- a/packages/lexical-code-core/src/FlatStructureUtils.ts +++ b/packages/lexical-code-core/src/FlatStructureUtils.ts @@ -25,6 +25,7 @@ import { $isLineBreakNode, $isTabNode, getTextDirection, + tokenizeRawText, } from 'lexical'; import { @@ -237,20 +238,10 @@ export function $getEndOfCodeInLine( */ export function $plainifyCodeContent(text: string): LexicalNode[] { const out: LexicalNode[] = []; - const lines = text.split('\n'); - lines.forEach((line, lineIdx) => { - if (lineIdx > 0) { - out.push($createLineBreakNode()); - } - const tabParts = line.split('\t'); - tabParts.forEach((part, partIdx) => { - if (partIdx > 0) { - out.push($createTabNode()); - } - if (part.length > 0) { - out.push($createCodeHighlightNode(part)); - } - }); + tokenizeRawText(text, { + linebreak: () => out.push($createLineBreakNode()), + tab: () => out.push($createTabNode()), + text: part => out.push($createCodeHighlightNode(part)), }); return out; } diff --git a/packages/lexical-code-prism/src/FacadePrism.ts b/packages/lexical-code-prism/src/FacadePrism.ts index 291d14b53b1..a8a6e858bb9 100644 --- a/packages/lexical-code-prism/src/FacadePrism.ts +++ b/packages/lexical-code-prism/src/FacadePrism.ts @@ -29,7 +29,7 @@ import 'prismjs/components/prism-java'; import 'prismjs/components/prism-cpp'; import {$createCodeHighlightNode} from '@lexical/code-core'; -import {$createLineBreakNode, $createTabNode} from 'lexical'; +import {$createLineBreakNode, $createTabNode, tokenizeRawText} from 'lexical'; declare global { interface Window { @@ -272,18 +272,11 @@ function $mapTokensToLexicalStructure( for (const token of tokens) { if (typeof token === 'string') { - const partials = token.split(/(\n|\t)/); - const partialsLength = partials.length; - for (let i = 0; i < partialsLength; i++) { - const part = partials[i]; - if (part === '\n' || part === '\r\n') { - nodes.push($createLineBreakNode()); - } else if (part === '\t') { - nodes.push($createTabNode()); - } else if (part.length > 0) { - nodes.push($createCodeHighlightNode(part, type)); - } - } + tokenizeRawText(token, { + linebreak: () => nodes.push($createLineBreakNode()), + tab: () => nodes.push($createTabNode()), + text: part => nodes.push($createCodeHighlightNode(part, type)), + }); } else { const {content, alias} = token; if (typeof content === 'string') { diff --git a/packages/lexical-code-shiki/src/FacadeShiki.ts b/packages/lexical-code-shiki/src/FacadeShiki.ts index 37e9ac3a040..da96ebdbfd3 100644 --- a/packages/lexical-code-shiki/src/FacadeShiki.ts +++ b/packages/lexical-code-shiki/src/FacadeShiki.ts @@ -19,7 +19,12 @@ import { stringifyTokenStyle, } from '@shikijs/core'; import {createJavaScriptRegexEngine} from '@shikijs/engine-javascript'; -import {$createLineBreakNode, $createTabNode, $getNodeByKey} from 'lexical'; +import { + $createLineBreakNode, + $createTabNode, + $getNodeByKey, + tokenizeRawText, +} from 'lexical'; import {bundledLanguagesInfo} from 'shiki/langs'; import {bundledThemesInfo} from 'shiki/themes'; @@ -195,19 +200,17 @@ function mapTokensToLexicalStructure( } } - const parts = text.split('\t'); - parts.forEach((part: string, pidx: number) => { - if (pidx) { - nodes.push($createTabNode()); - } - if (part !== '') { + const style = stringifyTokenStyle( + token.htmlStyle || getTokenStyleObject(token), + ); + tokenizeRawText(text, { + linebreak: () => nodes.push($createLineBreakNode()), + tab: () => nodes.push($createTabNode()), + text: (part: string) => { const node = $createCodeHighlightNode(part); - const style = stringifyTokenStyle( - token.htmlStyle || getTokenStyleObject(token), - ); node.setStyle(style); nodes.push(node); - } + }, }); }); }); diff --git a/packages/lexical-html/src/__tests__/unit/LexicalHtmlBackwardCompat.test.ts b/packages/lexical-html/src/__tests__/unit/LexicalHtmlBackwardCompat.test.ts new file mode 100644 index 00000000000..b93e528b166 --- /dev/null +++ b/packages/lexical-html/src/__tests__/unit/LexicalHtmlBackwardCompat.test.ts @@ -0,0 +1,60 @@ +/** + * 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 {createHeadlessEditor} from '@lexical/headless'; +import {$generateHtmlFromNodes} from '@lexical/html'; +import {$createParagraphNode, $createTextNode, $getRoot} from 'lexical'; +import {describe, expect, test} from 'vitest'; + +describe('$generateHtmlFromNodes backward compatibility', () => { + test('works inside legacy editor.getEditorState().read(cb) scope (no active editor)', () => { + const editor = createHeadlessEditor({ + nodes: [], + }); + + // Populate editor state + editor.update( + () => { + const paragraph = $createParagraphNode(); + paragraph.append($createTextNode('Hello world')); + $getRoot().append(paragraph); + }, + {discrete: true}, + ); + + // Legacy pattern: editor.getEditorState().read(cb) WITHOUT {editor} option. + // Before this fix, this would throw because $setTextContent calls $getEditor() + // which requires an active editor scope. + const html = editor.getEditorState().read(() => { + return $generateHtmlFromNodes(editor); + }); + + expect(html).toContain('Hello world'); + }); + + test('still works inside editor.read() scope (active editor present)', () => { + const editor = createHeadlessEditor({ + nodes: [], + }); + + editor.update( + () => { + const paragraph = $createParagraphNode(); + paragraph.append($createTextNode('Test content')); + $getRoot().append(paragraph); + }, + {discrete: true}, + ); + + const html = editor.read(() => { + return $generateHtmlFromNodes(editor); + }); + + expect(html).toContain('Test content'); + }); +}); diff --git a/packages/lexical-html/src/index.ts b/packages/lexical-html/src/index.ts index 084f9a35016..6ebb29fcc70 100644 --- a/packages/lexical-html/src/index.ts +++ b/packages/lexical-html/src/index.ts @@ -20,6 +20,7 @@ import type { import invariant from '@lexical/internal/invariant'; import {$sliceSelectedTextNodeContent} from '@lexical/selection'; import { + $assumeActiveEditor, $createLineBreakNode, $createParagraphNode, $getEditor, @@ -251,6 +252,10 @@ export function $generateHtmlFromNodes( 'To use $generateHtmlFromNodes in headless mode please initialize a headless browser implementation such as JSDom or use withDOM from @lexical/headless/dom before calling this function.', ); } + // BC: $setTextContent now requires an active-editor scope (added in #8519). + // If the caller is in a legacy `editorState.read(cb)` scope (no active editor), + // establish one via internal API. + $assumeActiveEditor(editor); return $generateDOMFromNodes(document.createElement('div'), selection, editor) .innerHTML; } diff --git a/packages/lexical-playground/__tests__/e2e/Autocomplete.spec.mjs b/packages/lexical-playground/__tests__/e2e/Autocomplete.spec.mjs index 0c15aa8f7fd..419df095f13 100644 --- a/packages/lexical-playground/__tests__/e2e/Autocomplete.spec.mjs +++ b/packages/lexical-playground/__tests__/e2e/Autocomplete.spec.mjs @@ -28,49 +28,45 @@ test.describe('Autocomplete', () => { test.beforeEach(({isCollab, page}) => initialize({isAutocomplete: true, isCollab, page}), ); - test( - 'Can autocomplete a word', - {tag: '@flaky'}, - async ({page, isPlainText}) => { - await focusEditor(page); - await page.keyboard.type('Sort by alpha'); - await sleep(500); - // The ghost is a DOM-only decoration on the active TextNode's span and - // is intentionally not part of EditorState, so it doesn't sync through - // Yjs to the right collab frame. - await assertHTML( - page, - html` -
-
- Sort by alpha
-
- betical (TAB)
-
+ test('Can autocomplete a word', async ({page, isPlainText}) => {
+ await focusEditor(page);
+ await page.keyboard.type('Sort by alpha');
+ await sleep(500);
+ // The ghost is a DOM-only decoration on the active TextNode's span and
+ // is intentionally not part of EditorState, so it doesn't sync through
+ // Yjs to the right collab frame.
+ await assertHTML(
+ page,
+ html`
+
+
+ Sort by alpha
+
+ betical (TAB)
-
- Sort by alpha
-
- Sort by alphabetical order:
-
+ Sort by alpha +
+ `, + ); + await page.keyboard.press('Tab'); + await page.keyboard.type(' order:'); + await assertHTML( + page, + html` ++ Sort by alphabetical order: +
+ `, + ); + }); test('Can autocomplete in the same format as the original text', async ({ page, diff --git a/packages/lexical-playground/__tests__/e2e/ClearFormatting.spec.mjs b/packages/lexical-playground/__tests__/e2e/ClearFormatting.spec.mjs index e1f04d08967..89815224874 100644 --- a/packages/lexical-playground/__tests__/e2e/ClearFormatting.spec.mjs +++ b/packages/lexical-playground/__tests__/e2e/ClearFormatting.spec.mjs @@ -104,86 +104,87 @@ test.describe('Clear All Formatting', () => { ); }); - test( - `Should preserve the default styling of hashtags and mentions`, - { - tag: '@flaky', - }, - async ({page}) => { - await focusEditor(page); + test(`Should preserve the default styling of hashtags and mentions`, async ({ + page, + }) => { + await focusEditor(page); - await page.keyboard.type('#facebook testing'); - await selectAll(page); - await toggleItalic(page); - await selectFromBackgroundColorPicker(page); - await selectFromColorPicker(page); - await selectFromAdditionalStylesDropdown(page, '.clear'); - await assertHTML( - page, - html` -- - #facebook - - testing -
- `, - ); + await page.keyboard.type('#facebook testing'); + await selectAll(page); + await toggleItalic(page); + await selectFromBackgroundColorPicker(page); + await selectFromColorPicker(page); + await selectFromAdditionalStylesDropdown(page, '.clear'); + await assertHTML( + page, + html` ++ + #facebook + + testing +
+ `, + ); - await clearEditor(page); + await clearEditor(page); - await page.keyboard.type('@Luke'); + await page.keyboard.type('@Luke'); - await waitForSelector(page, '#typeahead-menu ul li'); - await assertHTML( - page, - html` -- @Luke -
- `, - ); + // Wait until "Luke Skywalker" is the *highlighted* option, not merely + // present: while "@Luke" is still being typed, the partial query "@Lu" + // also matches "Agent Kallus" (kal**lu**s), which sorts earlier in the + // list and is highlighted first, so pressing Enter too early selects it. + await waitForSelector( + page, + '#typeahead-menu ul li[aria-selected="true"]:has-text("Luke Skywalker")', + ); + await assertHTML( + page, + html` ++ @Luke +
+ `, + ); - await page.keyboard.press('Enter'); - await assertHTML( - page, - html` -- - Luke Skywalker - -
- `, - ); + await page.keyboard.press('Enter'); + await assertHTML( + page, + html` ++ + Luke Skywalker + +
+ `, + ); - await page.keyboard.type(' is testing'); - await selectAll(page); - await toggleBold(page); - await selectFromColorPicker(page); - await selectFromAdditionalStylesDropdown(page, '.clear'); - await assertHTML( - page, - html` -- - Luke Skywalker - - is testing -
- `, - ); - }, - ); + await page.keyboard.type(' is testing'); + await selectAll(page); + await toggleBold(page); + await selectFromColorPicker(page); + await selectFromAdditionalStylesDropdown(page, '.clear'); + await assertHTML( + page, + html` ++ + Luke Skywalker + + is testing +
+ `, + ); + }); test(`Can clear left/center/right alignment when BIU formatting already applied`, async ({ page, diff --git a/packages/lexical-playground/__tests__/e2e/CopyAndPaste/lexical/CopyAndPaste.spec.mjs b/packages/lexical-playground/__tests__/e2e/CopyAndPaste/lexical/CopyAndPaste.spec.mjs index ecd039e5058..b2ea77eb236 100644 --- a/packages/lexical-playground/__tests__/e2e/CopyAndPaste/lexical/CopyAndPaste.spec.mjs +++ b/packages/lexical-playground/__tests__/e2e/CopyAndPaste/lexical/CopyAndPaste.spec.mjs @@ -816,61 +816,59 @@ test.describe('CopyAndPaste', () => { ); }); - test( - 'Pasting a decorator node on a blank line inserts before the line', - { - tag: '@flaky', - }, - async ({page, isCollab, isPlainText}) => { - // TODO: This is skipped on collab because the right frame won't have the block cursor HTML - test.skip(isPlainText || isCollab); - - // 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 - await withExclusiveClipboardAccess(async () => { - const clipboard = await copyToClipboard(page); - await page.keyboard.press('ArrowRight'); // this moves to a new line (empty paragraph node) - await pasteFromClipboard(page, clipboard); + test('Pasting a decorator node on a blank line inserts before the line', async ({ + page, + isCollab, + isPlainText, + }) => { + // TODO: This is skipped on collab because the right frame won't have the block cursor HTML + test.skip(isPlainText || isCollab); - await assertHTML( - page, - html` -+ 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], + }); - // Add a paragraph - await page.keyboard.type('Some text.'); + await withExclusiveClipboardAccess(async () => { + // 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, @@ -165,9 +219,12 @@ test.describe('Lists CopyAndPaste', () => { one+ Some text. +
++ Some text. +
+ `, + ); await assertSelection(page, { anchorOffset: 10, anchorPath: [1, 0, 0], - focusOffset: 3, - focusPath: [0, 2, 0, 0], - }); - - await withExclusiveClipboardAccess(async () => { - // 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` -- Some text. -
- `, - ); - await assertSelection(page, { - anchorOffset: 0, - anchorPath: [0, 1], - focusOffset: 0, - focusPath: [0, 1], - }); - - await pasteFromClipboard(page, clipboard); - - await assertHTML( - page, - html` -- Some text. -
-- Some text. -
- `, - ); - await assertSelection(page, { - anchorOffset: 10, - anchorPath: [1, 0, 0], - focusOffset: 10, - focusPath: [1, 0, 0], - }); + focusOffset: 10, + focusPath: [1, 0, 0], }); - }, - ); + }); + }); test('Copy list items and paste back into list', async ({ page, diff --git a/packages/lexical-playground/__tests__/e2e/History.spec.mjs b/packages/lexical-playground/__tests__/e2e/History.spec.mjs index dce4d2219b6..3242893b94b 100644 --- a/packages/lexical-playground/__tests__/e2e/History.spec.mjs +++ b/packages/lexical-playground/__tests__/e2e/History.spec.mjs @@ -26,414 +26,412 @@ import { test.describe('History', () => { test.beforeEach(({isCollab, page}) => initialize({isCollab, page})); - test( - `Can type two paragraphs of text and correctly undo and redo`, - { - tag: '@flaky', - }, - async ({isRichText, page, isCollab}) => { - test.skip(isCollab); - await page.focus('div[contenteditable="true"]'); - await page.keyboard.type('hello'); - await sleep(1050); // default merge interval is 1000, add 50ms as overhead due to CI latency. - await page.keyboard.type(' world'); - await page.keyboard.press('Enter'); - await page.keyboard.type('hello world again'); - await moveLeft(page, 6); - await page.keyboard.type(', again and'); - - if (isRichText) { - await assertHTML( - page, - html` -- hello world -
-- hello world, again and again -
- `, - ); - await assertSelection(page, { - anchorOffset: 22, - anchorPath: [1, 0, 0], - focusOffset: 22, - focusPath: [1, 0, 0], - }); - } else { - await assertHTML( - page, - html` -
- hello world
-
- hello world, again and again
-
- hello world -
-- hello world again -
- `, - ); - await assertSelection(page, { - anchorOffset: 11, - anchorPath: [1, 0, 0], - focusOffset: 11, - focusPath: [1, 0, 0], - }); - } else { - await assertHTML( - page, - html` -
- hello world
-
- hello world again
-
- hello world -
-
-
-
- hello world
-
-
-
+ hello world +
++ hello world, again and again +
+ `, + ); + await assertSelection(page, { + anchorOffset: 22, + anchorPath: [1, 0, 0], + focusOffset: 22, + focusPath: [1, 0, 0], + }); + } else { await assertHTML( page, html`
hello world
+
+ hello world, again and again
+
+ hello world +
++ hello world again
`, ); await assertSelection(page, { anchorOffset: 11, - anchorPath: [0, 0, 0], + anchorPath: [1, 0, 0], focusOffset: 11, - focusPath: [0, 0, 0], + focusPath: [1, 0, 0], + }); + } else { + await assertHTML( + page, + html` +
+ hello world
+
+ hello world again
+
- hello + hello world +
+
+
+ hello world
+
+
+
+ hello world +
+ `, + ); + await assertSelection(page, { + anchorOffset: 11, + anchorPath: [0, 0, 0], + focusOffset: 11, + focusPath: [0, 0, 0], + }); + + await undo(page); + + await assertHTML( + page, + html` ++ hello +
+ `, + ); + await assertSelection(page, { + anchorOffset: 5, + anchorPath: [0, 0, 0], + focusOffset: 5, + focusPath: [0, 0, 0], + }); + + await undo(page); + + await assertHTML( + page, + html` ++ hello +
+ `, + ); + await assertSelection(page, { + anchorOffset: 5, + anchorPath: [0, 0, 0], + focusOffset: 5, + focusPath: [0, 0, 0], + }); - await undo(page); + await redo(page); + + await assertHTML( + page, + html` ++ hello world +
+ `, + ); + await assertSelection(page, { + anchorOffset: 11, + anchorPath: [0, 0, 0], + focusOffset: 11, + focusPath: [0, 0, 0], + }); + + await redo(page); + if (isRichText) { await assertHTML( page, html` ++ hello world +
+ hello world
+
+
+
- hello + hello world +
++ hello world again
`, ); await assertSelection(page, { - anchorOffset: 5, - anchorPath: [0, 0, 0], - focusOffset: 5, - focusPath: [0, 0, 0], + anchorOffset: 11, + anchorPath: [1, 0, 0], + focusOffset: 11, + focusPath: [1, 0, 0], }); + } else { + assertHTML( + page, + html` +
+ hello world
+
+ hello world again
+
+ hello world +
++ hello world, again and again +
+ `, + ); + await assertSelection(page, { + anchorOffset: 22, + anchorPath: [1, 0, 0], + focusOffset: 22, + focusPath: [1, 0, 0], + }); + } else { + assertHTML( + page, + html` +
+ hello world
+
+ hello world, again and again
+
hello world
++ hello world, again again +
`, ); await assertSelection(page, { - anchorOffset: 11, - anchorPath: [0, 0, 0], - focusOffset: 11, - focusPath: [0, 0, 0], + anchorOffset: 18, + anchorPath: [1, 0, 0], + focusOffset: 18, + focusPath: [1, 0, 0], + }); + } else { + await assertHTML( + page, + html` +
+ hello world
+
+ hello world, again again
+
- hello world -
-
- hello world
-
-
-
- hello world -
-- hello world again -
- `, - ); - await assertSelection(page, { - anchorOffset: 11, - anchorPath: [1, 0, 0], - focusOffset: 11, - focusPath: [1, 0, 0], - }); - } else { - assertHTML( - page, - html` -
- hello world
-
- hello world again
-
- hello world -
-- hello world, again and again -
- `, - ); - await assertSelection(page, { - anchorOffset: 22, - anchorPath: [1, 0, 0], - focusOffset: 22, - focusPath: [1, 0, 0], - }); - } else { - assertHTML( - page, - html` -
- hello world
-
- hello world, again and again
-
- hello world -
-- hello world, again again -
- `, - ); - await assertSelection(page, { - anchorOffset: 18, - anchorPath: [1, 0, 0], - focusOffset: 18, - focusPath: [1, 0, 0], - }); - } else { - await assertHTML( - page, - html` -
- hello world
-
- hello world, again again
-
- hello world -
-- hello world, again and again -
- `, - ); - await assertSelection(page, { - anchorOffset: 22, - anchorPath: [1, 0, 0], - focusOffset: 22, - focusPath: [1, 0, 0], - }); - } else { - await assertHTML( - page, - html` -
- hello world
-
- hello world, again and again
-
+ hello world +
++ hello world, again and again +
+ `, + ); + await assertSelection(page, { + anchorOffset: 22, + anchorPath: [1, 0, 0], + focusOffset: 22, + focusPath: [1, 0, 0], + }); + } else { + await assertHTML( + page, + html` +
+ hello world
+
+ hello world, again and again
+
- Some text -
-- Some more text -
- `, - ); + await assertHTML( + page, + html` ++ Some text +
++ Some more text +
+ `, + ); - await moveToLineBeginning(page); + await moveToLineBeginning(page); - await page.keyboard.press('ArrowLeft'); + await page.keyboard.press('ArrowLeft'); - await page.keyboard.press('ArrowLeft'); + await page.keyboard.press('ArrowLeft'); - await assertSelection(page, { - anchorOffset: 1, - anchorPath: [0], - focusOffset: 1, - focusPath: [0], - }); + await assertSelection(page, { + anchorOffset: 1, + anchorPath: [0], + focusOffset: 1, + focusPath: [0], + }); - await pressBackspace(page, 10); + await pressBackspace(page, 10); - // Collab doesn't process the cursor correctly - if (!isCollab) { - await assertHTML( - page, - 'Some more text
', - ); - } + // Collab doesn't process the cursor correctly + if (!isCollab) { + await assertHTML( + page, + 'Some more text
', + ); + } - await assertSelection(page, { - anchorOffset: 0, - anchorPath: [], - focusOffset: 0, - focusPath: [], - }); - }, - ); + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [], + focusOffset: 0, + focusPath: [], + }); + }); test('Will add a horizontal rule at the end of a current TextNode and move selection to the new ParagraphNode.', async ({ page, diff --git a/packages/lexical-playground/__tests__/e2e/Links.spec.mjs b/packages/lexical-playground/__tests__/e2e/Links.spec.mjs index b2d2f9de02a..57895fe24cb 100644 --- a/packages/lexical-playground/__tests__/e2e/Links.spec.mjs +++ b/packages/lexical-playground/__tests__/e2e/Links.spec.mjs @@ -407,140 +407,132 @@ test.describe.parallel('Links', () => { ); }); - test( - `Can create a link with some text after, insert paragraph, then backspace, it should merge correctly`, - { - tag: '@flaky', - }, - async ({page}) => { - await focusEditor(page); - await page.keyboard.type(' abc def '); - await moveLeft(page, 5); - await selectCharacters(page, 'left', 3); + test(`Can create a link with some text after, insert paragraph, then backspace, it should merge correctly`, async ({ + page, + }) => { + await focusEditor(page); + await page.keyboard.type(' abc def '); + await moveLeft(page, 5); + await selectCharacters(page, 'left', 3); - // link - await click(page, '.link'); - await click(page, '.link-confirm'); + // link + await click(page, '.link'); + await click(page, '.link-confirm'); - await assertHTML( - page, - html` -- - - abc - - def -
- `, - ); + await assertHTML( + page, + html` ++ + + abc + + def +
+ `, + ); - await moveLeft(page, 1); - await moveRight(page, 2); - await page.keyboard.press('Enter'); + await moveLeft(page, 1); + await moveRight(page, 2); + await page.keyboard.press('Enter'); - await assertHTML( - page, - html` -- - - ab - -
-- - c - - def -
- `, - ); + await assertHTML( + page, + html` ++ + + ab + +
++ + c + + def +
+ `, + ); - await page.keyboard.press('Backspace'); + await page.keyboard.press('Backspace'); - await assertHTML( - page, - html` -- - - abc - - def -
- `, - ); - }, - ); + await assertHTML( + page, + html` ++ + + abc + + def +
+ `, + ); + }); - test( - `Can backspace across a link and it deletes text, not the whole link`, - { - tag: '@flaky', - }, - async ({page}) => { - await focusEditor(page); - await page.keyboard.type(' abc def '); - await moveLeft(page, 5); - await selectCharacters(page, 'left', 3); + test(`Can backspace across a link and it deletes text, not the whole link`, async ({ + page, + }) => { + await focusEditor(page); + await page.keyboard.type(' abc def '); + await moveLeft(page, 5); + await selectCharacters(page, 'left', 3); - // link - await click(page, '.link'); - await click(page, '.link-confirm'); + // link + await click(page, '.link'); + await click(page, '.link-confirm'); - await assertHTML( - page, - html` -- - - abc - - def -
- `, - ); + await assertHTML( + page, + html` ++ + + abc + + def +
+ `, + ); - await moveRight(page, 4); + await moveRight(page, 4); - await page.keyboard.press('Backspace'); - await page.keyboard.press('Backspace'); - await page.keyboard.press('Backspace'); - await page.keyboard.press('Backspace'); + await page.keyboard.press('Backspace'); + await page.keyboard.press('Backspace'); + await page.keyboard.press('Backspace'); + await page.keyboard.press('Backspace'); - await assertHTML( - page, - html` -- - - ab - - f -
- `, - ); - }, - ); + await assertHTML( + page, + html` ++ + + ab + + f +
+ `, + ); + }); test(`Can create a link then replace it with plain text`, async ({page}) => { await focusEditor(page); @@ -1841,120 +1833,111 @@ test.describe.parallel('Links', () => { ); }); - test( - 'Can handle pressing Enter inside a Link', - {tag: '@flaky'}, - async ({page}) => { - 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'); + test('Can handle pressing Enter inside a Link', async ({page}) => { + 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'); - await moveToLineBeginning(page); - await moveRight(page, 6); + await moveToLineBeginning(page); + await moveRight(page, 6); - await page.keyboard.press('Enter'); + await page.keyboard.press('Enter'); - await assertHTML( - page, - html` -- - Hello - -
-- - awesome - - world -
- `, - undefined, - {ignoreClasses: true}, - ); - }, - ); + await assertHTML( + page, + html` ++ + Hello + +
++ + awesome + + world +
+ `, + undefined, + {ignoreClasses: true}, + ); + }); - test( - 'Can handle pressing Enter inside a Link containing multiple TextNodes', - {tag: '@flaky'}, - async ({page, isCollab}) => { - await focusEditor(page); - await page.keyboard.type('Hello '); - await toggleBold(page); - await page.keyboard.type('awe'); - await toggleBold(page); - 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'); + test('Can handle pressing Enter inside a Link containing multiple TextNodes', async ({ + page, + isCollab, + }) => { + await focusEditor(page); + await page.keyboard.type('Hello '); + await toggleBold(page); + await page.keyboard.type('awe'); + await toggleBold(page); + 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'); - await moveToLineBeginning(page); - await moveRight(page, 6); + await moveToLineBeginning(page); + await moveRight(page, 6); - await page.keyboard.press('Enter'); + await page.keyboard.press('Enter'); - await assertHTML( - page, - html` -- - Hello - -
-- - awe - some - - world -
- `, - undefined, - {ignoreClasses: true}, - ); - }, - ); + await assertHTML( + page, + html` ++ + Hello + +
++ + awe + some + + world +
+ `, + undefined, + {ignoreClasses: true}, + ); + }); - test( - 'Can handle pressing Enter at the beginning of a Link', - { - tag: '@flaky', - }, - async ({page}) => { - 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'); + test('Can handle pressing Enter at the beginning of a Link', async ({ + page, + }) => { + 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'); - await moveToLineBeginning(page); - await page.keyboard.press('Enter'); + await moveToLineBeginning(page); + await page.keyboard.press('Enter'); - await assertHTML( - page, - html` -- - Hello awesome - - world -
- `, - undefined, - {ignoreClasses: true}, - ); - }, - ); + await assertHTML( + page, + html` ++ + Hello awesome + + world +
+ `, + undefined, + {ignoreClasses: true}, + ); + }); test('Can handle pressing Enter at the end of a Link', async ({ isCollab, diff --git a/packages/lexical-playground/__tests__/e2e/List.spec.mjs b/packages/lexical-playground/__tests__/e2e/List.spec.mjs index 5c3718a2393..7489a29f2ab 100644 --- a/packages/lexical-playground/__tests__/e2e/List.spec.mjs +++ b/packages/lexical-playground/__tests__/e2e/List.spec.mjs @@ -169,57 +169,54 @@ test.describe('Checklist focus option', () => { test.describe.parallel('Nested List', () => { test.beforeEach(({isCollab, page}) => initialize({isCollab, page})); - test( - `Can create a list and partially copy some content out of it`, - { - tag: '@flaky', - }, - async ({page, isCollab}) => { - 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.', - ); - await toggleBulletList(page); - await moveToEditorBeginning(page); - await moveRight(page, 6); - await selectCharacters(page, 'right', 11); - - await withExclusiveClipboardAccess(async () => { - const clipboard = await copyToClipboard(page); - - await moveToEditorEnd(page); - await page.keyboard.press('Enter'); - await page.keyboard.press('Enter'); - - await pasteFromClipboard(page, clipboard); - }); - - await assertHTML( - page, - html` -- ipsum dolor -
- `, - ); - }, - ); + test(`Can create a list and partially copy some content out of it`, async ({ + page, + isCollab, + }) => { + 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.', + ); + await toggleBulletList(page); + await moveToEditorBeginning(page); + await moveRight(page, 6); + await selectCharacters(page, 'right', 11); + + await withExclusiveClipboardAccess(async () => { + const clipboard = await copyToClipboard(page); + + await moveToEditorEnd(page); + await page.keyboard.press('Enter'); + await page.keyboard.press('Enter'); + + await pasteFromClipboard(page, clipboard); + }); + + await assertHTML( + page, + html` ++ ipsum dolor +
+ `, + ); + }); test('Should outdent if indented when the backspace key is pressed', async ({ page, @@ -2614,71 +2611,68 @@ test.describe.parallel('Nested List', () => { ); }); - test( - 'can navigate and check/uncheck with keyboard', - { - tag: '@flaky', - }, - async ({page, isCollab}) => { - await focusEditor(page); - await toggleCheckList(page); - // - // [ ] a - // [ ] b - // [ ] c - // [ ] d - // [ ] e - // [ ] f - await page.keyboard.type('a'); - await page.keyboard.press('Enter'); - await page.keyboard.type('b'); - await page.keyboard.press('Enter'); - await click(page, '.toolbar-item.alignment'); - await click(page, 'button:has-text("Indent")'); - await page.keyboard.type('c'); - await page.keyboard.press('Enter'); - await click(page, '.toolbar-item.alignment'); - await click(page, 'button:has-text("Indent")'); - await page.keyboard.type('d'); - await page.keyboard.press('Enter'); - await page.keyboard.type('e'); - await page.keyboard.press('Enter'); - await page.keyboard.press('Backspace'); - await page.keyboard.press('Backspace'); - await page.keyboard.type('f'); - - const assertCheckCount = async (checkCount, uncheckCount) => { - const pageOrFrame = await (isCollab ? page.frame('left') : page); - await expect( - pageOrFrame.locator('li[role="checkbox"][aria-checked="true"]'), - ).toHaveCount(checkCount); - await expect( - pageOrFrame.locator('li[role="checkbox"][aria-checked="false"]'), - ).toHaveCount(uncheckCount); - }; - - await assertCheckCount(0, 6); - - // Go back to select checkbox - await page.keyboard.press('ArrowLeft'); - await page.keyboard.press('ArrowLeft'); + test('can navigate and check/uncheck with keyboard', async ({ + page, + isCollab, + }) => { + await focusEditor(page); + await toggleCheckList(page); + // + // [ ] a + // [ ] b + // [ ] c + // [ ] d + // [ ] e + // [ ] f + await page.keyboard.type('a'); + await page.keyboard.press('Enter'); + await page.keyboard.type('b'); + await page.keyboard.press('Enter'); + await click(page, '.toolbar-item.alignment'); + await click(page, 'button:has-text("Indent")'); + await page.keyboard.type('c'); + await page.keyboard.press('Enter'); + await click(page, '.toolbar-item.alignment'); + await click(page, 'button:has-text("Indent")'); + await page.keyboard.type('d'); + await page.keyboard.press('Enter'); + await page.keyboard.type('e'); + await page.keyboard.press('Enter'); + await page.keyboard.press('Backspace'); + await page.keyboard.press('Backspace'); + await page.keyboard.type('f'); + + const assertCheckCount = async (checkCount, uncheckCount) => { + const pageOrFrame = await (isCollab ? page.frame('left') : page); + await expect( + pageOrFrame.locator('li[role="checkbox"][aria-checked="true"]'), + ).toHaveCount(checkCount); + await expect( + pageOrFrame.locator('li[role="checkbox"][aria-checked="false"]'), + ).toHaveCount(uncheckCount); + }; + + await assertCheckCount(0, 6); + + // Go back to select checkbox + await page.keyboard.press('ArrowLeft'); + await page.keyboard.press('ArrowLeft'); + await page.keyboard.press('Space'); + + await repeat(5, async () => { + await page.keyboard.press('ArrowUp', {delay: 50}); await page.keyboard.press('Space'); + }); - await repeat(5, async () => { - await page.keyboard.press('ArrowUp', {delay: 50}); - await page.keyboard.press('Space'); - }); - - await assertCheckCount(6, 0); + await assertCheckCount(6, 0); - await repeat(3, async () => { - await page.keyboard.press('ArrowDown', {delay: 50}); - await page.keyboard.press('Space'); - }); + await repeat(3, async () => { + await page.keyboard.press('ArrowDown', {delay: 50}); + await page.keyboard.press('Space'); + }); - await assertCheckCount(3, 3); - }, - ); + await assertCheckCount(3, 3); + }); test('replaces existing element node', async ({page}) => { // Create two quote blocks, select it and format to a list @@ -2736,56 +2730,52 @@ test.describe.parallel('Nested List', () => { }); }); - test( - 'remove list breaks when selection in empty nested list item 2', - { - tag: '@flaky', - }, - async ({page}) => { - await focusEditor(page); - await page.keyboard.type('Hello World'); - await page.keyboard.press('Enter'); - await page.keyboard.type('a'); - await toggleBulletList(page); - await page.keyboard.press('Enter'); - await page.keyboard.type('b'); - await page.keyboard.press('ArrowUp'); - await page.keyboard.press('Enter'); - await click(page, '.toolbar-item.alignment'); - await click(page, 'button:has-text("Indent")'); - await toggleBulletList(page); - await assertHTML( - page, - html` -- Hello World -
-
-
-
+ Hello World +
+
+
+
- すし - - すし -
- `, - ); - }, - ); + await assertHTML( + page, + html` ++ すし + + すし +
+ `, + ); + }); test('can tab inside code block #4399', async ({page, isPlainText}) => { test.skip(isPlainText); diff --git a/packages/lexical-playground/__tests__/e2e/Tables.spec.mjs b/packages/lexical-playground/__tests__/e2e/Tables.spec.mjs index 06bd677c9c8..ee117c50c76 100644 --- a/packages/lexical-playground/__tests__/e2e/Tables.spec.mjs +++ b/packages/lexical-playground/__tests__/e2e/Tables.spec.mjs @@ -847,67 +847,65 @@ test.describe.parallel('Tables', () => { }); }); - test( - `Can select cells using Table selection`, - { - tag: '@flaky', - }, - async ({page, isPlainText, isCollab}) => { - test.skip(isPlainText); - await initialize({isCollab, page}); + test(`Can select cells using Table selection`, async ({ + page, + isPlainText, + isCollab, + }) => { + test.skip(isPlainText); + await initialize({isCollab, page}); - await focusEditor(page); - await insertTable(page, 2, 3); + 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 fillTablePartiallyWithText(page); + await selectCellsFromTableCords( + page, + {x: 0, y: 0}, + {x: 1, y: 1}, + true, + false, + ); - await assertHTML( - page, - html` -|
- a - |
-
- bb - |
-
- cc - |
-
|---|---|---|
|
- d - |
-
- e - |
-
- f - |
-
|
+ a + |
+
+ bb + |
+
+ cc + |
+
|---|---|---|
|
+ d + |
+
+ e + |
+
+ f + |
+
|
- a - |
-
- bb - |
-
- cc - |
-
|---|---|---|
|
- d - |
-
- e - |
-
- f - |
-
|
+ a + |
+
+ bb + |
+
+ cc + |
+
|---|---|---|
|
+ d + |
+
+ e + |
+
+ f + |
+
|
- a - |
-
- bb - |
-
|---|---|
|
- d - |
-
- e - |
-
|
- a - |
-
- bb - |
-
- cc - |
-
|---|---|---|
|
- d - |
-
- e - |
-
- f - |
-
|
+ a + |
+
+ bb + |
+
|---|---|
|
+ d + |
+
+ e + |
+
|
+ a + |
+
+ bb + |
+
+ cc + |
+
|---|---|---|
|
+ d + |
+
+ e + |
+
+ f + |
+
|
- |
-
- |
-
- cc - |
-
|---|---|---|
|
- |
-
- |
-
- f - |
-
|
+ |
+
+ |
+
+ cc + |
+
|---|---|---|
|
+ |
+
+ |
+
+ f + |
+
|
- - Hello - - |
-
-
- |
-
|---|---|
|
-
- |
-
- - ${getExpectedDateTimeHtml()} - <- it works! - - |
-
|
+ + Hello + + |
+
+
+ |
+
|---|---|
|
+
+ |
+
+ + ${getExpectedDateTimeHtml()} + <- it works! + + |
+
|
-
- |
-
-
- |
- |
|---|---|---|
|
-
- |
- ||
|
-
- |
-
-
- |
-
-
- |
-
|
+
+ |
+
+
+ |
+ |
|---|---|---|
|
+
+ |
+ ||
|
+
+ |
+
+
+ |
+
+
+ |
+
|
-
- |
-
-
- |
-
-
- |
-
-
- |
-
-
- |
-
|---|---|---|---|---|
|
-
- |
-
-
- |
-
-
- |
-
-
- |
-
-
- |
-
|
-
- |
-
- - a - -- b - -- c - -- d - -- e - -- f - - |
-
-
- |
- ||
|
-
- |
-
-
- |
- |||
|
-
- |
-
-
- |
- |||
|
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
|---|---|---|---|---|
|
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
|
+
+ |
+
+ + a + ++ b + ++ c + ++ d + ++ e + ++ f + + |
+
+
+ |
+ ||
|
+
+ |
+
+
+ |
+ |||
|
+
+ |
+
+
+ |
+ |||
|
-
- |
-
-
- |
-
-
- |
-
-
- |
-
-
- |
-
|---|---|---|---|---|
|
-
- |
-
-
- |
-
-
- |
-
-
- |
-
-
- |
-
|
-
- |
-
- - a - -- b - -- c - -- d - -- e - -- f - - |
-
-
- |
-
-
- |
-
-
- |
-
|
-
- |
-
-
- |
-
-
- |
-
-
- |
-
-
- |
-
|
-
- |
-
-
- |
-
-
- |
-
-
- |
-
-
- |
-
|
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
|---|---|---|---|---|
|
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
|
+
+ |
+
+ + a + ++ b + ++ c + ++ d + ++ e + ++ f + + |
+
+
+ |
+
+
+ |
+
+
+ |
+
|
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
|
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+ A +
++ B +
++ C +
++ D +
+|
+
+ |
+
+
+ |
+ |
|---|---|---|
|
+
+ |
+
+
+ |
+ |
|
+
+ |
+
+
+ |
+
+
+ |
+
|
+
+ |
-
+ |
|||
|---|---|---|---|---|
|
- |
-
- - A - -- B - + |
- C
+ |
+
- D
+ |
-
+ |
+
+
+ |
+
+
+ |
|
-
- |
-
-
- |
- |
|---|---|---|
|
-
- |
-
-
- |
- |
|
-
- |
-
-
- |
-
-
- |
-
|
-
- |
-
-
- |
- |
|---|---|---|
|
-
- |
-
-
- |
- |
|
-
- |
-
-
- |
-
-
- |
-
|
-
- |
- ||
|---|---|---|
|
-
- |
-
-
- |
-
-
- |
-
|
-
- |
-
-
- |
-
-
- |
-
|---|---|---|
|
-
- |
-
-
- |
-
-
- |
-
|
-
- |
-
-
- |
-
-
- |
-
|
+
+ |
+ ||
|---|---|---|
|
+
+ |
+
+
+ |
+
+
+ |
+
|
+
+ |
+
+
+ |
+
+
+ |
+
|---|---|---|
|
+
+ |
+
+
+ |
+
+
+ |
+
|
+
+ |
+
+
+ |
+
+
+ |
+
|
+
+ |
+
+
+ |
||
|---|---|---|---|
|
- |
-
- |
-
+ |
-
- |
|
-
- |
-
-
- |
-
|---|---|
|
-
- |
-
|
-
- |
-
|---|
|
-
- |
-
|
+
+ |
+
|---|
|
+
+ |
+
|
- - a - - |
-
- - bb - - |
-
- cc - |
-
|---|---|---|
|
- - d - - |
-
- - e - - |
-
- f - |
-
|
+ + a + + |
+
+ + bb + + |
+
+ cc + |
+
|---|---|---|
|
+ + d + + |
+
+ + e + + |
+
+ f + |
+
- A - ${getExpectedDateTimeHtml({selected: true})} - BC -
- `, - ); - } - await toggleBold(page); - await assertHTML( - page, - html` -- A - ${getExpectedDateTimeHtml({formats: ['bold']})} - - B - - C -
- `, - ); - await toggleBold(page); + if (!isCollab) { await assertHTML( page, - // After formatting the text, the selection will be reset from the decorator node, - // so it will retain its previous format when toggleBold is triggered again html`A - ${getExpectedDateTimeHtml({formats: ['bold']})} + ${getExpectedDateTimeHtml({selected: true})} BC
`, ); - }, - ); + } + await toggleBold(page); + await assertHTML( + page, + html` ++ A + ${getExpectedDateTimeHtml({formats: ['bold']})} + + B + + C +
+ `, + ); + await toggleBold(page); + await assertHTML( + page, + // After formatting the text, the selection will be reset from the decorator node, + // so it will retain its previous format when toggleBold is triggered again + html` ++ A + ${getExpectedDateTimeHtml({formats: ['bold']})} + BC +
+ `, + ); + }); test('Multiline selection format ignores new lines', async ({ page, diff --git a/packages/lexical-playground/__tests__/e2e/Toolbar.spec.mjs b/packages/lexical-playground/__tests__/e2e/Toolbar.spec.mjs index 8470dbc4711..2ce6302f597 100644 --- a/packages/lexical-playground/__tests__/e2e/Toolbar.spec.mjs +++ b/packages/lexical-playground/__tests__/e2e/Toolbar.spec.mjs @@ -28,6 +28,7 @@ import { selectFromAlignDropdown, selectFromInsertDropdown, test, + waitForSelector, } from '../utils/index.mjs'; test.describe('Toolbar', () => { @@ -40,233 +41,232 @@ test.describe('Toolbar', () => { }), ); - test( - 'Insert image caption + table', - { - tag: '@flaky', - }, - async ({page, isPlainText}) => { - // TODO(collab-v2): nested editors are not supported yet - test.skip(isPlainText || IS_COLLAB_V2); - await focusEditor(page); + test('Insert image caption + table', async ({page, isPlainText}) => { + // TODO(collab-v2): nested editors are not supported yet + test.skip(isPlainText || IS_COLLAB_V2); + await focusEditor(page); - // Add caption - await insertSampleImage(page); - // Catch flakiness earlier - await assertHTML( - page, - html` -
-
-
-
-
-
-
-
- Yellow flower in tilt shift lens
-
-
+
+
+
+
+
+ Yellow flower in tilt shift lens
+
+
-
- Yellow flower in tilt shift lens
-
-
-
- Yellow flower in tilt shift lens
-
-
+
+ Yellow flower in tilt shift lens
+
+
+
+ Yellow flower in tilt shift lens
+
+
-
+
+
+ 🙂
+
+ or
+
+ 🙁
+
+
🙂
@@ -39,65 +64,17 @@ test.describe('Regression test #429', () => {
`,
);
await assertSelection(page, {
- anchorOffset: 2,
- anchorPath: [0, 2, 0, 0],
- focusOffset: 2,
- focusPath: [0, 2, 0, 0],
+ anchorOffset: 0,
+ anchorPath: [1, 0, 0, 0],
+ focusOffset: 0,
+ focusPath: [1, 0, 0, 0],
});
-
- await moveLeft(page, 6);
- await page.keyboard.press('Enter');
- if (isRichText) {
- await assertHTML(
- page,
- html`
-
-
- 🙂
-
- or
-
- 🙁
-
-
-
+
+
+ 🙂
+
+ or
+
+ 🙁
+
+
-
explicitly before asserting.
+ await waitForSelector(page, '.editor-image img', {timeout: 30000});
+ // Catch flakiness earlier
+ await assertHTML(
+ page,
+ html`
+
+
+
+
-
- `,
- undefined,
- {
- ignoreClasses: true,
- ignoreInlineStyles: true,
- },
- actualHtml =>
- // flaky fix: remove the extra
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
- 🙂
-
- or
-
- 🙁
-
-
🙂
@@ -110,10 +87,32 @@ test.describe('Regression test #429', () => {
);
await assertSelection(page, {
anchorOffset: 0,
- anchorPath: [0, 0, 0, 0],
+ anchorPath: [0, 1, 0, 0],
focusOffset: 0,
- focusPath: [0, 0, 0, 0],
+ focusPath: [0, 1, 0, 0],
});
- },
- );
+ }
+
+ await page.keyboard.press('Backspace');
+ await assertHTML(
+ page,
+ html`
+