Introduced a new model for custom collaborative types.

This commit is contained in:
DadaMonad 2015-01-16 19:13:01 +00:00
parent b647b2af58
commit f1f710b269
15 changed files with 1019 additions and 1311 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -11,18 +11,18 @@ class Engine
#
# @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 = []
#
# Parses an operatio from the json format. It uses the specified parser in your OperationType module.
#
parseOperation: (json)->
typeParser = @parser[json.type]
if typeParser?
typeParser json
type = @types[json.type]
if type?.parse?
type.parse json
else
throw new Error "You forgot to specify a parser for type #{json.type}. The message is #{JSON.stringify json}."

View File

@ -1,6 +1,6 @@
module.exports = (HB)->
# @see Engine.parse
parser = {}
types = {}
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.
#
class Operation
class types.Operation
#
# @param {Object} uid A unique identifier.
@ -192,12 +192,11 @@ module.exports = (HB)->
@unchecked = uninstantiated
success
#
# @nodoc
# 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.
@ -237,12 +236,12 @@ module.exports = (HB)->
#
# Define how to parse Delete operations.
#
parser['Delete'] = (o)->
types.Delete.parse = (o)->
{
'uid' : uid
'deletes': deletes_uid
} = o
new Delete uid, deletes_uid
new this(uid, deletes_uid)
#
# @nodoc
@ -254,7 +253,7 @@ module.exports = (HB)->
# - The short-list (abbrev. sl) maintains only the operations that are not deleted
# - 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.
@ -422,7 +421,7 @@ module.exports = (HB)->
position = 0
prev = @prev_cl
while true
if prev instanceof Delimiter
if prev instanceof types.Delimiter
break
if not prev.isDeleted()
position++
@ -433,7 +432,7 @@ module.exports = (HB)->
# @nodoc
# 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.
@ -455,18 +454,18 @@ module.exports = (HB)->
#
_encode: ()->
json = {
'type': "ImmutableObject"
'type': @type
'uid' : @getUid()
'content' : @content
}
json
parser['ImmutableObject'] = (json)->
types.ImmutableObject.parse = (json)->
{
'uid' : uid
'content' : content
} = json
new ImmutableObject uid, content
new this(uid, content)
#
# @nodoc
@ -474,7 +473,7 @@ module.exports = (HB)->
# This is necessary in order to have a beginning and an end even if the content
# 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 {Operation} prev_cl The predecessor of this operation in the complete-list (cl)
@ -527,29 +526,23 @@ module.exports = (HB)->
#
_encode: ()->
{
'type' : "Delimiter"
'type' : @type
'uid' : @getUid()
'prev' : @prev_cl?.getUid()
'next' : @next_cl?.getUid()
}
parser['Delimiter'] = (json)->
types.Delimiter.parse = (json)->
{
'uid' : uid
'prev' : prev
'next' : next
} = json
new Delimiter uid, prev, next
new this(uid, prev, next)
# This is what this module exports after initializing it with the HistoryBuffer
{
'types' :
'Delete' : Delete
'Insert' : Insert
'Delimiter': Delimiter
'Operation': Operation
'ImmutableObject' : ImmutableObject
'parser' : parser
'types' : types
'execution_listener' : execution_listener
}

View File

@ -3,100 +3,11 @@ text_types_uninitialized = require "./TextTypes"
module.exports = (HB)->
text_types = text_types_uninitialized HB
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.
#
class JsonType extends types.MapManager
class types.Object extends types.MapManager
#
# Identifies this class.
@ -104,11 +15,11 @@ module.exports = (HB)->
#
# @example
# var x = yatta.val('unknown')
# if (x.type === "JsonType") {
# if (x.type === "Object") {
# console.log JSON.stringify(x.toJson())
# }
#
type: "JsonType"
type: "Object"
applyDelete: ()->
super()
@ -130,7 +41,7 @@ module.exports = (HB)->
val = @val()
json = {}
for name, o of val
if o instanceof JsonType
if o instanceof types.Object
json[name] = o.toJson(transform_to_value)
else if transform_to_value and o instanceof types.Operation
json[name] = o.val()
@ -171,24 +82,6 @@ module.exports = (HB)->
changedBy:event.changedBy
@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()
# Get this as a Json object.
@ -197,70 +90,56 @@ module.exports = (HB)->
# @overload val(name)
# Get value of a 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)
# Set a new property.
# @param {String} name Name 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 mutable?
if mutable is true or mutable is 'mutable'
mutable = true
if content? and content.constructor?
type = types[content.constructor.name]
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
mutable = false
throw new Error "The #{content.constructor.name}-type is not (yet) supported in Yatta."
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()
else
if typeof content is 'string'
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!"
else # is this even necessary ? I have to define every type anyway.. (see Number type below)
super name
#
# @private
#
_encode: ()->
{
'type' : "JsonType"
'type' : @type
'uid' : @getUid()
}
parser['JsonType'] = (json)->
types.Object.parse = (json)->
{
'uid' : uid
} = 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['JsonType'] = JsonType
types.Number = {}
types.Number.create = (content)->
(new types.ImmutableObject undefined, content).execute()
text_types

View File

@ -3,13 +3,12 @@ basic_types_uninitialized = require "./BasicTypes"
module.exports = (HB)->
basic_types = basic_types_uninitialized HB
types = basic_types.types
parser = basic_types.parser
#
# @nodoc
# 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.
@ -69,7 +68,7 @@ module.exports = (HB)->
rm_uid =
noOperation: true
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
rm.setParent @, property_name
rm.execute()
@ -79,7 +78,7 @@ module.exports = (HB)->
# @nodoc
# 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!)
@ -156,10 +155,10 @@ module.exports = (HB)->
# Adds support for replace. The ReplaceManager manages Replaceable operations.
# Each Replaceable holds a value that is now replaceable.
#
# The WordType-type has implemented support for replace
# @see WordType
# The TextType-type has implemented support for replace
# @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_this The object on which the event shall be executed
@ -207,7 +206,7 @@ module.exports = (HB)->
#
replace: (content, replaceable_uid)->
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)
undefined
@ -219,7 +218,7 @@ module.exports = (HB)->
undefined
#
# Get the value of this WordType
# Get the value of this
# @return {String}
#
val: ()->
@ -234,7 +233,7 @@ module.exports = (HB)->
_encode: ()->
json =
{
'type': "ReplaceManager"
'type': @type
'uid' : @getUid()
'beginning' : @beginning.getUid()
'end' : @end.getUid()
@ -246,7 +245,7 @@ module.exports = (HB)->
# The ReplaceManager manages Replaceables.
# @see ReplaceManager
#
class Replaceable extends types.Insert
class types.Replaceable extends types.Insert
#
# @param {Operation} content The value that this Replaceable holds.
@ -321,7 +320,7 @@ module.exports = (HB)->
_encode: ()->
json =
{
'type': "Replaceable"
'type': @type
'content': @content?.getUid()
'parent' : @parent.getUid()
'prev': @prev_cl.getUid()
@ -332,7 +331,7 @@ module.exports = (HB)->
}
json
parser["Replaceable"] = (json)->
types.Replaceable.parse = (json)->
{
'content' : content
'parent' : parent
@ -342,12 +341,8 @@ module.exports = (HB)->
'origin' : origin
'is_deleted': is_deleted
} = 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

View File

@ -5,19 +5,11 @@ module.exports = (HB)->
types = structured_types.types
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
# 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 {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
@ -72,7 +64,7 @@ module.exports = (HB)->
_encode: ()->
json =
{
'type': "TextInsert"
'type': @type
'uid' : @getUid()
'prev': @prev_cl.getUid()
'next': @next_cl.getUid()
@ -86,7 +78,7 @@ module.exports = (HB)->
json['content'] = @content
json
parser["TextInsert"] = (json)->
types.TextInsert.parse = (json)->
{
'content' : content
'uid' : uid
@ -95,13 +87,109 @@ module.exports = (HB)->
'origin' : origin
'parent' : parent
} = 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
@
#
# 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 WordType-like data structures with support for insertText/deleteText at a word-position.
# Handles a String-like data structures with support for insert/delete at a word-position.
# @note Currently, only Text is supported!
#
class WordType extends types.ListManager
class types.String extends types.Array
#
# @private
@ -117,66 +205,11 @@ module.exports = (HB)->
#
# @example
# var x = yatta.val('unknown')
# if (x.type === "WordType") {
# if (x.type === "String") {
# console.log JSON.stringify(x.toJson())
# }
#
type: "WordType"
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()
@
type: "String"
#
# Get the String-representation of this word.
@ -191,14 +224,14 @@ module.exports = (HB)->
c.join('')
#
# Same as WordType.val
# @see WordType.val
# Same as String.val
# @see String.val
#
toString: ()->
@val()
#
# Bind this WordType to a textfield or input field.
# Bind this String to a textfield or input field.
#
# @example
# var textbox = document.getElementById("textfield");
@ -257,8 +290,8 @@ module.exports = (HB)->
if char.length > 0
pos = Math.min textfield.selectionStart, textfield.selectionEnd
diff = Math.abs(textfield.selectionEnd - textfield.selectionStart)
word.deleteText (pos), diff
word.insertText pos, char
word.delete (pos), diff
word.insert pos, char
new_pos = pos + char.length
textfield.setSelectionRange new_pos, new_pos
event.preventDefault()
@ -294,7 +327,7 @@ module.exports = (HB)->
diff = Math.abs(textfield.selectionEnd - textfield.selectionStart)
if event.keyCode? and event.keyCode is 8 # Backspace
if diff > 0
word.deleteText pos, diff
word.delete pos, diff
textfield.setSelectionRange pos, pos
else
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'
new_pos--
del_length++
word.deleteText new_pos, (pos-new_pos)
word.delete new_pos, (pos-new_pos)
textfield.setSelectionRange new_pos, new_pos
else
word.deleteText (pos-1), 1
word.delete (pos-1), 1
event.preventDefault()
else if event.keyCode? and event.keyCode is 46 # Delete
if diff > 0
word.deleteText pos, diff
word.delete pos, diff
textfield.setSelectionRange pos, pos
else
word.deleteText pos, 1
word.delete pos, 1
textfield.setSelectionRange pos, pos
event.preventDefault()
#
# @private
# Encode this operation in such a way that it can be parsed by remote peers.
#
_encode: ()->
json = {
'type': "WordType"
'type': @type
'uid' : @getUid()
}
json
parser['WordType'] = (json)->
types.String.parse = (json)->
{
'uid' : uid
} = 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

View File

@ -24,7 +24,7 @@ createYatta = (connector)->
# * Integer
# * Array
#
class Yatta extends types.JsonType
class Yatta extends types.Object
#
# @param {String} user_id Unique id of the peer.
@ -34,7 +34,7 @@ createYatta = (connector)->
@connector = connector
@HB = HB
@types = types
@engine = new Engine @HB, type_manager.parser
@engine = new Engine @HB, type_manager.types
adaptConnector @connector, @engine, @HB, type_manager.execution_listener
super

View File

@ -45,19 +45,19 @@ class JsonTest extends Test
f : (y)=> # SET PROPERTY
y.val(@getRandomKey(), @getRandomText(), 'immutable')
null
types : [types.JsonType]
types : [types.Object]
,
f : (y)=> # SET Object Property 1)
y.val(@getRandomObject())
types: [types.JsonType]
types: [types.Object]
,
f : (y)=> # SET Object Property 2)
y.val(@getRandomKey(), @getRandomObject())
types: [types.JsonType]
types: [types.Object]
,
f : (y)=> # SET PROPERTY TEXT
y.val(@getRandomKey(), @getRandomText(), 'mutable')
types: [types.JsonType]
types: [types.Object]
]
describe "JsonFramework", ->
@ -73,30 +73,6 @@ describe "JsonFramework", ->
console.log "" # TODO
@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", ->
@yTest.compareAll()
@ -119,9 +95,9 @@ describe "JsonFramework", ->
@yTest.users[1].val('a', 't', "mutable")
@yTest.compareAll()
q = @yTest.users[2].val('a')
q.insertText(0,'A')
q.insert(0,'A')
@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)", ->
@yTest.getSomeUser().val('x', {'a':'b'})
@ -130,10 +106,18 @@ describe "JsonFramework", ->
@yTest.getSomeUser().val('c', {'a':'c'})
@yTest.getSomeUser().val('c', {'a':'b'})
@yTest.compareAll()
q = @yTest.getSomeUser().value.a.a.q
q.insertText(0,'A')
q = @yTest.getSomeUser().val("a").val("a").val("q")
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()
expect(@yTest.getSomeUser().value.a.a.q.val()).to.equal("Adtrndtrtdrntdrnrtdnrtdnrtdnrtdnrdnrdt")
it "handles immutables and primitive data types", ->
@yTest.getSomeUser().val('string', "text", "immutable")

View File

@ -12,9 +12,9 @@ Connector = require "../bower_components/connector/lib/test-connector/test-conne
module.exports = class Test
constructor: (@name_suffix = "")->
@number_of_test_cases_multiplier = 1
@repeat_this = 503 * @number_of_test_cases_multiplier
@doSomething_amount = 5 * @number_of_test_cases_multiplier
@number_of_engines = 3 + @number_of_test_cases_multiplier - 1
@repeat_this = 3 * @number_of_test_cases_multiplier
@doSomething_amount = 123 * @number_of_test_cases_multiplier
@number_of_engines = 5 + @number_of_test_cases_multiplier - 1
@time = 0 # denotes to the time when run was started
@ops = 0 # number of operations (used with @time)
@ -31,6 +31,7 @@ module.exports = class Test
for user in @users
u.getConnector().join(user.getConnector()) # TODO: change the test-connector to make this more convenient
@users.push u
@initUsers?(@users[0])
@flushAll()
# is called by implementing class
@ -74,17 +75,17 @@ module.exports = class Test
f : (y)=> # INSERT TEXT
y
pos = _.random 0, (y.val().length-1)
y.insertText pos, @getRandomText()
y.insert pos, @getRandomText()
null
types: [types.WordType]
types: [types.String]
,
f : (y)-> # DELETE TEXT
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)
ops1 = y.deleteText pos, length
ops1 = y.delete pos, length
undefined
types : [types.WordType]
types : [types.String]
]
getRandomRoot: (user_num)->
throw new Error "overwrite me!"
@ -99,7 +100,7 @@ module.exports = class Test
y instanceof type
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)
choices[i].f y

View File

@ -19,6 +19,9 @@ class TextTest extends Test
conn = new Connector userId
new Yatta conn
initUsers: (u)->
u.val("TextTest","","mutable")
getRandomRoot: (user_num)->
@users[user_num].val("TextTest")
@ -29,21 +32,17 @@ describe "TextFramework", ->
beforeEach (done)->
@timeout 50000
@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()
it "simple multi-char insert", ->
u = @yTest.users[0].val("TextTest")
u.insertText 0, "abc"
u.insert 0, "abc"
u = @yTest.users[1].val("TextTest")
u.insertText 0, "xyz"
u.insert 0, "xyz"
@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)", ->
u = @yTest.users[0].val("TextTest","my awesome Text","mutable").val("TextTest")
@ -59,7 +58,7 @@ describe "TextFramework", ->
expect(change.changedBy).to.equal('0')
last_task = "observer1"
u.observe observer1
u.insertText 1, "a"
u.insert 1, "a"
expect(last_task).to.equal("observer1")
u.unobserve observer1
@ -74,7 +73,7 @@ describe "TextFramework", ->
last_task = "observer2"
u.observe observer2
v = @yTest.users[1].val("TextTest")
v.insertText 0, "x"
v.insert 0, "x"
@yTest.flushAll()
expect(last_task).to.equal("observer2")
u.unobserve observer2
@ -93,7 +92,7 @@ describe "TextFramework", ->
expect(change.changedBy).to.equal('0')
last_task = "observer1"
u.observe observer1
u.deleteText 1, 1
u.delete 1, 1
expect(last_task).to.equal("observer1")
u.unobserve observer1
@ -108,7 +107,7 @@ describe "TextFramework", ->
last_task = "observer2"
u.observe observer2
v = @yTest.users[1].val("TextTest")
v.deleteText 0, 1
v.delete 0, 1
@yTest.flushAll()
expect(last_task).to.equal("observer2")
u.unobserve observer2

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long