mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-14 06:03:45 +01:00
[document-updater] add track-changes support to setDoc (#35820)
GitOrigin-RevId: 5d39dd1945e710ec3f7068830fb2476e59f0f2a5
This commit is contained in:
@@ -756,6 +756,312 @@ describe('Setting a document', function () {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
describe('with track changes enabled', function () {
|
||||
async function getDocRaw(docId) {
|
||||
return JSON.parse(
|
||||
await docUpdaterRedis.get(Keys.docLines({ doc_id: docId }))
|
||||
)
|
||||
}
|
||||
|
||||
function expectRecentTimestamp(tracking) {
|
||||
expect(new Date(tracking.ts).getTime()).to.be.closeTo(
|
||||
Date.now(),
|
||||
30_000
|
||||
)
|
||||
return tracking.ts
|
||||
}
|
||||
|
||||
describe('when adding content', function () {
|
||||
beforeEach(async function () {
|
||||
this.body = await DocUpdaterClient.setDocLines(
|
||||
this.project_id,
|
||||
this.doc_id,
|
||||
['one', 'INSERT', 'two', 'three'],
|
||||
this.source,
|
||||
userId,
|
||||
false,
|
||||
true
|
||||
)
|
||||
})
|
||||
|
||||
it('should record the insertion as a tracked insert', async function () {
|
||||
const data = await getDocRaw(this.doc_id)
|
||||
const insertTs = expectRecentTimestamp(
|
||||
data.trackedChanges[0].tracking
|
||||
)
|
||||
expect(data).to.deep.equal({
|
||||
content: 'one\nINSERT\none and a half\ntwo\nthree',
|
||||
trackedChanges: [
|
||||
{
|
||||
range: { pos: 4, length: 'INSERT\n'.length },
|
||||
tracking: { ts: insertTs, type: 'insert', userId },
|
||||
},
|
||||
{
|
||||
range: { pos: 'INSERT\n'.length + 4, length: 15 },
|
||||
tracking: { ts, type: 'delete', userId },
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('when removing content', function () {
|
||||
beforeEach(async function () {
|
||||
this.body = await DocUpdaterClient.setDocLines(
|
||||
this.project_id,
|
||||
this.doc_id,
|
||||
['one', 'two'],
|
||||
this.source,
|
||||
userId,
|
||||
false,
|
||||
true
|
||||
)
|
||||
})
|
||||
|
||||
it('should record the removal as a tracked delete, keeping the content', async function () {
|
||||
const data = await getDocRaw(this.doc_id)
|
||||
const deleteTs = expectRecentTimestamp(
|
||||
data.trackedChanges[1].tracking
|
||||
)
|
||||
expect(data).to.deep.equal({
|
||||
content: 'one\none and a half\ntwo\nthree',
|
||||
trackedChanges: [
|
||||
{
|
||||
range: { pos: 4, length: 15 },
|
||||
tracking: { ts, type: 'delete', userId },
|
||||
},
|
||||
{
|
||||
range: { pos: 22, length: '\nthree'.length },
|
||||
tracking: { ts: deleteTs, type: 'delete', userId },
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
it('should hide the removed content from the doc lines', async function () {
|
||||
const doc = await DocUpdaterClient.getDoc(
|
||||
this.project_id,
|
||||
this.doc_id
|
||||
)
|
||||
expect(doc.lines).to.deep.equal(['one', 'two'])
|
||||
})
|
||||
})
|
||||
|
||||
describe('when appending content', function () {
|
||||
beforeEach(async function () {
|
||||
this.body = await DocUpdaterClient.appendToDoc(
|
||||
this.project_id,
|
||||
this.doc_id,
|
||||
['four'],
|
||||
this.source,
|
||||
userId,
|
||||
true
|
||||
)
|
||||
})
|
||||
|
||||
it('should record the appended content as a tracked insert', async function () {
|
||||
const data = await getDocRaw(this.doc_id)
|
||||
const insertTs = expectRecentTimestamp(
|
||||
data.trackedChanges[1].tracking
|
||||
)
|
||||
expect(data).to.deep.equal({
|
||||
content: 'one\none and a half\ntwo\nthree\nfour',
|
||||
trackedChanges: [
|
||||
{
|
||||
range: { pos: 4, length: 15 },
|
||||
tracking: { ts, type: 'delete', userId },
|
||||
},
|
||||
{
|
||||
range: { pos: 28, length: '\nfour'.length },
|
||||
tracking: { ts: insertTs, type: 'insert', userId },
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
it('should include the appended content in the doc lines', async function () {
|
||||
const doc = await DocUpdaterClient.getDoc(
|
||||
this.project_id,
|
||||
this.doc_id
|
||||
)
|
||||
expect(doc.lines).to.deep.equal(['one', 'two', 'three', 'four'])
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('with track changes enabled (sharejs-text-ot)', function () {
|
||||
const lines = ['one', 'one and a half', 'two', 'three']
|
||||
const idSeed = '587357bd35e64f6157'
|
||||
const userId = DocUpdaterClient.randomId()
|
||||
|
||||
beforeEach(async function () {
|
||||
this.project_id = DocUpdaterClient.randomId()
|
||||
this.doc_id = DocUpdaterClient.randomId()
|
||||
this.update = {
|
||||
doc: this.doc_id,
|
||||
op: [
|
||||
{
|
||||
d: 'one and a half\n',
|
||||
p: 4,
|
||||
},
|
||||
],
|
||||
meta: {
|
||||
tc: idSeed,
|
||||
user_id: userId,
|
||||
},
|
||||
v: this.version,
|
||||
}
|
||||
MockWebApi.insertDoc(this.project_id, this.doc_id, {
|
||||
lines,
|
||||
version: this.version,
|
||||
})
|
||||
await DocUpdaterClient.preloadDoc(this.project_id, this.doc_id)
|
||||
await DocUpdaterClient.sendUpdate(
|
||||
this.project_id,
|
||||
this.doc_id,
|
||||
this.update
|
||||
)
|
||||
await DocUpdaterClient.waitForPendingUpdates(this.doc_id)
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
MockProjectHistoryApi.flushProject.resetHistory()
|
||||
MockWebApi.setDocument.resetHistory()
|
||||
})
|
||||
|
||||
function expectPreservedTrackedDelete(changes) {
|
||||
const deleteChange = changes.find(
|
||||
change => change.op.d === 'one and a half\n'
|
||||
)
|
||||
expect(deleteChange.op).to.deep.equal({ d: 'one and a half\n', p: 4 })
|
||||
expect(deleteChange.id).to.equal(idSeed + '000001')
|
||||
expect(deleteChange.metadata.user_id).to.equal(userId)
|
||||
}
|
||||
|
||||
function expectNewTrackedChange(change, op) {
|
||||
expect(change.op).to.deep.equal(op)
|
||||
expect(change.id).to.match(/^[0-9a-f]{24}$/)
|
||||
expect(change.id.startsWith(idSeed)).to.equal(false)
|
||||
expect(change.metadata.user_id).to.equal(userId)
|
||||
expect(new Date(change.metadata.ts).getTime()).to.be.closeTo(
|
||||
Date.now(),
|
||||
30_000
|
||||
)
|
||||
}
|
||||
|
||||
describe('when adding content', function () {
|
||||
beforeEach(async function () {
|
||||
this.body = await DocUpdaterClient.setDocLines(
|
||||
this.project_id,
|
||||
this.doc_id,
|
||||
['one', 'two', 'INSERT', 'three'],
|
||||
this.source,
|
||||
userId,
|
||||
false,
|
||||
true
|
||||
)
|
||||
})
|
||||
|
||||
it('should record the insertion as a tracked insert', async function () {
|
||||
const doc = await DocUpdaterClient.getDoc(this.project_id, this.doc_id)
|
||||
expect(doc.lines).to.deep.equal(['one', 'two', 'INSERT', 'three'])
|
||||
expect(doc.ranges.changes).to.have.length(2)
|
||||
expectPreservedTrackedDelete(doc.ranges.changes)
|
||||
const insertChange = doc.ranges.changes.find(
|
||||
change => change.op.i != null
|
||||
)
|
||||
expectNewTrackedChange(insertChange, { i: 'INSERT\n', p: 8 })
|
||||
})
|
||||
})
|
||||
|
||||
describe('when removing content', function () {
|
||||
beforeEach(async function () {
|
||||
this.body = await DocUpdaterClient.setDocLines(
|
||||
this.project_id,
|
||||
this.doc_id,
|
||||
['one', 'two'],
|
||||
this.source,
|
||||
userId,
|
||||
false,
|
||||
true
|
||||
)
|
||||
})
|
||||
|
||||
it('should record the removal as a tracked delete', async function () {
|
||||
const doc = await DocUpdaterClient.getDoc(this.project_id, this.doc_id)
|
||||
expect(doc.lines).to.deep.equal(['one', 'two'])
|
||||
expect(doc.ranges.changes).to.have.length(2)
|
||||
expectPreservedTrackedDelete(doc.ranges.changes)
|
||||
const newDeleteChange = doc.ranges.changes.find(
|
||||
change => change.op.d === '\nthree'
|
||||
)
|
||||
expectNewTrackedChange(newDeleteChange, { d: '\nthree', p: 7 })
|
||||
})
|
||||
})
|
||||
|
||||
describe('when appending content', function () {
|
||||
beforeEach(async function () {
|
||||
this.body = await DocUpdaterClient.appendToDoc(
|
||||
this.project_id,
|
||||
this.doc_id,
|
||||
['four'],
|
||||
this.source,
|
||||
userId,
|
||||
true
|
||||
)
|
||||
})
|
||||
|
||||
it('should record the appended content as a tracked insert', async function () {
|
||||
const doc = await DocUpdaterClient.getDoc(this.project_id, this.doc_id)
|
||||
expect(doc.lines).to.deep.equal(['one', 'two', 'three', 'four'])
|
||||
expect(doc.ranges.changes).to.have.length(2)
|
||||
expectPreservedTrackedDelete(doc.ranges.changes)
|
||||
const insertChange = doc.ranges.changes.find(
|
||||
change => change.op.i != null
|
||||
)
|
||||
expectNewTrackedChange(insertChange, { i: '\nfour', p: 13 })
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('when track changes is requested without a user id', function () {
|
||||
beforeEach(function () {
|
||||
this.project_id = DocUpdaterClient.randomId()
|
||||
this.doc_id = DocUpdaterClient.randomId()
|
||||
})
|
||||
|
||||
it('should reject setting the document with a 400', async function () {
|
||||
await expect(
|
||||
DocUpdaterClient.setDocLines(
|
||||
this.project_id,
|
||||
this.doc_id,
|
||||
this.newLines,
|
||||
this.source,
|
||||
undefined,
|
||||
false,
|
||||
true
|
||||
)
|
||||
)
|
||||
.to.be.rejectedWith(RequestFailedError)
|
||||
.and.eventually.have.nested.property('response.status', 400)
|
||||
})
|
||||
|
||||
it('should reject appending to the document with a 400', async function () {
|
||||
await expect(
|
||||
DocUpdaterClient.appendToDoc(
|
||||
this.project_id,
|
||||
this.doc_id,
|
||||
this.newLines,
|
||||
this.source,
|
||||
undefined,
|
||||
true
|
||||
)
|
||||
)
|
||||
.to.be.rejectedWith(RequestFailedError)
|
||||
.and.eventually.have.nested.property('response.status', 400)
|
||||
})
|
||||
})
|
||||
describe('when the first request returns a connection error', function () {
|
||||
beforeEach(function () {
|
||||
|
||||
Reference in New Issue
Block a user