diff --git a/packages/lexical-y/src/__tests__/unit/UndoRunEnds.test.ts b/packages/lexical-y/src/__tests__/unit/UndoRunEnds.test.ts index 76bf75cfe9b..5a4ba96c6fc 100644 --- a/packages/lexical-y/src/__tests__/unit/UndoRunEnds.test.ts +++ b/packages/lexical-y/src/__tests__/unit/UndoRunEnds.test.ts @@ -15,10 +15,14 @@ // restore with " mid", and " plain" turns bold on every client (and a // comment covers it the same way). A delete of " mid" by hand keeps them. // -// The cases marked test.fails fail on main and pass with #9561 (an undo -// keeps the run ends of the text it removes), parked as a draft: with it, a -// collaborator's undo of text typed inside a run, concurrent with the undo -// of that run's value, gives the value back to the rest of the run (#9593). +// The cases marked test.fails fail on main, each tracked by the issue named +// above it: #9681 (an undo of text typed inside a run deletes that text's +// run ends; D74) or #9682 (an undo of a run's value leaves it after a +// restore the undo has seen; D64, documented in y.md's Limits, Undo). +// #9561 (an undo keeps the run ends of the text it removes) fixes both and +// is parked as a draft: with it, a collaborator's undo of text typed inside +// a run, concurrent with the undo of that run's value, gives the value back +// to the rest of the run (#9593). import {$addMarkID, $getNodeMarkIDs, $removeMarkID} from '@lexical/mark'; import {$patchStyleText} from '@lexical/selection'; @@ -129,6 +133,7 @@ async function race(t: Setup, undo: 'undo' | 'delete' | 'none') { } describe('an undo keeps the run ends of the text it removes', () => { + // #9681: an undo of text typed inside a run deletes its run ends. test.fails.each(ORDERS)( 'bold: text typed after its null stays plain (ids %i %i %i)', async (...ids) => { @@ -137,6 +142,7 @@ describe('an undo keeps the run ends of the text it removes', () => { }, ); + // #9681: an undo of text typed inside a run deletes its run ends. test.fails.each(ORDERS)( 'comment: text typed after its null stays uncommented (ids %i %i %i)', async (...ids) => { @@ -157,6 +163,7 @@ describe('an undo keeps the run ends of the text it removes', () => { }, ); + // #9681: an undo of text typed inside a run deletes its run ends. test.fails.each(ORDERS)( 'a paragraph inserted into a bold run split by Enter (ids %i %i %i)', async (...ids) => { @@ -195,6 +202,7 @@ describe('an undo keeps the run ends of the text it removes', () => { }, ); + // #9681: an undo of text typed inside a run deletes its run ends. test.fails.each(ORDERS)( 'bold text typed into plain text: text typed before it inside its run stays bold (ids %i %i %i)', async (...ids) => { @@ -226,6 +234,7 @@ describe('an undo keeps the run ends of the text it removes', () => { }, ); + // #9681: an undo of text typed inside a run deletes its run ends. test.fails('a redo puts the text back with its own value', async () => { const t = await start([1, 2, 3], 'bold'); expect(await race(t, 'undo')).toBe('one*| plain| two*'); @@ -275,6 +284,8 @@ describe('an undo keeps the run ends of the text it removes', () => { // an undo of that bold must not leave the content after the pair bold. describe('an undo of the run such run ends restate', () => { for (const how of ['undo', 'delete'] as const) { + // #9682 for the delete: the delete keeps F's run ends, and A's undo of + // the bold leaves it after F's restore. (how === 'delete' ? test.fails : test).each(ORDERS)( `F types " mid" into A's bold and ${how}s it; A undoes the bold, redoes it, undoes it (ids %i %i %i)`, async (...ids) => { @@ -316,6 +327,7 @@ describe('an undo of the run such run ends restate', () => { }); describe('undo run ends, more orders of events', () => { + // #9681: an undo of text typed inside a run deletes its run ends. test.fails.each(ORDERS)( "F has seen E's text when it undoes; then A undoes the bold (ids %i %i %i)", async (...ids) => { @@ -451,6 +463,7 @@ describe('undo run ends, more orders of events', () => { expect(t.a.read(() => $getRoot().getTextContent())).toBe('x'); expect([um.undoStack.length, um.redoStack.length]).toEqual([0, 2]); }); + // #9681: an undo of text typed inside a run deletes its run ends. test.fails.each([[[1, 2, 3, 4]], [[4, 3, 2, 1]]])( "text a third peer typed inside the collaborator's text stays plain too (ids %j)", async ids => { @@ -625,6 +638,7 @@ describe('undo run ends, more orders of events', () => { }, ); + // #9681: an undo of text typed inside a run deletes its run ends. test.fails.each(ORDERS)( 'the end a kept restore relies on stays ended when a collaborator deletes the run (ids %i %i %i)', async (...ids) => { @@ -817,6 +831,7 @@ describe('undo run ends, more orders of events', () => { ).toContain('T'); }, ); + // #9682: an undo of a run's value leaves it after a restore it has seen. test.fails.each( ORDERS.flatMap(ids => [ ['after " mid"', ...ids], @@ -1088,6 +1103,7 @@ describe('run ends an undo kept, after changes made since', () => { ); }); + // #9681: an undo of text typed inside a run deletes its run ends. test.fails.each(ORDERS)( 'with text a collaborator typed after the null: it stays plain, and a redo gives " two" its bold back (ids %i %i %i)', async (...ids) => { @@ -1123,6 +1139,7 @@ describe('run ends an undo kept, after changes made since', () => { }, ); + // #9681: an undo of text typed inside a run deletes its run ends. test.fails.each(ORDERS)( 'type, delete, undo, undo: text typed after the null stays plain (ids %i %i %i)', async (...ids) => { @@ -1202,6 +1219,7 @@ describe('run ends an undo kept, after changes made since', () => { }, ); + // #9682: an undo of a run's value leaves it after a restore it has seen. test.fails.each(ORDERS)( 'an undo ends its value past the restore of text it has seen, still there (ids %i %i %i)', async (...ids) => { @@ -1381,6 +1399,7 @@ describe('run ends an undo kept, after changes made since', () => { }, ); + // #9681: an undo of text typed inside a run deletes its run ends. test.fails.each(ORDERS)( 'style: text typed after its null stays unstyled (ids %i %i %i)', async (...ids) => { diff --git a/packages/lexical-y/src/__tests__/unit/regressions/Formats.test.ts b/packages/lexical-y/src/__tests__/unit/regressions/Formats.test.ts index c7c30f933e5..4239212c1a7 100644 --- a/packages/lexical-y/src/__tests__/unit/regressions/Formats.test.ts +++ b/packages/lexical-y/src/__tests__/unit/regressions/Formats.test.ts @@ -38,6 +38,7 @@ import { type Peer, setup, shape, + WITH_GC, } from '../../utils/harness'; function attach(peer: Peer) { @@ -907,12 +908,14 @@ describe('F30A-1: undo of a format change ‖ the same change made again', () => ], ]; for (const [name, $first, $second, $again, expected] of cases) { + // #9683: with gc on, the italic case takes the italic off the space. + const knownGcFailure = WITH_GC && name.startsWith('italic'); for (const ids of [ [1, 2, 3], [3, 2, 1], [2, 3, 1], ]) { - test(`${name} (ids ${ids})`, async () => { + (knownGcFailure ? test.fails : test)(`${name} (ids ${ids})`, async () => { const t = await setup($init, {ids}); t.a.update(() => { $first();