Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 23 additions & 4 deletions packages/lexical-y/src/__tests__/unit/UndoRunEnds.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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) => {
Expand All @@ -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) => {
Expand All @@ -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) => {
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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*');
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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 => {
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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],
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
type Peer,
setup,
shape,
WITH_GC,
} from '../../utils/harness';

function attach(peer: Peer) {
Expand Down Expand Up @@ -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');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional, non-blocking: the unit job in .github/workflows/call-core-tests.yml does not set LEXICAL_Y_GC, so CI only runs this case with gc off and will not flag this test.fails once #9683 is fixed. A note in #9683 that the fix needs a LEXICAL_Y_GC=1 run to drop the marker would keep it from going stale.

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();
Expand Down
Loading