Skip to content

[lexical] Bug Fix: TextNode setMode, setTextContent and isSimpleText read the latest state - #9162

Merged
etrepum merged 2 commits into
facebook:mainfrom
Dev-next-gen:fix/textnode-stale-early-return
Sep 13, 2026
Merged

etrepum merged 2 commits into
facebook:mainfrom
Dev-next-gen:fix/textnode-stale-early-return

Conversation

@Dev-next-gen

Copy link
Copy Markdown
Contributor

Description

setMode() and setTextContent() skip the write when the requested value equals this.__mode / this.__text, and isSimpleText() reads this.__mode. None of them go through getLatest(). In a later update, the first setter call clones the node through getWritable(), so a reference held from the previous update is stale from then on. Calling text.setTextContent('b') then text.setTextContent('a') on a node whose text was 'a' compares against the stale 'a' and returns early, so the node keeps 'b'. setMode('token') followed by setMode('normal') leaves the node in token mode the same way, and isSimpleText() keeps answering true after setMode('token') while isToken() already says true.

This reads the latest version in those three places, the same way getMode(), isToken() and isSegmented() already do, and the way #9080 fixed the LinkNode readers.

Test plan

Before

pnpm vitest run --project unit packages/lexical/src/tests/unit/TextNodeStaleState.test.ts

 × setTextContent can restore the original text through a stale reference
 × setMode can restore the original mode through a stale reference
 × isSimpleText reflects the latest mode

AssertionError: expected 'b' to be 'a' // Object.is equality
AssertionError: expected 'token' to be 'normal' // Object.is equality
AssertionError: expected true to be false // Object.is equality
Tests 3 failed (3)

After

✓ |unit| packages/lexical/src/tests/unit/TextNodeStaleState.test.ts (3 tests)
Tests 3 passed (3)

Unit suites for lexical, lexical-selection, lexical-link, lexical-markdown, lexical-text, lexical-rich-text and lexical-clipboard: 119 files, 2815 tests passed. eslint, prettier and tsc are clean. E2E browser tests were not run; the change is not browser-specific.

AI tools used

…read the latest state

## Description

setMode() and setTextContent() skip the write when the requested value equals `this.__mode` / `this.__text`, and isSimpleText() reads `this.__mode`. None of them go through getLatest(). In a later update, the first setter call clones the node through getWritable(), so a reference held from the previous update is stale from then on. Calling `text.setTextContent('b')` then `text.setTextContent('a')` on a node whose text was 'a' compares against the stale 'a' and returns early, so the node keeps 'b'. setMode('token') followed by setMode('normal') leaves the node in token mode the same way, and isSimpleText() keeps answering true after setMode('token') while isToken() already says true.

This reads the latest version in those three places, the same way getMode(), isToken() and isSegmented() already do, and the way facebook#9080 fixed the LinkNode readers.

## Test plan

### Before

pnpm vitest run --project unit packages/lexical/src/__tests__/unit/TextNodeStaleState.test.ts

     × setTextContent can restore the original text through a stale reference
     × setMode can restore the original mode through a stale reference
     × isSimpleText reflects the latest mode
AssertionError: expected 'b' to be 'a' // Object.is equality
AssertionError: expected 'token' to be 'normal' // Object.is equality
AssertionError: expected true to be false // Object.is equality
      Tests  3 failed (3)

### After

 ✓ |unit| packages/lexical/src/__tests__/unit/TextNodeStaleState.test.ts (3 tests)
      Tests  3 passed (3)

Unit suites for lexical, lexical-selection, lexical-link, lexical-markdown, lexical-text, lexical-rich-text and lexical-clipboard: 119 files, 2815 tests passed. eslint, prettier and tsc are clean. E2E browser tests were not run; the change is not browser-specific.
@vercel

vercel Bot commented Sep 12, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
lexical Ready Ready Preview Sep 13, 2026 8:15pm UTC
lexical-playground Ready Ready Preview Sep 13, 2026 8:15pm UTC

Request Review

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Sep 12, 2026
@etrepum etrepum added the extended-tests Run extended e2e tests on a PR label Sep 13, 2026
Comment thread packages/lexical/src/__tests__/unit/TextNodeStaleState.test.ts Outdated
…orFromExtensions

## Description

The test added with the setMode/setTextContent/isSimpleText fix used
initializeUnitTest and awaited each editor.update(). Both are the legacy
testing methodology; newer unit tests build the editor with
buildEditorFromExtensions and run their updates with discrete: true, which
also removes the need for async tests.

This rewrites the file that way: one `using editor = buildEditorFromExtensions(...)`
per test, a shared helper that seeds the paragraph and returns the node
reference that goes stale, `{discrete: true}` on every update, and the
assertions moved out of the update callbacks into editor.read(). The
behaviour under test is unchanged.

## Test plan

### Before

Reverting the LexicalTextNode.ts fix and running the rewritten test:

pnpm vitest run --project unit packages/lexical/src/__tests__/unit/TextNodeStaleState.test.ts

 FAIL  packages/lexical/src/__tests__/unit/TextNodeStaleState.test.ts > TextNode stale state readers > setTextContent can restore the original text through a stale reference
 FAIL  packages/lexical/src/__tests__/unit/TextNodeStaleState.test.ts > TextNode stale state readers > setMode can restore the original mode through a stale reference
 FAIL  packages/lexical/src/__tests__/unit/TextNodeStaleState.test.ts > TextNode stale state readers > isSimpleText reflects the latest mode
AssertionError: expected 'b' to be 'a' // Object.is equality
AssertionError: expected 'token' to be 'normal' // Object.is equality
AssertionError: expected true to be false // Object.is equality
      Tests  3 failed (3)

### After

With the fix back in place:

 ✓ |unit| packages/lexical/src/__tests__/unit/TextNodeStaleState.test.ts (3 tests) 22ms
      Tests  3 passed (3)

pnpm vitest run --project unit packages/lexical

      Test Files  318 passed (318)
      Tests  5106 passed | 1 skipped (5107)

eslint, prettier and tsc are clean on the rewritten file. E2E browser tests
were not run; the change is test-only and not browser-specific.
@etrepum
etrepum added this pull request to the merge queue Sep 13, 2026
Merged via the queue into facebook:main with commit 0e9a9f5 Sep 13, 2026
50 checks passed
@etrepum etrepum mentioned this pull request Sep 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. extended-tests Run extended e2e tests on a PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants