|
|
@@ -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' },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ ])
|
|
|
+ })
|
|
|
})
|
|
|
})
|
|
|
})
|