Introduced a new model for custom collaborative types.
This commit is contained in:
+186
-234
File diff suppressed because one or more lines are too long
+186
-234
File diff suppressed because one or more lines are too long
+213
-253
File diff suppressed because one or more lines are too long
+203
-249
File diff suppressed because one or more lines are too long
+5
-5
@@ -11,18 +11,18 @@ class Engine
|
|||||||
|
|
||||||
#
|
#
|
||||||
# @param {HistoryBuffer} HB
|
# @param {HistoryBuffer} HB
|
||||||
# @param {Array} parser Defines how to parse encoded messages.
|
# @param {Object} types list of available types
|
||||||
#
|
#
|
||||||
constructor: (@HB, @parser)->
|
constructor: (@HB, @types)->
|
||||||
@unprocessed_ops = []
|
@unprocessed_ops = []
|
||||||
|
|
||||||
#
|
#
|
||||||
# Parses an operatio from the json format. It uses the specified parser in your OperationType module.
|
# Parses an operatio from the json format. It uses the specified parser in your OperationType module.
|
||||||
#
|
#
|
||||||
parseOperation: (json)->
|
parseOperation: (json)->
|
||||||
typeParser = @parser[json.type]
|
type = @types[json.type]
|
||||||
if typeParser?
|
if type?.parse?
|
||||||
typeParser json
|
type.parse json
|
||||||
else
|
else
|
||||||
throw new Error "You forgot to specify a parser for type #{json.type}. The message is #{JSON.stringify json}."
|
throw new Error "You forgot to specify a parser for type #{json.type}. The message is #{JSON.stringify json}."
|
||||||
|
|
||||||
|
|||||||
+16
-23
@@ -1,6 +1,6 @@
|
|||||||
module.exports = (HB)->
|
module.exports = (HB)->
|
||||||
# @see Engine.parse
|
# @see Engine.parse
|
||||||
parser = {}
|
types = {}
|
||||||
execution_listener = []
|
execution_listener = []
|
||||||
|
|
||||||
#
|
#
|
||||||
@@ -16,7 +16,7 @@ module.exports = (HB)->
|
|||||||
#
|
#
|
||||||
# Furthermore an encodable operation has a parser. We extend the parser object in order to parse encoded operations.
|
# Furthermore an encodable operation has a parser. We extend the parser object in order to parse encoded operations.
|
||||||
#
|
#
|
||||||
class Operation
|
class types.Operation
|
||||||
|
|
||||||
#
|
#
|
||||||
# @param {Object} uid A unique identifier.
|
# @param {Object} uid A unique identifier.
|
||||||
@@ -192,12 +192,11 @@ module.exports = (HB)->
|
|||||||
@unchecked = uninstantiated
|
@unchecked = uninstantiated
|
||||||
success
|
success
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# @nodoc
|
# @nodoc
|
||||||
# A simple Delete-type operation that deletes an operation.
|
# A simple Delete-type operation that deletes an operation.
|
||||||
#
|
#
|
||||||
class Delete extends Operation
|
class types.Delete extends types.Operation
|
||||||
|
|
||||||
#
|
#
|
||||||
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
|
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
|
||||||
@@ -237,12 +236,12 @@ module.exports = (HB)->
|
|||||||
#
|
#
|
||||||
# Define how to parse Delete operations.
|
# Define how to parse Delete operations.
|
||||||
#
|
#
|
||||||
parser['Delete'] = (o)->
|
types.Delete.parse = (o)->
|
||||||
{
|
{
|
||||||
'uid' : uid
|
'uid' : uid
|
||||||
'deletes': deletes_uid
|
'deletes': deletes_uid
|
||||||
} = o
|
} = o
|
||||||
new Delete uid, deletes_uid
|
new this(uid, deletes_uid)
|
||||||
|
|
||||||
#
|
#
|
||||||
# @nodoc
|
# @nodoc
|
||||||
@@ -254,7 +253,7 @@ module.exports = (HB)->
|
|||||||
# - The short-list (abbrev. sl) maintains only the operations that are not deleted
|
# - The short-list (abbrev. sl) maintains only the operations that are not deleted
|
||||||
# - The complete-list (abbrev. cl) maintains all operations
|
# - The complete-list (abbrev. cl) maintains all operations
|
||||||
#
|
#
|
||||||
class Insert extends Operation
|
class types.Insert extends types.Operation
|
||||||
|
|
||||||
#
|
#
|
||||||
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
|
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
|
||||||
@@ -422,7 +421,7 @@ module.exports = (HB)->
|
|||||||
position = 0
|
position = 0
|
||||||
prev = @prev_cl
|
prev = @prev_cl
|
||||||
while true
|
while true
|
||||||
if prev instanceof Delimiter
|
if prev instanceof types.Delimiter
|
||||||
break
|
break
|
||||||
if not prev.isDeleted()
|
if not prev.isDeleted()
|
||||||
position++
|
position++
|
||||||
@@ -433,7 +432,7 @@ module.exports = (HB)->
|
|||||||
# @nodoc
|
# @nodoc
|
||||||
# Defines an object that is cannot be changed. You can use this to set an immutable string, or a number.
|
# Defines an object that is cannot be changed. You can use this to set an immutable string, or a number.
|
||||||
#
|
#
|
||||||
class ImmutableObject extends Operation
|
class types.ImmutableObject extends types.Operation
|
||||||
|
|
||||||
#
|
#
|
||||||
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
|
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
|
||||||
@@ -455,18 +454,18 @@ module.exports = (HB)->
|
|||||||
#
|
#
|
||||||
_encode: ()->
|
_encode: ()->
|
||||||
json = {
|
json = {
|
||||||
'type': "ImmutableObject"
|
'type': @type
|
||||||
'uid' : @getUid()
|
'uid' : @getUid()
|
||||||
'content' : @content
|
'content' : @content
|
||||||
}
|
}
|
||||||
json
|
json
|
||||||
|
|
||||||
parser['ImmutableObject'] = (json)->
|
types.ImmutableObject.parse = (json)->
|
||||||
{
|
{
|
||||||
'uid' : uid
|
'uid' : uid
|
||||||
'content' : content
|
'content' : content
|
||||||
} = json
|
} = json
|
||||||
new ImmutableObject uid, content
|
new this(uid, content)
|
||||||
|
|
||||||
#
|
#
|
||||||
# @nodoc
|
# @nodoc
|
||||||
@@ -474,7 +473,7 @@ module.exports = (HB)->
|
|||||||
# This is necessary in order to have a beginning and an end even if the content
|
# This is necessary in order to have a beginning and an end even if the content
|
||||||
# of the Engine is empty.
|
# of the Engine is empty.
|
||||||
#
|
#
|
||||||
class Delimiter extends Operation
|
class types.Delimiter extends types.Operation
|
||||||
#
|
#
|
||||||
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
|
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
|
||||||
# @param {Operation} prev_cl The predecessor of this operation in the complete-list (cl)
|
# @param {Operation} prev_cl The predecessor of this operation in the complete-list (cl)
|
||||||
@@ -527,29 +526,23 @@ module.exports = (HB)->
|
|||||||
#
|
#
|
||||||
_encode: ()->
|
_encode: ()->
|
||||||
{
|
{
|
||||||
'type' : "Delimiter"
|
'type' : @type
|
||||||
'uid' : @getUid()
|
'uid' : @getUid()
|
||||||
'prev' : @prev_cl?.getUid()
|
'prev' : @prev_cl?.getUid()
|
||||||
'next' : @next_cl?.getUid()
|
'next' : @next_cl?.getUid()
|
||||||
}
|
}
|
||||||
|
|
||||||
parser['Delimiter'] = (json)->
|
types.Delimiter.parse = (json)->
|
||||||
{
|
{
|
||||||
'uid' : uid
|
'uid' : uid
|
||||||
'prev' : prev
|
'prev' : prev
|
||||||
'next' : next
|
'next' : next
|
||||||
} = json
|
} = json
|
||||||
new Delimiter uid, prev, next
|
new this(uid, prev, next)
|
||||||
|
|
||||||
# This is what this module exports after initializing it with the HistoryBuffer
|
# This is what this module exports after initializing it with the HistoryBuffer
|
||||||
{
|
{
|
||||||
'types' :
|
'types' : types
|
||||||
'Delete' : Delete
|
|
||||||
'Insert' : Insert
|
|
||||||
'Delimiter': Delimiter
|
|
||||||
'Operation': Operation
|
|
||||||
'ImmutableObject' : ImmutableObject
|
|
||||||
'parser' : parser
|
|
||||||
'execution_listener' : execution_listener
|
'execution_listener' : execution_listener
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+30
-151
@@ -3,100 +3,11 @@ text_types_uninitialized = require "./TextTypes"
|
|||||||
module.exports = (HB)->
|
module.exports = (HB)->
|
||||||
text_types = text_types_uninitialized HB
|
text_types = text_types_uninitialized HB
|
||||||
types = text_types.types
|
types = text_types.types
|
||||||
parser = text_types.parser
|
|
||||||
|
|
||||||
createJsonTypeWrapper = (_jsonType)->
|
|
||||||
|
|
||||||
#
|
|
||||||
# @note EXPERIMENTAL
|
|
||||||
#
|
|
||||||
# A JsonTypeWrapper was intended to be a convenient wrapper for the JsonType.
|
|
||||||
# But it can make things more difficult than they are.
|
|
||||||
# @see JsonType
|
|
||||||
#
|
|
||||||
# @example create a JsonTypeWrapper
|
|
||||||
# # You get a JsonTypeWrapper from a JsonType by calling
|
|
||||||
# w = yatta.value
|
|
||||||
#
|
|
||||||
# It creates Javascripts -getter and -setter methods for each property that JsonType maintains.
|
|
||||||
# @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
|
|
||||||
#
|
|
||||||
# @example Getter Example
|
|
||||||
# # you can access the x property of yatta by calling
|
|
||||||
# w.x
|
|
||||||
# # instead of
|
|
||||||
# yatta.val('x')
|
|
||||||
#
|
|
||||||
# @note You can only overwrite existing values! Setting a new property won't have any effect!
|
|
||||||
#
|
|
||||||
# @example Setter Example
|
|
||||||
# # you can set an existing x property of yatta by calling
|
|
||||||
# w.x = "text"
|
|
||||||
# # instead of
|
|
||||||
# yatta.val('x', "text")
|
|
||||||
#
|
|
||||||
# In order to set a new property you have to overwrite an existing property.
|
|
||||||
# Therefore the JsonTypeWrapper supports a special feature that should make things more convenient
|
|
||||||
# (we can argue about that, use the JsonType if you don't like it ;).
|
|
||||||
# If you overwrite an object property of the JsonTypeWrapper with a new object, it will result in a merged version of the objects.
|
|
||||||
# Let `yatta.value.p` the property that is to be overwritten and o the new value. E.g. `yatta.value.p = o`
|
|
||||||
# * The result has all properties of o
|
|
||||||
# * The result has all properties of w.p if they don't occur under the same property-name in o.
|
|
||||||
#
|
|
||||||
# @example Conflict Example
|
|
||||||
# yatta.value = {a : "string"}
|
|
||||||
# w = yatta.value
|
|
||||||
# console.log(w) # {a : "string"}
|
|
||||||
# w.a = {a : {b : "string"}}
|
|
||||||
# console.log(w) # {a : {b : "String"}}
|
|
||||||
# w.a = {a : {c : 4}}
|
|
||||||
# console.log(w) # {a : {b : "String", c : 4}}
|
|
||||||
#
|
|
||||||
# @example Common Pitfalls
|
|
||||||
# w = yatta.value
|
|
||||||
# # Setting a new property
|
|
||||||
# w.newProperty = "Awesome"
|
|
||||||
# console.log(w.newProperty == "Awesome") # false, w.newProperty is undefined
|
|
||||||
# # overwrite the w object
|
|
||||||
# w = {newProperty : "Awesome"}
|
|
||||||
# console.log(w.newProperty == "Awesome") # true!, but ..
|
|
||||||
# console.log(yatta.value.newProperty == "Awesome") # false, you are only allowed to set properties!
|
|
||||||
# # The solution
|
|
||||||
# yatta.value = {newProperty : "Awesome"}
|
|
||||||
# console.log(w.newProperty == "Awesome") # true!
|
|
||||||
#
|
|
||||||
class JsonTypeWrapper
|
|
||||||
|
|
||||||
#
|
|
||||||
# @param {JsonType} jsonType Instance of the JsonType that this class wrappes.
|
|
||||||
#
|
|
||||||
constructor: (jsonType)->
|
|
||||||
for name, obj of jsonType.map
|
|
||||||
do (name, obj)->
|
|
||||||
Object.defineProperty JsonTypeWrapper.prototype, name,
|
|
||||||
get : ->
|
|
||||||
x = obj.val()
|
|
||||||
if x instanceof JsonType
|
|
||||||
createJsonTypeWrapper x
|
|
||||||
else if x instanceof types.ImmutableObject
|
|
||||||
x.val()
|
|
||||||
else
|
|
||||||
x
|
|
||||||
set : (o)->
|
|
||||||
overwrite = jsonType.val(name)
|
|
||||||
if o.constructor is {}.constructor and overwrite instanceof types.Operation
|
|
||||||
for o_name,o_obj of o
|
|
||||||
overwrite.val(o_name, o_obj, 'immutable')
|
|
||||||
else
|
|
||||||
jsonType.val(name, o, 'immutable')
|
|
||||||
enumerable: true
|
|
||||||
configurable: false
|
|
||||||
new JsonTypeWrapper _jsonType
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Manages Object-like values.
|
# Manages Object-like values.
|
||||||
#
|
#
|
||||||
class JsonType extends types.MapManager
|
class types.Object extends types.MapManager
|
||||||
|
|
||||||
#
|
#
|
||||||
# Identifies this class.
|
# Identifies this class.
|
||||||
@@ -104,11 +15,11 @@ module.exports = (HB)->
|
|||||||
#
|
#
|
||||||
# @example
|
# @example
|
||||||
# var x = yatta.val('unknown')
|
# var x = yatta.val('unknown')
|
||||||
# if (x.type === "JsonType") {
|
# if (x.type === "Object") {
|
||||||
# console.log JSON.stringify(x.toJson())
|
# console.log JSON.stringify(x.toJson())
|
||||||
# }
|
# }
|
||||||
#
|
#
|
||||||
type: "JsonType"
|
type: "Object"
|
||||||
|
|
||||||
applyDelete: ()->
|
applyDelete: ()->
|
||||||
super()
|
super()
|
||||||
@@ -130,7 +41,7 @@ module.exports = (HB)->
|
|||||||
val = @val()
|
val = @val()
|
||||||
json = {}
|
json = {}
|
||||||
for name, o of val
|
for name, o of val
|
||||||
if o instanceof JsonType
|
if o instanceof types.Object
|
||||||
json[name] = o.toJson(transform_to_value)
|
json[name] = o.toJson(transform_to_value)
|
||||||
else if transform_to_value and o instanceof types.Operation
|
else if transform_to_value and o instanceof types.Operation
|
||||||
json[name] = o.val()
|
json[name] = o.val()
|
||||||
@@ -171,24 +82,6 @@ module.exports = (HB)->
|
|||||||
changedBy:event.changedBy
|
changedBy:event.changedBy
|
||||||
@bound_json
|
@bound_json
|
||||||
|
|
||||||
#
|
|
||||||
# Whether the default is 'mutable' (true) or 'immutable' (false)
|
|
||||||
#
|
|
||||||
mutable_default:
|
|
||||||
false
|
|
||||||
|
|
||||||
#
|
|
||||||
# Set if the default is 'mutable' or 'immutable'
|
|
||||||
# @param {String|Boolean} mutable Set either 'mutable' / true or 'immutable' / false
|
|
||||||
setMutableDefault: (mutable)->
|
|
||||||
if mutable is true or mutable is 'mutable'
|
|
||||||
JsonType.prototype.mutable_default = true
|
|
||||||
else if mutable is false or mutable is 'immutable'
|
|
||||||
JsonType.prototype.mutable_default = false
|
|
||||||
else
|
|
||||||
throw new Error 'Set mutable either "mutable" or "immutable"!'
|
|
||||||
'OK'
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# @overload val()
|
# @overload val()
|
||||||
# Get this as a Json object.
|
# Get this as a Json object.
|
||||||
@@ -197,70 +90,56 @@ module.exports = (HB)->
|
|||||||
# @overload val(name)
|
# @overload val(name)
|
||||||
# Get value of a property.
|
# Get value of a property.
|
||||||
# @param {String} name Name of the object property.
|
# @param {String} name Name of the object property.
|
||||||
# @return [JsonType|WordType|String|Object] Depending on the value of the property. If mutable it will return a Operation-type object, if immutable it will return String/Object.
|
# @return [Object Type||String|Object] Depending on the value of the property. If mutable it will return a Operation-type object, if immutable it will return String/Object.
|
||||||
#
|
#
|
||||||
# @overload val(name, content)
|
# @overload val(name, content)
|
||||||
# Set a new property.
|
# Set a new property.
|
||||||
# @param {String} name Name of the object property.
|
# @param {String} name Name of the object property.
|
||||||
# @param {Object|String} content Content of the object property.
|
# @param {Object|String} content Content of the object property.
|
||||||
# @return [JsonType] This object. (supports chaining)
|
# @return [Object Type] This object. (supports chaining)
|
||||||
#
|
#
|
||||||
val: (name, content, mutable)->
|
val: (name, content)->
|
||||||
if name? and arguments.length > 1
|
if name? and arguments.length > 1
|
||||||
if mutable?
|
if content? and content.constructor?
|
||||||
if mutable is true or mutable is 'mutable'
|
type = types[content.constructor.name]
|
||||||
mutable = true
|
if type? and type.create?
|
||||||
|
args = []
|
||||||
|
for i in [1...arguments.length]
|
||||||
|
args.push arguments[i]
|
||||||
|
o = type.create.apply null, args
|
||||||
|
super name, o
|
||||||
else
|
else
|
||||||
mutable = false
|
throw new Error "The #{content.constructor.name}-type is not (yet) supported in Yatta."
|
||||||
else
|
else
|
||||||
mutable = @mutable_default
|
|
||||||
if typeof content is 'function'
|
|
||||||
@ # Just do nothing
|
|
||||||
else if (not content?) or (((not mutable) or typeof content is 'number') and content.constructor isnt Object)
|
|
||||||
super name, (new types.ImmutableObject undefined, content).execute()
|
super name, (new types.ImmutableObject undefined, content).execute()
|
||||||
else
|
else # is this even necessary ? I have to define every type anyway.. (see Number type below)
|
||||||
if typeof content is 'string'
|
super name
|
||||||
word = (new types.WordType undefined).execute()
|
|
||||||
word.insertText 0, content
|
|
||||||
super name, word
|
|
||||||
else if content.constructor is Object
|
|
||||||
json = new JsonType().execute()
|
|
||||||
for n,o of content
|
|
||||||
json.val n, o, mutable
|
|
||||||
super name, json
|
|
||||||
else
|
|
||||||
throw new Error "You must not set #{typeof content}-types in collaborative Json-objects!"
|
|
||||||
else
|
|
||||||
super name, content
|
|
||||||
|
|
||||||
Object.defineProperty JsonType.prototype, 'value',
|
|
||||||
get : -> createJsonTypeWrapper @
|
|
||||||
set : (o)->
|
|
||||||
if o.constructor is {}.constructor
|
|
||||||
for o_name,o_obj of o
|
|
||||||
@val(o_name, o_obj, 'immutable')
|
|
||||||
else
|
|
||||||
throw new Error "You must only set Object values!"
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# @private
|
# @private
|
||||||
#
|
#
|
||||||
_encode: ()->
|
_encode: ()->
|
||||||
{
|
{
|
||||||
'type' : "JsonType"
|
'type' : @type
|
||||||
'uid' : @getUid()
|
'uid' : @getUid()
|
||||||
}
|
}
|
||||||
|
|
||||||
parser['JsonType'] = (json)->
|
types.Object.parse = (json)->
|
||||||
{
|
{
|
||||||
'uid' : uid
|
'uid' : uid
|
||||||
} = json
|
} = json
|
||||||
new JsonType uid
|
new this(uid)
|
||||||
|
|
||||||
|
types.Object.create = (content, mutable)->
|
||||||
|
json = new types.Object().execute()
|
||||||
|
for n,o of content
|
||||||
|
json.val n, o, mutable
|
||||||
|
json
|
||||||
|
|
||||||
|
|
||||||
|
types.Number = {}
|
||||||
|
types.Number.create = (content)->
|
||||||
types['JsonType'] = JsonType
|
(new types.ImmutableObject undefined, content).execute()
|
||||||
|
|
||||||
text_types
|
text_types
|
||||||
|
|
||||||
|
|||||||
@@ -3,13 +3,12 @@ basic_types_uninitialized = require "./BasicTypes"
|
|||||||
module.exports = (HB)->
|
module.exports = (HB)->
|
||||||
basic_types = basic_types_uninitialized HB
|
basic_types = basic_types_uninitialized HB
|
||||||
types = basic_types.types
|
types = basic_types.types
|
||||||
parser = basic_types.parser
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# @nodoc
|
# @nodoc
|
||||||
# Manages map like objects. E.g. Json-Type and XML attributes.
|
# Manages map like objects. E.g. Json-Type and XML attributes.
|
||||||
#
|
#
|
||||||
class MapManager extends types.Operation
|
class types.MapManager extends types.Operation
|
||||||
|
|
||||||
#
|
#
|
||||||
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
|
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
|
||||||
@@ -69,7 +68,7 @@ module.exports = (HB)->
|
|||||||
rm_uid =
|
rm_uid =
|
||||||
noOperation: true
|
noOperation: true
|
||||||
alt: map_uid
|
alt: map_uid
|
||||||
rm = new ReplaceManager event_properties, event_this, rm_uid # this operation shall not be saved in the HB
|
rm = new types.ReplaceManager event_properties, event_this, rm_uid # this operation shall not be saved in the HB
|
||||||
@map[property_name] = rm
|
@map[property_name] = rm
|
||||||
rm.setParent @, property_name
|
rm.setParent @, property_name
|
||||||
rm.execute()
|
rm.execute()
|
||||||
@@ -79,7 +78,7 @@ module.exports = (HB)->
|
|||||||
# @nodoc
|
# @nodoc
|
||||||
# Manages a list of Insert-type operations.
|
# Manages a list of Insert-type operations.
|
||||||
#
|
#
|
||||||
class ListManager extends types.Operation
|
class types.ListManager extends types.Operation
|
||||||
|
|
||||||
#
|
#
|
||||||
# A ListManager maintains a non-empty list that has a beginning and an end (both Delimiters!)
|
# A ListManager maintains a non-empty list that has a beginning and an end (both Delimiters!)
|
||||||
@@ -156,10 +155,10 @@ module.exports = (HB)->
|
|||||||
# Adds support for replace. The ReplaceManager manages Replaceable operations.
|
# Adds support for replace. The ReplaceManager manages Replaceable operations.
|
||||||
# Each Replaceable holds a value that is now replaceable.
|
# Each Replaceable holds a value that is now replaceable.
|
||||||
#
|
#
|
||||||
# The WordType-type has implemented support for replace
|
# The TextType-type has implemented support for replace
|
||||||
# @see WordType
|
# @see TextType
|
||||||
#
|
#
|
||||||
class ReplaceManager extends ListManager
|
class types.ReplaceManager extends types.ListManager
|
||||||
#
|
#
|
||||||
# @param {Object} event_properties Decorates the event that is thrown by the RM
|
# @param {Object} event_properties Decorates the event that is thrown by the RM
|
||||||
# @param {Object} event_this The object on which the event shall be executed
|
# @param {Object} event_this The object on which the event shall be executed
|
||||||
@@ -207,7 +206,7 @@ module.exports = (HB)->
|
|||||||
#
|
#
|
||||||
replace: (content, replaceable_uid)->
|
replace: (content, replaceable_uid)->
|
||||||
o = @getLastOperation()
|
o = @getLastOperation()
|
||||||
relp = (new Replaceable content, @, replaceable_uid, o, o.next_cl).execute()
|
relp = (new types.Replaceable content, @, replaceable_uid, o, o.next_cl).execute()
|
||||||
# TODO: delete repl (for debugging)
|
# TODO: delete repl (for debugging)
|
||||||
undefined
|
undefined
|
||||||
|
|
||||||
@@ -219,7 +218,7 @@ module.exports = (HB)->
|
|||||||
undefined
|
undefined
|
||||||
|
|
||||||
#
|
#
|
||||||
# Get the value of this WordType
|
# Get the value of this
|
||||||
# @return {String}
|
# @return {String}
|
||||||
#
|
#
|
||||||
val: ()->
|
val: ()->
|
||||||
@@ -234,7 +233,7 @@ module.exports = (HB)->
|
|||||||
_encode: ()->
|
_encode: ()->
|
||||||
json =
|
json =
|
||||||
{
|
{
|
||||||
'type': "ReplaceManager"
|
'type': @type
|
||||||
'uid' : @getUid()
|
'uid' : @getUid()
|
||||||
'beginning' : @beginning.getUid()
|
'beginning' : @beginning.getUid()
|
||||||
'end' : @end.getUid()
|
'end' : @end.getUid()
|
||||||
@@ -246,7 +245,7 @@ module.exports = (HB)->
|
|||||||
# The ReplaceManager manages Replaceables.
|
# The ReplaceManager manages Replaceables.
|
||||||
# @see ReplaceManager
|
# @see ReplaceManager
|
||||||
#
|
#
|
||||||
class Replaceable extends types.Insert
|
class types.Replaceable extends types.Insert
|
||||||
|
|
||||||
#
|
#
|
||||||
# @param {Operation} content The value that this Replaceable holds.
|
# @param {Operation} content The value that this Replaceable holds.
|
||||||
@@ -321,7 +320,7 @@ module.exports = (HB)->
|
|||||||
_encode: ()->
|
_encode: ()->
|
||||||
json =
|
json =
|
||||||
{
|
{
|
||||||
'type': "Replaceable"
|
'type': @type
|
||||||
'content': @content?.getUid()
|
'content': @content?.getUid()
|
||||||
'parent' : @parent.getUid()
|
'parent' : @parent.getUid()
|
||||||
'prev': @prev_cl.getUid()
|
'prev': @prev_cl.getUid()
|
||||||
@@ -332,7 +331,7 @@ module.exports = (HB)->
|
|||||||
}
|
}
|
||||||
json
|
json
|
||||||
|
|
||||||
parser["Replaceable"] = (json)->
|
types.Replaceable.parse = (json)->
|
||||||
{
|
{
|
||||||
'content' : content
|
'content' : content
|
||||||
'parent' : parent
|
'parent' : parent
|
||||||
@@ -342,12 +341,8 @@ module.exports = (HB)->
|
|||||||
'origin' : origin
|
'origin' : origin
|
||||||
'is_deleted': is_deleted
|
'is_deleted': is_deleted
|
||||||
} = json
|
} = json
|
||||||
new Replaceable content, parent, uid, prev, next, origin, is_deleted
|
new this(content, parent, uid, prev, next, origin, is_deleted)
|
||||||
|
|
||||||
types['ListManager'] = ListManager
|
|
||||||
types['MapManager'] = MapManager
|
|
||||||
types['ReplaceManager'] = ReplaceManager
|
|
||||||
types['Replaceable'] = Replaceable
|
|
||||||
|
|
||||||
basic_types
|
basic_types
|
||||||
|
|
||||||
|
|||||||
+128
-89
@@ -5,19 +5,11 @@ module.exports = (HB)->
|
|||||||
types = structured_types.types
|
types = structured_types.types
|
||||||
parser = structured_types.parser
|
parser = structured_types.parser
|
||||||
|
|
||||||
#
|
|
||||||
# @nodoc
|
|
||||||
# At the moment TextDelete type equals the Delete type in BasicTypes.
|
|
||||||
# @see BasicTypes.Delete
|
|
||||||
#
|
|
||||||
class TextDelete extends types.Delete
|
|
||||||
parser["TextDelete"] = parser["Delete"]
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# @nodoc
|
# @nodoc
|
||||||
# Extends the basic Insert type to an operation that holds a text value
|
# Extends the basic Insert type to an operation that holds a text value
|
||||||
#
|
#
|
||||||
class TextInsert extends types.Insert
|
class types.TextInsert extends types.Insert
|
||||||
#
|
#
|
||||||
# @param {String} content The content of this Insert-type Operation. Usually you restrict the length of content to size 1
|
# @param {String} content The content of this Insert-type Operation. Usually you restrict the length of content to size 1
|
||||||
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
|
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
|
||||||
@@ -72,7 +64,7 @@ module.exports = (HB)->
|
|||||||
_encode: ()->
|
_encode: ()->
|
||||||
json =
|
json =
|
||||||
{
|
{
|
||||||
'type': "TextInsert"
|
'type': @type
|
||||||
'uid' : @getUid()
|
'uid' : @getUid()
|
||||||
'prev': @prev_cl.getUid()
|
'prev': @prev_cl.getUid()
|
||||||
'next': @next_cl.getUid()
|
'next': @next_cl.getUid()
|
||||||
@@ -86,7 +78,7 @@ module.exports = (HB)->
|
|||||||
json['content'] = @content
|
json['content'] = @content
|
||||||
json
|
json
|
||||||
|
|
||||||
parser["TextInsert"] = (json)->
|
types.TextInsert.parse = (json)->
|
||||||
{
|
{
|
||||||
'content' : content
|
'content' : content
|
||||||
'uid' : uid
|
'uid' : uid
|
||||||
@@ -95,13 +87,109 @@ module.exports = (HB)->
|
|||||||
'origin' : origin
|
'origin' : origin
|
||||||
'parent' : parent
|
'parent' : parent
|
||||||
} = json
|
} = json
|
||||||
new TextInsert content, uid, prev, next, origin, parent
|
new types.TextInsert content, uid, prev, next, origin, parent
|
||||||
|
|
||||||
|
|
||||||
|
class types.Array extends types.ListManager
|
||||||
|
|
||||||
|
type: "Array"
|
||||||
|
|
||||||
|
applyDelete: ()->
|
||||||
|
o = @end
|
||||||
|
while o?
|
||||||
|
o.applyDelete()
|
||||||
|
o = o.prev_cl
|
||||||
|
super()
|
||||||
|
|
||||||
|
cleanup: ()->
|
||||||
|
super()
|
||||||
|
|
||||||
|
val: ()->
|
||||||
|
o = @beginning.next_cl
|
||||||
|
result = []
|
||||||
|
while o isnt @end
|
||||||
|
result.push o.val()
|
||||||
|
o = o.next_cl
|
||||||
|
result
|
||||||
|
|
||||||
|
push: (content)->
|
||||||
|
@insertAfter @end.prev_cl, 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
|
||||||
|
if content.type?
|
||||||
|
(new types.TextInsert content, undefined, left, right).execute()
|
||||||
|
else
|
||||||
|
for c in content
|
||||||
|
tmp = (new types.TextInsert c, undefined, left, right).execute()
|
||||||
|
left = tmp
|
||||||
|
@
|
||||||
|
|
||||||
#
|
#
|
||||||
# Handles a WordType-like data structures with support for insertText/deleteText at a word-position.
|
# Inserts a string into the word.
|
||||||
|
#
|
||||||
|
# @return {Array Type} This String object.
|
||||||
|
#
|
||||||
|
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
|
||||||
|
|
||||||
|
#
|
||||||
|
# Deletes a part of the word.
|
||||||
|
#
|
||||||
|
# @return {Array Type} This String object
|
||||||
|
#
|
||||||
|
delete: (position, length)->
|
||||||
|
o = @getOperationByPosition(position+1) # position 0 in this case is the deletion of the first character
|
||||||
|
|
||||||
|
delete_ops = []
|
||||||
|
for i in [0...length]
|
||||||
|
if o instanceof types.Delimiter
|
||||||
|
break
|
||||||
|
d = (new types.Delete undefined, o).execute()
|
||||||
|
o = o.next_cl
|
||||||
|
while not (o instanceof types.Delimiter) and o.isDeleted()
|
||||||
|
o = o.next_cl
|
||||||
|
delete_ops.push d._encode()
|
||||||
|
@
|
||||||
|
|
||||||
|
#
|
||||||
|
# @private
|
||||||
|
# Encode this operation in such a way that it can be parsed by remote peers.
|
||||||
|
#
|
||||||
|
_encode: ()->
|
||||||
|
json = {
|
||||||
|
'type': @type
|
||||||
|
'uid' : @getUid()
|
||||||
|
}
|
||||||
|
json
|
||||||
|
|
||||||
|
types.Array.parse = (json)->
|
||||||
|
{
|
||||||
|
'uid' : uid
|
||||||
|
} = json
|
||||||
|
new this(uid)
|
||||||
|
|
||||||
|
types.Array.create = (content, mutable)->
|
||||||
|
if (mutable is "mutable")
|
||||||
|
list = new types.Array().execute()
|
||||||
|
list.insert 0, content
|
||||||
|
list
|
||||||
|
else if (not mutable?) or (mutable is "immutable")
|
||||||
|
(new types.ImmutableObject undefined, content).execute()
|
||||||
|
else
|
||||||
|
throw new Error "Specify either \"mutable\" or \"immutable\"!!"
|
||||||
|
|
||||||
|
#
|
||||||
|
# Handles a String-like data structures with support for insert/delete at a word-position.
|
||||||
# @note Currently, only Text is supported!
|
# @note Currently, only Text is supported!
|
||||||
#
|
#
|
||||||
class WordType extends types.ListManager
|
class types.String extends types.Array
|
||||||
|
|
||||||
#
|
#
|
||||||
# @private
|
# @private
|
||||||
@@ -117,66 +205,11 @@ module.exports = (HB)->
|
|||||||
#
|
#
|
||||||
# @example
|
# @example
|
||||||
# var x = yatta.val('unknown')
|
# var x = yatta.val('unknown')
|
||||||
# if (x.type === "WordType") {
|
# if (x.type === "String") {
|
||||||
# console.log JSON.stringify(x.toJson())
|
# console.log JSON.stringify(x.toJson())
|
||||||
# }
|
# }
|
||||||
#
|
#
|
||||||
type: "WordType"
|
type: "String"
|
||||||
|
|
||||||
applyDelete: ()->
|
|
||||||
o = @end
|
|
||||||
while o?
|
|
||||||
o.applyDelete()
|
|
||||||
o = o.prev_cl
|
|
||||||
super()
|
|
||||||
|
|
||||||
cleanup: ()->
|
|
||||||
super()
|
|
||||||
|
|
||||||
push: (content)->
|
|
||||||
@insertAfter @end.prev_cl, 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
|
|
||||||
if content.type?
|
|
||||||
(new TextInsert content, undefined, left, right).execute()
|
|
||||||
else
|
|
||||||
for c in content
|
|
||||||
tmp = (new TextInsert c, undefined, left, right).execute()
|
|
||||||
left = tmp
|
|
||||||
@
|
|
||||||
#
|
|
||||||
# Inserts a string into the word.
|
|
||||||
#
|
|
||||||
# @return {WordType} This WordType object.
|
|
||||||
#
|
|
||||||
insertText: (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
|
|
||||||
|
|
||||||
#
|
|
||||||
# Deletes a part of the word.
|
|
||||||
#
|
|
||||||
# @return {WordType} This WordType object
|
|
||||||
#
|
|
||||||
deleteText: (position, length)->
|
|
||||||
o = @getOperationByPosition(position+1) # position 0 in this case is the deletion of the first character
|
|
||||||
|
|
||||||
delete_ops = []
|
|
||||||
for i in [0...length]
|
|
||||||
if o instanceof types.Delimiter
|
|
||||||
break
|
|
||||||
d = (new TextDelete undefined, o).execute()
|
|
||||||
o = o.next_cl
|
|
||||||
while not (o instanceof types.Delimiter) and o.isDeleted()
|
|
||||||
o = o.next_cl
|
|
||||||
delete_ops.push d._encode()
|
|
||||||
@
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Get the String-representation of this word.
|
# Get the String-representation of this word.
|
||||||
@@ -191,14 +224,14 @@ module.exports = (HB)->
|
|||||||
c.join('')
|
c.join('')
|
||||||
|
|
||||||
#
|
#
|
||||||
# Same as WordType.val
|
# Same as String.val
|
||||||
# @see WordType.val
|
# @see String.val
|
||||||
#
|
#
|
||||||
toString: ()->
|
toString: ()->
|
||||||
@val()
|
@val()
|
||||||
|
|
||||||
#
|
#
|
||||||
# Bind this WordType to a textfield or input field.
|
# Bind this String to a textfield or input field.
|
||||||
#
|
#
|
||||||
# @example
|
# @example
|
||||||
# var textbox = document.getElementById("textfield");
|
# var textbox = document.getElementById("textfield");
|
||||||
@@ -257,8 +290,8 @@ module.exports = (HB)->
|
|||||||
if char.length > 0
|
if char.length > 0
|
||||||
pos = Math.min textfield.selectionStart, textfield.selectionEnd
|
pos = Math.min textfield.selectionStart, textfield.selectionEnd
|
||||||
diff = Math.abs(textfield.selectionEnd - textfield.selectionStart)
|
diff = Math.abs(textfield.selectionEnd - textfield.selectionStart)
|
||||||
word.deleteText (pos), diff
|
word.delete (pos), diff
|
||||||
word.insertText pos, char
|
word.insert pos, char
|
||||||
new_pos = pos + char.length
|
new_pos = pos + char.length
|
||||||
textfield.setSelectionRange new_pos, new_pos
|
textfield.setSelectionRange new_pos, new_pos
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
@@ -294,7 +327,7 @@ module.exports = (HB)->
|
|||||||
diff = Math.abs(textfield.selectionEnd - textfield.selectionStart)
|
diff = Math.abs(textfield.selectionEnd - textfield.selectionStart)
|
||||||
if event.keyCode? and event.keyCode is 8 # Backspace
|
if event.keyCode? and event.keyCode is 8 # Backspace
|
||||||
if diff > 0
|
if diff > 0
|
||||||
word.deleteText pos, diff
|
word.delete pos, diff
|
||||||
textfield.setSelectionRange pos, pos
|
textfield.setSelectionRange pos, pos
|
||||||
else
|
else
|
||||||
if event.ctrlKey? and event.ctrlKey
|
if event.ctrlKey? and event.ctrlKey
|
||||||
@@ -307,42 +340,48 @@ module.exports = (HB)->
|
|||||||
while new_pos > 0 and val[new_pos] isnt " " and val[new_pos] isnt '\n'
|
while new_pos > 0 and val[new_pos] isnt " " and val[new_pos] isnt '\n'
|
||||||
new_pos--
|
new_pos--
|
||||||
del_length++
|
del_length++
|
||||||
word.deleteText new_pos, (pos-new_pos)
|
word.delete new_pos, (pos-new_pos)
|
||||||
textfield.setSelectionRange new_pos, new_pos
|
textfield.setSelectionRange new_pos, new_pos
|
||||||
else
|
else
|
||||||
word.deleteText (pos-1), 1
|
word.delete (pos-1), 1
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
else if event.keyCode? and event.keyCode is 46 # Delete
|
else if event.keyCode? and event.keyCode is 46 # Delete
|
||||||
if diff > 0
|
if diff > 0
|
||||||
word.deleteText pos, diff
|
word.delete pos, diff
|
||||||
textfield.setSelectionRange pos, pos
|
textfield.setSelectionRange pos, pos
|
||||||
else
|
else
|
||||||
word.deleteText pos, 1
|
word.delete pos, 1
|
||||||
textfield.setSelectionRange pos, pos
|
textfield.setSelectionRange pos, pos
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# @private
|
# @private
|
||||||
# Encode this operation in such a way that it can be parsed by remote peers.
|
# Encode this operation in such a way that it can be parsed by remote peers.
|
||||||
#
|
#
|
||||||
_encode: ()->
|
_encode: ()->
|
||||||
json = {
|
json = {
|
||||||
'type': "WordType"
|
'type': @type
|
||||||
'uid' : @getUid()
|
'uid' : @getUid()
|
||||||
}
|
}
|
||||||
json
|
json
|
||||||
|
|
||||||
parser['WordType'] = (json)->
|
types.String.parse = (json)->
|
||||||
{
|
{
|
||||||
'uid' : uid
|
'uid' : uid
|
||||||
} = json
|
} = json
|
||||||
new WordType uid
|
new this(uid)
|
||||||
|
|
||||||
|
types.String.create = (content, mutable)->
|
||||||
|
if (mutable is "mutable")
|
||||||
|
word = new types.String().execute()
|
||||||
|
word.insert 0, content
|
||||||
|
word
|
||||||
|
else if (not mutable?) or (mutable is "immutable")
|
||||||
|
(new types.ImmutableObject undefined, content).execute()
|
||||||
|
else
|
||||||
|
throw new Error "Specify either \"mutable\" or \"immutable\"!!"
|
||||||
|
|
||||||
|
|
||||||
types['TextInsert'] = TextInsert
|
|
||||||
types['TextDelete'] = TextDelete
|
|
||||||
types['WordType'] = WordType
|
|
||||||
structured_types
|
structured_types
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -24,7 +24,7 @@ createYatta = (connector)->
|
|||||||
# * Integer
|
# * Integer
|
||||||
# * Array
|
# * Array
|
||||||
#
|
#
|
||||||
class Yatta extends types.JsonType
|
class Yatta extends types.Object
|
||||||
|
|
||||||
#
|
#
|
||||||
# @param {String} user_id Unique id of the peer.
|
# @param {String} user_id Unique id of the peer.
|
||||||
@@ -34,7 +34,7 @@ createYatta = (connector)->
|
|||||||
@connector = connector
|
@connector = connector
|
||||||
@HB = HB
|
@HB = HB
|
||||||
@types = types
|
@types = types
|
||||||
@engine = new Engine @HB, type_manager.parser
|
@engine = new Engine @HB, type_manager.types
|
||||||
adaptConnector @connector, @engine, @HB, type_manager.execution_listener
|
adaptConnector @connector, @engine, @HB, type_manager.execution_listener
|
||||||
super
|
super
|
||||||
|
|
||||||
|
|||||||
+17
-33
@@ -45,19 +45,19 @@ class JsonTest extends Test
|
|||||||
f : (y)=> # SET PROPERTY
|
f : (y)=> # SET PROPERTY
|
||||||
y.val(@getRandomKey(), @getRandomText(), 'immutable')
|
y.val(@getRandomKey(), @getRandomText(), 'immutable')
|
||||||
null
|
null
|
||||||
types : [types.JsonType]
|
types : [types.Object]
|
||||||
,
|
,
|
||||||
f : (y)=> # SET Object Property 1)
|
f : (y)=> # SET Object Property 1)
|
||||||
y.val(@getRandomObject())
|
y.val(@getRandomObject())
|
||||||
types: [types.JsonType]
|
types: [types.Object]
|
||||||
,
|
,
|
||||||
f : (y)=> # SET Object Property 2)
|
f : (y)=> # SET Object Property 2)
|
||||||
y.val(@getRandomKey(), @getRandomObject())
|
y.val(@getRandomKey(), @getRandomObject())
|
||||||
types: [types.JsonType]
|
types: [types.Object]
|
||||||
,
|
,
|
||||||
f : (y)=> # SET PROPERTY TEXT
|
f : (y)=> # SET PROPERTY TEXT
|
||||||
y.val(@getRandomKey(), @getRandomText(), 'mutable')
|
y.val(@getRandomKey(), @getRandomText(), 'mutable')
|
||||||
types: [types.JsonType]
|
types: [types.Object]
|
||||||
]
|
]
|
||||||
|
|
||||||
describe "JsonFramework", ->
|
describe "JsonFramework", ->
|
||||||
@@ -73,30 +73,6 @@ describe "JsonFramework", ->
|
|||||||
console.log "" # TODO
|
console.log "" # TODO
|
||||||
@yTest.run()
|
@yTest.run()
|
||||||
|
|
||||||
### TODO
|
|
||||||
it "has a update listener", ()->
|
|
||||||
addName = false
|
|
||||||
change = false
|
|
||||||
change2 = 0
|
|
||||||
@test_user.on 'add', (eventname, property_name)->
|
|
||||||
if property_name is 'x'
|
|
||||||
addName = true
|
|
||||||
@test_user.val('x',5)
|
|
||||||
@test_user.on 'change', (eventname, property_name)->
|
|
||||||
if property_name is 'x'
|
|
||||||
change = true
|
|
||||||
@test_user.val('x', 6)
|
|
||||||
@test_user.val('ins', "text", 'mutable')
|
|
||||||
@test_user.on 'update', (eventname, property_name)->
|
|
||||||
if property_name is 'ins'
|
|
||||||
change2++
|
|
||||||
@test_user.val('ins').insertText 4, " yay"
|
|
||||||
@test_user.val('ins').deleteText 0, 4
|
|
||||||
expect(addName).to.be.ok
|
|
||||||
expect(change).to.be.ok
|
|
||||||
expect(change2).to.equal 8
|
|
||||||
###
|
|
||||||
|
|
||||||
it "has a working test suite", ->
|
it "has a working test suite", ->
|
||||||
@yTest.compareAll()
|
@yTest.compareAll()
|
||||||
|
|
||||||
@@ -119,9 +95,9 @@ describe "JsonFramework", ->
|
|||||||
@yTest.users[1].val('a', 't', "mutable")
|
@yTest.users[1].val('a', 't', "mutable")
|
||||||
@yTest.compareAll()
|
@yTest.compareAll()
|
||||||
q = @yTest.users[2].val('a')
|
q = @yTest.users[2].val('a')
|
||||||
q.insertText(0,'A')
|
q.insert(0,'A')
|
||||||
@yTest.compareAll()
|
@yTest.compareAll()
|
||||||
expect(@yTest.getSomeUser().value.a.val()).to.equal("At")
|
expect(@yTest.getSomeUser().val("a").val()).to.equal("At")
|
||||||
|
|
||||||
it "can handle creaton of complex json (2)", ->
|
it "can handle creaton of complex json (2)", ->
|
||||||
@yTest.getSomeUser().val('x', {'a':'b'})
|
@yTest.getSomeUser().val('x', {'a':'b'})
|
||||||
@@ -130,10 +106,18 @@ describe "JsonFramework", ->
|
|||||||
@yTest.getSomeUser().val('c', {'a':'c'})
|
@yTest.getSomeUser().val('c', {'a':'c'})
|
||||||
@yTest.getSomeUser().val('c', {'a':'b'})
|
@yTest.getSomeUser().val('c', {'a':'b'})
|
||||||
@yTest.compareAll()
|
@yTest.compareAll()
|
||||||
q = @yTest.getSomeUser().value.a.a.q
|
q = @yTest.getSomeUser().val("a").val("a").val("q")
|
||||||
q.insertText(0,'A')
|
q.insert(0,'A')
|
||||||
|
@yTest.compareAll()
|
||||||
|
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")
|
||||||
|
@yTest.users[1].val('l', [4,5,6], "mutable")
|
||||||
|
@yTest.compareAll()
|
||||||
|
@yTest.users[2].val('l').insert(0,'A')
|
||||||
|
@yTest.users[1].val('l').insert(0,'B')
|
||||||
@yTest.compareAll()
|
@yTest.compareAll()
|
||||||
expect(@yTest.getSomeUser().value.a.a.q.val()).to.equal("Adtrndtrtdrntdrnrtdnrtdnrtdnrtdnrdnrdt")
|
|
||||||
|
|
||||||
it "handles immutables and primitive data types", ->
|
it "handles immutables and primitive data types", ->
|
||||||
@yTest.getSomeUser().val('string', "text", "immutable")
|
@yTest.getSomeUser().val('string', "text", "immutable")
|
||||||
|
|||||||
+10
-9
@@ -12,9 +12,9 @@ Connector = require "../bower_components/connector/lib/test-connector/test-conne
|
|||||||
module.exports = class Test
|
module.exports = class Test
|
||||||
constructor: (@name_suffix = "")->
|
constructor: (@name_suffix = "")->
|
||||||
@number_of_test_cases_multiplier = 1
|
@number_of_test_cases_multiplier = 1
|
||||||
@repeat_this = 503 * @number_of_test_cases_multiplier
|
@repeat_this = 3 * @number_of_test_cases_multiplier
|
||||||
@doSomething_amount = 5 * @number_of_test_cases_multiplier
|
@doSomething_amount = 123 * @number_of_test_cases_multiplier
|
||||||
@number_of_engines = 3 + @number_of_test_cases_multiplier - 1
|
@number_of_engines = 5 + @number_of_test_cases_multiplier - 1
|
||||||
|
|
||||||
@time = 0 # denotes to the time when run was started
|
@time = 0 # denotes to the time when run was started
|
||||||
@ops = 0 # number of operations (used with @time)
|
@ops = 0 # number of operations (used with @time)
|
||||||
@@ -31,6 +31,7 @@ module.exports = class Test
|
|||||||
for user in @users
|
for user in @users
|
||||||
u.getConnector().join(user.getConnector()) # TODO: change the test-connector to make this more convenient
|
u.getConnector().join(user.getConnector()) # TODO: change the test-connector to make this more convenient
|
||||||
@users.push u
|
@users.push u
|
||||||
|
@initUsers?(@users[0])
|
||||||
@flushAll()
|
@flushAll()
|
||||||
|
|
||||||
# is called by implementing class
|
# is called by implementing class
|
||||||
@@ -74,17 +75,17 @@ module.exports = class Test
|
|||||||
f : (y)=> # INSERT TEXT
|
f : (y)=> # INSERT TEXT
|
||||||
y
|
y
|
||||||
pos = _.random 0, (y.val().length-1)
|
pos = _.random 0, (y.val().length-1)
|
||||||
y.insertText pos, @getRandomText()
|
y.insert pos, @getRandomText()
|
||||||
null
|
null
|
||||||
types: [types.WordType]
|
types: [types.String]
|
||||||
,
|
,
|
||||||
f : (y)-> # DELETE TEXT
|
f : (y)-> # DELETE TEXT
|
||||||
if y.val().length > 0
|
if y.val().length > 0
|
||||||
pos = _.random 0, (y.val().length-1)
|
pos = _.random 0, (y.val().length-1) # TODO: put here also arbitrary number (test behaviour in error cases)
|
||||||
length = _.random 0, (y.val().length - pos)
|
length = _.random 0, (y.val().length - pos)
|
||||||
ops1 = y.deleteText pos, length
|
ops1 = y.delete pos, length
|
||||||
undefined
|
undefined
|
||||||
types : [types.WordType]
|
types : [types.String]
|
||||||
]
|
]
|
||||||
getRandomRoot: (user_num)->
|
getRandomRoot: (user_num)->
|
||||||
throw new Error "overwrite me!"
|
throw new Error "overwrite me!"
|
||||||
@@ -99,7 +100,7 @@ module.exports = class Test
|
|||||||
y instanceof type
|
y instanceof type
|
||||||
|
|
||||||
if choices.length is 0
|
if choices.length is 0
|
||||||
throw new Error "You forgot to specify a test generation methot for this Operation!"
|
throw new Error "You forgot to specify a test generation methot for this Operation! (#{y.type})"
|
||||||
i = _.random 0, (choices.length-1)
|
i = _.random 0, (choices.length-1)
|
||||||
choices[i].f y
|
choices[i].f y
|
||||||
|
|
||||||
|
|||||||
+12
-13
@@ -19,6 +19,9 @@ class TextTest extends Test
|
|||||||
conn = new Connector userId
|
conn = new Connector userId
|
||||||
new Yatta conn
|
new Yatta conn
|
||||||
|
|
||||||
|
initUsers: (u)->
|
||||||
|
u.val("TextTest","","mutable")
|
||||||
|
|
||||||
getRandomRoot: (user_num)->
|
getRandomRoot: (user_num)->
|
||||||
@users[user_num].val("TextTest")
|
@users[user_num].val("TextTest")
|
||||||
|
|
||||||
@@ -29,21 +32,17 @@ describe "TextFramework", ->
|
|||||||
beforeEach (done)->
|
beforeEach (done)->
|
||||||
@timeout 50000
|
@timeout 50000
|
||||||
@yTest = new TextTest()
|
@yTest = new TextTest()
|
||||||
@users = @yTest.users
|
|
||||||
test_user_connector = new Connector 'test_user'
|
|
||||||
@test_user = @yTest.makeNewUser 'test_user', test_user_connector
|
|
||||||
test_user_connector.join @users[0].connector
|
|
||||||
@users[0].val("TextTest","","mutable")
|
|
||||||
@yTest.flushAll()
|
|
||||||
done()
|
done()
|
||||||
|
|
||||||
it "simple multi-char insert", ->
|
it "simple multi-char insert", ->
|
||||||
u = @yTest.users[0].val("TextTest")
|
u = @yTest.users[0].val("TextTest")
|
||||||
u.insertText 0, "abc"
|
u.insert 0, "abc"
|
||||||
u = @yTest.users[1].val("TextTest")
|
u = @yTest.users[1].val("TextTest")
|
||||||
u.insertText 0, "xyz"
|
u.insert 0, "xyz"
|
||||||
@yTest.compareAll()
|
@yTest.compareAll()
|
||||||
expect(u.val()).to.equal("abcxyz")
|
u.delete 0, 1
|
||||||
|
@yTest.compareAll()
|
||||||
|
expect(u.val()).to.equal("bcxyz")
|
||||||
|
|
||||||
it "Observers work on shared Text (insert type observers, local and foreign)", ->
|
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","my awesome Text","mutable").val("TextTest")
|
||||||
@@ -59,7 +58,7 @@ describe "TextFramework", ->
|
|||||||
expect(change.changedBy).to.equal('0')
|
expect(change.changedBy).to.equal('0')
|
||||||
last_task = "observer1"
|
last_task = "observer1"
|
||||||
u.observe observer1
|
u.observe observer1
|
||||||
u.insertText 1, "a"
|
u.insert 1, "a"
|
||||||
expect(last_task).to.equal("observer1")
|
expect(last_task).to.equal("observer1")
|
||||||
u.unobserve observer1
|
u.unobserve observer1
|
||||||
|
|
||||||
@@ -74,7 +73,7 @@ describe "TextFramework", ->
|
|||||||
last_task = "observer2"
|
last_task = "observer2"
|
||||||
u.observe observer2
|
u.observe observer2
|
||||||
v = @yTest.users[1].val("TextTest")
|
v = @yTest.users[1].val("TextTest")
|
||||||
v.insertText 0, "x"
|
v.insert 0, "x"
|
||||||
@yTest.flushAll()
|
@yTest.flushAll()
|
||||||
expect(last_task).to.equal("observer2")
|
expect(last_task).to.equal("observer2")
|
||||||
u.unobserve observer2
|
u.unobserve observer2
|
||||||
@@ -93,7 +92,7 @@ describe "TextFramework", ->
|
|||||||
expect(change.changedBy).to.equal('0')
|
expect(change.changedBy).to.equal('0')
|
||||||
last_task = "observer1"
|
last_task = "observer1"
|
||||||
u.observe observer1
|
u.observe observer1
|
||||||
u.deleteText 1, 1
|
u.delete 1, 1
|
||||||
expect(last_task).to.equal("observer1")
|
expect(last_task).to.equal("observer1")
|
||||||
u.unobserve observer1
|
u.unobserve observer1
|
||||||
|
|
||||||
@@ -108,7 +107,7 @@ describe "TextFramework", ->
|
|||||||
last_task = "observer2"
|
last_task = "observer2"
|
||||||
u.observe observer2
|
u.observe observer2
|
||||||
v = @yTest.users[1].val("TextTest")
|
v = @yTest.users[1].val("TextTest")
|
||||||
v.deleteText 0, 1
|
v.delete 0, 1
|
||||||
@yTest.flushAll()
|
@yTest.flushAll()
|
||||||
expect(last_task).to.equal("observer2")
|
expect(last_task).to.equal("observer2")
|
||||||
u.unobserve observer2
|
u.unobserve observer2
|
||||||
|
|||||||
+1
-1
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user