Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
50fa81d191 | ||
|
|
93e75e0111 | ||
|
|
cbcdebf33e | ||
|
|
7c842efd52 | ||
|
|
60daa082f7 | ||
|
|
5681ba84bf |
19
README.md
19
README.md
@@ -1,23 +1,23 @@
|
|||||||
|
|
||||||
# 
|
# 
|
||||||
|
|
||||||
[](http://layers.dbis.rwth-aachen.de/jenkins/job/Yatta/)
|
[](http://layers.dbis.rwth-aachen.de/jenkins/job/Yatta/)
|
||||||
|
|
||||||
|
Yjs is a framework for optimistic concurrency control and automatic conflict resolution on arbitrary data types. The framework implements a new OT-like concurrency algorithm and provides similar functionality as [ShareJs] and [OpenCoweb]. Yjs was designed to take away the pain from concurrently editing complex data types like Text, Json, and XML. You can find some applications for this framework [here](https://dadamonad.github.io/yjs/examples/).
|
||||||
Y is a framework for optimistic concurrency control and automatic conflict resolution on arbitrary data types. The framework implements a new OT-like concurrency algorithm and provides similar functionality as [ShareJs] and [OpenCoweb]. Y was designed to take away the pain from concurrently editing complex data types like Text, Json, and XML. For more information you should check out the [website](https://dadamonad.github.io/yjs/)!
|
|
||||||
|
|
||||||
In the future, we want to enable users to implement their own collaborative types. Currently we provide data types for
|
In the future, we want to enable users to implement their own collaborative types. Currently we provide data types for
|
||||||
* Text
|
* Text
|
||||||
* Json
|
* Json
|
||||||
* XML
|
* XML
|
||||||
|
|
||||||
Unlike other frameworks, Y supports P2P message propagation and is not bound to a specific communication protocol. Therefore, Y is extremely scalable and can be used in a wide range of application scenarios.
|
Unlike other frameworks, Yjs supports P2P message propagation and is not bound to a specific communication protocol. Therefore, Yjs is extremely scalable and can be used in a wide range of application scenarios.
|
||||||
|
|
||||||
We support several communication protocols as so called *Connectors*. You find a bunch of Connectors in the [Y-Connectors](https://github.com/rwth-acis/y-connectors) repository. Currently supported communication protocols:
|
We support several communication protocols as so called *Connectors*. You find a bunch of Connectors in the [y-connectors](https://github.com/rwth-acis/y-connectors) repository. Currently supported communication protocols:
|
||||||
* [XMPP-Connector](http://xmpp.org) - Propagates updates in a XMPP multi-user-chat room
|
* [XMPP-Connector](http://xmpp.org) - Propagates updates in a XMPP multi-user-chat room
|
||||||
* [WebRTC-Connector](http://peerjs.com/) - Propagate updates directly with WebRTC
|
* [WebRTC-Connector](http://peerjs.com/) - Propagate updates directly with WebRTC
|
||||||
* [IWC-Connector](http://dbis.rwth-aachen.de/cms/projects/the-xmpp-experience#interwidget-communication) - Inter-widget Communication
|
* [IWC-Connector](http://dbis.rwth-aachen.de/cms/projects/the-xmpp-experience#interwidget-communication) - Inter-widget Communication
|
||||||
|
|
||||||
You can use Y client-, and server- side. You can get it as via npm, and bower. We even provide a polymer element for Y!
|
You can use Yjs client-, and server- side. You can get it as via npm, and bower. We even provide a polymer element for Yjs!
|
||||||
|
|
||||||
The theoretical advantages over similar frameworks are support for
|
The theoretical advantages over similar frameworks are support for
|
||||||
* .. P2P message propagation and arbitrary communication protocols
|
* .. P2P message propagation and arbitrary communication protocols
|
||||||
@@ -26,6 +26,7 @@ The theoretical advantages over similar frameworks are support for
|
|||||||
* .. AnyUndo: Undo *any* action that was executed in constant time (unimplemented)
|
* .. AnyUndo: Undo *any* action that was executed in constant time (unimplemented)
|
||||||
* .. Intention Preservation: When working on Text, the intention of your changes are preserved. This is particularily important when working offline.
|
* .. Intention Preservation: When working on Text, the intention of your changes are preserved. This is particularily important when working offline.
|
||||||
|
|
||||||
|
|
||||||
## Use it!
|
## Use it!
|
||||||
You find a tutorial, examples, and documentation on the [website](https://dadamonad.github.io/yjs/).
|
You find a tutorial, examples, and documentation on the [website](https://dadamonad.github.io/yjs/).
|
||||||
|
|
||||||
@@ -33,7 +34,7 @@ Either clone this git repository, install it with [bower](http://bower.io/), or
|
|||||||
|
|
||||||
### Bower
|
### Bower
|
||||||
```
|
```
|
||||||
bower install yjs
|
bower install rwth-acis/yjs
|
||||||
```
|
```
|
||||||
Then you include the libraries directly from the installation folder.
|
Then you include the libraries directly from the installation folder.
|
||||||
```
|
```
|
||||||
@@ -51,7 +52,7 @@ Y = require("yjs");
|
|||||||
```
|
```
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
yjs is still in an early development phase. Don't expect that everything is working fine.
|
Yjs is still in an early development phase. Don't expect that everything is working fine.
|
||||||
But I would become really motivated if you gave me some feedback :) ([github](https://github.com/rwth-acis/yjs/issues)).
|
But I would become really motivated if you gave me some feedback :) ([github](https://github.com/rwth-acis/yjs/issues)).
|
||||||
|
|
||||||
### Current Issues
|
### Current Issues
|
||||||
@@ -65,7 +66,7 @@ Please report _any_ issues to the [Github issue page](https://github.com/rwth-ac
|
|||||||
I would appreciate if developers give me feedback on how _convenient_ the framework is, and if it is easy to use. Particularly the XML-support may not support every DOM-methods - if you encounter a method that does not cause any change on other peers, please state function name, and sample parameters. However, there are browser-specific features, that Y won't support.
|
I would appreciate if developers give me feedback on how _convenient_ the framework is, and if it is easy to use. Particularly the XML-support may not support every DOM-methods - if you encounter a method that does not cause any change on other peers, please state function name, and sample parameters. However, there are browser-specific features, that Y won't support.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
yjs is licensed under the [MIT License](./LICENSE.txt).
|
Yjs is licensed under the [MIT License](./LICENSE.txt).
|
||||||
|
|
||||||
[ShareJs]: https://github.com/share/ShareJS
|
[ShareJs]: https://github.com/share/ShareJS
|
||||||
[OpenCoweb]: https://github.com/opencoweb/coweb
|
[OpenCoweb]: https://github.com/opencoweb/coweb
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "yjs",
|
"name": "yjs",
|
||||||
"version": "0.3.0",
|
"version": "0.3.1",
|
||||||
"homepage": "https://github.com/DadaMonad/yjs",
|
"homepage": "https://github.com/DadaMonad/yjs",
|
||||||
"authors": [
|
"authors": [
|
||||||
"Kevin Jahns <kevin.jahns@rwth-aachen.de>"
|
"Kevin Jahns <kevin.jahns@rwth-aachen.de>"
|
||||||
|
|||||||
2164
build/browser/y-object.js
Normal file
2164
build/browser/y-object.js
Normal file
File diff suppressed because one or more lines are too long
2069
build/browser/y.js
Normal file
2069
build/browser/y.js
Normal file
File diff suppressed because one or more lines are too long
@@ -55,14 +55,12 @@ adaptConnector = function(connector, engine, HB, execution_listener) {
|
|||||||
connector.getStateVector = getStateVector;
|
connector.getStateVector = getStateVector;
|
||||||
connector.getHB = getHB;
|
connector.getHB = getHB;
|
||||||
connector.applyHB = applyHB;
|
connector.applyHB = applyHB;
|
||||||
connector.whenReceiving(function(sender, op) {
|
connector.receive_handlers.push(function(sender, op) {
|
||||||
if (op.uid.creator !== HB.getUserId()) {
|
if (op.uid.creator !== HB.getUserId()) {
|
||||||
return engine.applyOp(op);
|
return engine.applyOp(op);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (connector._whenBoundToY != null) {
|
return connector.setIsBoundToY();
|
||||||
return connector._whenBoundToY();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = adaptConnector;
|
module.exports = adaptConnector;
|
||||||
|
|||||||
@@ -322,20 +322,23 @@ module.exports = function(HB) {
|
|||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
createRange = function(fix) {
|
createRange = function(fix) {
|
||||||
var clength, left, right, s;
|
var clength, edited_element, range, s;
|
||||||
|
range = {};
|
||||||
s = dom_root.getSelection();
|
s = dom_root.getSelection();
|
||||||
clength = textfield.textContent.length;
|
clength = textfield.textContent.length;
|
||||||
left = Math.min(s.anchorOffset, clength);
|
range.left = Math.min(s.anchorOffset, clength);
|
||||||
right = Math.min(s.focusOffset, clength);
|
range.right = Math.min(s.focusOffset, clength);
|
||||||
if (fix != null) {
|
if (fix != null) {
|
||||||
left = fix(left);
|
range.left = fix(range.left);
|
||||||
right = fix(right);
|
range.right = fix(range.right);
|
||||||
}
|
}
|
||||||
return {
|
edited_element = s.focusNode;
|
||||||
left: left,
|
if (edited_element === textfield || edited_element === textfield.childNodes[0]) {
|
||||||
right: right,
|
range.isReal = true;
|
||||||
isReal: true
|
} else {
|
||||||
};
|
range.isReal = false;
|
||||||
|
}
|
||||||
|
return range;
|
||||||
};
|
};
|
||||||
writeRange = function(range) {
|
writeRange = function(range) {
|
||||||
var r, s, textnode;
|
var r, s, textnode;
|
||||||
|
|||||||
92
build/node/y-object.js
Normal file
92
build/node/y-object.js
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
var Y, bindToChildren;
|
||||||
|
|
||||||
|
Y = require('./y');
|
||||||
|
|
||||||
|
bindToChildren = function(that) {
|
||||||
|
var attr, i, _i, _ref;
|
||||||
|
for (i = _i = 0, _ref = that.children.length; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) {
|
||||||
|
attr = that.children.item(i);
|
||||||
|
if (attr.name != null) {
|
||||||
|
attr.val = that.val.val(attr.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return that.val.observe(function(events) {
|
||||||
|
var event, newVal, _j, _len, _results;
|
||||||
|
_results = [];
|
||||||
|
for (_j = 0, _len = events.length; _j < _len; _j++) {
|
||||||
|
event = events[_j];
|
||||||
|
if (event.name != null) {
|
||||||
|
_results.push((function() {
|
||||||
|
var _k, _ref1, _results1;
|
||||||
|
_results1 = [];
|
||||||
|
for (i = _k = 0, _ref1 = that.children.length; 0 <= _ref1 ? _k < _ref1 : _k > _ref1; i = 0 <= _ref1 ? ++_k : --_k) {
|
||||||
|
attr = that.children.item(i);
|
||||||
|
if ((attr.name != null) && attr.name === event.name) {
|
||||||
|
newVal = that.val.val(attr.name);
|
||||||
|
if (attr.val !== newVal) {
|
||||||
|
_results1.push(attr.val = newVal);
|
||||||
|
} else {
|
||||||
|
_results1.push(void 0);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_results1.push(void 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return _results1;
|
||||||
|
})());
|
||||||
|
} else {
|
||||||
|
_results.push(void 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return _results;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Polymer("y-object", {
|
||||||
|
ready: function() {
|
||||||
|
if (this.connector != null) {
|
||||||
|
this.val = new Y(this.connector);
|
||||||
|
return bindToChildren(this);
|
||||||
|
} else if (this.val != null) {
|
||||||
|
return bindToChildren(this);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
valChanged: function() {
|
||||||
|
if ((this.val != null) && this.val.type === "Object") {
|
||||||
|
return bindToChildren(this);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
connectorChanged: function() {
|
||||||
|
if (this.val == null) {
|
||||||
|
this.val = new Y(this.connector);
|
||||||
|
return bindToChildren(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Polymer("y-property", {
|
||||||
|
ready: function() {
|
||||||
|
if ((this.val != null) && (this.name != null)) {
|
||||||
|
if (this.val.constructor === Object) {
|
||||||
|
this.val = this.parentElement.val(this.name, this.val).val(this.name);
|
||||||
|
} else if (typeof this.val === "string") {
|
||||||
|
this.parentElement.val(this.name, this.val);
|
||||||
|
}
|
||||||
|
if (this.val.type === "Object") {
|
||||||
|
return bindToChildren(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
valChanged: function() {
|
||||||
|
var _ref;
|
||||||
|
if ((this.val != null) && (this.name != null)) {
|
||||||
|
if (this.val.constructor === Object) {
|
||||||
|
return this.val = this.parentElement.val.val(this.name, this.val).val(this.name);
|
||||||
|
} else if (this.val.type === "Object") {
|
||||||
|
return bindToChildren(this);
|
||||||
|
} else if ((((_ref = this.parentElement.val) != null ? _ref.val : void 0) != null) && this.val !== this.parentElement.val.val(this.name)) {
|
||||||
|
return this.parentElement.val.val(this.name, this.val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
54
build/node/y.js
Normal file
54
build/node/y.js
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
var Engine, HistoryBuffer, adaptConnector, createY, json_types_uninitialized,
|
||||||
|
__hasProp = {}.hasOwnProperty,
|
||||||
|
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
|
||||||
|
|
||||||
|
json_types_uninitialized = require("./Types/JsonTypes");
|
||||||
|
|
||||||
|
HistoryBuffer = require("./HistoryBuffer");
|
||||||
|
|
||||||
|
Engine = require("./Engine");
|
||||||
|
|
||||||
|
adaptConnector = require("./ConnectorAdapter");
|
||||||
|
|
||||||
|
createY = function(connector) {
|
||||||
|
var HB, Y, type_manager, types, user_id;
|
||||||
|
user_id = null;
|
||||||
|
if (connector.id != null) {
|
||||||
|
user_id = connector.id;
|
||||||
|
} else {
|
||||||
|
user_id = "_temp";
|
||||||
|
connector.onUserIdSet(function(id) {
|
||||||
|
user_id = id;
|
||||||
|
return HB.resetUserId(id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
HB = new HistoryBuffer(user_id);
|
||||||
|
type_manager = json_types_uninitialized(HB);
|
||||||
|
types = type_manager.types;
|
||||||
|
Y = (function(_super) {
|
||||||
|
__extends(Y, _super);
|
||||||
|
|
||||||
|
function Y() {
|
||||||
|
this.connector = connector;
|
||||||
|
this.HB = HB;
|
||||||
|
this.types = types;
|
||||||
|
this.engine = new Engine(this.HB, type_manager.types);
|
||||||
|
adaptConnector(this.connector, this.engine, this.HB, type_manager.execution_listener);
|
||||||
|
Y.__super__.constructor.apply(this, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
Y.prototype.getConnector = function() {
|
||||||
|
return this.connector;
|
||||||
|
};
|
||||||
|
|
||||||
|
return Y;
|
||||||
|
|
||||||
|
})(types.Object);
|
||||||
|
return new Y(HB.getReservedUniqueIdentifier()).execute();
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = createY;
|
||||||
|
|
||||||
|
if ((typeof window !== "undefined" && window !== null) && (window.Y == null)) {
|
||||||
|
window.Y = createY;
|
||||||
|
}
|
||||||
16763
build/test/Json_test.js
Normal file
16763
build/test/Json_test.js
Normal file
File diff suppressed because one or more lines are too long
16535
build/test/Text_test.js
Normal file
16535
build/test/Text_test.js
Normal file
File diff suppressed because one or more lines are too long
38
examples/XMPP-Polymer/y-test.html
Normal file
38
examples/XMPP-Polymer/y-test.html
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<link rel="import" href="../../y-object.html">
|
||||||
|
<link rel="import" href="../../../y-connectors/y-xmpp/y-xmpp.html">
|
||||||
|
<link rel="import" href="../../../paper-slider/paper-slider.html">
|
||||||
|
|
||||||
|
<polymer-element name="y-test" attributes="y connector stuff">
|
||||||
|
<template>
|
||||||
|
<h1 id="text" contentEditable> Check this out !</h1>
|
||||||
|
<y-xmpp id="connector" connector={{connector}} room="testy-xmpp-polymer"></y-xmpp>
|
||||||
|
<y-object connector={{connector}} val={{y}}>
|
||||||
|
<y-property name="slider" val={{slider}}>
|
||||||
|
</y-property>
|
||||||
|
<y-property name="stuff" val={{stuff}}>
|
||||||
|
<y-property id="otherstuff" name="otherstuff" val={{otherstuff}}>
|
||||||
|
</y-property>
|
||||||
|
</y-property>
|
||||||
|
</y-object>
|
||||||
|
<y-object val={{otherstuff}}>
|
||||||
|
<y-property name="nostuff" val={{nostuff}}>
|
||||||
|
</y-property>
|
||||||
|
</y-object>
|
||||||
|
<paper-slider min="0" max="200" immediateValue={{slider}}></paper-slider>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
Polymer({
|
||||||
|
ready: function(){
|
||||||
|
window.y_stuff_property = this.$.otherstuff;
|
||||||
|
this.y.val("slider",50)
|
||||||
|
var that = this;
|
||||||
|
this.connector.whenSynced(function(){
|
||||||
|
if(that.y.val("text") == null){
|
||||||
|
that.y.val("text","stuff","mutable");
|
||||||
|
}
|
||||||
|
that.y.val("text").bind(that.$.text,that.shadowRoot)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
</polymer-element>
|
||||||
@@ -46,11 +46,10 @@ adaptConnector = (connector, engine, HB, execution_listener)->
|
|||||||
connector.getHB = getHB
|
connector.getHB = getHB
|
||||||
connector.applyHB = applyHB
|
connector.applyHB = applyHB
|
||||||
|
|
||||||
connector.whenReceiving (sender, op)->
|
connector.receive_handlers.push (sender, op)->
|
||||||
if op.uid.creator isnt HB.getUserId()
|
if op.uid.creator isnt HB.getUserId()
|
||||||
engine.applyOp op
|
engine.applyOp op
|
||||||
|
|
||||||
if connector._whenBoundToY?
|
connector.setIsBoundToY()
|
||||||
connector._whenBoundToY()
|
|
||||||
|
|
||||||
module.exports = adaptConnector
|
module.exports = adaptConnector
|
||||||
@@ -315,18 +315,21 @@ module.exports = (HB)->
|
|||||||
textfield.value = content
|
textfield.value = content
|
||||||
else
|
else
|
||||||
createRange = (fix)->
|
createRange = (fix)->
|
||||||
|
range = {}
|
||||||
s = dom_root.getSelection()
|
s = dom_root.getSelection()
|
||||||
clength = textfield.textContent.length
|
clength = textfield.textContent.length
|
||||||
left = Math.min s.anchorOffset, clength
|
range.left = Math.min s.anchorOffset, clength
|
||||||
right = Math.min s.focusOffset, clength
|
range.right = Math.min s.focusOffset, clength
|
||||||
if fix?
|
if fix?
|
||||||
left = fix left
|
range.left = fix range.left
|
||||||
right = fix right
|
range.right = fix range.right
|
||||||
{
|
|
||||||
left: left
|
edited_element = s.focusNode
|
||||||
right: right
|
if edited_element is textfield or edited_element is textfield.childNodes[0]
|
||||||
isReal: true
|
range.isReal = true
|
||||||
}
|
else
|
||||||
|
range.isReal = false
|
||||||
|
range
|
||||||
|
|
||||||
writeRange = (range)->
|
writeRange = (range)->
|
||||||
writeContent word.val()
|
writeContent word.val()
|
||||||
|
|||||||
59
lib/y-object.coffee
Normal file
59
lib/y-object.coffee
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
|
||||||
|
Y = require './y'
|
||||||
|
|
||||||
|
bindToChildren = (that)->
|
||||||
|
for i in [0...that.children.length]
|
||||||
|
attr = that.children.item(i)
|
||||||
|
if attr.name?
|
||||||
|
attr.val = that.val.val(attr.name)
|
||||||
|
that.val.observe (events)->
|
||||||
|
for event in events
|
||||||
|
if event.name?
|
||||||
|
for i in [0...that.children.length]
|
||||||
|
attr = that.children.item(i)
|
||||||
|
if attr.name? and attr.name is event.name
|
||||||
|
newVal = that.val.val(attr.name)
|
||||||
|
if attr.val isnt newVal
|
||||||
|
attr.val = newVal
|
||||||
|
|
||||||
|
Polymer "y-object",
|
||||||
|
ready: ()->
|
||||||
|
if @connector?
|
||||||
|
@val = new Y @connector
|
||||||
|
bindToChildren @
|
||||||
|
else if @val?
|
||||||
|
bindToChildren @
|
||||||
|
|
||||||
|
valChanged: ()->
|
||||||
|
if @val? and @val.type is "Object"
|
||||||
|
bindToChildren @
|
||||||
|
|
||||||
|
connectorChanged: ()->
|
||||||
|
if (not @val?)
|
||||||
|
@val = new Y @connector
|
||||||
|
bindToChildren @
|
||||||
|
|
||||||
|
Polymer "y-property",
|
||||||
|
ready: ()->
|
||||||
|
if @val? and @name?
|
||||||
|
if @val.constructor is Object
|
||||||
|
@val = @parentElement.val(@name,@val).val(@name)
|
||||||
|
# TODO: please use instanceof instead of .type,
|
||||||
|
# since it is more safe (consider someone putting a custom Object type here)
|
||||||
|
else if typeof @val is "string"
|
||||||
|
@parentElement.val(@name,@val)
|
||||||
|
if @val.type is "Object"
|
||||||
|
bindToChildren @
|
||||||
|
|
||||||
|
valChanged: ()->
|
||||||
|
if @val? and @name?
|
||||||
|
if @val.constructor is Object
|
||||||
|
@val = @parentElement.val.val(@name,@val).val(@name)
|
||||||
|
# TODO: please use instanceof instead of .type,
|
||||||
|
# since it is more safe (consider someone putting a custom Object type here)
|
||||||
|
else if @val.type is "Object"
|
||||||
|
bindToChildren @
|
||||||
|
else if @parentElement.val?.val? and @val isnt @parentElement.val.val(@name)
|
||||||
|
@parentElement.val.val @name, @val
|
||||||
|
|
||||||
|
|
||||||
48
lib/y.coffee
Normal file
48
lib/y.coffee
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
|
||||||
|
json_types_uninitialized = require "./Types/JsonTypes"
|
||||||
|
HistoryBuffer = require "./HistoryBuffer"
|
||||||
|
Engine = require "./Engine"
|
||||||
|
adaptConnector = require "./ConnectorAdapter"
|
||||||
|
|
||||||
|
createY = (connector)->
|
||||||
|
user_id = null
|
||||||
|
if connector.id?
|
||||||
|
user_id = connector.id # TODO: change to getUniqueId()
|
||||||
|
else
|
||||||
|
user_id = "_temp"
|
||||||
|
connector.onUserIdSet (id)->
|
||||||
|
user_id = id
|
||||||
|
HB.resetUserId id
|
||||||
|
HB = new HistoryBuffer user_id
|
||||||
|
type_manager = json_types_uninitialized HB
|
||||||
|
types = type_manager.types
|
||||||
|
|
||||||
|
#
|
||||||
|
# Framework for Json data-structures.
|
||||||
|
# Known values that are supported:
|
||||||
|
# * String
|
||||||
|
# * Integer
|
||||||
|
# * Array
|
||||||
|
#
|
||||||
|
class Y extends types.Object
|
||||||
|
|
||||||
|
#
|
||||||
|
# @param {String} user_id Unique id of the peer.
|
||||||
|
# @param {Connector} Connector the connector class.
|
||||||
|
#
|
||||||
|
constructor: ()->
|
||||||
|
@connector = connector
|
||||||
|
@HB = HB
|
||||||
|
@types = types
|
||||||
|
@engine = new Engine @HB, type_manager.types
|
||||||
|
adaptConnector @connector, @engine, @HB, type_manager.execution_listener
|
||||||
|
super
|
||||||
|
|
||||||
|
getConnector: ()->
|
||||||
|
@connector
|
||||||
|
|
||||||
|
return new Y(HB.getReservedUniqueIdentifier()).execute()
|
||||||
|
|
||||||
|
module.exports = createY
|
||||||
|
if window? and not window.Y?
|
||||||
|
window.Y = createY
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "yjs",
|
"name": "yjs",
|
||||||
"version": "0.3.0",
|
"version": "0.3.1",
|
||||||
"description": "A Framework that enables Real-Time Collaboration on arbitrary data structures.",
|
"description": "A Framework that enables Real-Time Collaboration on arbitrary data structures.",
|
||||||
"main": "./build/node/y.js",
|
"main": "./build/node/y.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
274
test/Json_test.coffee
Normal file
274
test/Json_test.coffee
Normal file
@@ -0,0 +1,274 @@
|
|||||||
|
chai = require('chai')
|
||||||
|
expect = chai.expect
|
||||||
|
should = chai.should()
|
||||||
|
sinon = require('sinon')
|
||||||
|
sinonChai = require('sinon-chai')
|
||||||
|
_ = require("underscore")
|
||||||
|
|
||||||
|
chai.use(sinonChai)
|
||||||
|
|
||||||
|
Connector = require "../../y-connectors/lib/y-test/y-test.coffee"
|
||||||
|
Y = require "../lib/y.coffee"
|
||||||
|
|
||||||
|
Test = require "./TestSuite"
|
||||||
|
|
||||||
|
class JsonTest extends Test
|
||||||
|
makeNewUser: (userId)->
|
||||||
|
conn = new Connector userId
|
||||||
|
super new Y conn
|
||||||
|
|
||||||
|
type: "JsonTest"
|
||||||
|
|
||||||
|
getRandomRoot: (user_num, root)->
|
||||||
|
root ?= @users[user_num]
|
||||||
|
types = @users[user_num].types
|
||||||
|
if _.random(0,1) is 1 # take root
|
||||||
|
root
|
||||||
|
else # take child
|
||||||
|
elems = null
|
||||||
|
if root.type is "Object"
|
||||||
|
elems =
|
||||||
|
for oname,val of root.val()
|
||||||
|
val
|
||||||
|
else if root.type is "Array"
|
||||||
|
elems = root.val()
|
||||||
|
else
|
||||||
|
return root
|
||||||
|
|
||||||
|
elems = elems.filter (elem)->
|
||||||
|
(elem.type is "Array") or (elem.type is "Object")
|
||||||
|
if elems.length is 0
|
||||||
|
root
|
||||||
|
else
|
||||||
|
p = elems[_.random(0, elems.length-1)]
|
||||||
|
@getRandomRoot user_num, p
|
||||||
|
|
||||||
|
|
||||||
|
getContent: (user_num)->
|
||||||
|
@users[user_num].toJson(true)
|
||||||
|
|
||||||
|
getGeneratingFunctions: (user_num)->
|
||||||
|
types = @users[user_num].types
|
||||||
|
super(user_num).concat [
|
||||||
|
f : (y)=> # SET PROPERTY
|
||||||
|
l = y.val().length
|
||||||
|
y.val(_.random(0, l-1), @getRandomText(), 'immutable')
|
||||||
|
null
|
||||||
|
types : [types.Array]
|
||||||
|
, f : (y)=> # Delete Array Element
|
||||||
|
list = y.val()
|
||||||
|
if list.length > 0
|
||||||
|
key = list[_random(0,list.length-1)]
|
||||||
|
y.delete(key)
|
||||||
|
types: [types.Array]
|
||||||
|
, f : (y)=> # insert TEXT mutable
|
||||||
|
l = y.val().length
|
||||||
|
y.val(_.random(0, l-1), @getRamdomObject())
|
||||||
|
types: [types.Array]
|
||||||
|
, f : (y)=> # insert string
|
||||||
|
l = y.val().length
|
||||||
|
y.val(_.random(0, l-1), @getRandomText(), 'immutable')
|
||||||
|
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]
|
||||||
|
]
|
||||||
|
|
||||||
|
describe "JsonFramework", ->
|
||||||
|
beforeEach (done)->
|
||||||
|
@timeout 50000
|
||||||
|
@yTest = new JsonTest()
|
||||||
|
@users = @yTest.users
|
||||||
|
|
||||||
|
@test_user = @yTest.makeNewUser "test_user"
|
||||||
|
done()
|
||||||
|
|
||||||
|
it "can handle many engines, many operations, concurrently (random)", ->
|
||||||
|
console.log "" # TODO
|
||||||
|
@yTest.run()
|
||||||
|
|
||||||
|
it "has a working test suite", ->
|
||||||
|
@yTest.compareAll()
|
||||||
|
|
||||||
|
it "handles double-late-join", ->
|
||||||
|
test = new JsonTest("double")
|
||||||
|
test.run()
|
||||||
|
@yTest.run()
|
||||||
|
u1 = test.users[0]
|
||||||
|
u2 = @yTest.users[1]
|
||||||
|
ops1 = u1.HB._encode()
|
||||||
|
ops2 = u2.HB._encode()
|
||||||
|
u1.HB.renewStateVector u2.HB.getOperationCounter()
|
||||||
|
u2.HB.renewStateVector u1.HB.getOperationCounter()
|
||||||
|
u1.engine.applyOps ops2
|
||||||
|
u2.engine.applyOps ops1
|
||||||
|
expect(test.getContent(0)).to.deep.equal(@yTest.getContent(1))
|
||||||
|
|
||||||
|
it "can handle creaton of complex json (1)", ->
|
||||||
|
@yTest.users[0].val('a', 'q', "mutable")
|
||||||
|
@yTest.users[1].val('a', 't', "mutable")
|
||||||
|
@yTest.compareAll()
|
||||||
|
q = @yTest.users[2].val('a')
|
||||||
|
q.insert(0,'A')
|
||||||
|
@yTest.compareAll()
|
||||||
|
expect(@yTest.getSomeUser().val("a").val()).to.equal("At")
|
||||||
|
|
||||||
|
it "can handle creaton of complex json (2)", ->
|
||||||
|
@yTest.getSomeUser().val('x', {'a':'b'})
|
||||||
|
@yTest.getSomeUser().val('a', {'a':{q:"dtrndtrtdrntdrnrtdnrtdnrtdnrtdnrdnrdt"}}, "mutable")
|
||||||
|
@yTest.getSomeUser().val('b', {'a':{}})
|
||||||
|
@yTest.getSomeUser().val('c', {'a':'c'})
|
||||||
|
@yTest.getSomeUser().val('c', {'a':'b'})
|
||||||
|
@yTest.compareAll()
|
||||||
|
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')
|
||||||
|
w = @yTest.users[1].val('l').insert(0,'B', "mutable").val(0)
|
||||||
|
w.insert 1, "C"
|
||||||
|
expect(w.val()).to.equal("BC")
|
||||||
|
@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('null', null)
|
||||||
|
@yTest.compareAll()
|
||||||
|
expect(@yTest.getSomeUser().val('string')).to.equal "text"
|
||||||
|
expect(@yTest.getSomeUser().val('number')).to.equal 4
|
||||||
|
expect(@yTest.getSomeUser().val('object').val('q')).to.equal "rr"
|
||||||
|
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"}, "immutable")
|
||||||
|
@yTest.users[0].val('null', null)
|
||||||
|
@yTest.compareAll()
|
||||||
|
expect(@yTest.getSomeUser().val('string')).to.equal "text"
|
||||||
|
expect(@yTest.getSomeUser().val('number')).to.equal 4
|
||||||
|
expect(@yTest.getSomeUser().val('object').val('q')).to.equal "rr"
|
||||||
|
expect(@yTest.getSomeUser().val('null') is null).to.be.ok
|
||||||
|
|
||||||
|
it "Observers work on JSON Types (add type observers, local and foreign)", ->
|
||||||
|
u = @yTest.users[0]
|
||||||
|
@yTest.flushAll()
|
||||||
|
last_task = null
|
||||||
|
observer1 = (changes)->
|
||||||
|
expect(changes.length).to.equal(1)
|
||||||
|
change = changes[0]
|
||||||
|
expect(change.type).to.equal("add")
|
||||||
|
expect(change.object).to.equal(u)
|
||||||
|
expect(change.changedBy).to.equal('0')
|
||||||
|
expect(change.name).to.equal("newStuff")
|
||||||
|
last_task = "observer1"
|
||||||
|
u.observe observer1
|
||||||
|
u.val("newStuff","someStuff","mutable")
|
||||||
|
expect(last_task).to.equal("observer1")
|
||||||
|
u.unobserve observer1
|
||||||
|
|
||||||
|
observer2 = (changes)->
|
||||||
|
expect(changes.length).to.equal(1)
|
||||||
|
change = changes[0]
|
||||||
|
expect(change.type).to.equal("add")
|
||||||
|
expect(change.object).to.equal(u)
|
||||||
|
expect(change.changedBy).to.equal('1')
|
||||||
|
expect(change.name).to.equal("moreStuff")
|
||||||
|
last_task = "observer2"
|
||||||
|
u.observe observer2
|
||||||
|
v = @yTest.users[1]
|
||||||
|
v.val("moreStuff","someMoreStuff")
|
||||||
|
@yTest.flushAll()
|
||||||
|
expect(last_task).to.equal("observer2")
|
||||||
|
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")
|
||||||
|
@yTest.flushAll()
|
||||||
|
last_task = null
|
||||||
|
observer1 = (changes)->
|
||||||
|
expect(changes.length).to.equal(1)
|
||||||
|
change = changes[0]
|
||||||
|
expect(change.type).to.equal("update")
|
||||||
|
expect(change.object).to.equal(u)
|
||||||
|
expect(change.changedBy).to.equal('0')
|
||||||
|
expect(change.name).to.equal("newStuff")
|
||||||
|
expect(change.oldValue.val()).to.equal("oldStuff")
|
||||||
|
last_task = "observer1"
|
||||||
|
u.observe observer1
|
||||||
|
u.val("newStuff","someStuff")
|
||||||
|
expect(last_task).to.equal("observer1")
|
||||||
|
u.unobserve observer1
|
||||||
|
|
||||||
|
observer2 = (changes)->
|
||||||
|
expect(changes.length).to.equal(1)
|
||||||
|
change = changes[0]
|
||||||
|
expect(change.type).to.equal("update")
|
||||||
|
expect(change.object).to.equal(u)
|
||||||
|
expect(change.changedBy).to.equal('1')
|
||||||
|
expect(change.name).to.equal("moreStuff")
|
||||||
|
expect(change.oldValue.val()).to.equal("moreOldStuff")
|
||||||
|
last_task = "observer2"
|
||||||
|
u.observe observer2
|
||||||
|
v = @yTest.users[1]
|
||||||
|
v.val("moreStuff","someMoreStuff")
|
||||||
|
@yTest.flushAll()
|
||||||
|
expect(last_task).to.equal("observer2")
|
||||||
|
u.unobserve observer2
|
||||||
|
|
||||||
|
|
||||||
|
it "Observers work on JSON Types (delete type observers, local and foreign)", ->
|
||||||
|
u = @yTest.users[0].val("newStuff","oldStuff","mutable").val("moreStuff","moreOldStuff","mutable")
|
||||||
|
@yTest.flushAll()
|
||||||
|
last_task = null
|
||||||
|
observer1 = (changes)->
|
||||||
|
expect(changes.length).to.equal(1)
|
||||||
|
change = changes[0]
|
||||||
|
expect(change.type).to.equal("delete")
|
||||||
|
expect(change.object).to.equal(u)
|
||||||
|
expect(change.changedBy).to.equal('0')
|
||||||
|
expect(change.name).to.equal("newStuff")
|
||||||
|
expect(change.oldValue.val()).to.equal("oldStuff")
|
||||||
|
last_task = "observer1"
|
||||||
|
u.observe observer1
|
||||||
|
u.delete("newStuff")
|
||||||
|
expect(last_task).to.equal("observer1")
|
||||||
|
u.unobserve observer1
|
||||||
|
|
||||||
|
observer2 = (changes)->
|
||||||
|
expect(changes.length).to.equal(1)
|
||||||
|
change = changes[0]
|
||||||
|
expect(change.type).to.equal("delete")
|
||||||
|
expect(change.object).to.equal(u)
|
||||||
|
expect(change.changedBy).to.equal('1')
|
||||||
|
expect(change.name).to.equal("moreStuff")
|
||||||
|
expect(change.oldValue.val()).to.equal("moreOldStuff")
|
||||||
|
last_task = "observer2"
|
||||||
|
u.observe observer2
|
||||||
|
v = @yTest.users[1]
|
||||||
|
v.delete("moreStuff")
|
||||||
|
@yTest.flushAll()
|
||||||
|
expect(last_task).to.equal("observer2")
|
||||||
|
u.unobserve observer2
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
119
test/Text_test.coffee
Normal file
119
test/Text_test.coffee
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
chai = require('chai')
|
||||||
|
expect = chai.expect
|
||||||
|
should = chai.should()
|
||||||
|
sinon = require('sinon')
|
||||||
|
sinonChai = require('sinon-chai')
|
||||||
|
_ = require("underscore")
|
||||||
|
|
||||||
|
chai.use(sinonChai)
|
||||||
|
|
||||||
|
Y = require "../lib/y"
|
||||||
|
Connector = require "../../y-connectors/lib/y-test/y-test.coffee"
|
||||||
|
|
||||||
|
Test = require "./TestSuite"
|
||||||
|
class TextTest extends Test
|
||||||
|
|
||||||
|
type: "TextTest"
|
||||||
|
|
||||||
|
makeNewUser: (userId)->
|
||||||
|
conn = new Connector userId
|
||||||
|
new Y conn
|
||||||
|
|
||||||
|
initUsers: (u)->
|
||||||
|
u.val("TextTest","","mutable")
|
||||||
|
|
||||||
|
getRandomRoot: (user_num)->
|
||||||
|
@users[user_num].val("TextTest")
|
||||||
|
|
||||||
|
getContent: (user_num)->
|
||||||
|
@users[user_num].val("TextTest").val()
|
||||||
|
|
||||||
|
describe "TextFramework", ->
|
||||||
|
beforeEach (done)->
|
||||||
|
@timeout 50000
|
||||||
|
@yTest = new TextTest()
|
||||||
|
done()
|
||||||
|
|
||||||
|
it "simple multi-char insert", ->
|
||||||
|
u = @yTest.users[0].val("TextTest")
|
||||||
|
u.insert 0, "abc"
|
||||||
|
u = @yTest.users[1].val("TextTest")
|
||||||
|
u.insert 0, "xyz"
|
||||||
|
@yTest.compareAll()
|
||||||
|
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")
|
||||||
|
@yTest.flushAll()
|
||||||
|
last_task = null
|
||||||
|
observer1 = (changes)->
|
||||||
|
expect(changes.length).to.equal(1)
|
||||||
|
change = changes[0]
|
||||||
|
expect(change.type).to.equal("insert")
|
||||||
|
expect(change.object).to.equal(u)
|
||||||
|
expect(change.value).to.equal("a")
|
||||||
|
expect(change.position).to.equal(1)
|
||||||
|
expect(change.changedBy).to.equal('0')
|
||||||
|
last_task = "observer1"
|
||||||
|
u.observe observer1
|
||||||
|
u.insert 1, "a"
|
||||||
|
expect(last_task).to.equal("observer1")
|
||||||
|
u.unobserve observer1
|
||||||
|
|
||||||
|
observer2 = (changes)->
|
||||||
|
expect(changes.length).to.equal(1)
|
||||||
|
change = changes[0]
|
||||||
|
expect(change.type).to.equal("insert")
|
||||||
|
expect(change.object).to.equal(u)
|
||||||
|
expect(change.value).to.equal("x")
|
||||||
|
expect(change.position).to.equal(0)
|
||||||
|
expect(change.changedBy).to.equal('1')
|
||||||
|
last_task = "observer2"
|
||||||
|
u.observe observer2
|
||||||
|
v = @yTest.users[1].val("TextTest")
|
||||||
|
v.insert 0, "x"
|
||||||
|
@yTest.flushAll()
|
||||||
|
expect(last_task).to.equal("observer2")
|
||||||
|
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")
|
||||||
|
@yTest.flushAll()
|
||||||
|
last_task = null
|
||||||
|
observer1 = (changes)->
|
||||||
|
expect(changes.length).to.equal(1)
|
||||||
|
change = changes[0]
|
||||||
|
expect(change.type).to.equal("delete")
|
||||||
|
expect(change.object).to.equal(u)
|
||||||
|
expect(change.position).to.equal(1)
|
||||||
|
expect(change.length).to.equal(1)
|
||||||
|
expect(change.changedBy).to.equal('0')
|
||||||
|
last_task = "observer1"
|
||||||
|
u.observe observer1
|
||||||
|
u.delete 1, 1
|
||||||
|
expect(last_task).to.equal("observer1")
|
||||||
|
u.unobserve observer1
|
||||||
|
|
||||||
|
observer2 = (changes)->
|
||||||
|
expect(changes.length).to.equal(1)
|
||||||
|
change = changes[0]
|
||||||
|
expect(change.type).to.equal("delete")
|
||||||
|
expect(change.object).to.equal(u)
|
||||||
|
expect(change.position).to.equal(0)
|
||||||
|
expect(change.length).to.equal(1)
|
||||||
|
expect(change.changedBy).to.equal('1')
|
||||||
|
last_task = "observer2"
|
||||||
|
u.observe observer2
|
||||||
|
v = @yTest.users[1].val("TextTest")
|
||||||
|
v.delete 0, 1
|
||||||
|
@yTest.flushAll()
|
||||||
|
expect(last_task).to.equal("observer2")
|
||||||
|
u.unobserve observer2
|
||||||
|
|
||||||
|
it "can handle many engines, many operations, concurrently (random)", ->
|
||||||
|
console.log("testiy deleted this TODO:dtrn")
|
||||||
|
@yTest.run()
|
||||||
|
|
||||||
|
|
||||||
196
test/Xml_test_browser.coffee
Normal file
196
test/Xml_test_browser.coffee
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
chai = require('chai')
|
||||||
|
expect = chai.expect
|
||||||
|
should = chai.should()
|
||||||
|
sinon = require('sinon')
|
||||||
|
sinonChai = require('sinon-chai')
|
||||||
|
_ = require("underscore")
|
||||||
|
$ = require("jquery")
|
||||||
|
document?.$ = $ # for browser
|
||||||
|
require 'coffee-errors'
|
||||||
|
|
||||||
|
chai.use(sinonChai)
|
||||||
|
|
||||||
|
Y = require "../lib/index"
|
||||||
|
Connector = require "../../Yatta-Connectors/lib/test-connector/test-connector.coffee"
|
||||||
|
|
||||||
|
Test = require "./TestSuite"
|
||||||
|
class XmlTest extends Test
|
||||||
|
|
||||||
|
type: "XmlTest"
|
||||||
|
|
||||||
|
makeNewUser: (user, conn)->
|
||||||
|
super new Y.XmlFramework user, conn
|
||||||
|
|
||||||
|
getRandomRoot: (user_num)->
|
||||||
|
@users[user_num].getSharedObject()
|
||||||
|
|
||||||
|
getContent: (user_num)->
|
||||||
|
@users[user_num].val()
|
||||||
|
|
||||||
|
|
||||||
|
describe "XmlFramework", ->
|
||||||
|
beforeEach (done)->
|
||||||
|
@timeout 50000
|
||||||
|
@yTest = new XmlTest()
|
||||||
|
###
|
||||||
|
|
||||||
|
@users = @yTest.users
|
||||||
|
###
|
||||||
|
test_users = []
|
||||||
|
connector = (new Connector 0, test_users)
|
||||||
|
@test_user = @yTest.makeNewUser 0, connector
|
||||||
|
test_users.push @test_user
|
||||||
|
# test_user_listen listens to the actions of test_user. He will update his dom when he receives from test_user.
|
||||||
|
@test_user_listen = @yTest.makeNewUser 2, connector
|
||||||
|
test_users.push @test_user_listen
|
||||||
|
@test_user2 = @yTest.makeNewUser 1, (Connector_uninitialized [])
|
||||||
|
|
||||||
|
$("#test_dom").replaceWith('<div id="test_dom" test_attribute="the test" class="stuffy" style="color: blue"><p id="replaceme">replace me</p><p id="removeme">remove me</p><p>This is a test object for <b>XmlFramework</b></p><span class="span_element"><p>span</p></span></div>')
|
||||||
|
@$dom = $("#test_dom")
|
||||||
|
@dom = @$dom[0]
|
||||||
|
@test_user.val(@dom)
|
||||||
|
@test_user_listen.getConnector().flushAll()
|
||||||
|
@test_user_listen_dom = @test_user_listen.val()
|
||||||
|
|
||||||
|
@check = ()=>
|
||||||
|
dom_ = @dom.outerHTML
|
||||||
|
# now test if other collaborators can parse the HB and result in the same content
|
||||||
|
hb = @test_user.HB._encode()
|
||||||
|
@test_user2.engine.applyOps(hb)
|
||||||
|
dom2 = @test_user2.val()
|
||||||
|
expect(dom_).to.equal(dom2.outerHTML)
|
||||||
|
@test_user_listen.getConnector().flushAll()
|
||||||
|
expect(dom_).to.equal(@test_user_listen_dom.outerHTML)
|
||||||
|
done()
|
||||||
|
|
||||||
|
it "can transform to a new real Dom element", ->
|
||||||
|
dom_ = @test_user.val(true)
|
||||||
|
expect(dom_ isnt @dom).to.be.true
|
||||||
|
|
||||||
|
it "supports dom.insertBefore", ->
|
||||||
|
newdom = $("<p>dtrn</p>")[0]
|
||||||
|
newdom2 = $("<p>dtrn2</p>")[0]
|
||||||
|
n = $("#removeme")[0]
|
||||||
|
@dom.insertBefore(newdom, null)
|
||||||
|
@dom.insertBefore(newdom2, n)
|
||||||
|
@check()
|
||||||
|
|
||||||
|
it "supports dom.appendChild", ->
|
||||||
|
newdom = $("<p>dtrn</p>")[0]
|
||||||
|
@dom.appendChild(newdom)
|
||||||
|
@check()
|
||||||
|
|
||||||
|
it "supports dom.setAttribute", ->
|
||||||
|
@dom.setAttribute("test_attribute", "newVal")
|
||||||
|
@check()
|
||||||
|
|
||||||
|
it "supports dom.removeAttribute", ->
|
||||||
|
@dom.removeAttribute("test_attribute")
|
||||||
|
@check()
|
||||||
|
|
||||||
|
it "supports dom.removeChild", ->
|
||||||
|
@dom.removeChild($("#removeme")[0])
|
||||||
|
expect($("#removeme").length).to.equal(0)
|
||||||
|
@check()
|
||||||
|
|
||||||
|
it "supports dom.replaceChild", ->
|
||||||
|
newdom = $("<p>replaced</p>")[0]
|
||||||
|
replace = $("#replaceme")[0]
|
||||||
|
@dom.replaceChild(newdom,replace)
|
||||||
|
expect($("#replaceme").length).to.equal(0)
|
||||||
|
@check()
|
||||||
|
|
||||||
|
it "supports dom.classList.add", ->
|
||||||
|
@dom.classList.add "classy"
|
||||||
|
@check()
|
||||||
|
|
||||||
|
|
||||||
|
it "supports dom.textcontent", -> #TODO!!!!
|
||||||
|
@dom.classList.add "classy"
|
||||||
|
@check()
|
||||||
|
|
||||||
|
it "supports jquery.addClass", ->
|
||||||
|
@$dom.addClass("testy")
|
||||||
|
@check()
|
||||||
|
|
||||||
|
it "supports jquery.after", ->
|
||||||
|
d = $("#test_dom p")
|
||||||
|
d.after("<div class=\"inserted_after\">after</div>")
|
||||||
|
@check()
|
||||||
|
|
||||||
|
it "supports jquery.append", ->
|
||||||
|
d = $("#test_dom p")
|
||||||
|
d.after("<b>appended</b>")
|
||||||
|
@check()
|
||||||
|
|
||||||
|
it "supports jquery.appendTo", ->
|
||||||
|
$("<b>appendedTo</b>").appendTo("#test_dom p")
|
||||||
|
$("p").appendTo("#test_dom")
|
||||||
|
@check()
|
||||||
|
|
||||||
|
it "supports jquery.before", ->
|
||||||
|
d = $("#test_dom p")
|
||||||
|
d.before("<div>before</div>")
|
||||||
|
@check()
|
||||||
|
|
||||||
|
it "supports jquery.detach", ->
|
||||||
|
d = $(".inserted_after")
|
||||||
|
d.detach()
|
||||||
|
@check()
|
||||||
|
|
||||||
|
it "supports jquery.empty", ->
|
||||||
|
d = $("#test_dom p")
|
||||||
|
d.empty()
|
||||||
|
@check()
|
||||||
|
|
||||||
|
it "supports jquery.insertAfter", ->
|
||||||
|
$("<p>after span</p>").insertAfter(".span_element")
|
||||||
|
@check()
|
||||||
|
|
||||||
|
it "supports jquery.insertBefore", ->
|
||||||
|
$("<p>before span</p>").insertBefore(".span_element")
|
||||||
|
@check()
|
||||||
|
|
||||||
|
it "supports jquery.prepend", ->
|
||||||
|
d = $("#test_dom div")
|
||||||
|
d.prepend("<p>prepended</p>")
|
||||||
|
@check()
|
||||||
|
|
||||||
|
it "supports jquery.prependTo", ->
|
||||||
|
$("<p atone=false attwo=\"dtrn\" class=\"attr_node sudo su\">prepended to</p>").prependTo("#test_dom div")
|
||||||
|
@check()
|
||||||
|
|
||||||
|
it "supports jquery.remove", ->
|
||||||
|
d = $("#test_dom b")
|
||||||
|
d.remove()
|
||||||
|
@check()
|
||||||
|
|
||||||
|
it "supports jquery.removeAttr", ->
|
||||||
|
d = $(".attr_node")
|
||||||
|
d.removeAttr("attwo")
|
||||||
|
@check()
|
||||||
|
|
||||||
|
it "supports jquery.removeClass", ->
|
||||||
|
d = $(".attr_node")
|
||||||
|
d.removeClass("sudo")
|
||||||
|
@check()
|
||||||
|
|
||||||
|
it "supports jquery.attr", ->
|
||||||
|
d = $(".attr_node")
|
||||||
|
d.attr("atone", true)
|
||||||
|
@check()
|
||||||
|
|
||||||
|
it "supports jquery.replaceAll", ->
|
||||||
|
$("<span>New span content </span>").replaceAll("#test_dom div")
|
||||||
|
@check()
|
||||||
|
|
||||||
|
it "supports jquery.replaceWith", ->
|
||||||
|
d = $("#test_dom span")
|
||||||
|
d.replaceWith("<div>me is div again </div>")
|
||||||
|
@check()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
8
y-object.html
Normal file
8
y-object.html
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
|
||||||
|
<polymer-element name="y-object" hidden attributes="val connector y">
|
||||||
|
</polymer-element>
|
||||||
|
<polymer-element name="y-property" hidden attributes="val name y">
|
||||||
|
</polymer-element>
|
||||||
|
|
||||||
|
<script src="./build/browser/y-object.js"></script>
|
||||||
2
y-object.js
Normal file
2
y-object.js
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user