From 3fc9fb616b7ba7f355d73dd89ae6683b4507826c Mon Sep 17 00:00:00 2001 From: wnhlee <40269597+2wheeh@users.noreply.github.com> Date: Fri, 10 May 2024 11:07:09 +0900 Subject: [PATCH] [lexical-text] Bug Fix: for handling multiple matches on hashtags (#6056) --- .../__tests__/e2e/Hashtags.spec.mjs | 117 ++++++++++++++++++ .../src/registerLexicalTextEntity.ts | 8 ++ scripts/error-codes/codes.json | 3 +- 3 files changed, 127 insertions(+), 1 deletion(-) diff --git a/packages/lexical-playground/__tests__/e2e/Hashtags.spec.mjs b/packages/lexical-playground/__tests__/e2e/Hashtags.spec.mjs index e04c417cb7e..b3d190dfa19 100644 --- a/packages/lexical-playground/__tests__/e2e/Hashtags.spec.mjs +++ b/packages/lexical-playground/__tests__/e2e/Hashtags.spec.mjs @@ -14,9 +14,11 @@ import { import { assertHTML, assertSelection, + click, focusEditor, html, initialize, + pasteFromClipboard, pressToggleBold, repeat, test, @@ -383,4 +385,119 @@ test.describe('Hashtags', () => { `, ); }); + + test('Can handle hashtags following multiple invalid hashtags', async ({ + page, + }) => { + await focusEditor(page); + await page.keyboard.type('#hello'); + + await page.keyboard.press('Space'); + + await page.keyboard.type('#world'); + await page.keyboard.type('#invalid'); + await page.keyboard.type('#invalid'); + await page.keyboard.type('#invalid'); + + await page.keyboard.press('Space'); + + await page.keyboard.type('#valid'); + + await page.keyboard.press('Space'); + + await page.keyboard.type('#valid'); + await page.keyboard.type('#invalid'); + + await page.keyboard.press('Space'); + await page.keyboard.type('#valid'); + + await waitForSelector(page, '.PlaygroundEditorTheme__hashtag'); + + await assertHTML( + page, + html` +

+ + #hello + + + + #world + + #invalid#invalid#invalid + + #valid + + + + #valid + + #invalid + + #valid + +

+ `, + ); + }); + + test('Should not break when pasting multiple matches', 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 +

+ `, + ); + }); + + test('Should not break while importing and exporting multiple matches', async ({ + page, + }) => { + await focusEditor(page); + await page.keyboard.type('```markdown #hello#invalid #a #b'); + + await click(page, '.action-button .markdown'); + await click(page, '.action-button .markdown'); + await click(page, '.action-button .markdown'); + + await assertHTML( + page, + html` +

+ + #hello + + #invalid + + #a + + + + #b + +

+ `, + ); + }); }); diff --git a/packages/lexical-text/src/registerLexicalTextEntity.ts b/packages/lexical-text/src/registerLexicalTextEntity.ts index b29ae7d2d9f..320e3285983 100644 --- a/packages/lexical-text/src/registerLexicalTextEntity.ts +++ b/packages/lexical-text/src/registerLexicalTextEntity.ts @@ -14,6 +14,7 @@ import { LexicalNode, TextNode, } from 'lexical'; +import invariant from 'shared/invariant'; export type EntityMatch = {end: number; start: number}; @@ -151,6 +152,13 @@ export function registerLexicalTextEntity( match.end + prevMatchLengthToSkip, ); } + + invariant( + nodeToReplace !== undefined, + '%s should not be undefined. You may want to check splitOffsets passed to the splitText.', + 'nodeToReplace', + ); + const replacementNode = createNode(nodeToReplace); replacementNode.setFormat(nodeToReplace.getFormat()); nodeToReplace.replace(replacementNode); diff --git a/scripts/error-codes/codes.json b/scripts/error-codes/codes.json index bdb4a97372d..6e36a6c2f51 100644 --- a/scripts/error-codes/codes.json +++ b/scripts/error-codes/codes.json @@ -163,5 +163,6 @@ "161": "Unexpected dirty selection to be null", "162": "Root element not registered", "163": "node is not a ListNode", - "164": "Root element count less than 0" + "164": "Root element count less than 0", + "165": "%s should not be undefined. You may want to check splitOffsets passed to the splitText." }