Merge pull request #18930 from overleaf/em-resync-ranges

Fix resyncs when diffs move ranges

GitOrigin-RevId: 121c3a16cf19649538445e6ed8bc0a1129735eb9
This commit is contained in:
Eric Mc Sween
2024-06-19 08:04:57 +00:00
committed by Copybot
parent c342dc70a5
commit 9f0f42a012
3 changed files with 180 additions and 64 deletions
@@ -2,7 +2,7 @@ import sinon from 'sinon'
import { expect } from 'chai'
import mongodb from 'mongodb-legacy'
import tk from 'timekeeper'
import { Comment, TrackedChange, Range } from 'overleaf-editor-core'
import { File, Comment, TrackedChange, Range } from 'overleaf-editor-core'
import { strict as esmock } from 'esmock'
const { ObjectId } = mongodb
@@ -491,17 +491,7 @@ describe('SyncManager', function () {
path: '1.png',
_hash: 'abcde',
}
this.loadedSnapshotDoc = {
isEditable: sinon.stub().returns(true),
getContent: sinon.stub().returns(this.persistedDocContent),
getComments: sinon
.stub()
.returns({ toArray: sinon.stub().returns([]) }),
getTrackedChanges: sinon
.stub()
.returns({ asSorted: sinon.stub().returns([]) }),
getHash: sinon.stub().returns(null),
}
this.loadedSnapshotDoc = File.fromString(this.persistedDocContent)
this.fileMap = {
'main.tex': {
isEditable: sinon.stub().returns(true),
@@ -1050,10 +1040,11 @@ describe('SyncManager', function () {
[this.persistedDoc],
[this.persistedFile]
),
docContentSyncUpdate(this.persistedDoc, 'a'),
docContentSyncUpdate(this.persistedDoc, 'the quick brown fox'),
]
this.loadedSnapshotDoc.getContent.returns('stored content')
this.UpdateCompressor.diffAsShareJsOps.returns([{ d: 'sdf', p: 1 }])
this.UpdateCompressor.diffAsShareJsOps.returns([
{ d: ' jumps over the lazy dog', p: 19 },
])
this.expandedUpdates =
await this.SyncManager.promises.expandSyncUpdates(
this.projectId,
@@ -1067,10 +1058,10 @@ describe('SyncManager', function () {
expect(this.expandedUpdates).to.deep.equal([
{
doc: this.persistedDoc.doc,
op: [{ d: 'sdf', p: 1 }],
op: [{ d: ' jumps over the lazy dog', p: 19 }],
meta: {
pathname: this.persistedDoc.path,
doc_length: 'stored content'.length,
doc_length: this.persistedDocContent.length,
resync: true,
ts: TIMESTAMP,
origin: { kind: 'history-resync' },
@@ -1084,14 +1075,12 @@ describe('SyncManager', function () {
describe('syncing comments', function () {
beforeEach(function () {
this.loadedSnapshotDoc.getComments.returns({
toArray: sinon
.stub()
.returns([
new Comment('comment1', [new Range(4, 5)]),
new Comment('comment2', [new Range(10, 5)], true),
]),
})
this.loadedSnapshotDoc
.getComments()
.add(new Comment('comment1', [new Range(4, 5)]))
this.loadedSnapshotDoc
.getComments()
.add(new Comment('comment2', [new Range(10, 5)], true))
this.comments = [
makeComment('comment1', 4, 'quick'),
makeComment('comment2', 10, 'brown'),
@@ -1288,11 +1277,12 @@ describe('SyncManager', function () {
})
it('treats zero length comments as detached comments', async function () {
this.loadedSnapshotDoc.getComments.returns({
toArray: sinon.stub().returns([new Comment('comment1', [])]),
})
this.comments = [makeComment('comment1', 16, '')]
this.resolvedCommentIds = []
this.loadedSnapshotDoc.getComments().add(new Comment('comment1', []))
this.comments = [
makeComment('comment1', 16, ''),
makeComment('comment2', 10, 'brown'),
]
this.resolvedCommentIds = ['comment2']
const updates = [
docContentSyncUpdate(
@@ -1315,34 +1305,101 @@ describe('SyncManager', function () {
expect(expandedUpdates).to.deep.equal([])
})
it('adjusts comment positions when the underlying text has changed', async function () {
const updates = [
docContentSyncUpdate(
this.persistedDoc,
'quick brown fox',
{
comments: [
makeComment('comment1', 0, 'quick'),
makeComment('comment2', 12, 'fox'),
],
},
this.resolvedCommentIds
),
]
this.UpdateCompressor.diffAsShareJsOps.returns([
{ d: 'the ', p: 0 },
{ d: ' jumps over the lazy dog', p: 15 },
])
const expandedUpdates =
await this.SyncManager.promises.expandSyncUpdates(
this.projectId,
this.historyId,
updates,
this.extendLock
)
expect(expandedUpdates).to.deep.equal([
{
doc: this.persistedDoc.doc,
op: [
{ d: 'the ', p: 0 },
{ d: ' jumps over the lazy dog', p: 15 },
],
meta: {
pathname: this.persistedDoc.path,
doc_length: this.persistedDocContent.length,
resync: true,
ts: TIMESTAMP,
origin: { kind: 'history-resync' },
},
},
{
doc: this.persistedDoc.doc,
op: [
{
c: 'fox',
p: 12,
t: 'comment2',
resolved: true,
},
],
meta: {
origin: {
kind: 'history-resync',
},
pathname: this.persistedDoc.path,
resync: true,
ts: TIMESTAMP,
doc_length: 'quick brown fox'.length,
},
},
])
})
})
describe('syncing tracked changes', function () {
beforeEach(function () {
this.loadedSnapshotDoc.getTrackedChanges.returns({
asSorted: sinon.stub().returns([
new TrackedChange(new Range(4, 6), {
type: 'delete',
userId: USER_ID,
ts: new Date(TIMESTAMP),
}),
new TrackedChange(new Range(10, 6), {
type: 'insert',
userId: USER_ID,
ts: new Date(TIMESTAMP),
}),
new TrackedChange(new Range(20, 6), {
type: 'delete',
userId: USER_ID,
ts: new Date(TIMESTAMP),
}),
new TrackedChange(new Range(40, 3), {
type: 'insert',
userId: USER_ID,
ts: new Date(TIMESTAMP),
}),
]),
})
this.loadedSnapshotDoc.getTrackedChanges().add(
new TrackedChange(new Range(4, 6), {
type: 'delete',
userId: USER_ID,
ts: new Date(TIMESTAMP),
})
)
this.loadedSnapshotDoc.getTrackedChanges().add(
new TrackedChange(new Range(10, 6), {
type: 'insert',
userId: USER_ID,
ts: new Date(TIMESTAMP),
})
)
this.loadedSnapshotDoc.getTrackedChanges().add(
new TrackedChange(new Range(20, 6), {
type: 'delete',
userId: USER_ID,
ts: new Date(TIMESTAMP),
})
)
this.loadedSnapshotDoc.getTrackedChanges().add(
new TrackedChange(new Range(40, 3), {
type: 'insert',
userId: USER_ID,
ts: new Date(TIMESTAMP),
})
)
this.changes = [
makeTrackedChange('td1', { p: 4, d: 'quick ' }),
makeTrackedChange('ti1', { p: 4, hpos: 10, i: 'brown ' }),
@@ -1515,6 +1572,50 @@ describe('SyncManager', function () {
},
])
})
it('adjusts tracked change positions when the underlying text has changed', async function () {
const updates = [
docContentSyncUpdate(
this.persistedDoc,
'every fox jumps over the lazy dog',
{
changes: [
makeTrackedChange('ti1', { p: 5, i: ' ' }), // the space after every is still a tracked insert
makeTrackedChange('td2', { p: 10, d: 'jumps ' }),
makeTrackedChange('ti2', { p: 24, hpos: 30, i: 'dog' }),
],
},
this.resolvedCommentIds
),
]
this.UpdateCompressor.diffAsShareJsOps.returns([
{ d: 'the quick brown', p: 0 },
{ i: 'every', p: 0 },
])
const expandedUpdates =
await this.SyncManager.promises.expandSyncUpdates(
this.projectId,
this.historyId,
updates,
this.extendLock
)
expect(expandedUpdates).to.deep.equal([
{
doc: this.persistedDoc.doc,
op: [
{ d: 'the quick brown', p: 0 },
{ i: 'every', p: 0 },
],
meta: {
pathname: this.persistedDoc.path,
doc_length: this.persistedDocContent.length,
resync: true,
ts: TIMESTAMP,
origin: { kind: 'history-resync' },
},
},
])
})
})
})
})