mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
[project-history] fix convertToDiffUpdates when track delete is moved by previous op (#17681)
* [project-history] fix convertToDiffUpdates * fix test * preserve source ranges for applyRetain * use sourceRanges for applyDelete * use cursor difference for offset * move statement to closer to original * handle deletion before tracked delete * Revert "handle deletion before tracked delete" This reverts commit 6f2570f22473bd64516c166b29a34639ec701230. * using resultCursor and sourceCursor * refactor, use scanCursor * skip track delete properly * prettier * remove .sort() in applyRetain * small test fixes GitOrigin-RevId: 94755c219c90d6fedcdc64284d71137cf56d2442
This commit is contained in:
parent
af037ddb43
commit
1557338775
2 changed files with 540 additions and 26 deletions
|
@ -447,32 +447,52 @@ class TextUpdateBuilder {
|
||||||
* @param {RetainOp} retain
|
* @param {RetainOp} retain
|
||||||
*/
|
*/
|
||||||
applyRetain(retain) {
|
applyRetain(retain) {
|
||||||
const rangeOfRetention = new Range(this.sourceCursor, retain.length)
|
const resultRetentionRange = new Range(this.result.length, retain.length)
|
||||||
let cursor = this.sourceCursor
|
const sourceRetentionRange = new Range(this.sourceCursor, retain.length)
|
||||||
|
|
||||||
|
let scanCursor = this.result.length
|
||||||
if (retain.tracking) {
|
if (retain.tracking) {
|
||||||
// We are modifying existing tracked deletes. We need to treat removal
|
// We are modifying existing tracked deletes. We need to treat removal
|
||||||
// (type insert/none) of a tracked delete as an insertion. Similarly, any
|
// (type insert/none) of a tracked delete as an insertion. Similarly, any
|
||||||
// range we introduce as a tracked deletion must be reported as a deletion.
|
// range we introduce as a tracked deletion must be reported as a deletion.
|
||||||
const trackedDeletes = this.trackedChanges.trackedChanges.filter(
|
const trackedDeletes = this.trackedChanges.trackedChanges.filter(
|
||||||
tc =>
|
tc =>
|
||||||
tc.tracking.type === 'delete' && tc.range.overlaps(rangeOfRetention)
|
tc.tracking.type === 'delete' &&
|
||||||
|
tc.range.overlaps(resultRetentionRange)
|
||||||
)
|
)
|
||||||
|
|
||||||
for (const trackedDelete of trackedDeletes) {
|
for (const trackedDelete of trackedDeletes) {
|
||||||
if (cursor < trackedDelete.range.start) {
|
const resultTrackedDelete = trackedDelete.range
|
||||||
|
const sourceTrackedDelete = trackedDelete.range.moveBy(
|
||||||
|
this.sourceCursor - this.result.length
|
||||||
|
)
|
||||||
|
|
||||||
|
if (scanCursor < resultTrackedDelete.start) {
|
||||||
if (retain.tracking.type === 'delete') {
|
if (retain.tracking.type === 'delete') {
|
||||||
this.changes.push({
|
this.changes.push({
|
||||||
d: this.source.slice(cursor, trackedDelete.range.start),
|
d: this.source.slice(
|
||||||
|
this.sourceCursor,
|
||||||
|
sourceTrackedDelete.start
|
||||||
|
),
|
||||||
p: this.result.length,
|
p: this.result.length,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
this.result += this.source.slice(cursor, trackedDelete.range.start)
|
this.result += this.source.slice(
|
||||||
cursor = trackedDelete.range.start
|
this.sourceCursor,
|
||||||
|
sourceTrackedDelete.start
|
||||||
|
)
|
||||||
|
scanCursor = resultTrackedDelete.start
|
||||||
|
this.sourceCursor = sourceTrackedDelete.start
|
||||||
}
|
}
|
||||||
const endOfInsertion = Math.min(
|
const endOfInsertionResult = Math.min(
|
||||||
trackedDelete.range.end,
|
resultTrackedDelete.end,
|
||||||
rangeOfRetention.end
|
resultRetentionRange.end
|
||||||
)
|
)
|
||||||
const text = this.source.slice(cursor, endOfInsertion)
|
const endOfInsertionSource = Math.min(
|
||||||
|
sourceTrackedDelete.end,
|
||||||
|
sourceRetentionRange.end
|
||||||
|
)
|
||||||
|
const text = this.source.slice(this.sourceCursor, endOfInsertionSource)
|
||||||
if (
|
if (
|
||||||
retain.tracking.type === 'none' ||
|
retain.tracking.type === 'none' ||
|
||||||
retain.tracking.type === 'insert'
|
retain.tracking.type === 'insert'
|
||||||
|
@ -483,16 +503,22 @@ class TextUpdateBuilder {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
this.result += text
|
this.result += text
|
||||||
cursor = endOfInsertion
|
// skip the tracked delete itself
|
||||||
if (cursor >= rangeOfRetention.end) {
|
scanCursor = endOfInsertionResult
|
||||||
|
this.sourceCursor = endOfInsertionSource
|
||||||
|
|
||||||
|
if (scanCursor >= resultRetentionRange.end) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (cursor < rangeOfRetention.end) {
|
if (scanCursor < resultRetentionRange.end) {
|
||||||
// The last region is not a tracked delete. But we should still handle
|
// The last region is not a tracked delete. But we should still handle
|
||||||
// a new tracked delete as a deletion.
|
// a new tracked delete as a deletion.
|
||||||
const text = this.source.slice(cursor, rangeOfRetention.end)
|
const text = this.source.slice(
|
||||||
|
this.sourceCursor,
|
||||||
|
sourceRetentionRange.end
|
||||||
|
)
|
||||||
if (retain.tracking?.type === 'delete') {
|
if (retain.tracking?.type === 'delete') {
|
||||||
this.changes.push({
|
this.changes.push({
|
||||||
d: text,
|
d: text,
|
||||||
|
@ -501,7 +527,7 @@ class TextUpdateBuilder {
|
||||||
}
|
}
|
||||||
this.result += text
|
this.result += text
|
||||||
}
|
}
|
||||||
this.sourceCursor += retain.length
|
this.sourceCursor = sourceRetentionRange.end
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -525,34 +551,49 @@ class TextUpdateBuilder {
|
||||||
* @param {RemoveOp} deletion
|
* @param {RemoveOp} deletion
|
||||||
*/
|
*/
|
||||||
applyDelete(deletion) {
|
applyDelete(deletion) {
|
||||||
const rangeOfDeletion = new Range(this.sourceCursor, deletion.length)
|
const sourceDeletionRange = new Range(this.sourceCursor, deletion.length)
|
||||||
|
const resultDeletionRange = new Range(this.result.length, deletion.length)
|
||||||
|
|
||||||
const trackedDeletes = this.trackedChanges.trackedChanges
|
const trackedDeletes = this.trackedChanges.trackedChanges
|
||||||
.filter(
|
.filter(
|
||||||
tc =>
|
tc =>
|
||||||
tc.tracking.type === 'delete' && tc.range.overlaps(rangeOfDeletion)
|
tc.tracking.type === 'delete' &&
|
||||||
|
tc.range.overlaps(resultDeletionRange)
|
||||||
)
|
)
|
||||||
.sort((a, b) => a.range.start - b.range.start)
|
.sort((a, b) => a.range.start - b.range.start)
|
||||||
let cursor = this.sourceCursor
|
|
||||||
|
let scanCursor = this.result.length
|
||||||
|
|
||||||
for (const trackedDelete of trackedDeletes) {
|
for (const trackedDelete of trackedDeletes) {
|
||||||
if (cursor < trackedDelete.range.start) {
|
const resultTrackDeleteRange = trackedDelete.range
|
||||||
|
const sourceTrackDeleteRange = trackedDelete.range.moveBy(
|
||||||
|
this.sourceCursor - this.result.length
|
||||||
|
)
|
||||||
|
|
||||||
|
if (scanCursor < resultTrackDeleteRange.start) {
|
||||||
this.changes.push({
|
this.changes.push({
|
||||||
d: this.source.slice(cursor, trackedDelete.range.start),
|
d: this.source.slice(this.sourceCursor, sourceTrackDeleteRange.start),
|
||||||
p: this.result.length,
|
p: this.result.length,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// skip the tracked delete itself
|
// skip the tracked delete itself
|
||||||
cursor = Math.min(trackedDelete.range.end, rangeOfDeletion.end)
|
scanCursor = Math.min(resultTrackDeleteRange.end, resultDeletionRange.end)
|
||||||
if (cursor >= rangeOfDeletion.end) {
|
this.sourceCursor = Math.min(
|
||||||
|
sourceTrackDeleteRange.end,
|
||||||
|
sourceDeletionRange.end
|
||||||
|
)
|
||||||
|
|
||||||
|
if (scanCursor >= resultDeletionRange.end) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (cursor < rangeOfDeletion.end) {
|
if (scanCursor < resultDeletionRange.end) {
|
||||||
this.changes.push({
|
this.changes.push({
|
||||||
d: this.source.slice(cursor, rangeOfDeletion.end),
|
d: this.source.slice(this.sourceCursor, sourceDeletionRange.end),
|
||||||
p: this.result.length,
|
p: this.result.length,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
this.sourceCursor = rangeOfDeletion.end
|
this.sourceCursor = sourceDeletionRange.end
|
||||||
}
|
}
|
||||||
|
|
||||||
finish() {
|
finish() {
|
||||||
|
|
|
@ -2277,6 +2277,479 @@ describe('ChunkTranslator', function () {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should properly create changes when deleting after moved track deletes', function (done) {
|
||||||
|
this.chunk = {
|
||||||
|
chunk: {
|
||||||
|
startVersion: 0,
|
||||||
|
history: {
|
||||||
|
snapshot: {
|
||||||
|
files: {
|
||||||
|
'main.tex': {
|
||||||
|
hash: this.fileHash,
|
||||||
|
stringLength: 34,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
operations: [
|
||||||
|
{
|
||||||
|
pathname: 'main.tex',
|
||||||
|
textOperation: [
|
||||||
|
// [...] is a tracked delete
|
||||||
|
// // He[ll]o planet world, this is a test
|
||||||
|
2,
|
||||||
|
{
|
||||||
|
r: 2,
|
||||||
|
tracking: {
|
||||||
|
type: 'delete',
|
||||||
|
userId: this.author1.id,
|
||||||
|
ts: this.date.toISOString(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
30,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
timestamp: this.date.toISOString(),
|
||||||
|
authors: [this.author1.id],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
operations: [
|
||||||
|
{
|
||||||
|
pathname: 'main.tex',
|
||||||
|
textOperation: [
|
||||||
|
// [...] is a tracked delete
|
||||||
|
// {...} is a tracked insert
|
||||||
|
// He[ll]o {TEST }planet world, this is a test
|
||||||
|
6,
|
||||||
|
{
|
||||||
|
i: 'TEST ',
|
||||||
|
tracking: {
|
||||||
|
type: 'insert',
|
||||||
|
userId: this.author1.id,
|
||||||
|
ts: this.date.toISOString(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
28,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
timestamp: this.date.toISOString(),
|
||||||
|
authors: [this.author1.id],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
operations: [
|
||||||
|
{
|
||||||
|
pathname: 'main.tex',
|
||||||
|
textOperation: [
|
||||||
|
// [...] is a tracked delete
|
||||||
|
// {...} is a tracked insert
|
||||||
|
// He[ll]o {TEST }planet world, [this] is a test
|
||||||
|
25,
|
||||||
|
{
|
||||||
|
r: 4,
|
||||||
|
tracking: {
|
||||||
|
type: 'delete',
|
||||||
|
userId: this.author1.id,
|
||||||
|
ts: this.date.toISOString(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
10,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
timestamp: this.date.toISOString(),
|
||||||
|
authors: [this.author1.id],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
operations: [
|
||||||
|
{
|
||||||
|
pathname: 'main.tex',
|
||||||
|
textOperation: [
|
||||||
|
// [...] is a tracked delete
|
||||||
|
// {...} is a tracked insert
|
||||||
|
2, // He|[ll]o {TEST }planet world, [this] is a test
|
||||||
|
-2, // He|o {TEST }planet world, [this] is a test
|
||||||
|
2, // Heo |{TEST }planet world, [this] is a test
|
||||||
|
{
|
||||||
|
r: 5,
|
||||||
|
tracking: {
|
||||||
|
type: 'none',
|
||||||
|
userId: '65f1baeb3cee7c3563fbd1d1',
|
||||||
|
ts: '2024-03-27T13:20:23.253Z',
|
||||||
|
},
|
||||||
|
}, // Heo TEST| planet world, [this] is a test
|
||||||
|
14, // Heo TEST planet world, |[this] is a test
|
||||||
|
-4, // Heo TEST planet world, | is a test
|
||||||
|
10, // Heo TEST planet world, is a test|
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
timestamp: this.date.toISOString(),
|
||||||
|
authors: [this.author1.id],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
authors: [this.author1.id],
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ChunkTranslator.convertToDiffUpdates(
|
||||||
|
this.projectId,
|
||||||
|
this.chunk,
|
||||||
|
'main.tex',
|
||||||
|
0,
|
||||||
|
4,
|
||||||
|
(error, diff) => {
|
||||||
|
expect(error).to.be.null
|
||||||
|
expect(diff.updates).to.deep.equal([
|
||||||
|
{
|
||||||
|
op: [
|
||||||
|
{
|
||||||
|
d: 'll',
|
||||||
|
p: 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
meta: {
|
||||||
|
users: [this.author1.id],
|
||||||
|
start_ts: this.date.getTime(),
|
||||||
|
end_ts: this.date.getTime(),
|
||||||
|
},
|
||||||
|
v: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
op: [
|
||||||
|
{
|
||||||
|
i: 'TEST ',
|
||||||
|
p: 4,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
meta: {
|
||||||
|
users: [this.author1.id],
|
||||||
|
start_ts: this.date.getTime(),
|
||||||
|
end_ts: this.date.getTime(),
|
||||||
|
},
|
||||||
|
v: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
op: [
|
||||||
|
{
|
||||||
|
d: 'this',
|
||||||
|
p: 23,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
meta: {
|
||||||
|
users: [this.author1.id],
|
||||||
|
start_ts: this.date.getTime(),
|
||||||
|
end_ts: this.date.getTime(),
|
||||||
|
},
|
||||||
|
v: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
op: [],
|
||||||
|
meta: {
|
||||||
|
users: [this.author1.id],
|
||||||
|
start_ts: this.date.getTime(),
|
||||||
|
end_ts: this.date.getTime(),
|
||||||
|
},
|
||||||
|
v: 3,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should properly create changes when retaining after moved track deletes', function (done) {
|
||||||
|
this.chunk = {
|
||||||
|
chunk: {
|
||||||
|
startVersion: 0,
|
||||||
|
history: {
|
||||||
|
snapshot: {
|
||||||
|
files: {
|
||||||
|
'main.tex': {
|
||||||
|
hash: this.fileHash,
|
||||||
|
stringLength: 34,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
operations: [
|
||||||
|
{
|
||||||
|
pathname: 'main.tex',
|
||||||
|
textOperation: [
|
||||||
|
// [...] is a tracked delete
|
||||||
|
// // He[ll]o planet world, this is a test
|
||||||
|
2,
|
||||||
|
{
|
||||||
|
r: 2,
|
||||||
|
tracking: {
|
||||||
|
type: 'delete',
|
||||||
|
userId: this.author1.id,
|
||||||
|
ts: this.date.toISOString(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
30,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
timestamp: this.date.toISOString(),
|
||||||
|
authors: [this.author1.id],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
operations: [
|
||||||
|
{
|
||||||
|
pathname: 'main.tex',
|
||||||
|
textOperation: [
|
||||||
|
// [...] is a tracked delete
|
||||||
|
// He[ll]o planet world, [this] is a test
|
||||||
|
20,
|
||||||
|
{
|
||||||
|
r: 4,
|
||||||
|
tracking: {
|
||||||
|
type: 'delete',
|
||||||
|
userId: this.author1.id,
|
||||||
|
ts: this.date.toISOString(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
10,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
timestamp: this.date.toISOString(),
|
||||||
|
authors: [this.author1.id],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
operations: [
|
||||||
|
{
|
||||||
|
pathname: 'main.tex',
|
||||||
|
textOperation: [
|
||||||
|
// [...] is a tracked delete
|
||||||
|
// {...} is a tracked insert
|
||||||
|
// He[ll]o planet world, [this] {TEST }is a test
|
||||||
|
25,
|
||||||
|
{
|
||||||
|
i: 'TEST ',
|
||||||
|
tracking: {
|
||||||
|
type: 'insert',
|
||||||
|
userId: this.author1.id,
|
||||||
|
ts: this.date.toISOString(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
9,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
timestamp: this.date.toISOString(),
|
||||||
|
authors: [this.author1.id],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
operations: [
|
||||||
|
{
|
||||||
|
pathname: 'main.tex',
|
||||||
|
textOperation: [
|
||||||
|
// [...] is a tracked delete
|
||||||
|
// {...} is a tracked insert
|
||||||
|
2, // He|[ll]o planet world, [this] {TEST }is a test
|
||||||
|
-2, // He|o planet world, [this] {TEST }is a test
|
||||||
|
{
|
||||||
|
r: 39,
|
||||||
|
tracking: {
|
||||||
|
type: 'none',
|
||||||
|
userId: this.author1.id,
|
||||||
|
ts: this.date.toISOString(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
], // He|o planet world, this TEST is a test
|
||||||
|
},
|
||||||
|
],
|
||||||
|
timestamp: this.date.toISOString(),
|
||||||
|
authors: [this.author1.id],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
authors: [this.author1.id],
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ChunkTranslator.convertToDiffUpdates(
|
||||||
|
this.projectId,
|
||||||
|
this.chunk,
|
||||||
|
'main.tex',
|
||||||
|
0,
|
||||||
|
4,
|
||||||
|
(error, diff) => {
|
||||||
|
expect(error).to.be.null
|
||||||
|
expect(diff.updates).to.deep.equal([
|
||||||
|
{
|
||||||
|
op: [
|
||||||
|
{
|
||||||
|
d: 'll',
|
||||||
|
p: 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
meta: {
|
||||||
|
users: [this.author1.id],
|
||||||
|
start_ts: this.date.getTime(),
|
||||||
|
end_ts: this.date.getTime(),
|
||||||
|
},
|
||||||
|
v: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
op: [
|
||||||
|
{
|
||||||
|
d: 'this',
|
||||||
|
p: 18,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
meta: {
|
||||||
|
users: [this.author1.id],
|
||||||
|
start_ts: this.date.getTime(),
|
||||||
|
end_ts: this.date.getTime(),
|
||||||
|
},
|
||||||
|
v: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
op: [
|
||||||
|
{
|
||||||
|
i: 'TEST ',
|
||||||
|
p: 19,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
meta: {
|
||||||
|
users: [this.author1.id],
|
||||||
|
start_ts: this.date.getTime(),
|
||||||
|
end_ts: this.date.getTime(),
|
||||||
|
},
|
||||||
|
v: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
op: [
|
||||||
|
{
|
||||||
|
i: 'this',
|
||||||
|
p: 18,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
meta: {
|
||||||
|
users: [this.author1.id],
|
||||||
|
start_ts: this.date.getTime(),
|
||||||
|
end_ts: this.date.getTime(),
|
||||||
|
},
|
||||||
|
v: 3,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should handle deletion that starts before tracked delete', function (done) {
|
||||||
|
this.chunk = {
|
||||||
|
chunk: {
|
||||||
|
startVersion: 0,
|
||||||
|
history: {
|
||||||
|
snapshot: {
|
||||||
|
files: {
|
||||||
|
'main.tex': {
|
||||||
|
hash: this.fileHash,
|
||||||
|
stringLength: 34,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
operations: [
|
||||||
|
{
|
||||||
|
pathname: 'main.tex',
|
||||||
|
textOperation: [
|
||||||
|
// [...] is a tracked delete
|
||||||
|
// Hello planet world, [this] is a test
|
||||||
|
20,
|
||||||
|
{
|
||||||
|
r: 4,
|
||||||
|
tracking: {
|
||||||
|
type: 'delete',
|
||||||
|
userId: this.author1.id,
|
||||||
|
ts: this.date.toISOString(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
10,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
timestamp: this.date.toISOString(),
|
||||||
|
authors: [this.author1.id],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
operations: [
|
||||||
|
{
|
||||||
|
pathname: 'main.tex',
|
||||||
|
textOperation: [
|
||||||
|
// [...] is a tracked delete
|
||||||
|
5, // Hello| planet world, [this] is a test
|
||||||
|
-25, // Hellotest
|
||||||
|
4,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
timestamp: this.date.toISOString(),
|
||||||
|
authors: [this.author1.id],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
authors: [this.author1.id],
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ChunkTranslator.convertToDiffUpdates(
|
||||||
|
this.projectId,
|
||||||
|
this.chunk,
|
||||||
|
'main.tex',
|
||||||
|
0,
|
||||||
|
2,
|
||||||
|
(error, diff) => {
|
||||||
|
expect(error).to.be.null
|
||||||
|
expect(diff.updates).to.deep.equal([
|
||||||
|
{
|
||||||
|
op: [
|
||||||
|
{
|
||||||
|
d: 'this',
|
||||||
|
p: 20,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
meta: {
|
||||||
|
users: [this.author1.id],
|
||||||
|
start_ts: this.date.getTime(),
|
||||||
|
end_ts: this.date.getTime(),
|
||||||
|
},
|
||||||
|
v: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
op: [
|
||||||
|
{
|
||||||
|
d: ' planet world, ',
|
||||||
|
p: 5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
d: ' is a ',
|
||||||
|
p: 5,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
meta: {
|
||||||
|
users: [this.author1.id],
|
||||||
|
start_ts: this.date.getTime(),
|
||||||
|
end_ts: this.date.getTime(),
|
||||||
|
},
|
||||||
|
v: 1,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue