Skip to content

Commit 3fc9fb6

Browse files
authored
[lexical-text] Bug Fix: for handling multiple matches on hashtags (#6056)
1 parent 6a33ccf commit 3fc9fb6

File tree

3 files changed

+127
-1
lines changed

3 files changed

+127
-1
lines changed

packages/lexical-playground/__tests__/e2e/Hashtags.spec.mjs

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@ import {
1414
import {
1515
assertHTML,
1616
assertSelection,
17+
click,
1718
focusEditor,
1819
html,
1920
initialize,
21+
pasteFromClipboard,
2022
pressToggleBold,
2123
repeat,
2224
test,
@@ -383,4 +385,119 @@ test.describe('Hashtags', () => {
383385
`,
384386
);
385387
});
388+
389+
test('Can handle hashtags following multiple invalid hashtags', async ({
390+
page,
391+
}) => {
392+
await focusEditor(page);
393+
await page.keyboard.type('#hello');
394+
395+
await page.keyboard.press('Space');
396+
397+
await page.keyboard.type('#world');
398+
await page.keyboard.type('#invalid');
399+
await page.keyboard.type('#invalid');
400+
await page.keyboard.type('#invalid');
401+
402+
await page.keyboard.press('Space');
403+
404+
await page.keyboard.type('#valid');
405+
406+
await page.keyboard.press('Space');
407+
408+
await page.keyboard.type('#valid');
409+
await page.keyboard.type('#invalid');
410+
411+
await page.keyboard.press('Space');
412+
await page.keyboard.type('#valid');
413+
414+
await waitForSelector(page, '.PlaygroundEditorTheme__hashtag');
415+
416+
await assertHTML(
417+
page,
418+
html`
419+
<p
420+
class="PlaygroundEditorTheme__paragraph PlaygroundEditorTheme__ltr"
421+
dir="ltr">
422+
<span class="PlaygroundEditorTheme__hashtag" data-lexical-text="true">
423+
#hello
424+
</span>
425+
<span data-lexical-text="true"></span>
426+
<span class="PlaygroundEditorTheme__hashtag" data-lexical-text="true">
427+
#world
428+
</span>
429+
<span data-lexical-text="true">#invalid#invalid#invalid</span>
430+
<span class="PlaygroundEditorTheme__hashtag" data-lexical-text="true">
431+
#valid
432+
</span>
433+
<span data-lexical-text="true"></span>
434+
<span class="PlaygroundEditorTheme__hashtag" data-lexical-text="true">
435+
#valid
436+
</span>
437+
<span data-lexical-text="true">#invalid</span>
438+
<span class="PlaygroundEditorTheme__hashtag" data-lexical-text="true">
439+
#valid
440+
</span>
441+
</p>
442+
`,
443+
);
444+
});
445+
446+
test('Should not break when pasting multiple matches', async ({
447+
page,
448+
isPlainText,
449+
}) => {
450+
test.skip(isPlainText);
451+
452+
await focusEditor(page);
453+
454+
const clipboard = {'text/html': '#hello#world'};
455+
await pasteFromClipboard(page, clipboard);
456+
457+
await assertHTML(
458+
page,
459+
html`
460+
<p
461+
class="PlaygroundEditorTheme__paragraph PlaygroundEditorTheme__ltr"
462+
dir="ltr">
463+
<span class="PlaygroundEditorTheme__hashtag" data-lexical-text="true">
464+
#hello
465+
</span>
466+
<span data-lexical-text="true">#world</span>
467+
</p>
468+
`,
469+
);
470+
});
471+
472+
test('Should not break while importing and exporting multiple matches', async ({
473+
page,
474+
}) => {
475+
await focusEditor(page);
476+
await page.keyboard.type('```markdown #hello#invalid #a #b');
477+
478+
await click(page, '.action-button .markdown');
479+
await click(page, '.action-button .markdown');
480+
await click(page, '.action-button .markdown');
481+
482+
await assertHTML(
483+
page,
484+
html`
485+
<p
486+
class="PlaygroundEditorTheme__paragraph PlaygroundEditorTheme__ltr"
487+
dir="ltr">
488+
<span class="PlaygroundEditorTheme__hashtag" data-lexical-text="true">
489+
#hello
490+
</span>
491+
<span data-lexical-text="true">#invalid</span>
492+
<span class="PlaygroundEditorTheme__hashtag" data-lexical-text="true">
493+
#a
494+
</span>
495+
<span data-lexical-text="true"></span>
496+
<span class="PlaygroundEditorTheme__hashtag" data-lexical-text="true">
497+
#b
498+
</span>
499+
</p>
500+
`,
501+
);
502+
});
386503
});

packages/lexical-text/src/registerLexicalTextEntity.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
LexicalNode,
1515
TextNode,
1616
} from 'lexical';
17+
import invariant from 'shared/invariant';
1718

1819
export type EntityMatch = {end: number; start: number};
1920

@@ -151,6 +152,13 @@ export function registerLexicalTextEntity<T extends TextNode>(
151152
match.end + prevMatchLengthToSkip,
152153
);
153154
}
155+
156+
invariant(
157+
nodeToReplace !== undefined,
158+
'%s should not be undefined. You may want to check splitOffsets passed to the splitText.',
159+
'nodeToReplace',
160+
);
161+
154162
const replacementNode = createNode(nodeToReplace);
155163
replacementNode.setFormat(nodeToReplace.getFormat());
156164
nodeToReplace.replace(replacementNode);

scripts/error-codes/codes.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,5 +163,6 @@
163163
"161": "Unexpected dirty selection to be null",
164164
"162": "Root element not registered",
165165
"163": "node is not a ListNode",
166-
"164": "Root element count less than 0"
166+
"164": "Root element count less than 0",
167+
"165": "%s should not be undefined. You may want to check splitOffsets passed to the splitText."
167168
}

0 commit comments

Comments
 (0)