@@ -316,6 +316,23 @@ describe("version diff of a moved block", () => {
316316 ) . toEqual ( [ "[block moved]: bob" , "Moved: bob" ] ) ;
317317 } ) ;
318318
319+ // To be fixed by #3172.
320+ it . fails ( "does not show a moved block as deleted" , ( ) => {
321+ const base = blocks ( ) ;
322+ const server = history ( base ) ;
323+ // Moving "next" above "moved" reorders them (not an indent).
324+ const after = server . apply (
325+ editOf ( base , 2 , ( editor ) => {
326+ editor . setTextCursorPosition ( "next" ) ;
327+ editor . moveBlocksUp ( ) ;
328+ } ) ,
329+ "bob" ,
330+ ) ;
331+ expect (
332+ deletions ( Y . encodeStateAsUpdateV2 ( base ) , after , server . attributions ) ,
333+ ) . toEqual ( [ ] ) ;
334+ } ) ;
335+
319336 // To be fixed by #3166.
320337 it . fails ( "names no author for a block moved into a concurrently deleted one" , ( ) => {
321338 const base = blocks ( ) ;
@@ -405,3 +422,229 @@ describe("version diff of a moved block", () => {
405422 } ,
406423 ) ;
407424} ) ;
425+
426+ describe ( "version diff of a type change" , ( ) => {
427+ const blocks = ( ) =>
428+ baseDocument ( [
429+ {
430+ id : "parent" ,
431+ type : "paragraph" ,
432+ content : "Parent text" ,
433+ children : [ { id : "child" , type : "paragraph" , content : "Child text" } ] ,
434+ } ,
435+ { id : "next" , type : "paragraph" , content : "Next" } ,
436+ ] ) ;
437+ function toHeading ( editor : BlockNoteEditor < any , any , any > ) {
438+ editor . updateBlock ( "parent" , { type : "heading" } ) ;
439+ }
440+ /** Changes as `kind text: users`, attribute changes as `attrs <node>: users`. */
441+ function diff ( before : Uint8Array , after : Uint8Array , map : Y . ContentMap ) {
442+ const { editor, view } = showDiff ( before , after , map ) ;
443+ const out : string [ ] = [ ] ;
444+ editor . prosemirrorState . doc . descendants ( ( node ) => {
445+ const replaced = node . marks . some ( ( mark ) =>
446+ [ "y-attributed-insert" , "y-attributed-delete" ] . includes ( mark . type . name ) ,
447+ ) ;
448+ for ( const mark of node . marks ) {
449+ if ( mark . type . name === "y-attributed-attrs" && ! replaced ) {
450+ const users = new Set (
451+ Object . values (
452+ mark . attrs [ "changes" ] as Record < string , { userIds : string [ ] } > ,
453+ ) . flatMap ( ( change ) => change . userIds ) ,
454+ ) ;
455+ out . push ( `attrs <${ node . type . name } >: ${ [ ...users ] . join ( ", " ) } ` ) ;
456+ } else if (
457+ mark . type . name === "y-attributed-insert" ||
458+ mark . type . name === "y-attributed-delete"
459+ ) {
460+ const what = node . isText ? node . text : `<${ node . type . name } >` ;
461+ const kind = mark . attrs [ "moved" ] ? "moved" : mark . type . name . slice ( 13 ) ;
462+ out . push ( `${ kind } ${ what } : ${ mark . attrs [ "userIds" ] . join ( ", " ) } ` ) ;
463+ }
464+ }
465+ return true ;
466+ } ) ;
467+ view . close ( ) ;
468+ return out ;
469+ }
470+
471+ // To be fixed by #3172.
472+ it . fails ( "shows a type change as a formatting change, not as replaced text" , ( ) => {
473+ const base = blocks ( ) ;
474+ const server = history ( base ) ;
475+ const after = server . apply ( editOf ( base , 2 , toHeading ) , "bob" ) ;
476+ expect (
477+ diff ( Y . encodeStateAsUpdateV2 ( base ) , after , server . attributions ) ,
478+ ) . toEqual ( [ "attrs <heading>: bob" ] ) ;
479+ } ) ;
480+
481+ // To be fixed by #3172.
482+ it . fails ( "credits a type-changed block's text to its writer, from before it existed" , ( ) => {
483+ const base = baseDocument ( [
484+ { id : "next" , type : "paragraph" , content : "Next" } ,
485+ ] ) ;
486+ const server = history ( base ) ;
487+ server . apply (
488+ editOf ( base , 1 , ( editor ) =>
489+ editor . insertBlocks (
490+ [ { id : "parent" , type : "paragraph" , content : "Parent text" } ] ,
491+ "next" ,
492+ "before" ,
493+ ) ,
494+ ) ,
495+ "alice" ,
496+ ) ;
497+ const after = server . apply ( editOf ( server . server , 2 , toHeading ) , "bob" ) ;
498+ expect (
499+ diff ( Y . encodeStateAsUpdateV2 ( base ) , after , server . attributions ) ,
500+ ) . toEqual ( [
501+ "insert <blockContainer>: alice" ,
502+ "insert <heading>: alice" ,
503+ "insert Parent text: alice" ,
504+ ] ) ;
505+ } ) ;
506+
507+ // To be fixed by #3172.
508+ it . fails ( "keeps later edits to a type-changed block as their author's" , ( ) => {
509+ const base = blocks ( ) ;
510+ const server = history ( base ) ;
511+ server . apply ( editOf ( base , 2 , toHeading ) , "bob" ) ;
512+ const after = server . apply (
513+ editOf ( server . server , 3 , ( editor ) => {
514+ editor . setTextCursorPosition ( "parent" , "end" ) ;
515+ editor . insertInlineContent ( " by Carol" ) ;
516+ } ) ,
517+ "carol" ,
518+ ) ;
519+ expect (
520+ diff ( Y . encodeStateAsUpdateV2 ( base ) , after , server . attributions ) ,
521+ ) . toEqual ( [ "attrs <heading>: bob" , "insert by Carol: carol" ] ) ;
522+ } ) ;
523+
524+ it ( "shows both copies when two users change the type concurrently" , ( ) => {
525+ const base = blocks ( ) ;
526+ const bob = editOf ( base , 2 , toHeading ) ;
527+ const carol = editOf ( base , 3 , ( editor ) =>
528+ editor . updateBlock ( "parent" , { type : "bulletListItem" } ) ,
529+ ) ;
530+ const server = history ( base ) ;
531+ server . apply ( bob , "bob" ) ;
532+ const after = server . apply ( carol , "carol" ) ;
533+ const changes = diff (
534+ Y . encodeStateAsUpdateV2 ( base ) ,
535+ after ,
536+ server . attributions ,
537+ ) ;
538+ expect ( changes . filter ( ( change ) => change . startsWith ( "insert <" ) ) ) . toEqual ( [
539+ "insert <blockContainer>: bob" ,
540+ "insert <heading>: bob" ,
541+ "insert <blockGroup>: bob" ,
542+ "insert <blockContainer>: bob" ,
543+ "insert <paragraph>: bob" ,
544+ "insert <blockContainer>: carol" ,
545+ "insert <bulletListItem>: carol" ,
546+ "insert <blockGroup>: carol" ,
547+ "insert <blockContainer>: carol" ,
548+ "insert <paragraph>: carol" ,
549+ ] ) ;
550+ } ) ;
551+
552+ // To be fixed by #3172.
553+ it . fails ( "credits an indented block's text to its writer, from before it existed" , ( ) => {
554+ const base = blocks ( ) ;
555+ const server = history ( base ) ;
556+ server . apply (
557+ editOf ( base , 1 , ( editor ) =>
558+ editor . insertBlocks (
559+ [ { id : "new" , type : "paragraph" , content : "New text" } ] ,
560+ "next" ,
561+ "after" ,
562+ ) ,
563+ ) ,
564+ "alice" ,
565+ ) ;
566+ const after = server . apply (
567+ editOf ( server . server , 2 , ( editor ) => {
568+ editor . setTextCursorPosition ( "new" ) ;
569+ editor . nestBlock ( ) ;
570+ } ) ,
571+ "bob" ,
572+ ) ;
573+ expect (
574+ diff ( Y . encodeStateAsUpdateV2 ( base ) , after , server . attributions ) ,
575+ ) . toEqual ( [
576+ // Bob's indent created the parent's child group.
577+ "insert <blockGroup>: bob" ,
578+ "insert <blockContainer>: alice" ,
579+ "insert <paragraph>: alice" ,
580+ "insert New text: alice" ,
581+ ] ) ;
582+ } ) ;
583+
584+ // To be fixed by #3172.
585+ it . fails ( "shows a block moved among its siblings as a move" , ( ) => {
586+ const base = baseDocument ( [
587+ { id : "first" , type : "paragraph" , content : "First" } ,
588+ { id : "second" , type : "paragraph" , content : "Second" } ,
589+ ] ) ;
590+ const server = history ( base ) ;
591+ const after = server . apply (
592+ editOf ( base , 2 , ( editor ) => {
593+ editor . setTextCursorPosition ( "second" ) ;
594+ editor . moveBlocksUp ( ) ;
595+ } ) ,
596+ "bob" ,
597+ ) ;
598+ expect (
599+ diff ( Y . encodeStateAsUpdateV2 ( base ) , after , server . attributions ) ,
600+ ) . toEqual ( [ "moved <blockContainer>: bob" ] ) ;
601+ } ) ;
602+
603+ // To be fixed by #3172.
604+ it . fails ( "shows a type change as a formatting change after the text was rewritten" , ( ) => {
605+ const base = blocks ( ) ;
606+ const server = history ( base ) ;
607+ // The rewrite reuses some characters, so the block's stored text mixes
608+ // kept characters with characters deleted before the type change.
609+ const rewritten = server . apply (
610+ editOf ( base , 1 , ( editor ) =>
611+ editor . updateBlock ( "parent" , { content : "Parts were rewritten" } ) ,
612+ ) ,
613+ "alice" ,
614+ ) ;
615+ const after = server . apply ( editOf ( server . server , 2 , toHeading ) , "bob" ) ;
616+ expect ( diff ( rewritten , after , server . attributions ) ) . toEqual ( [
617+ "attrs <heading>: bob" ,
618+ ] ) ;
619+ } ) ;
620+
621+ it . each ( [
622+ [
623+ "a paragraph into an image" ,
624+ { type : "paragraph" , content : "Some text" } ,
625+ { type : "image" , props : { url : "https://example.com/a.png" } } ,
626+ ] ,
627+ [
628+ "an image into a paragraph" ,
629+ { type : "image" , props : { url : "https://example.com/a.png" } } ,
630+ { type : "paragraph" , content : "New text" } ,
631+ ] ,
632+ ] ) ( "shows turning %s as a deletion and an insertion" , ( _ , from , to ) => {
633+ const base = baseDocument ( [
634+ { id : "block" , ...from } ,
635+ { id : "next" , type : "paragraph" , content : "Next" } ,
636+ ] ) ;
637+ const server = history ( base ) ;
638+ const after = server . apply (
639+ editOf ( base , 2 , ( editor ) => editor . updateBlock ( "block" , to as any ) ) ,
640+ "bob" ,
641+ ) ;
642+ const changes = diff (
643+ Y . encodeStateAsUpdateV2 ( base ) ,
644+ after ,
645+ server . attributions ,
646+ ) ;
647+ expect ( changes ) . toContain ( "delete <blockContainer>: bob" ) ;
648+ expect ( changes ) . toContain ( "insert <blockContainer>: bob" ) ;
649+ } ) ;
650+ } ) ;
0 commit comments