@@ -4,6 +4,7 @@ import { h, defineComponent, ref, shallowRef, reactive, nextTick } from 'vue';
44import { DOCX } from '@superdoc/common' ;
55import { Schema } from 'prosemirror-model' ;
66import { EditorState , TextSelection } from 'prosemirror-state' ;
7+ import { ySyncPluginKey } from 'y-prosemirror' ;
78import { Extension } from '../../super-editor/src/editors/v1/core/Extension.js' ;
89import {
910 CommentsPlugin ,
@@ -744,6 +745,7 @@ describe('SuperDoc.vue', () => {
744745 expect ( commentsStoreStub . syncTrackedChangeComments ) . toHaveBeenCalledWith ( {
745746 superdoc : superdocStub ,
746747 editor : editorMock ,
748+ broadcastChanges : true ,
747749 } ) ;
748750
749751 commentsStoreStub . syncTrackedChangePositionsWithDocument . mockClear ( ) ;
@@ -762,9 +764,112 @@ describe('SuperDoc.vue', () => {
762764 expect ( commentsStoreStub . syncTrackedChangeComments ) . toHaveBeenCalledWith ( {
763765 superdoc : superdocStub ,
764766 editor : editorMock ,
767+ broadcastChanges : true ,
765768 } ) ;
766769 } ) ;
767770
771+ it ( 'resyncs tracked-change threads on collaboration undo/redo transactions' , async ( ) => {
772+ const superdocStub = createSuperdocStub ( ) ;
773+ const wrapper = await mountComponent ( superdocStub ) ;
774+ await nextTick ( ) ;
775+
776+ const options = wrapper . findComponent ( SuperEditorStub ) . props ( 'options' ) ;
777+ const editorMock = { options : { documentId : 'doc-1' } } ;
778+
779+ const makeTransaction = ( { inputType, ySyncMeta } = { } ) => ( {
780+ getMeta : vi . fn ( ( key ) => {
781+ if ( key === 'inputType' ) return inputType ;
782+ if ( key === ySyncPluginKey ) return ySyncMeta ;
783+ return undefined ;
784+ } ) ,
785+ } ) ;
786+
787+ options . onTransaction ( {
788+ editor : editorMock ,
789+ transaction : makeTransaction ( {
790+ ySyncMeta : { isChangeOrigin : true , isUndoRedoOperation : true } ,
791+ } ) ,
792+ duration : 4 ,
793+ } ) ;
794+
795+ expect ( commentsStoreStub . syncTrackedChangePositionsWithDocument ) . toHaveBeenCalledWith ( {
796+ documentId : 'doc-1' ,
797+ editor : editorMock ,
798+ } ) ;
799+ expect ( commentsStoreStub . syncTrackedChangeComments ) . toHaveBeenCalledWith ( {
800+ superdoc : superdocStub ,
801+ editor : editorMock ,
802+ broadcastChanges : true ,
803+ } ) ;
804+ } ) ;
805+
806+ it ( 'resyncs tracked-change threads on peer collaboration replays' , async ( ) => {
807+ const superdocStub = createSuperdocStub ( ) ;
808+ const wrapper = await mountComponent ( superdocStub ) ;
809+ await nextTick ( ) ;
810+
811+ const options = wrapper . findComponent ( SuperEditorStub ) . props ( 'options' ) ;
812+ const editorMock = { options : { documentId : 'doc-1' } } ;
813+
814+ const makeTransaction = ( { inputType, ySyncMeta, docChanged = false } = { } ) => ( {
815+ docChanged,
816+ getMeta : vi . fn ( ( key ) => {
817+ if ( key === 'inputType' ) return inputType ;
818+ if ( key === ySyncPluginKey ) return ySyncMeta ;
819+ return undefined ;
820+ } ) ,
821+ } ) ;
822+
823+ options . onTransaction ( {
824+ editor : editorMock ,
825+ transaction : makeTransaction ( {
826+ docChanged : true ,
827+ ySyncMeta : { isChangeOrigin : true , isUndoRedoOperation : false } ,
828+ } ) ,
829+ duration : 6 ,
830+ } ) ;
831+
832+ expect ( commentsStoreStub . syncTrackedChangePositionsWithDocument ) . toHaveBeenCalledWith ( {
833+ documentId : 'doc-1' ,
834+ editor : editorMock ,
835+ } ) ;
836+ expect ( commentsStoreStub . syncTrackedChangeComments ) . toHaveBeenCalledWith ( {
837+ superdoc : superdocStub ,
838+ editor : editorMock ,
839+ broadcastChanges : false ,
840+ } ) ;
841+ } ) ;
842+
843+ it ( 'does not resync tracked-change threads on meta-only collaboration updates' , async ( ) => {
844+ const superdocStub = createSuperdocStub ( ) ;
845+ const wrapper = await mountComponent ( superdocStub ) ;
846+ await nextTick ( ) ;
847+
848+ const options = wrapper . findComponent ( SuperEditorStub ) . props ( 'options' ) ;
849+ const editorMock = { options : { documentId : 'doc-1' } } ;
850+
851+ const makeTransaction = ( { inputType, ySyncMeta, docChanged = false } = { } ) => ( {
852+ docChanged,
853+ getMeta : vi . fn ( ( key ) => {
854+ if ( key === 'inputType' ) return inputType ;
855+ if ( key === ySyncPluginKey ) return ySyncMeta ;
856+ return undefined ;
857+ } ) ,
858+ } ) ;
859+
860+ options . onTransaction ( {
861+ editor : editorMock ,
862+ transaction : makeTransaction ( {
863+ docChanged : false ,
864+ ySyncMeta : { isChangeOrigin : true , isUndoRedoOperation : false } ,
865+ } ) ,
866+ duration : 7 ,
867+ } ) ;
868+
869+ expect ( commentsStoreStub . syncTrackedChangePositionsWithDocument ) . not . toHaveBeenCalled ( ) ;
870+ expect ( commentsStoreStub . syncTrackedChangeComments ) . not . toHaveBeenCalled ( ) ;
871+ } ) ;
872+
768873 it ( 'reconciles replay updates by importedId before commentId to avoid duplicate comments' , async ( ) => {
769874 const superdocStub = createSuperdocStub ( ) ;
770875 const wrapper = await mountComponent ( superdocStub ) ;
0 commit comments