mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Initial commit of script for compressing one docs history
This commit is contained in:
commit
7e96933cf2
8 changed files with 547 additions and 0 deletions
4
services/track-changes/.gitignore
vendored
Normal file
4
services/track-changes/.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
**.swp
|
||||
node_modules/
|
||||
app/js
|
||||
test/unit/js
|
121
services/track-changes/app/coffee/ConcatManager.coffee
Normal file
121
services/track-changes/app/coffee/ConcatManager.coffee
Normal file
|
@ -0,0 +1,121 @@
|
|||
strInject = (s1, pos, s2) -> s1[...pos] + s2 + s1[pos..]
|
||||
strRemove = (s1, pos, length) -> s1[...pos] + s1[(pos + length)..]
|
||||
|
||||
module.exports = ConcatManager =
|
||||
normalizeUpdate: (update) ->
|
||||
updates = []
|
||||
for op in update.op
|
||||
updates.push
|
||||
op: [op]
|
||||
meta:
|
||||
start_ts: update.meta.ts
|
||||
end_ts: update.meta.ts
|
||||
user_id: update.meta.user_id
|
||||
return updates
|
||||
|
||||
MAX_TIME_BETWEEN_UPDATES: oneMinute = 60 * 1000
|
||||
|
||||
concatTwoUpdates: (firstUpdate, secondUpdate) ->
|
||||
firstUpdate =
|
||||
op: firstUpdate.op
|
||||
meta:
|
||||
user_id: firstUpdate.meta.user_id
|
||||
start_ts: firstUpdate.meta.start_ts or firstUpdate.meta.ts
|
||||
end_ts: firstUpdate.meta.end_ts or firstUpdate.meta.ts
|
||||
secondUpdate =
|
||||
op: secondUpdate.op
|
||||
meta:
|
||||
user_id: secondUpdate.meta.user_id
|
||||
start_ts: secondUpdate.meta.start_ts or secondUpdate.meta.ts
|
||||
end_ts: secondUpdate.meta.end_ts or secondUpdate.meta.ts
|
||||
|
||||
if firstUpdate.meta.user_id != secondUpdate.meta.user_id
|
||||
return [firstUpdate, secondUpdate]
|
||||
|
||||
if secondUpdate.meta.start_ts - firstUpdate.meta.end_ts > ConcatManager.MAX_TIME_BETWEEN_UPDATES
|
||||
return [firstUpdate, secondUpdate]
|
||||
|
||||
firstOp = firstUpdate.op[0]
|
||||
secondOp = secondUpdate.op[0]
|
||||
# Two inserts
|
||||
if firstOp.i? and secondOp.i? and firstOp.p <= secondOp.p <= (firstOp.p + firstOp.i.length)
|
||||
return [
|
||||
meta:
|
||||
start_ts: firstUpdate.meta.start_ts
|
||||
end_ts: secondUpdate.meta.end_ts
|
||||
user_id: firstUpdate.meta.user_id
|
||||
op: [
|
||||
p: firstOp.p
|
||||
i: strInject(firstOp.i, secondOp.p - firstOp.p, secondOp.i)
|
||||
]
|
||||
]
|
||||
# Two deletes
|
||||
else if firstOp.d? and secondOp.d? and secondOp.p <= firstOp.p <= (secondOp.p + secondOp.d.length)
|
||||
return [
|
||||
meta:
|
||||
start_ts: firstUpdate.meta.start_ts
|
||||
end_ts: secondUpdate.meta.end_ts
|
||||
user_id: firstUpdate.meta.user_id
|
||||
op: [
|
||||
p: secondOp.p
|
||||
d: strInject(secondOp.d, firstOp.p - secondOp.p, firstOp.d)
|
||||
]
|
||||
]
|
||||
# An insert and then a delete
|
||||
else if firstOp.i? and secondOp.d? and firstOp.p <= secondOp.p <= (firstOp.p + firstOp.i.length)
|
||||
offset = secondOp.p - firstOp.p
|
||||
insertedText = firstOp.i.slice(offset, offset + secondOp.d.length)
|
||||
if insertedText == secondOp.d
|
||||
insert = strRemove(firstOp.i, offset, secondOp.d.length)
|
||||
return [] if insert == ""
|
||||
return [
|
||||
meta:
|
||||
start_ts: firstUpdate.meta.start_ts
|
||||
end_ts: secondUpdate.meta.end_ts
|
||||
user_id: firstUpdate.meta.user_id
|
||||
op: [
|
||||
p: firstOp.p
|
||||
i: insert
|
||||
]
|
||||
]
|
||||
else
|
||||
# This shouldn't be possible!
|
||||
return [firstUpdate, secondUpdate]
|
||||
else if firstOp.d? and secondOp.i? and firstOp.p == secondOp.p
|
||||
offset = firstOp.d.indexOf(secondOp.i)
|
||||
if offset == -1
|
||||
return [firstUpdate, secondUpdate]
|
||||
headD = firstOp.d.slice(0, offset)
|
||||
inserted = firstOp.d.slice(offset, secondOp.i.length)
|
||||
tailD = firstOp.d.slice(offset + secondOp.i.length)
|
||||
headP = firstOp.p
|
||||
tailP = firstOp.p + secondOp.i.length
|
||||
updates = []
|
||||
if headD != ""
|
||||
updates.push
|
||||
meta:
|
||||
start_ts: firstUpdate.meta.start_ts
|
||||
end_ts: secondUpdate.meta.end_ts
|
||||
user_id: firstUpdate.meta.user_id
|
||||
op: [
|
||||
p: headP
|
||||
d: headD
|
||||
]
|
||||
if tailD != ""
|
||||
updates.push
|
||||
meta:
|
||||
start_ts: firstUpdate.meta.start_ts
|
||||
end_ts: secondUpdate.meta.end_ts
|
||||
user_id: firstUpdate.meta.user_id
|
||||
op: [
|
||||
p: tailP
|
||||
d: tailD
|
||||
]
|
||||
if updates.length == 2
|
||||
updates[0].meta.start_ts = updates[0].meta.end_ts = firstUpdate.meta.start_ts
|
||||
updates[1].meta.start_ts = updates[1].meta.end_ts = secondUpdate.meta.end_ts
|
||||
return updates
|
||||
|
||||
else
|
||||
return [firstUpdate, secondUpdate]
|
||||
|
8
services/track-changes/app/coffee/mongojs.coffee
Normal file
8
services/track-changes/app/coffee/mongojs.coffee
Normal file
|
@ -0,0 +1,8 @@
|
|||
Settings = require "settings-sharelatex"
|
||||
mongojs = require "mongojs"
|
||||
db = mongojs.connect(Settings.mongo.url, ["docHistory", "docOps"])
|
||||
module.exports =
|
||||
db: db
|
||||
ObjectId: mongojs.ObjectId
|
||||
|
||||
|
74
services/track-changes/compressHistory.coffee
Normal file
74
services/track-changes/compressHistory.coffee
Normal file
|
@ -0,0 +1,74 @@
|
|||
{db, ObjectId} = require "./app/js/mongojs"
|
||||
ConcatManager = require "./app/js/ConcatManager"
|
||||
|
||||
doc_id = process.argv.pop()
|
||||
console.log "DOC ID", doc_id
|
||||
|
||||
OPS_TO_LEAVE = 10
|
||||
|
||||
removeLatestCompressedUpdate = (doc_id, callback = (error) ->) ->
|
||||
db.docHistory.update { doc_id: ObjectId(doc_id) }, { $pop: { docOps: 1 } }, callback
|
||||
|
||||
getLatestCompressedUpdate = (doc_id, callback = (error) ->) ->
|
||||
db.docHistory.find { doc_id: ObjectId(doc_id) }, { docOps: { $slice: -1 } }, (error, history) ->
|
||||
return callback(error) if error?
|
||||
history = history[0] or { docOps: [] }
|
||||
callback null, history.docOps.slice(-1)[0]
|
||||
|
||||
insertCompressedUpdates = (doc_id, updates, callback = (error) ->) ->
|
||||
db.docHistory.update { doc_id: ObjectId(doc_id) }, { $push: { docOps: { $each: updates } } }, { upsert: true }, callback
|
||||
|
||||
trimLastRawUpdate = (doc_id, tailVersion, callback = (error) ->) ->
|
||||
db.docOps.update { doc_id: ObjectId(doc_id) }, { $pop: { docOps: -1 }, $set: { tailVersion: tailVersion + 1 } }, callback
|
||||
|
||||
done = () ->
|
||||
console.log "DONE! Here's the history:"
|
||||
db.docHistory.find { doc_id: ObjectId(doc_id) }, (error, docs) ->
|
||||
throw error if error?
|
||||
doc = docs[0]
|
||||
for update in doc.docOps
|
||||
op = update.op[0]
|
||||
if op.i?
|
||||
console.log update.meta.start_ts, update.meta.end_ts, update.meta.user_id, "INSERT", op.p, op.i
|
||||
else if op.d?
|
||||
console.log update.meta.start_ts, update.meta.end_ts, update.meta.user_id, "DELETE", op.p, op.d
|
||||
process.exit()
|
||||
|
||||
do next = () ->
|
||||
db.docOps.find { doc_id: ObjectId(doc_id) }, { version: true, tailVersion: true, docOps: { $slice: 1 } }, (error, docs) ->
|
||||
throw error if error?
|
||||
throw "doc not found" if docs.length < 1
|
||||
doc = docs[0]
|
||||
tailVersion = doc.tailVersion or 0
|
||||
version = doc.version
|
||||
|
||||
rawUpdate = doc.docOps[0]
|
||||
rawUpdates = ConcatManager.normalizeUpdate(rawUpdate)
|
||||
|
||||
if version - tailVersion > OPS_TO_LEAVE
|
||||
getLatestCompressedUpdate doc_id, (error, lastCompressedUpdate) ->
|
||||
throw error if error?
|
||||
if lastCompressedUpdate?
|
||||
compressedUpdates = [lastCompressedUpdate]
|
||||
for rawUpdate in rawUpdates
|
||||
lastCompressedUpdate = compressedUpdates.pop()
|
||||
compressedUpdates = compressedUpdates.concat ConcatManager.concatTwoUpdates lastCompressedUpdate, rawUpdate
|
||||
removeLatestCompressedUpdate doc_id, (error) ->
|
||||
throw error if error?
|
||||
insertCompressedUpdates doc_id, compressedUpdates, (error) ->
|
||||
throw error if error?
|
||||
trimLastRawUpdate doc_id, tailVersion, (error) ->
|
||||
throw error if error?
|
||||
console.log "Pushed compressed op"
|
||||
next()
|
||||
else
|
||||
insertCompressedUpdates doc_id, rawUpdates, (error) ->
|
||||
trimLastRawUpdate doc_id, tailVersion, (error) ->
|
||||
throw error if error?
|
||||
console.log "Pushed first op"
|
||||
next()
|
||||
else
|
||||
console.log "Up to date"
|
||||
done()
|
||||
|
||||
|
3
services/track-changes/config/settings.testing.coffee
Executable file
3
services/track-changes/config/settings.testing.coffee
Executable file
|
@ -0,0 +1,3 @@
|
|||
module.exports =
|
||||
mongo:
|
||||
url: 'mongodb://127.0.0.1/sharelatexTesting'
|
11
services/track-changes/package.json
Normal file
11
services/track-changes/package.json
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"name": "history-sharelatex",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"chai": "",
|
||||
"sandboxed-module": "",
|
||||
"sinon": "",
|
||||
"mongojs": "0.7.2",
|
||||
"settings": "git+ssh://git@bitbucket.org:sharelatex/settings-sharelatex.git#master"
|
||||
}
|
||||
}
|
112
services/track-changes/rakefile.rb
Normal file
112
services/track-changes/rakefile.rb
Normal file
|
@ -0,0 +1,112 @@
|
|||
require 'fileutils'
|
||||
|
||||
siteurl = "https://www.sharelatex.com"
|
||||
|
||||
desc "Compile JavaScirpt into CoffeeScript"
|
||||
namespace 'setup' do
|
||||
|
||||
desc "installes npm packages json and global stuff like less"
|
||||
task :installDependencys do
|
||||
sh %{npm install}
|
||||
sh %{npm install -g coffee-script}
|
||||
sh %{git submodule init}
|
||||
sh %{git submodule update}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
namespace 'run' do
|
||||
desc "compiles and runs the javascirpt version of the app"
|
||||
task :app => ["compile:app"] do
|
||||
sh %{node app.js | bunyan} do |ok, res|
|
||||
if ! ok
|
||||
raise "error compiling app folder tests : #{res}"
|
||||
end
|
||||
puts 'finished app compile'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
namespace 'compile' do
|
||||
desc "compiles main app folder"
|
||||
task :app do
|
||||
puts "Compiling app folder to JS"
|
||||
FileUtils.rm_rf "app/js"
|
||||
sh %{coffee -c -o app/js/ app/coffee/} do |ok, res|
|
||||
if ! ok
|
||||
raise "error compiling app folder tests : #{res}"
|
||||
end
|
||||
puts 'finished app compile'
|
||||
end
|
||||
end
|
||||
|
||||
desc "compiles unit tests"
|
||||
task :unittests => ["compile:app"] do
|
||||
puts "Compiling Unit Tests to JS"
|
||||
`coffee -c -o test/unit/js/ test/unit/coffee/`
|
||||
end
|
||||
|
||||
desc "compiles acceptance tests"
|
||||
task :acceptancetests => ["compile:app"] do
|
||||
puts "Compiling Acceptance Tests to JS"
|
||||
sh %{coffee -c -o test/acceptance/js/ test/acceptance/coffee/} do |ok, res|
|
||||
if ! ok
|
||||
raise "error compiling acceptance tests: #{res}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
namespace 'test' do
|
||||
|
||||
desc "runs all test"
|
||||
task :all => ["test:unit", "test:acceptance"] do
|
||||
puts "testing everything"
|
||||
end
|
||||
|
||||
desc "Run Acceptance Tests"
|
||||
task :acceptance => ["compile:acceptancetests"]do
|
||||
puts "Running Acceptance Tests"
|
||||
feature = ENV['feature']
|
||||
if feature.nil?
|
||||
featureFlags = ""
|
||||
else
|
||||
featureFlags = "-g \"#{feature}\""
|
||||
end
|
||||
sh %{mocha -R spec #{featureFlags} test/acceptance/js/*} do |ok, res|
|
||||
if ! ok
|
||||
raise "error running acceptance tests: #{res}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
desc "run unit tests"
|
||||
task :unit => ["compile:unittests"]do
|
||||
puts "Running Unit Tests"
|
||||
featurePath = ENV['feature']
|
||||
puts featurePath
|
||||
if featurePath.nil?
|
||||
featurePath = ''
|
||||
elsif featurePath.include? '/'
|
||||
elsif !featurePath.include? '/'
|
||||
featurePath +='/'
|
||||
else
|
||||
featurePath = ''
|
||||
end
|
||||
|
||||
sh %{mocha -R spec test/unit/js/#{featurePath}* --ignore-leaks} do |ok, res|
|
||||
if ! ok
|
||||
raise "error running unit tests : #{res}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
namespace 'deploy' do
|
||||
desc "safley deploys app"
|
||||
task :live do
|
||||
sh %{git push origin}
|
||||
sh %{cap live deploy}
|
||||
end
|
||||
end
|
|
@ -0,0 +1,214 @@
|
|||
sinon = require('sinon')
|
||||
chai = require('chai')
|
||||
should = chai.should()
|
||||
expect = chai.expect
|
||||
modulePath = "../../../../app/js/ConcatManager.js"
|
||||
SandboxedModule = require('sandboxed-module')
|
||||
|
||||
describe "ConcatManager", ->
|
||||
beforeEach ->
|
||||
@ConcatManager = SandboxedModule.require modulePath
|
||||
@user_id = "user-id-1"
|
||||
@other_user_id = "user-id-2"
|
||||
@ts1 = Date.now()
|
||||
@ts2 = Date.now() + 1000
|
||||
|
||||
describe "concatTwoUpdates", ->
|
||||
describe "insert - insert", ->
|
||||
it "should append one insert to the other", ->
|
||||
expect(@ConcatManager.concatTwoUpdates({
|
||||
op: [ p: 3, i: "foo" ]
|
||||
meta: ts: @ts1, user_id: @user_id
|
||||
}, {
|
||||
op: [ p: 6, i: "bar" ]
|
||||
meta: ts: @ts2, user_id: @user_id
|
||||
}))
|
||||
.to.deep.equal [{
|
||||
op: [ p: 3, i: "foobar" ]
|
||||
meta: start_ts: @ts1, end_ts: @ts2, user_id: @user_id
|
||||
}]
|
||||
|
||||
it "should insert one insert inside the other", ->
|
||||
expect(@ConcatManager.concatTwoUpdates({
|
||||
op: [ p: 3, i: "foo" ]
|
||||
meta: ts: @ts1, user_id: @user_id
|
||||
}, {
|
||||
op: [ p: 5, i: "bar" ]
|
||||
meta: ts: @ts2, user_id: @user_id
|
||||
}))
|
||||
.to.deep.equal [{
|
||||
op: [ p: 3, i: "fobaro" ]
|
||||
meta: start_ts: @ts1, end_ts: @ts2, user_id: @user_id
|
||||
}]
|
||||
|
||||
it "should not append separated inserts", ->
|
||||
expect(@ConcatManager.concatTwoUpdates({
|
||||
op: [ p: 3, i: "foo" ]
|
||||
meta: ts: @ts1, user_id: @user_id
|
||||
}, {
|
||||
op: [ p: 9, i: "bar" ]
|
||||
meta: ts: @ts2, user_id: @user_id
|
||||
}))
|
||||
.to.deep.equal [{
|
||||
op: [ p: 3, i: "foo" ]
|
||||
meta: start_ts: @ts1, end_ts: @ts1, user_id: @user_id
|
||||
}, {
|
||||
op: [ p: 9, i: "bar" ]
|
||||
meta: start_ts: @ts2, end_ts: @ts2, user_id: @user_id
|
||||
}]
|
||||
|
||||
describe "delete - delete", ->
|
||||
it "should append one delete to the other", ->
|
||||
expect(@ConcatManager.concatTwoUpdates({
|
||||
op: [ p: 3, d: "foo" ]
|
||||
meta: ts: @ts1, user_id: @user_id
|
||||
}, {
|
||||
op: [ p: 3, d: "bar" ]
|
||||
meta: ts: @ts2, user_id: @user_id
|
||||
}))
|
||||
.to.deep.equal [{
|
||||
op: [ p: 3, d: "foobar" ]
|
||||
meta: start_ts: @ts1, end_ts: @ts2, user_id: @user_id
|
||||
}]
|
||||
|
||||
it "should insert one delete inside the other", ->
|
||||
expect(@ConcatManager.concatTwoUpdates({
|
||||
op: [ p: 3, d: "foo" ]
|
||||
meta: ts: @ts1, user_id: @user_id
|
||||
}, {
|
||||
op: [ p: 1, d: "bar" ]
|
||||
meta: ts: @ts2, user_id: @user_id
|
||||
}))
|
||||
.to.deep.equal [{
|
||||
op: [ p: 1, d: "bafoor" ]
|
||||
meta: start_ts: @ts1, end_ts: @ts2, user_id: @user_id
|
||||
}]
|
||||
|
||||
it "should not append separated deletes", ->
|
||||
expect(@ConcatManager.concatTwoUpdates({
|
||||
op: [ p: 3, d: "foo" ]
|
||||
meta: ts: @ts1, user_id: @user_id
|
||||
}, {
|
||||
op: [ p: 9, d: "bar" ]
|
||||
meta: ts: @ts2, user_id: @user_id
|
||||
}))
|
||||
.to.deep.equal [{
|
||||
op: [ p: 3, d: "foo" ]
|
||||
meta: start_ts: @ts1, end_ts: @ts1, user_id: @user_id
|
||||
}, {
|
||||
op: [ p: 9, d: "bar" ]
|
||||
meta: start_ts: @ts2, end_ts: @ts2, user_id: @user_id
|
||||
}]
|
||||
|
||||
describe "insert - delete", ->
|
||||
it "should undo a previous insert", ->
|
||||
expect(@ConcatManager.concatTwoUpdates({
|
||||
op: [ p: 3, i: "foo" ]
|
||||
meta: ts: @ts1, user_id: @user_id
|
||||
}, {
|
||||
op: [ p: 5, d: "o" ]
|
||||
meta: ts: @ts2, user_id: @user_id
|
||||
}))
|
||||
.to.deep.equal [{
|
||||
op: [ p: 3, i: "fo" ]
|
||||
meta: start_ts: @ts1, end_ts: @ts2, user_id: @user_id
|
||||
}]
|
||||
|
||||
it "should remove part of an insert from the middle", ->
|
||||
expect(@ConcatManager.concatTwoUpdates({
|
||||
op: [ p: 3, i: "fobaro" ]
|
||||
meta: ts: @ts1, user_id: @user_id
|
||||
}, {
|
||||
op: [ p: 5, d: "bar" ]
|
||||
meta: ts: @ts2, user_id: @user_id
|
||||
}))
|
||||
.to.deep.equal [{
|
||||
op: [ p: 3, i: "foo" ]
|
||||
meta: start_ts: @ts1, end_ts: @ts2, user_id: @user_id
|
||||
}]
|
||||
|
||||
it "should cancel out two opposite updates", ->
|
||||
expect(@ConcatManager.concatTwoUpdates({
|
||||
op: [ p: 3, i: "foo" ]
|
||||
meta: ts: @ts1, user_id: @user_id
|
||||
}, {
|
||||
op: [ p: 3, d: "foo" ]
|
||||
meta: ts: @ts2, user_id: @user_id
|
||||
}))
|
||||
.to.deep.equal []
|
||||
|
||||
it "should not combine separated updates", ->
|
||||
expect(@ConcatManager.concatTwoUpdates({
|
||||
op: [ p: 3, i: "foo" ]
|
||||
meta: ts: @ts1, user_id: @user_id
|
||||
}, {
|
||||
op: [ p: 9, d: "bar" ]
|
||||
meta: ts: @ts2, user_id: @user_id
|
||||
}))
|
||||
.to.deep.equal [{
|
||||
op: [ p: 3, i: "foo" ]
|
||||
meta: start_ts: @ts1, end_ts: @ts1, user_id: @user_id
|
||||
}, {
|
||||
op: [ p: 9, d: "bar" ]
|
||||
meta: start_ts: @ts2, end_ts: @ts2, user_id: @user_id
|
||||
}]
|
||||
|
||||
describe "delete - insert", ->
|
||||
it "should redo a previous delete at the beginning", ->
|
||||
expect(@ConcatManager.concatTwoUpdates({
|
||||
op: [ p: 3, d: "foo" ]
|
||||
meta: ts: @ts1, user_id: @user_id
|
||||
}, {
|
||||
op: [ p: 3, i: "f" ]
|
||||
meta: ts: @ts2, user_id: @user_id
|
||||
}))
|
||||
.to.deep.equal [{
|
||||
op: [ p: 4, d: "oo" ]
|
||||
meta: start_ts: @ts1, end_ts: @ts2, user_id: @user_id
|
||||
}]
|
||||
|
||||
it "should redo a previous delete from halfway through", ->
|
||||
expect(@ConcatManager.concatTwoUpdates({
|
||||
op: [ p: 3, d: "foobar" ]
|
||||
meta: ts: @ts1, user_id: @user_id
|
||||
}, {
|
||||
op: [ p: 3, i: "oo" ]
|
||||
meta: ts: @ts2, user_id: @user_id
|
||||
}))
|
||||
.to.deep.equal [{
|
||||
op: [ p: 3, d: "f" ]
|
||||
meta: start_ts: @ts1, end_ts: @ts1, user_id: @user_id
|
||||
}, {
|
||||
op: [ p: 5, d: "bar" ]
|
||||
meta: start_ts: @ts2, end_ts: @ts2, user_id: @user_id
|
||||
}]
|
||||
|
||||
it "should not combine the ops if the insert text does not match the delete text", ->
|
||||
expect(@ConcatManager.concatTwoUpdates({
|
||||
op: [ p: 3, d: "foobar" ]
|
||||
meta: ts: @ts1, user_id: @user_id
|
||||
}, {
|
||||
op: [ p: 3, i: "xy" ]
|
||||
meta: ts: @ts2, user_id: @user_id
|
||||
}))
|
||||
.to.deep.equal [{
|
||||
op: [ p: 3, d: "foobar" ]
|
||||
meta: start_ts: @ts1, end_ts: @ts1, user_id: @user_id
|
||||
}, {
|
||||
op: [ p: 3, i: "xy" ]
|
||||
meta: start_ts: @ts2, end_ts: @ts2, user_id: @user_id
|
||||
}]
|
||||
|
||||
it "should cancel two equal updates", ->
|
||||
expect(@ConcatManager.concatTwoUpdates({
|
||||
op: [ p: 3, d: "foo" ]
|
||||
meta: ts: @ts1, user_id: @user_id
|
||||
}, {
|
||||
op: [ p: 3, i: "foo" ]
|
||||
meta: ts: @ts2, user_id: @user_id
|
||||
}))
|
||||
.to.deep.equal []
|
||||
|
||||
|
||||
|
||||
|
Loading…
Reference in a new issue