Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Avatar for KuCalc : devops.
Download
50640 views
1
###############################################################################
2
#
3
# CoCalc: Collaborative web-based calculation
4
# Copyright (C) 2017, Sagemath Inc.
5
# AGPLv3
6
#
7
###############################################################################
8
require('coffee-cache')
9
10
syncstring = require('../syncstring')
11
misc = require('../misc')
12
13
expect = require('expect')
14
15
###
16
Test the SortedPatchList class
17
###
18
19
describe "basic test of SortedPatchList -- ", ->
20
spl = undefined
21
times = (misc.minutes_ago(n) for n in [3, 2, 1, 0])
22
from_str = (str) -> new syncstring._testStringDocument(str)
23
24
it 'creates a SortedPatchList', ->
25
spl = new syncstring.SortedPatchList(from_str)
26
27
it 'creates and adds a patch and does some checks', ->
28
patch =
29
time : times[0]
30
user_id : 1
31
patch : syncstring.make_patch('', 'hello world')
32
spl.add([patch])
33
expect(spl.value().to_str()).toEqual('hello world')
34
expect(spl.user_id(times[1])).toEqual(undefined)
35
expect(spl.user_id(times[0])).toEqual(1)
36
expect(spl.time_sent(times[0])).toEqual(undefined)
37
expect(spl.patch(times[0])).toEqual(patch)
38
expect(spl.patch(times[1])).toEqual(undefined)
39
expect(spl.versions()).toEqual([times[0]])
40
expect(spl.snapshot_times()).toEqual([])
41
42
it 'adds another patch and does further checks', ->
43
patch =
44
time : times[1]
45
user_id : 0
46
patch : syncstring.make_patch('hello world', 'CoCalc: "hello world"')
47
spl.add([patch])
48
expect(spl.value().to_str()).toEqual('CoCalc: "hello world"')
49
expect(spl.value(times[0]).to_str()).toEqual('hello world')
50
expect(spl.user_id(times[1])).toEqual(0)
51
expect(spl.user_id(times[0])).toEqual(1)
52
expect(spl.time_sent(times[1])).toEqual(undefined)
53
expect(spl.patch(times[1])).toEqual(patch)
54
expect(spl.versions()).toEqual([times[0], times[1]])
55
expect(spl.snapshot_times()).toEqual([])
56
57
it 'adds two more patches', ->
58
patch2 =
59
time : times[2]
60
user_id : 2
61
patch : syncstring.make_patch('CoCalc: "hello world"', 'CoCalc: "Hello World!"')
62
snapshot : 'CoCalc: "Hello World!"'
63
patch3 =
64
time : times[3]
65
user_id : 3
66
patch : syncstring.make_patch('CoCalc: "Hello World!"', 'CoCalc: "HELLO!!"')
67
snapshot : 'CoCalc: "HELLO!!"'
68
spl.add([patch2, patch3])
69
expect(spl.value().to_str()).toEqual('CoCalc: "HELLO!!"')
70
expect(spl.value(times[1]).to_str()).toEqual('CoCalc: "hello world"')
71
expect(spl.value(times[2]).to_str()).toEqual('CoCalc: "Hello World!"')
72
expect(spl.value(times[3]).to_str()).toEqual('CoCalc: "HELLO!!"')
73
expect(spl.versions()).toEqual(times)
74
75
it 'verifies snapshot times', ->
76
expect(spl.snapshot_times()).toEqual([times[2], times[3]])
77
expect(spl.newest_snapshot_time()).toEqual(times[3])
78
79
it 'closes SortedPatchList and verifies that it is closed', ->
80
spl.close()
81
expect(spl._patches).toBe(undefined)
82
83
describe "very basic test of syncstring -- ", ->
84
client = new syncstring.TestBrowserClient1()
85
project_id = misc.uuid()
86
path = 'test.txt'
87
queries = {}
88
ss = undefined
89
90
it 'creates a sync string', (done) ->
91
ss = client.sync_string
92
project_id : project_id
93
path : path
94
cursors : false
95
# Wait for the various queries
96
client.once 'query', (opts) =>
97
#console.log JSON.stringify(opts)
98
queries.syncstring = opts
99
opts.cb(undefined, {query: opts.query})
100
client.once 'query', (opts) =>
101
#console.log JSON.stringify(opts)
102
queries.patches = opts
103
opts.cb(undefined, {query:{patches:[]}})
104
ss.on "connected", -> done()
105
106
it 'get the blank new sync string', ->
107
expect(ss.to_str()).toEqual('')
108
109
it 'from_str the sync string', ->
110
ss.from_str("cocalc")
111
expect(ss.to_str()).toEqual('cocalc')
112
113
it 'saves the sync string', (done) ->
114
client.once 'query', (opts) =>
115
expect(opts.query.length).toEqual(1)
116
patch = opts.query[0].patches
117
expect(patch.patch).toEqual('[[[[1,"cocalc"]],0,0,0,6]]')
118
opts.cb()
119
ss.save(done)
120
121
it 'changes the sync string again', ->
122
ss.from_str("CoCalc")
123
expect(ss.to_str()).toEqual('CoCalc')
124
125
it 'saves the sync string', (done) ->
126
client.once 'query', (opts) =>
127
expect(opts.query.length).toEqual(1)
128
patch = opts.query[0].patches
129
expect(patch.patch).toEqual('[[[[-1,"coc"],[1,"CoC"],[0,"alc"]],0,0,6,6]]')
130
opts.cb()
131
ss.save(done)
132
133
it 'closes the sync string', ->
134
ss.close()
135
136
describe "test sync editing of two syncstring -- ", ->
137
client = new syncstring.TestBrowserClient1()
138
project_id = misc.uuid()
139
path = 'test.txt'
140
queries = [{}, {}]
141
ss = [undefined, undefined]
142
143
all_queries = []
144
client.on 'query', (opts) ->
145
#console.log(JSON.stringify(opts.query))
146
all_queries.push(opts)
147
148
it 'creates first syncstring', (done) ->
149
ss[0] = client.sync_string(project_id: project_id, path: path)
150
client.once 'query', (opts) =>
151
queries[0].syncstring = opts
152
opts.cb(undefined, {query: opts.query})
153
client.once 'query', (opts) =>
154
queries[0].patches = opts
155
opts.cb(undefined, {query:{patches:[]}})
156
ss[0].on "connected", -> done()
157
158
it 'creates second syncstring', (done) ->
159
ss[1] = client.sync_string(project_id: project_id, path: path)
160
client.once 'query', (opts) =>
161
queries[1].syncstring = opts
162
opts.cb(undefined, {query: opts.query})
163
client.once 'query', (opts) =>
164
queries[1].patches = opts
165
opts.cb(undefined, {query:{patches:[]}})
166
ss[1].on "connected", -> done()
167
168
it 'verify starting state', ->
169
for s in ss
170
expect(s.to_str()).toEqual('')
171
172
it 'from_str the sync string of one', ->
173
ss[0].from_str("cocalc")
174
expect(ss[0].to_str()).toEqual('cocalc')
175
expect(ss[1].to_str()).toEqual('')
176
177
it 'saves the sync string, hence sending the changes to the other one', (done) ->
178
ss[1].once 'change', ->
179
# this is what we want to happen
180
expect(ss[1].to_str()).toEqual('cocalc')
181
done()
182
client.once 'query', (opts) ->
183
expect(opts.query.length).toEqual(1)
184
patch = opts.query[0].patches
185
expect(patch.patch).toEqual('[[[[1,"cocalc"]],0,0,0,6]]')
186
opts.cb()
187
queries[1].patches.cb(undefined, {new_val:patch})
188
ss[0].save() # this triggers above query
189
190
it 'makes change to both strings then save, and see that changes merge', (done) ->
191
ss[0].from_str("cocalcX")
192
ss[1].from_str("Ycocalc")
193
194
ss[1].once 'change', ->
195
expect(ss[1].to_str()).toEqual('YcocalcX')
196
done()
197
client.once 'query', (opts) ->
198
opts.cb()
199
queries[1].patches.cb(undefined, {new_val:opts.query[0].patches})
200
ss[0].save()
201
202
it 'and the other direction', (done) ->
203
ss[0].once 'change', ->
204
expect(ss[0].to_str()).toEqual('YcocalcX')
205
done()
206
# Note that when ss[1] above changed it also sent out its patch already, so
207
# we can't wait for it here like we did above. It is in all_queries.
208
queries[0].patches.cb(undefined, {new_val:all_queries[all_queries.length-1].query[0].patches})
209
210
it 'closes the sync strings', ->
211
ss[0].close()
212
ss[1].close()
213
214
215
216
describe "test conflicting changes to two syncstrings -- ", ->
217
client = new syncstring.TestBrowserClient1()
218
project_id = misc.uuid()
219
path = 'test.txt'
220
queries = [{}, {}]
221
ss = [undefined, undefined]
222
223
all_queries = []
224
client.on 'query', (opts) ->
225
#console.log(JSON.stringify(opts.query))
226
all_queries.push(opts)
227
228
it 'creates first syncstring', (done) ->
229
ss[0] = client.sync_string(project_id: project_id, path: path)
230
client.once 'query', (opts) =>
231
queries[0].syncstring = opts
232
opts.cb(undefined, {query: opts.query})
233
client.once 'query', (opts) =>
234
queries[0].patches = opts
235
opts.cb(undefined, {query:{patches:[]}})
236
ss[0].on "connected", -> done()
237
238
it 'creates second syncstring', (done) ->
239
ss[1] = client.sync_string(project_id: project_id, path: path)
240
client.once 'query', (opts) =>
241
queries[1].syncstring = opts
242
opts.cb(undefined, {query: opts.query})
243
client.once 'query', (opts) =>
244
queries[1].patches = opts
245
opts.cb(undefined, {query:{patches:[]}})
246
ss[1].on "connected", -> done()
247
248
it 'make first change', (done) ->
249
ss[0].from_str('{"a":389}')
250
setTimeout(done, 2) # wait 2ms
251
252
it 'makes conflicting change to both strings then save', (done) ->
253
ss[1].from_str('{"a":433}')
254
255
# OBSERVE that though both lines are valid JSON, the resulting
256
# merge is **invalid** (hence corrupted).
257
ss[1].once 'change', ->
258
expect(ss[1].to_str()).toEqual('{"a":433}{"a":389}')
259
done()
260
client.once 'query', (opts) ->
261
opts.cb()
262
queries[1].patches.cb(undefined, {new_val:opts.query[0].patches})
263
ss[0].save()
264
265
it 'and the other direction', (done) ->
266
ss[0].once 'change', ->
267
expect(ss[0].to_str()).toEqual('{"a":433}{"a":389}')
268
done()
269
# Note that when ss[1] above changed it also sent out its patch already, so
270
# we can't wait for it here like we did above. It is in all_queries.
271
queries[0].patches.cb(undefined, {new_val:all_queries[all_queries.length-1].query[0].patches})
272
273
it 'closes the sync strings', ->
274
ss[0].close()
275
ss[1].close()
276
277
278
279
280