Skip to content
This repository was archived by the owner on Apr 2, 2024. It is now read-only.

Commit a8923fc

Browse files
committed
implement test type that better demos the intent of deserialization
1 parent 0858a8e commit a8923fc

File tree

3 files changed

+236
-132
lines changed

3 files changed

+236
-132
lines changed

test/client/deserialized-type.js

+139
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
2+
// A basic type that uses a custom linked list object type as its snapshot
3+
// data and therefore requires custom serialization into JSON
4+
exports.type = {
5+
name: 'test-deserialized-type',
6+
uri: 'http://sharejs.org/types/test-deserialized-type',
7+
create: create,
8+
deserialize: deserialize,
9+
apply: apply,
10+
transform: transform
11+
};
12+
13+
// Type that additionally defines createDeserialized and supports passing
14+
// deserialized data to doc.create()
15+
exports.type2 = {
16+
name: 'test-deserialized-type2',
17+
uri: 'http://sharejs.org/types/test-deserialized-type2',
18+
create: create,
19+
createDeserialized: createDeserialized,
20+
deserialize: deserialize,
21+
apply: apply,
22+
transform: transform
23+
};
24+
25+
// A node of a singly linked list to demonstrate use of a non-JSON type as
26+
// snapshot data
27+
exports.Node = Node;
28+
29+
// When type.createDeserialized is defined, it will be called on the client
30+
// instead of type.create, and type.create will be called on the server. Only
31+
// serialized data should be passed to type.create
32+
function create(array) {
33+
return array || [];
34+
}
35+
36+
// If type.deserialize is defined and type.createDeserialized is not,
37+
// type.create + type.deserialize will be called in the client Doc class when
38+
// a create operation is created locally or received from the server. The type
39+
// may implement this method to support creating doc data in the deserialized
40+
// format directly in addition to creating from the serialized form
41+
function createDeserialized(data) {
42+
if (data instanceof Node) {
43+
return data;
44+
}
45+
if (data == null) {
46+
return null;
47+
}
48+
return deserialize(data);
49+
}
50+
51+
// Method called when a snapshot is ingested to cast it into deserialized type
52+
// before setting on doc.data
53+
function deserialize(array) {
54+
var node = null;
55+
for (var i = array.length; i--;) {
56+
var value = array[i];
57+
node = new Node(value, node);
58+
}
59+
return node;
60+
}
61+
62+
// When deserialized is defined, apply must do type checking on the input and
63+
// return deserialized data when passed deserialized data or serialized data
64+
// when passed serialized data
65+
function apply(data, op) {
66+
return (data instanceof Node) ?
67+
deserializedApply(data, op) :
68+
serializedApply(data, op);
69+
}
70+
71+
// Deserialized apply is used in the client for all client submitted and
72+
// incoming ops. It should apply with the snapshot in the deserialized format
73+
function deserializedApply(node, op) {
74+
if (typeof op.insert === 'number') {
75+
return node.insert(op.insert, op.value);
76+
}
77+
throw new Error('Op not recognized');
78+
}
79+
80+
// Serialized apply is needed for applying ops on the server to the snapshot
81+
// data stored in the database. For maximum efficiency, the serialized apply
82+
// can implement the equivalent apply method on JSON data directly, though for
83+
// a simpler implementation, it can also call deserialize its input, use the
84+
// same deserialized apply, and serialize again before returning
85+
function serializedApply(array, op) {
86+
if (typeof op.insert === 'number') {
87+
array.splice(array.insert, 0, op.value);
88+
return array;
89+
}
90+
throw new Error('Op not recognized');
91+
}
92+
93+
function transform(op1, op2, side) {
94+
if (
95+
typeof op1.insert === 'number' &&
96+
typeof op2.insert === 'number'
97+
) {
98+
var index = op1.insert;
99+
if (op2.insert < index || (op2.insert === index && side === 'left')) {
100+
index++;
101+
}
102+
return {
103+
insert: index,
104+
value: op1.value
105+
};
106+
}
107+
throw new Error('Op not recognized');
108+
}
109+
110+
// A custom linked list object type to demonstrate custom deserialization
111+
function Node(value, next) {
112+
this.value = value;
113+
this.next = next || null;
114+
}
115+
Node.prototype.at = function(index) {
116+
var node = this;
117+
while (index--) {
118+
node = node.next;
119+
}
120+
return node;
121+
};
122+
Node.prototype.insert = function(index, value) {
123+
if (index === 0) {
124+
return new Node(value, this);
125+
}
126+
var previous = this.at(index - 1);
127+
var node = new Node(value, previous.next);
128+
previous.next = node;
129+
return this;
130+
};
131+
// Implementing a toJSON serialization method for the doc.data object is
132+
// needed if doc.create() is called with deserialized data
133+
Node.prototype.toJSON = function() {
134+
var out = [];
135+
for (var node = this; node; node = node.next) {
136+
out.push(node.value);
137+
}
138+
return out;
139+
};

test/client/submit.js

+97-70
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
var async = require('async');
22
var expect = require('expect.js');
33
var types = require('../../lib/types');
4-
var serializable_type = require('../ot-mock-serializable-json0').type;
5-
6-
types.register(serializable_type);
4+
var deserializedType = require('./deserialized-type');
5+
types.register(deserializedType.type);
6+
types.register(deserializedType.type2);
77

88
module.exports = function() {
99
describe('client submit', function() {
@@ -43,16 +43,6 @@ describe('client submit', function() {
4343
});
4444
});
4545

46-
it('can create a new doc with a serializable type', function(done) {
47-
var doc = this.backend.connect().get('dogs', 'fido');
48-
doc.create(serializable_type.serialize({age: 3}), serializable_type.uri, function(err) {
49-
if (err) return done(err);
50-
expect(doc.data).eql({age: 3});
51-
expect(doc.version).eql(1);
52-
done();
53-
});
54-
});
55-
5646
it('can create then delete then create a doc', function(done) {
5747
var doc = this.backend.connect().get('dogs', 'fido');
5848
doc.create({age: 3}, function(err) {
@@ -88,19 +78,6 @@ describe('client submit', function() {
8878
});
8979
});
9080

91-
it('can create then submit an op on a serializable type', function(done) {
92-
var doc = this.backend.connect().get('dogs', 'fido');
93-
doc.create(serializable_type.serialize({age: 3}), serializable_type.uri, function(err) {
94-
if (err) return done(err);
95-
doc.submitOp({p: ['age'], na: 2}, function(err) {
96-
if (err) return done(err);
97-
expect(doc.data).eql({age: 5});
98-
expect(doc.version).eql(2);
99-
done();
100-
});
101-
});
102-
});
103-
10481
it('can create then submit an op sync', function(done) {
10582
var doc = this.backend.connect().get('dogs', 'fido');
10683
doc.create({age: 3});
@@ -167,33 +144,6 @@ describe('client submit', function() {
167144
});
168145
});
169146

170-
it('ops submitted sync get composed with a serializable type', function(done) {
171-
var doc = this.backend.connect().get('dogs', 'fido');
172-
doc.create(serializable_type.serialize({age: 3}), serializable_type.uri);
173-
doc.submitOp({p: ['age'], na: 2});
174-
doc.submitOp({p: ['age'], na: 2}, function(err) {
175-
if (err) return done(err);
176-
expect(doc.data).eql({age: 7});
177-
// Version is 1 instead of 3, because the create and ops got composed
178-
expect(doc.version).eql(1);
179-
doc.submitOp({p: ['age'], na: 2});
180-
doc.submitOp({p: ['age'], na: 2}, function(err) {
181-
if (err) return done(err);
182-
expect(doc.data).eql({age: 11});
183-
// Ops get composed
184-
expect(doc.version).eql(2);
185-
doc.submitOp({p: ['age'], na: 2});
186-
doc.del(function(err) {
187-
if (err) return done(err);
188-
expect(doc.data).eql(undefined);
189-
// del DOES NOT get composed
190-
expect(doc.version).eql(4);
191-
done();
192-
});
193-
});
194-
});
195-
});
196-
197147
it('does not compose ops when doc.preventCompose is true', function(done) {
198148
var doc = this.backend.connect().get('dogs', 'fido');
199149
doc.preventCompose = true;
@@ -434,23 +384,6 @@ describe('client submit', function() {
434384
});
435385
});
436386

437-
it('can commit then fetch in a new connection to get the same data with a serializable type', function(done) {
438-
var doc = this.backend.connect().get('dogs', 'fido');
439-
var doc2 = this.backend.connect().get('dogs', 'fido');
440-
doc.create(serializable_type.serialize({age: 3}), serializable_type.uri, function(err) {
441-
if (err) return done(err);
442-
doc2.fetch(function(err) {
443-
if (err) return done(err);
444-
expect(doc.data).eql({age: 3});
445-
expect(doc2.data).eql({age: 3});
446-
expect(doc.version).eql(1);
447-
expect(doc2.version).eql(1);
448-
expect(doc.data).not.equal(doc2.data);
449-
done();
450-
});
451-
});
452-
});
453-
454387
it('an op submitted concurrently is transformed by the first', function(done) {
455388
var doc = this.backend.connect().get('dogs', 'fido');
456389
var doc2 = this.backend.connect().get('dogs', 'fido');
@@ -1111,5 +1044,99 @@ describe('client submit', function() {
11111044
});
11121045
});
11131046

1047+
describe('type.deserialize', function() {
1048+
it('can create a new doc', function(done) {
1049+
var doc = this.backend.connect().get('dogs', 'fido');
1050+
doc.create([3], deserializedType.type.uri, function(err) {
1051+
if (err) return done(err);
1052+
expect(doc.data).a(deserializedType.Node);
1053+
expect(doc.data).eql({value: 3, next: null});
1054+
done();
1055+
});
1056+
});
1057+
1058+
it('is stored serialized in backend', function() {
1059+
var db = this.backend.db;
1060+
var doc = this.backend.connect().get('dogs', 'fido');
1061+
doc.create([3], deserializedType.type.uri, function(err) {
1062+
if (err) return done(err);
1063+
db.getSnapshot('dogs', fido, null, null, function(err, snapshot) {
1064+
if (err) return done(err);
1065+
expect(snapshot.data).eql([3]);
1066+
done();
1067+
});
1068+
});
1069+
});
1070+
1071+
it('deserializes on fetch', function(done) {
1072+
var doc = this.backend.connect().get('dogs', 'fido');
1073+
var doc2 = this.backend.connect().get('dogs', 'fido');
1074+
var backend = this.backend;
1075+
doc.create([3], deserializedType.type.uri, function(err) {
1076+
if (err) return done(err);
1077+
doc2.fetch(function(err) {
1078+
if (err) return done(err);
1079+
expect(doc2.data).a(deserializedType.Node);
1080+
expect(doc2.data).eql({value: 3, next: null});
1081+
done();
1082+
});
1083+
});
1084+
});
1085+
1086+
it('can create then submit an op', function(done) {
1087+
var doc = this.backend.connect().get('dogs', 'fido');
1088+
doc.create([3], deserializedType.type.uri, function(err) {
1089+
if (err) return done(err);
1090+
doc.submitOp({insert: 0, value: 2}, function(err) {
1091+
if (err) return done(err);
1092+
expect(doc.data).eql({value: 2, next: {value: 3, next: null}});
1093+
done();
1094+
});
1095+
});
1096+
});
1097+
1098+
it('server fetches and transforms by already committed op', function(done) {
1099+
var doc = this.backend.connect().get('dogs', 'fido');
1100+
var doc2 = this.backend.connect().get('dogs', 'fido');
1101+
var backend = this.backend;
1102+
doc.create([3], deserializedType.type.uri, function(err) {
1103+
if (err) return done(err);
1104+
doc2.fetch(function(err) {
1105+
if (err) return done(err);
1106+
doc.submitOp({insert: 0, value: 2}, function(err) {
1107+
if (err) return done(err);
1108+
doc2.submitOp({insert: 1, value: 4}, function(err) {
1109+
if (err) return done(err);
1110+
expect(doc2.data).eql({value: 2, next: {value: 3, next: {value: 4, next: null}}});
1111+
done();
1112+
});
1113+
});
1114+
});
1115+
});
1116+
});
1117+
});
1118+
1119+
describe('type.createDeserialized', function() {
1120+
it('can create a new doc', function(done) {
1121+
var doc = this.backend.connect().get('dogs', 'fido');
1122+
doc.create([3], deserializedType.type2.uri, function(err) {
1123+
if (err) return done(err);
1124+
expect(doc.data).a(deserializedType.Node);
1125+
expect(doc.data).eql({value: 3, next: null});
1126+
done();
1127+
});
1128+
});
1129+
1130+
it('can create a new doc from deserialized form', function(done) {
1131+
var doc = this.backend.connect().get('dogs', 'fido');
1132+
doc.create(new deserializedType.Node(3), deserializedType.type2.uri, function(err) {
1133+
if (err) return done(err);
1134+
expect(doc.data).a(deserializedType.Node);
1135+
expect(doc.data).eql({value: 3, next: null});
1136+
done();
1137+
});
1138+
});
1139+
});
1140+
11141141
});
11151142
};

0 commit comments

Comments
 (0)