added text as a custom type, more tests are working

This commit is contained in:
DadaMonad 2015-02-23 11:41:04 +00:00
parent 860934de06
commit 2e9f8f6d03
22 changed files with 2604 additions and 2255 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -7,7 +7,10 @@ module.exports = function() {
ops = {};
execution_listener = [];
ops.Operation = (function() {
function Operation(uid) {
function Operation(custom_type, uid) {
if (custom_type != null) {
this.custom_type = custom_type;
}
this.is_deleted = false;
this.garbage_collected = false;
this.event_listeners = [];
@ -181,9 +184,9 @@ module.exports = function() {
ops.Delete = (function(_super) {
__extends(Delete, _super);
function Delete(uid, deletes) {
function Delete(custom_type, uid, deletes) {
this.saveOperation('deletes', deletes);
Delete.__super__.constructor.call(this, uid);
Delete.__super__.constructor.call(this, custom_type, uid);
}
Delete.prototype.type = "Delete";
@ -215,12 +218,12 @@ module.exports = function() {
ops.Delete.parse = function(o) {
var deletes_uid, uid;
uid = o['uid'], deletes_uid = o['deletes'];
return new this(uid, deletes_uid);
return new this(null, uid, deletes_uid);
};
ops.Insert = (function(_super) {
__extends(Insert, _super);
function Insert(content, uid, prev_cl, next_cl, origin, parent) {
function Insert(custom_type, content, uid, prev_cl, next_cl, origin, parent) {
if (content === void 0) {
} else if ((content != null) && (content.creator != null)) {
@ -236,7 +239,7 @@ module.exports = function() {
} else {
this.saveOperation('origin', prev_cl);
}
Insert.__super__.constructor.call(this, uid);
Insert.__super__.constructor.call(this, custom_type, uid);
}
Insert.prototype.type = "Insert";
@ -371,14 +374,21 @@ module.exports = function() {
};
Insert.prototype.callOperationSpecificInsertEvents = function() {
var _ref;
var getContentType, _ref;
getContentType = function(content) {
if (content instanceof ops.Operation) {
return content.getCustomType();
} else {
return content;
}
};
return (_ref = this.parent) != null ? _ref.callEvent([
{
type: "insert",
position: this.getPosition(),
object: this.parent,
object: this.parent.getCustomType(),
changedBy: this.uid.creator,
value: this.content
value: getContentType(this.content)
}
]) : void 0;
};
@ -388,7 +398,7 @@ module.exports = function() {
{
type: "delete",
position: this.getPosition(),
object: this.parent,
object: this.parent.getCustomType(),
length: 1,
changedBy: o.uid.creator
}
@ -442,14 +452,14 @@ module.exports = function() {
if (typeof content === "string") {
content = JSON.parse(content);
}
return new this(content, uid, prev, next, origin, parent);
return new this(null, content, uid, prev, next, origin, parent);
};
ops.ImmutableObject = (function(_super) {
__extends(ImmutableObject, _super);
function ImmutableObject(uid, _at_content) {
function ImmutableObject(custom_type, uid, _at_content) {
this.content = _at_content;
ImmutableObject.__super__.constructor.call(this, uid);
ImmutableObject.__super__.constructor.call(this, custom_type, uid);
}
ImmutableObject.prototype.type = "ImmutableObject";
@ -474,7 +484,7 @@ module.exports = function() {
ops.ImmutableObject.parse = function(json) {
var content, uid;
uid = json['uid'], content = json['content'];
return new this(uid, content);
return new this(null, uid, content);
};
ops.Delimiter = (function(_super) {
__extends(Delimiter, _super);
@ -483,7 +493,7 @@ module.exports = function() {
this.saveOperation('prev_cl', prev_cl);
this.saveOperation('next_cl', next_cl);
this.saveOperation('origin', prev_cl);
Delimiter.__super__.constructor.call(this, {
Delimiter.__super__.constructor.call(this, null, {
noOperation: true
});
}

View File

@ -12,11 +12,8 @@ module.exports = function() {
__extends(MapManager, _super);
function MapManager(custom_type, uid) {
if (custom_type != null) {
this.custom_type = custom_type;
}
this._map = {};
MapManager.__super__.constructor.call(this, uid);
MapManager.__super__.constructor.call(this, custom_type, uid);
}
MapManager.prototype.type = "MapManager";
@ -48,8 +45,8 @@ module.exports = function() {
MapManager.prototype.val = function(name, content) {
var o, prop, rep, res, result, _ref;
if (arguments.length > 1) {
if ((content != null) && (content._model != null) && content._model instanceof ops.Operation) {
rep = content._model;
if ((content != null) && (content._getModel != null)) {
rep = content._getModel(this.custom_types, this.operations);
} else {
rep = content;
}
@ -73,12 +70,7 @@ module.exports = function() {
for (name in _ref) {
o = _ref[name];
if (!o.isContentDeleted()) {
res = prop.val();
if (res instanceof ops.Operation) {
result[name] = res.getCustomType();
} else {
result[name] = res;
}
result[name] = o.val();
}
}
return result;
@ -105,7 +97,7 @@ module.exports = function() {
sub: property_name,
alt: this
};
rm = new ops.ReplaceManager(event_properties, event_this, rm_uid);
rm = new ops.ReplaceManager(null, event_properties, event_this, rm_uid);
this._map[property_name] = rm;
rm.setParent(this, property_name);
rm.execute();
@ -138,13 +130,13 @@ module.exports = function() {
ops.ListManager = (function(_super) {
__extends(ListManager, _super);
function ListManager(uid) {
function ListManager(custom_type, uid) {
this.beginning = new ops.Delimiter(void 0, void 0);
this.end = new ops.Delimiter(this.beginning, void 0);
this.beginning.next_cl = this.end;
this.beginning.execute();
this.end.execute();
ListManager.__super__.constructor.call(this, uid);
ListManager.__super__.constructor.call(this, custom_type, uid);
}
ListManager.prototype.type = "ListManager";
@ -281,42 +273,29 @@ module.exports = function() {
return this.insertAfter(this.end.prev_cl, content);
};
ListManager.prototype.insertAfter = function(left, content, options) {
var c, createContent, right, tmp, _i, _len;
createContent = function(content, options) {
var type;
if ((content != null) && (content.constructor != null)) {
type = ops[content.constructor.name];
if ((type != null) && (type.create != null)) {
return type.create(content, options);
} else {
throw new Error("The " + content.constructor.name + "-type is not (yet) supported in Y.");
}
} else {
return content;
}
};
ListManager.prototype.insertAfter = function(left, content) {
var c, right, tmp, _i, _len;
right = left.next_cl;
while (right.isDeleted()) {
right = right.next_cl;
}
left = right.prev_cl;
if (content instanceof ops.Operation) {
(new ops.Insert(content, void 0, left, right)).execute();
(new ops.Insert(null, content, void 0, left, right)).execute();
} else {
for (_i = 0, _len = content.length; _i < _len; _i++) {
c = content[_i];
tmp = (new ops.Insert(createContent(c, options), void 0, left, right)).execute();
tmp = (new ops.Insert(null, c, void 0, left, right)).execute();
left = tmp;
}
}
return this;
};
ListManager.prototype.insert = function(position, content, options) {
ListManager.prototype.insert = function(position, content) {
var ith;
ith = this.getOperationByPosition(position);
return this.insertAfter(ith, [content], options);
return this.insertAfter(ith, [content]);
};
ListManager.prototype["delete"] = function(position, length) {
@ -327,7 +306,7 @@ module.exports = function() {
if (o instanceof ops.Delimiter) {
break;
}
d = (new ops.Delete(void 0, o)).execute();
d = (new ops.Delete(null, void 0, o)).execute();
o = o.next_cl;
while ((!(o instanceof ops.Delimiter)) && o.isDeleted()) {
o = o.next_cl;
@ -343,6 +322,11 @@ module.exports = function() {
'type': this.type,
'uid': this.getUid()
};
if (this.custom_type.constructor === String) {
json.custom_type = this.custom_type;
} else {
json.custom_type = this.custom_type._name;
}
return json;
};
@ -350,34 +334,20 @@ module.exports = function() {
})(ops.Operation);
ops.ListManager.parse = function(json) {
var uid;
uid = json['uid'];
return new this(uid);
};
ops.Array = function() {};
ops.Array.create = function(content, mutable) {
var ith, list;
if (mutable === "mutable") {
list = new ops.ListManager().execute();
ith = list.getOperationByPosition(0);
list.insertAfter(ith, content);
return list;
} else if ((mutable == null) || (mutable === "immutable")) {
return content;
} else {
throw new Error("Specify either \"mutable\" or \"immutable\"!!");
}
var custom_type, uid;
uid = json['uid'], custom_type = json['custom_type'];
return new this(custom_type, uid);
};
ops.ReplaceManager = (function(_super) {
__extends(ReplaceManager, _super);
function ReplaceManager(_at_event_properties, _at_event_this, uid, beginning, end) {
function ReplaceManager(custom_type, _at_event_properties, _at_event_this, uid, beginning, end) {
this.event_properties = _at_event_properties;
this.event_this = _at_event_this;
if (this.event_properties['object'] == null) {
this.event_properties['object'] = this.event_this;
}
ReplaceManager.__super__.constructor.call(this, uid, beginning, end);
ReplaceManager.__super__.constructor.call(this, custom_type, uid, beginning, end);
}
ReplaceManager.prototype.type = "ReplaceManager";
@ -415,7 +385,7 @@ module.exports = function() {
ReplaceManager.prototype.replace = function(content, replaceable_uid) {
var o, relp;
o = this.getLastOperation();
relp = (new ops.Replaceable(content, this, replaceable_uid, o, o.next_cl)).execute();
relp = (new ops.Replaceable(null, content, this, replaceable_uid, o, o.next_cl)).execute();
return void 0;
};
@ -424,7 +394,7 @@ module.exports = function() {
};
ReplaceManager.prototype.deleteContent = function() {
(new ops.Delete(void 0, this.getLastOperation().uid)).execute();
(new ops.Delete(null, void 0, this.getLastOperation().uid)).execute();
return void 0;
};
@ -451,9 +421,9 @@ module.exports = function() {
ops.Replaceable = (function(_super) {
__extends(Replaceable, _super);
function Replaceable(content, parent, uid, prev, next, origin, is_deleted) {
function Replaceable(custom_type, content, parent, uid, prev, next, origin, is_deleted) {
this.saveOperation('parent', parent);
Replaceable.__super__.constructor.call(this, content, uid, prev, next, origin);
Replaceable.__super__.constructor.call(this, custom_type, content, uid, prev, next, origin);
this.is_deleted = is_deleted;
}
@ -560,9 +530,9 @@ module.exports = function() {
})(ops.Insert);
ops.Replaceable.parse = function(json) {
var content, is_deleted, next, origin, parent, prev, uid;
content = json['content'], parent = json['parent'], uid = json['uid'], prev = json['prev'], next = json['next'], origin = json['origin'], is_deleted = json['is_deleted'];
return new this(content, parent, uid, prev, next, origin, is_deleted);
var content, custom_type, is_deleted, next, origin, parent, prev, uid;
content = json['content'], parent = json['parent'], uid = json['uid'], prev = json['prev'], next = json['next'], origin = json['origin'], is_deleted = json['is_deleted'], custom_type = json['custom_type'];
return new this(custom_type, content, parent, uid, prev, next, origin, is_deleted);
};
return basic_ops;
};

View File

@ -63,6 +63,10 @@ YObject = (function() {
}
};
YObject.prototype["delete"] = function(name) {
return this._model["delete"](name);
};
return YObject;
})();

335
build/node/Types/Text.js Normal file
View File

@ -0,0 +1,335 @@
var YText;
YText = (function() {
function YText(text) {
this.textfields = [];
if (text == null) {
this._text = "";
} else if (text.constructor === String) {
this._text = text;
} else {
throw new Error("Y.Text expects a String as a constructor");
}
}
YText.prototype._name = "Text";
YText.prototype._getModel = function(types, ops) {
if (this._model == null) {
this._model = new ops.ListManager(this).execute();
this.insert(0, this._text);
}
delete this._text;
return this._model;
};
YText.prototype._setModel = function(_at__model) {
this._model = _at__model;
return delete this._text;
};
YText.prototype.val = function() {
return this._model.fold("", function(left, o) {
return left + o.val();
});
};
YText.prototype.observe = function() {
return this._model.observe.apply(this._model, arguments);
};
YText.prototype.unobserve = function() {
return this._model.unobserve.apply(this._model, arguments);
};
YText.prototype.toString = function() {
return this.val();
};
YText.prototype.insert = function(position, content) {
var ith;
if (content.constructor !== String) {
throw new Error("Y.String.insert expects a String as the second parameter!");
}
if (typeof position !== "number") {
throw new Error("Y.String.insert expects a Number as the second parameter!");
}
if (content.length > 0) {
ith = this._model.getOperationByPosition(position);
return this._model.insertAfter(ith, content);
}
};
YText.prototype["delete"] = function(position, length) {
return this._model["delete"](position, length);
};
YText.prototype.bind = function(textfield, dom_root) {
var createRange, creator_token, t, word, writeContent, writeRange, _i, _len, _ref;
if (dom_root == null) {
dom_root = window;
}
if (dom_root.getSelection == null) {
dom_root = window;
}
_ref = this.textfields;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
t = _ref[_i];
if (t === textfield) {
return;
}
}
creator_token = false;
word = this;
textfield.value = this.val();
this.textfields.push(textfield);
if ((textfield.selectionStart != null) && (textfield.setSelectionRange != null)) {
createRange = function(fix) {
var left, right;
left = textfield.selectionStart;
right = textfield.selectionEnd;
if (fix != null) {
left = fix(left);
right = fix(right);
}
return {
left: left,
right: right
};
};
writeRange = function(range) {
writeContent(word.val());
return textfield.setSelectionRange(range.left, range.right);
};
writeContent = function(content) {
return textfield.value = content;
};
} else {
createRange = function(fix) {
var clength, edited_element, range, s;
range = {};
s = dom_root.getSelection();
clength = textfield.textContent.length;
range.left = Math.min(s.anchorOffset, clength);
range.right = Math.min(s.focusOffset, clength);
if (fix != null) {
range.left = fix(range.left);
range.right = fix(range.right);
}
edited_element = s.focusNode;
if (edited_element === textfield || edited_element === textfield.childNodes[0]) {
range.isReal = true;
} else {
range.isReal = false;
}
return range;
};
writeRange = function(range) {
var r, s, textnode;
writeContent(word.val());
textnode = textfield.childNodes[0];
if (range.isReal && (textnode != null)) {
if (range.left < 0) {
range.left = 0;
}
range.right = Math.max(range.left, range.right);
if (range.right > textnode.length) {
range.right = textnode.length;
}
range.left = Math.min(range.left, range.right);
r = document.createRange();
r.setStart(textnode, range.left);
r.setEnd(textnode, range.right);
s = window.getSelection();
s.removeAllRanges();
return s.addRange(r);
}
};
writeContent = function(content) {
var c, content_array, i, _j, _len1, _results;
content_array = content.replace(new RegExp("\n", 'g'), " ").split(" ");
textfield.innerText = "";
_results = [];
for (i = _j = 0, _len1 = content_array.length; _j < _len1; i = ++_j) {
c = content_array[i];
textfield.innerText += c;
if (i !== content_array.length - 1) {
_results.push(textfield.innerHTML += '&nbsp;');
} else {
_results.push(void 0);
}
}
return _results;
};
}
writeContent(this.val());
this.observe(function(events) {
var event, fix, o_pos, r, _j, _len1, _results;
_results = [];
for (_j = 0, _len1 = events.length; _j < _len1; _j++) {
event = events[_j];
if (!creator_token) {
if (event.type === "insert") {
o_pos = event.position;
fix = function(cursor) {
if (cursor <= o_pos) {
return cursor;
} else {
cursor += 1;
return cursor;
}
};
r = createRange(fix);
_results.push(writeRange(r));
} else if (event.type === "delete") {
o_pos = event.position;
fix = function(cursor) {
if (cursor < o_pos) {
return cursor;
} else {
cursor -= 1;
return cursor;
}
};
r = createRange(fix);
_results.push(writeRange(r));
} else {
_results.push(void 0);
}
} else {
_results.push(void 0);
}
}
return _results;
});
textfield.onkeypress = function(event) {
var char, diff, pos, r;
if (word.is_deleted) {
textfield.onkeypress = null;
return true;
}
creator_token = true;
char = null;
if (event.keyCode === 13) {
char = '\n';
} else if (event.key != null) {
if (event.charCode === 32) {
char = " ";
} else {
char = event.key;
}
} else {
char = window.String.fromCharCode(event.keyCode);
}
if (char.length > 1) {
return true;
} else if (char.length > 0) {
r = createRange();
pos = Math.min(r.left, r.right);
diff = Math.abs(r.right - r.left);
word["delete"](pos, diff);
word.insert(pos, char);
r.left = pos + char.length;
r.right = r.left;
writeRange(r);
}
event.preventDefault();
creator_token = false;
return false;
};
textfield.onpaste = function(event) {
if (word.is_deleted) {
textfield.onpaste = null;
return true;
}
return event.preventDefault();
};
textfield.oncut = function(event) {
if (word.is_deleted) {
textfield.oncut = null;
return true;
}
return event.preventDefault();
};
return textfield.onkeydown = function(event) {
var del_length, diff, new_pos, pos, r, val;
creator_token = true;
if (word.is_deleted) {
textfield.onkeydown = null;
return true;
}
r = createRange();
pos = Math.min(r.left, r.right, word.val().length);
diff = Math.abs(r.left - r.right);
if ((event.keyCode != null) && event.keyCode === 8) {
if (diff > 0) {
word["delete"](pos, diff);
r.left = pos;
r.right = pos;
writeRange(r);
} else {
if ((event.ctrlKey != null) && event.ctrlKey) {
val = word.val();
new_pos = pos;
del_length = 0;
if (pos > 0) {
new_pos--;
del_length++;
}
while (new_pos > 0 && val[new_pos] !== " " && val[new_pos] !== '\n') {
new_pos--;
del_length++;
}
word["delete"](new_pos, pos - new_pos);
r.left = new_pos;
r.right = new_pos;
writeRange(r);
} else {
if (pos > 0) {
word["delete"](pos - 1, 1);
r.left = pos - 1;
r.right = pos - 1;
writeRange(r);
}
}
}
event.preventDefault();
creator_token = false;
return false;
} else if ((event.keyCode != null) && event.keyCode === 46) {
if (diff > 0) {
word["delete"](pos, diff);
r.left = pos;
r.right = pos;
writeRange(r);
} else {
word["delete"](pos, 1);
r.left = pos;
r.right = pos;
writeRange(r);
}
event.preventDefault();
creator_token = false;
return false;
} else {
creator_token = false;
return true;
}
};
};
return YText;
})();
if (typeof window !== "undefined" && window !== null) {
if (window.Y != null) {
window.Y.Text = YText;
} else {
throw new Error("You must first import Y!");
}
}
if (typeof module !== "undefined" && module !== null) {
module.exports = YText;
}

View File

@ -1,6 +1,6 @@
var Engine, HistoryBuffer, adaptConnector, createY, text_ops_uninitialized;
var Engine, HistoryBuffer, adaptConnector, createY, structured_ops_uninitialized;
text_ops_uninitialized = require("./Operations/Text");
structured_ops_uninitialized = require("./Operations/Structured");
HistoryBuffer = require("./HistoryBuffer");
@ -21,7 +21,7 @@ createY = function(connector) {
};
}
HB = new HistoryBuffer(user_id);
ops_manager = text_ops_uninitialized(HB, this.constructor);
ops_manager = structured_ops_uninitialized(HB, this.constructor);
ops = ops_manager.operations;
engine = new Engine(HB, ops);
adaptConnector(connector, engine, HB, ops_manager.execution_listener);
@ -29,7 +29,7 @@ createY = function(connector) {
ops.Operation.prototype.operations = ops;
ops.Operation.prototype.engine = engine;
ops.Operation.prototype.connector = connector;
ops.Operation.prototype.custom_ops = this.constructor;
ops.Operation.prototype.custom_types = this.constructor;
ct = new createY.Object();
model = new ops.MapManager(ct, HB.getReservedUniqueIdentifier()).execute();
ct._setModel(model);
@ -43,3 +43,5 @@ if (typeof window !== "undefined" && window !== null) {
}
createY.Object = require("./Types/Object");
createY.Text = require("./Types/Text");

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -29,7 +29,7 @@ files =
test : ['./test/Json_test.coffee', './test/Text_test.coffee']
gulp : ['./gulpfile.coffee']
examples : ['./examples/**/*.js']
other: ['./lib/**/*']
other: ['./lib/**/*', './test/*']
files.all = []
for name,file_list of files

View File

@ -22,7 +22,9 @@ module.exports = ()->
# @param {Object} uid A unique identifier.
# If uid is undefined, a new uid will be created before at the end of the execution sequence
#
constructor: (uid)->
constructor: (custom_type, uid)->
if custom_type?
@custom_type = custom_type
@is_deleted = false
@garbage_collected = false
@event_listeners = [] # TODO: rename to observers or sth like that
@ -221,9 +223,9 @@ module.exports = ()->
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
# @param {Object} deletes UID or reference of the operation that this to be deleted.
#
constructor: (uid, deletes)->
constructor: (custom_type, uid, deletes)->
@saveOperation 'deletes', deletes
super uid
super custom_type, uid
type: "Delete"
@ -260,7 +262,7 @@ module.exports = ()->
'uid' : uid
'deletes': deletes_uid
} = o
new this(uid, deletes_uid)
new this(null, uid, deletes_uid)
#
# @nodoc
@ -279,7 +281,7 @@ module.exports = ()->
# @param {Operation} prev_cl The predecessor of this operation in the complete-list (cl)
# @param {Operation} next_cl The successor of this operation in the complete-list (cl)
#
constructor: (content, uid, prev_cl, next_cl, origin, parent)->
constructor: (custom_type, content, uid, prev_cl, next_cl, origin, parent)->
# see encode to see, why we are doing it this way
if content is undefined
# nop
@ -294,7 +296,7 @@ module.exports = ()->
@saveOperation 'origin', origin
else
@saveOperation 'origin', prev_cl
super uid
super custom_type, uid
type: "Insert"
@ -436,19 +438,24 @@ module.exports = ()->
@
callOperationSpecificInsertEvents: ()->
getContentType = (content)->
if content instanceof ops.Operation
content.getCustomType()
else
content
@parent?.callEvent [
type: "insert"
position: @getPosition()
object: @parent
object: @parent.getCustomType()
changedBy: @uid.creator
value: @content
value: getContentType @content
]
callOperationSpecificDeleteEvents: (o)->
@parent.callEvent [
type: "delete"
position: @getPosition()
object: @parent # TODO: You can combine getPosition + getParent in a more efficient manner! (only left Delimiter will hold @parent)
object: @parent.getCustomType() # TODO: You can combine getPosition + getParent in a more efficient manner! (only left Delimiter will hold @parent)
length: 1
changedBy: o.uid.creator
]
@ -503,7 +510,7 @@ module.exports = ()->
} = json
if typeof content is "string"
content = JSON.parse(content)
new this content, uid, prev, next, origin, parent
new this null, content, uid, prev, next, origin, parent
@ -517,8 +524,8 @@ module.exports = ()->
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
# @param {Object} content
#
constructor: (uid, @content)->
super uid
constructor: (custom_type, uid, @content)->
super custom_type, uid
type: "ImmutableObject"
@ -544,7 +551,7 @@ module.exports = ()->
'uid' : uid
'content' : content
} = json
new this(uid, content)
new this(null, uid, content)
#
# @nodoc
@ -562,7 +569,7 @@ module.exports = ()->
@saveOperation 'prev_cl', prev_cl
@saveOperation 'next_cl', next_cl
@saveOperation 'origin', prev_cl
super {noOperation: true}
super null, {noOperation: true}
type: "Delimiter"

View File

@ -14,10 +14,8 @@ module.exports = ()->
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
#
constructor: (custom_type, uid)->
if custom_type?
@custom_type = custom_type
@_map = {}
super uid
super custom_type, uid
type: "MapManager"
@ -39,8 +37,8 @@ module.exports = ()->
#
val: (name, content)->
if arguments.length > 1
if content? and content._model? and content._model instanceof ops.Operation
rep = content._model
if content? and content._getModel?
rep = content._getModel(@custom_types, @operations)
else
rep = content
@retrieveSub(name).replace rep
@ -59,11 +57,7 @@ module.exports = ()->
result = {}
for name,o of @_map
if not o.isContentDeleted()
res = prop.val()
if res instanceof ops.Operation
result[name] = res.getCustomType()
else
result[name] = res
result[name] = o.val()
result
delete: (name)->
@ -79,7 +73,7 @@ module.exports = ()->
noOperation: true
sub: property_name
alt: @
rm = new ops.ReplaceManager event_properties, event_this, rm_uid # this operation shall not be saved in the HB
rm = new ops.ReplaceManager null, event_properties, event_this, rm_uid # this operation shall not be saved in the HB
@_map[property_name] = rm
rm.setParent @, property_name
rm.execute()
@ -119,13 +113,13 @@ module.exports = ()->
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
# @param {Delimiter} beginning Reference or Object.
# @param {Delimiter} end Reference or Object.
constructor: (uid)->
constructor: (custom_type, uid)->
@beginning = new ops.Delimiter undefined, undefined
@end = new ops.Delimiter @beginning, undefined
@beginning.next_cl = @end
@beginning.execute()
@end.execute()
super uid
super custom_type, uid
type: "ListManager"
@ -238,27 +232,18 @@ module.exports = ()->
push: (content)->
@insertAfter @end.prev_cl, content
insertAfter: (left, content, options)->
createContent = (content, options)->
if content? and content.constructor?
type = ops[content.constructor.name]
if type? and type.create?
type.create content, options
else
throw new Error "The #{content.constructor.name}-type is not (yet) supported in Y."
else
content
insertAfter: (left, content)->
right = left.next_cl
while right.isDeleted()
right = right.next_cl # find the first character to the right, that is not deleted. In the case that position is 0, its the Delimiter.
left = right.prev_cl
# TODO: always expect an array as content. Then you can combine this with the other option (else)
if content instanceof ops.Operation
(new ops.Insert content, undefined, left, right).execute()
(new ops.Insert null, content, undefined, left, right).execute()
else
for c in content
tmp = (new ops.Insert createContent(c, options), undefined, left, right).execute()
tmp = (new ops.Insert null, c, undefined, left, right).execute()
left = tmp
@
@ -267,11 +252,11 @@ module.exports = ()->
#
# @return {ListManager Type} This String object.
#
insert: (position, content, options)->
insert: (position, content)->
ith = @getOperationByPosition position
# the (i-1)th character. e.g. "abc" the 1th character is "a"
# the 0th character is the left Delimiter
@insertAfter ith, [content], options
@insertAfter ith, [content]
#
# Deletes a part of the word.
@ -285,7 +270,7 @@ module.exports = ()->
for i in [0...length]
if o instanceof ops.Delimiter
break
d = (new ops.Delete undefined, o).execute()
d = (new ops.Delete null, undefined, o).execute()
o = o.next_cl
while (not (o instanceof ops.Delimiter)) and o.isDeleted()
o = o.next_cl
@ -301,26 +286,18 @@ module.exports = ()->
'type': @type
'uid' : @getUid()
}
if @custom_type.constructor is String
json.custom_type = @custom_type
else
json.custom_type = @custom_type._name
json
ops.ListManager.parse = (json)->
{
'uid' : uid
'custom_type': custom_type
} = json
new this(uid)
ops.Array = ()->
ops.Array.create = (content, mutable)->
if (mutable is "mutable")
list = new ops.ListManager().execute()
ith = list.getOperationByPosition 0
list.insertAfter ith, content
list
else if (not mutable?) or (mutable is "immutable")
content
else
throw new Error "Specify either \"mutable\" or \"immutable\"!!"
new this(custom_type, uid)
#
# @nodoc
@ -338,10 +315,10 @@ module.exports = ()->
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
# @param {Delimiter} beginning Reference or Object.
# @param {Delimiter} end Reference or Object.
constructor: (@event_properties, @event_this, uid, beginning, end)->
constructor: (custom_type, @event_properties, @event_this, uid, beginning, end)->
if not @event_properties['object']?
@event_properties['object'] = @event_this
super uid, beginning, end
super custom_type, uid, beginning, end
type: "ReplaceManager"
@ -378,7 +355,7 @@ module.exports = ()->
#
replace: (content, replaceable_uid)->
o = @getLastOperation()
relp = (new ops.Replaceable content, @, replaceable_uid, o, o.next_cl).execute()
relp = (new ops.Replaceable null, content, @, replaceable_uid, o, o.next_cl).execute()
# TODO: delete repl (for debugging)
undefined
@ -386,7 +363,7 @@ module.exports = ()->
@getLastOperation().isDeleted()
deleteContent: ()->
(new ops.Delete undefined, @getLastOperation().uid).execute()
(new ops.Delete null, undefined, @getLastOperation().uid).execute()
undefined
#
@ -424,9 +401,9 @@ module.exports = ()->
# @param {ReplaceManager} parent Used to replace this Replaceable with another one.
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
#
constructor: (content, parent, uid, prev, next, origin, is_deleted)->
constructor: (custom_type, content, parent, uid, prev, next, origin, is_deleted)->
@saveOperation 'parent', parent
super content, uid, prev, next, origin # Parent is already saved by Replaceable
super custom_type, content, uid, prev, next, origin # Parent is already saved by Replaceable
@is_deleted = is_deleted
type: "Replaceable"
@ -525,8 +502,9 @@ module.exports = ()->
'next': next
'origin' : origin
'is_deleted': is_deleted
'custom_type' : custom_type
} = json
new this(content, parent, uid, prev, next, origin, is_deleted)
new this(custom_type, content, parent, uid, prev, next, origin, is_deleted)
basic_ops

View File

@ -1,308 +0,0 @@
structured_ops_uninitialized = require "./Structured"
module.exports = ()->
structured_ops = structured_ops_uninitialized()
ops = structured_ops.operations
#
# Handles a String-like data structures with support for insert/delete at a word-position.
# @note Currently, only Text is supported!
#
class ops.String extends ops.ListManager
#
# @private
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
#
constructor: (uid)->
@textfields = []
super uid
#
# Identifies this class.
# Use it to check whether this is a word-type or something else.
#
# @example
# var x = y.val('unknown')
# if (x.type === "String") {
# console.log JSON.stringify(x.toJson())
# }
#
type: "String"
#
# Get the String-representation of this word.
# @return {String} The String-representation of this object.
#
val: ()->
@fold "", (left, o)->
left + o.val()
#
# Same as String.val
# @see String.val
#
toString: ()->
@val()
#
# Inserts a string into the word.
#
# @return {ListManager Type} This String object.
#
insert: (position, content, options)->
ith = @getOperationByPosition position
# the (i-1)th character. e.g. "abc" the 1th character is "a"
# the 0th character is the left Delimiter
@insertAfter ith, content, options
#
# Bind this String to a textfield or input field.
#
# @example
# var textbox = document.getElementById("textfield");
# y.bind(textbox);
#
bind: (textfield, dom_root)->
dom_root ?= window
if (not dom_root.getSelection?)
dom_root = window
# don't duplicate!
for t in @textfields
if t is textfield
return
creator_token = false;
word = @
textfield.value = @val()
@textfields.push textfield
if textfield.selectionStart? and textfield.setSelectionRange?
createRange = (fix)->
left = textfield.selectionStart
right = textfield.selectionEnd
if fix?
left = fix left
right = fix right
{
left: left
right: right
}
writeRange = (range)->
writeContent word.val()
textfield.setSelectionRange range.left, range.right
writeContent = (content)->
textfield.value = content
else
createRange = (fix)->
range = {}
s = dom_root.getSelection()
clength = textfield.textContent.length
range.left = Math.min s.anchorOffset, clength
range.right = Math.min s.focusOffset, clength
if fix?
range.left = fix range.left
range.right = fix range.right
edited_element = s.focusNode
if edited_element is textfield or edited_element is textfield.childNodes[0]
range.isReal = true
else
range.isReal = false
range
writeRange = (range)->
writeContent word.val()
textnode = textfield.childNodes[0]
if range.isReal and textnode?
if range.left < 0
range.left = 0
range.right = Math.max range.left, range.right
if range.right > textnode.length
range.right = textnode.length
range.left = Math.min range.left, range.right
r = document.createRange()
r.setStart(textnode, range.left)
r.setEnd(textnode, range.right)
s = window.getSelection()
s.removeAllRanges()
s.addRange(r)
writeContent = (content)->
content_array = content.replace(new RegExp("\n",'g')," ").split(" ")
textfield.innerText = ""
for c, i in content_array
textfield.innerText += c
if i isnt content_array.length-1
textfield.innerHTML += '&nbsp;'
writeContent this.val()
@observe (events)->
for event in events
if not creator_token
if event.type is "insert"
o_pos = event.position
fix = (cursor)->
if cursor <= o_pos
cursor
else
cursor += 1
cursor
r = createRange fix
writeRange r
else if event.type is "delete"
o_pos = event.position
fix = (cursor)->
if cursor < o_pos
cursor
else
cursor -= 1
cursor
r = createRange fix
writeRange r
# consume all text-insert changes.
textfield.onkeypress = (event)->
if word.is_deleted
# if word is deleted, do not do anything ever again
textfield.onkeypress = null
return true
creator_token = true
char = null
if event.keyCode is 13
char = '\n'
else if event.key?
if event.charCode is 32
char = " "
else
char = event.key
else
char = window.String.fromCharCode event.keyCode
if char.length > 1
return true
else if char.length > 0
r = createRange()
pos = Math.min r.left, r.right
diff = Math.abs(r.right - r.left)
word.delete pos, diff
word.insert pos, char
r.left = pos + char.length
r.right = r.left
writeRange r
event.preventDefault()
creator_token = false
false
textfield.onpaste = (event)->
if word.is_deleted
# if word is deleted, do not do anything ever again
textfield.onpaste = null
return true
event.preventDefault()
textfield.oncut = (event)->
if word.is_deleted
# if word is deleted, do not do anything ever again
textfield.oncut = null
return true
event.preventDefault()
#
# consume deletes. Note that
# chrome: won't consume deletions on keypress event.
# keyCode is deprecated. BUT: I don't see another way.
# since event.key is not implemented in the current version of chrome.
# Every browser supports keyCode. Let's stick with it for now..
#
textfield.onkeydown = (event)->
creator_token = true
if word.is_deleted
# if word is deleted, do not do anything ever again
textfield.onkeydown = null
return true
r = createRange()
pos = Math.min(r.left, r.right, word.val().length)
diff = Math.abs(r.left - r.right)
if event.keyCode? and event.keyCode is 8 # Backspace
if diff > 0
word.delete pos, diff
r.left = pos
r.right = pos
writeRange r
else
if event.ctrlKey? and event.ctrlKey
val = word.val()
new_pos = pos
del_length = 0
if pos > 0
new_pos--
del_length++
while new_pos > 0 and val[new_pos] isnt " " and val[new_pos] isnt '\n'
new_pos--
del_length++
word.delete new_pos, (pos-new_pos)
r.left = new_pos
r.right = new_pos
writeRange r
else
if pos > 0
word.delete (pos-1), 1
r.left = pos-1
r.right = pos-1
writeRange r
event.preventDefault()
creator_token = false
return false
else if event.keyCode? and event.keyCode is 46 # Delete
if diff > 0
word.delete pos, diff
r.left = pos
r.right = pos
writeRange r
else
word.delete pos, 1
r.left = pos
r.right = pos
writeRange r
event.preventDefault()
creator_token = false
return false
else
creator_token = false
true
#
# @private
# Encode this operation in such a way that it can be parsed by remote peers.
#
_encode: ()->
json = {
'type': @type
'uid' : @getUid()
}
json
ops.String.parse = (json)->
{
'uid' : uid
} = json
new this(uid)
ops.String.create = (content, mutable)->
if (mutable is "mutable")
word = new ops.String().execute()
word.insert 0, content
word
else if (not mutable?) or (mutable is "immutable")
content
else
throw new Error "Specify either \"mutable\" or \"immutable\"!!"
structured_ops

0
lib/Types/List.coffee Normal file
View File

View File

@ -55,6 +55,9 @@ class YObject
res[n] = v
res
delete: (name)->
@_model.delete(name)
if window?
if window.Y?
window.Y.Object = YObject

306
lib/Types/Text.coffee Normal file
View File

@ -0,0 +1,306 @@
#
# Handles a String-like data structures with support for insert/delete at a word-position.
# @note Currently, only Text is supported!
#
class YText
#
# @private
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
#
constructor: (text)->
@textfields = []
if not text?
@_text = ""
else if text.constructor is String
@_text = text
else
throw new Error "Y.Text expects a String as a constructor"
_name: "Text"
_getModel: (types, ops)->
if not @_model?
@_model = new ops.ListManager(@).execute()
@insert 0, @_text
delete @_text
@_model
_setModel: (@_model)->
delete @_text
#
# Get the String-representation of this word.
# @return {String} The String-representation of this object.
#
val: ()->
@_model.fold "", (left, o)->
left + o.val()
observe: ()->
@_model.observe.apply @_model, arguments
unobserve: ()->
@_model.unobserve.apply @_model, arguments
#
# Same as String.val
# @see String.val
#
toString: ()->
@val()
#
# Inserts a string into the word.
#
# @return {ListManager Type} This String object.
#
insert: (position, content)->
if content.constructor isnt String
throw new Error "Y.String.insert expects a String as the second parameter!"
if typeof position isnt "number"
throw new Error "Y.String.insert expects a Number as the second parameter!"
if content.length > 0
ith = @_model.getOperationByPosition position
# the (i-1)th character. e.g. "abc" the 1th character is "a"
# the 0th character is the left Delimiter
@_model.insertAfter ith, content
delete: (position, length)->
@_model.delete position, length
#
# Bind this String to a textfield or input field.
#
# @example
# var textbox = document.getElementById("textfield");
# y.bind(textbox);
#
bind: (textfield, dom_root)->
dom_root ?= window
if (not dom_root.getSelection?)
dom_root = window
# don't duplicate!
for t in @textfields
if t is textfield
return
creator_token = false;
word = @
textfield.value = @val()
@textfields.push textfield
if textfield.selectionStart? and textfield.setSelectionRange?
createRange = (fix)->
left = textfield.selectionStart
right = textfield.selectionEnd
if fix?
left = fix left
right = fix right
{
left: left
right: right
}
writeRange = (range)->
writeContent word.val()
textfield.setSelectionRange range.left, range.right
writeContent = (content)->
textfield.value = content
else
createRange = (fix)->
range = {}
s = dom_root.getSelection()
clength = textfield.textContent.length
range.left = Math.min s.anchorOffset, clength
range.right = Math.min s.focusOffset, clength
if fix?
range.left = fix range.left
range.right = fix range.right
edited_element = s.focusNode
if edited_element is textfield or edited_element is textfield.childNodes[0]
range.isReal = true
else
range.isReal = false
range
writeRange = (range)->
writeContent word.val()
textnode = textfield.childNodes[0]
if range.isReal and textnode?
if range.left < 0
range.left = 0
range.right = Math.max range.left, range.right
if range.right > textnode.length
range.right = textnode.length
range.left = Math.min range.left, range.right
r = document.createRange()
r.setStart(textnode, range.left)
r.setEnd(textnode, range.right)
s = window.getSelection()
s.removeAllRanges()
s.addRange(r)
writeContent = (content)->
content_array = content.replace(new RegExp("\n",'g')," ").split(" ")
textfield.innerText = ""
for c, i in content_array
textfield.innerText += c
if i isnt content_array.length-1
textfield.innerHTML += '&nbsp;'
writeContent this.val()
@observe (events)->
for event in events
if not creator_token
if event.type is "insert"
o_pos = event.position
fix = (cursor)->
if cursor <= o_pos
cursor
else
cursor += 1
cursor
r = createRange fix
writeRange r
else if event.type is "delete"
o_pos = event.position
fix = (cursor)->
if cursor < o_pos
cursor
else
cursor -= 1
cursor
r = createRange fix
writeRange r
# consume all text-insert changes.
textfield.onkeypress = (event)->
if word.is_deleted
# if word is deleted, do not do anything ever again
textfield.onkeypress = null
return true
creator_token = true
char = null
if event.keyCode is 13
char = '\n'
else if event.key?
if event.charCode is 32
char = " "
else
char = event.key
else
char = window.String.fromCharCode event.keyCode
if char.length > 1
return true
else if char.length > 0
r = createRange()
pos = Math.min r.left, r.right
diff = Math.abs(r.right - r.left)
word.delete pos, diff
word.insert pos, char
r.left = pos + char.length
r.right = r.left
writeRange r
event.preventDefault()
creator_token = false
false
textfield.onpaste = (event)->
if word.is_deleted
# if word is deleted, do not do anything ever again
textfield.onpaste = null
return true
event.preventDefault()
textfield.oncut = (event)->
if word.is_deleted
# if word is deleted, do not do anything ever again
textfield.oncut = null
return true
event.preventDefault()
#
# consume deletes. Note that
# chrome: won't consume deletions on keypress event.
# keyCode is deprecated. BUT: I don't see another way.
# since event.key is not implemented in the current version of chrome.
# Every browser supports keyCode. Let's stick with it for now..
#
textfield.onkeydown = (event)->
creator_token = true
if word.is_deleted
# if word is deleted, do not do anything ever again
textfield.onkeydown = null
return true
r = createRange()
pos = Math.min(r.left, r.right, word.val().length)
diff = Math.abs(r.left - r.right)
if event.keyCode? and event.keyCode is 8 # Backspace
if diff > 0
word.delete pos, diff
r.left = pos
r.right = pos
writeRange r
else
if event.ctrlKey? and event.ctrlKey
val = word.val()
new_pos = pos
del_length = 0
if pos > 0
new_pos--
del_length++
while new_pos > 0 and val[new_pos] isnt " " and val[new_pos] isnt '\n'
new_pos--
del_length++
word.delete new_pos, (pos-new_pos)
r.left = new_pos
r.right = new_pos
writeRange r
else
if pos > 0
word.delete (pos-1), 1
r.left = pos-1
r.right = pos-1
writeRange r
event.preventDefault()
creator_token = false
return false
else if event.keyCode? and event.keyCode is 46 # Delete
if diff > 0
word.delete pos, diff
r.left = pos
r.right = pos
writeRange r
else
word.delete pos, 1
r.left = pos
r.right = pos
writeRange r
event.preventDefault()
creator_token = false
return false
else
creator_token = false
true
if window?
if window.Y?
window.Y.Text = YText
else
throw new Error "You must first import Y!"
if module?
module.exports = YText

View File

@ -1,5 +1,5 @@
text_ops_uninitialized = require "./Operations/Text"
structured_ops_uninitialized = require "./Operations/Structured"
HistoryBuffer = require "./HistoryBuffer"
Engine = require "./Engine"
@ -15,7 +15,7 @@ createY = (connector)->
user_id = id
HB.resetUserId id
HB = new HistoryBuffer user_id
ops_manager = text_ops_uninitialized HB, this.constructor
ops_manager = structured_ops_uninitialized HB, this.constructor
ops = ops_manager.operations
engine = new Engine HB, ops
@ -25,7 +25,7 @@ createY = (connector)->
ops.Operation.prototype.operations = ops
ops.Operation.prototype.engine = engine
ops.Operation.prototype.connector = connector
ops.Operation.prototype.custom_ops = this.constructor
ops.Operation.prototype.custom_types = this.constructor
ct = new createY.Object()
model = new ops.MapManager(ct, HB.getReservedUniqueIdentifier()).execute()
@ -37,3 +37,4 @@ if window?
window.Y = createY
createY.Object = require "./Types/Object"
createY.Text = require "./Types/Text"

View File

@ -11,17 +11,16 @@ Connector = require "../../y-test/lib/y-test.coffee"
Y = require "../lib/y.coffee"
compare = (o1, o2)->
if o1.type? and o1.type isnt o2.type
if o1._name? and o1._name isnt o2._name
throw new Error "different types"
else if o1.type is "Object"
else if o1._name is "Object"
for name, val of o1.val()
compare(val, o2.val(name))
else if o1.type?
else if o1._name?
compare(o1.val(), o2.val())
else if o1 isnt o2
throw new Error "different values"
Test = require "./TestSuite"
class JsonTest extends Test
@ -38,17 +37,17 @@ class JsonTest extends Test
root
else # take child
elems = null
if root.type is "Object"
if root._name is "Object"
elems =
for oname,val of root.val()
val
else if root.type is "Array"
else if root._name is "Array"
elems = root.val()
else
return root
elems = elems.filter (elem)->
elem? and ((elem.type is "Array") or (elem.type is "Object"))
elem? and ((elem._name is "Array") or (elem._name is "Object"))
if elems.length is 0
root
else
@ -58,9 +57,26 @@ class JsonTest extends Test
getGeneratingFunctions: (user_num)->
types = @users[user_num]._model.operations
super(user_num).concat [
f : (y)=> # Delete Object Property
list = for name, o of y.val()
name
if list.length > 0
key = list[_.random(0,list.length-1)]
y.delete(key)
types: [Y.Object]
,
f : (y)=> # SET Object Property
y.val(@getRandomKey(), new Y.Object(@getRandomObject()))
types: [Y.Object]
,
f : (y)=> # SET PROPERTY TEXT
y.val(@getRandomKey(), new Y.Text(@getRandomText()))
types: [Y.Object]
]
###
f : (y)=> # SET PROPERTY
l = y.val().length
y.val(_.random(0, l-1), @getRandomText(), 'immutable')
y.val(_.random(0, l-1), @getRandomText())
null
types : [types.Array]
, f : (y)=> # Delete Array Element
@ -71,28 +87,14 @@ class JsonTest extends Test
types: [types.Array]
, f : (y)=> # insert TEXT mutable
l = y.val().length
y.val(_.random(0, l-1), @getRamdomObject())
y.val(_.random(0, l-1), new Y.Object(@getRamdomObject()))
types: [types.Array]
, f : (y)=> # insert string
l = y.val().length
y.val(_.random(0, l-1), @getRandomText(), 'immutable')
y.val(_.random(0, l-1), @getRandomText())
null
types : [types.Array]
, f : (y)=> # Delete Object Property
list = for name, o of y.val()
name
if list.length > 0
key = list[_random(0,list.length-1)]
y.delete(key)
types: [types.Object]
, f : (y)=> # SET Object Property
y.val(@getRandomKey(), @getRandomObject())
types: [types.Object]
,
f : (y)=> # SET PROPERTY TEXT
y.val(@getRandomKey(), @getRandomText(), 'mutable')
types: [types.Object]
]
types : [Y.Array]
###
describe "JsonFramework", ->
@timeout 500000
@ -117,16 +119,16 @@ describe "JsonFramework", ->
@yTest.run()
u1 = test.users[0]
u2 = @yTest.users[1]
ops1 = u1.HB._encode()
ops2 = u2.HB._encode()
u1.engine.applyOp ops2, true
u2.engine.applyOp ops1, true
ops1 = u1._model.HB._encode()
ops2 = u2._model.HB._encode()
u1._model.engine.applyOp ops2, true
u2._model.engine.applyOp ops1, true
expect(compare(u1, u2)).to.not.be.undefined
it "can handle creaton of complex json (1)", ->
@yTest.users[0].val('a', 'q', "mutable")
@yTest.users[1].val('a', 't', "mutable")
@yTest.users[0].val('a', new Y.Text('q'))
@yTest.users[1].val('a', new Y.Text('t'))
@yTest.compareAll()
q = @yTest.users[2].val('a')
q.insert(0,'A')
@ -135,7 +137,7 @@ describe "JsonFramework", ->
it "can handle creaton of complex json (2)", ->
@yTest.getSomeUser().val('x', new Y.Object({'a':'b'}))
@yTest.getSomeUser().val('a', new Y.Object({'a':{q:"dtrndtrtdrntdrnrtdnrtdnrtdnrtdnrdnrdt"}}))
@yTest.getSomeUser().val('a', new Y.Object({'a':{q: new Y.Text("dtrndtrtdrntdrnrtdnrtdnrtdnrtdnrdnrdt")}}))
@yTest.getSomeUser().val('b', new Y.Object({'a':{}}))
@yTest.getSomeUser().val('c', new Y.Object({'a':'c'}))
@yTest.getSomeUser().val('c', new Y.Object({'a':'b'}))
@ -143,7 +145,7 @@ describe "JsonFramework", ->
q = @yTest.getSomeUser().val("a").val("a").val("q")
q.insert(0,'A')
@yTest.compareAll()
expect(@yTest.getSomeUser().val("a").val("a").val("q")).to.equal("Adtrndtrtdrntdrnrtdnrtdnrtdnrtdnrdnrdt")
expect(@yTest.getSomeUser().val("a").val("a").val("q").val()).to.equal("Adtrndtrtdrntdrnrtdnrtdnrtdnrtdnrdnrdt")
it "can handle creaton of complex json (3)", ->
@yTest.users[0].val('l', [1,2,3], "mutable")
@ -156,9 +158,9 @@ describe "JsonFramework", ->
@yTest.compareAll()
it "handles immutables and primitive data types", ->
@yTest.getSomeUser().val('string', "text", "immutable")
@yTest.getSomeUser().val('number', 4, "immutable")
@yTest.getSomeUser().val('object', {q:"rr"}, "immutable")
@yTest.getSomeUser().val('string', new Y.Text("text"))
@yTest.getSomeUser().val('number', 4)
@yTest.getSomeUser().val('object', {q:"rr"})
@yTest.getSomeUser().val('null', null)
@yTest.compareAll()
expect(@yTest.getSomeUser().val('string')).to.equal "text"
@ -167,9 +169,9 @@ describe "JsonFramework", ->
expect(@yTest.getSomeUser().val('null') is null).to.be.ok
it "handles immutables and primitive data types (2)", ->
@yTest.users[0].val('string', "text", "immutable")
@yTest.users[1].val('number', 4, "immutable")
@yTest.users[2].val('object', {q:"rr"})
@yTest.users[0].val('string', "text")
@yTest.users[1].val('number', 4)
@yTest.users[2].val('object', new Y.Object({q:"rr"}))
@yTest.users[0].val('null', null)
@yTest.compareAll()
expect(@yTest.getSomeUser().val('string')).to.equal "text"
@ -190,7 +192,7 @@ describe "JsonFramework", ->
expect(change.name).to.equal("newStuff")
last_task = "observer1"
u.observe observer1
u.val("newStuff","someStuff","mutable")
u.val("newStuff",new Y.Text("someStuff"))
expect(last_task).to.equal("observer1")
u.unobserve observer1
@ -210,7 +212,7 @@ describe "JsonFramework", ->
u.unobserve observer2
it "Observers work on JSON Types (update type observers, local and foreign)", ->
u = @yTest.users[0].val("newStuff","oldStuff","mutable").val("moreStuff","moreOldStuff","mutable")
u = @yTest.users[0].val("newStuff", new Y.Text("oldStuff")).val("moreStuff",new Y.Text("moreOldStuff"))
@yTest.flushAll()
last_task = null
observer1 = (changes)->
@ -245,7 +247,7 @@ describe "JsonFramework", ->
it "Observers work on JSON Types (delete type observers, local and foreign)", ->
u = @yTest.users[0].val("newStuff","oldStuff","mutable").val("moreStuff","moreOldStuff","mutable")
u = @yTest.users[0].val("newStuff",new Y.Text("oldStuff")).val("moreStuff",new Y.Text("moreOldStuff"))
@yTest.flushAll()
last_task = null
observer1 = (changes)->

View File

@ -7,6 +7,7 @@ _ = require("underscore")
chai.use(sinonChai)
Connector = require "../../y-test/lib/y-test.coffee"
Y = require "../lib/y"
module.exports = class Test
constructor: (@name_suffix = "")->
@ -76,7 +77,7 @@ module.exports = class Test
pos = _.random 0, (y.val().length-1)
y.insert pos, @getRandomText()
null
types: [types.String]
types: [Y.Text]
,
f : (y)-> # DELETE TEXT
if y.val().length > 0
@ -84,13 +85,24 @@ module.exports = class Test
length = _.random 0, (y.val().length - pos)
ops1 = y.delete pos, length
undefined
types : [types.String]
types : [Y.Text]
]
getRandomRoot: (user_num)->
throw new Error "overwrite me!"
throw new Error "implement me!"
getContent: (user_num)->
throw new Error "overwrite me!"
throw new Error "implement me!"
compare: (o1, o2)->
if o1._name? and o1._name isnt o2._name
throw new Error "different types"
else if o1._name is "Object"
for name, val of o1.val()
@compare(val, o2.val(name))
else if o1._name?
@compare(o1.val(), o2.val())
else if o1 isnt o2
throw new Error "different values"
generateRandomOp: (user_num)=>
y = @getRandomRoot(user_num)
@ -171,7 +183,7 @@ module.exports = class Test
ops = printOpsInExecutionOrder i+1, i
console.log ""
expect(@getContent(i)).to.deep.equal(@getContent(i+1))
expect(@compare(@users[i], @users[i+1])).to.not.be.undefined
run: ()->
if @debug
@ -182,7 +194,7 @@ module.exports = class Test
@doSomething()
@flushAll(false)
for u in @users
u.HB.emptyGarbage()
u._model.HB.emptyGarbage()
for i in [1..Math.floor(@doSomething_amount/2)]
@doSomething()
@ -195,12 +207,11 @@ module.exports = class Test
# in case of JsonFramework, every user will create its JSON first! therefore, the testusers id must be small than all the others (see InsertType)
@users[@users.length] = @makeNewUser (-1) # this does not want to join with anymody
@users[@users.length-1].HB.renewStateVector @users[0].HB.getOperationCounter()
@users[@users.length-1].engine.applyOps @users[0].HB._encode()
@users[@users.length-1]._model.HB.renewStateVector @users[0]._model.HB.getOperationCounter()
@users[@users.length-1]._model.engine.applyOps @users[0]._model.HB._encode()
#if @getContent(@users.length-1) isnt @getContent(0)
# console.log "testHBencoding:"
# console.log "Unprocessed ops first: #{@users[0].engine.unprocessed_ops.length}"
# console.log "Unprocessed ops last: #{@users[@users.length-1].engine.unprocessed_ops.length}"
expect(@getContent(@users.length-1)).to.deep.equal(@getContent(0))
expect(@compare(@users[@users.length-1], @users[0])).to.not.be.undefined

View File

@ -20,7 +20,7 @@ class TextTest extends Test
new Y conn
initUsers: (u)->
u.val("TextTest","","mutable")
u.val("TextTest",new Y.Text())
getRandomRoot: (user_num)->
@users[user_num].val("TextTest")
@ -46,7 +46,7 @@ describe "TextFramework", ->
expect(u.val()).to.equal("bcxyz")
it "Observers work on shared Text (insert type observers, local and foreign)", ->
u = @yTest.users[0].val("TextTest","my awesome Text","mutable").val("TextTest")
u = @yTest.users[0].val("TextTest",new Y.Text("my awesome Text")).val("TextTest")
@yTest.flushAll()
last_task = null
observer1 = (changes)->
@ -80,7 +80,7 @@ describe "TextFramework", ->
u.unobserve observer2
it "Observers work on shared Text (delete type observers, local and foreign)", ->
u = @yTest.users[0].val("TextTest","my awesome Text","mutable").val("TextTest")
u = @yTest.users[0].val("TextTest",new Y.Text("my awesome Text")).val("TextTest")
@yTest.flushAll()
last_task = null
observer1 = (changes)->
@ -123,17 +123,17 @@ describe "TextFramework", ->
@yTest.run()
u1 = test.users[0]
u2 = @yTest.users[1]
ops1 = u1.HB._encode()
ops2 = u2.HB._encode()
u1.engine.applyOp ops2, true
u2.engine.applyOp ops1, true
ops1 = u1._model.HB._encode()
ops2 = u2._model.HB._encode()
u1._model.engine.applyOp ops2, true
u2._model.engine.applyOp ops1, true
compare = (o1, o2)->
if o1.type? and o1.type isnt o2.type
if o1._name? and o1._name isnt o2._name
throw new Error "different types"
else if o1.type is "Object"
else if o1._name is "Object"
for name, val of o1.val()
compare(val, o2.val(name))
else if o1.type?
else if o1._name?
compare(o1.val(), o2.val())
else if o1 isnt o2
throw new Error "different values"

File diff suppressed because one or more lines are too long

4
y.js

File diff suppressed because one or more lines are too long