graph-crdt
Version:
A Delta State Graph CRDT variant
427 lines (302 loc) • 13.8 kB
JavaScript
;
var _toConsumableArray2 = require('babel-runtime/helpers/toConsumableArray');
var _toConsumableArray3 = _interopRequireDefault(_toConsumableArray2);
var _slicedToArray2 = require('babel-runtime/helpers/slicedToArray');
var _slicedToArray3 = _interopRequireDefault(_slicedToArray2);
var _stringify = require('babel-runtime/core-js/json/stringify');
var _stringify2 = _interopRequireDefault(_stringify);
var _defineProperty2 = require('babel-runtime/helpers/defineProperty');
var _defineProperty3 = _interopRequireDefault(_defineProperty2);
var _expect = require('expect');
var _expect2 = _interopRequireDefault(_expect);
var _Graph = require('../Graph');
var _Graph2 = _interopRequireDefault(_Graph);
var _Node = require('../Node');
var _Node2 = _interopRequireDefault(_Node);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
describe('Graph static method', function () {
describe('source()', function () {
it('uses the input as it\'s data source', function () {
var node = _Node2.default.create({ uid: 'member' });
var graph = _Graph2.default.create();
node.merge({ data: true });
graph.merge((0, _defineProperty3.default)({}, node, node));
var copy = _Graph2.default.source(graph.toJSON());
(0, _expect2.default)(copy.value('member').value('data')).toBe(true);
});
it('sources nested POJOs into nodes', function () {
var node = _Node2.default.create();
node.merge({ data: true });
var copy = JSON.parse((0, _stringify2.default)(node));
var graph = _Graph2.default.source({
// "copy" is a plain object.
'placeholder': copy
});
var _map = [].concat((0, _toConsumableArray3.default)(graph)).map(function (_ref) {
var _ref2 = (0, _slicedToArray3.default)(_ref, 1),
key = _ref2[0];
return key;
}),
_map2 = (0, _slicedToArray3.default)(_map, 1),
key = _map2[0];
(0, _expect2.default)(graph.value(key)).toBeA(_Node2.default);
});
});
}); /* eslint-env mocha */
describe('A graph', function () {
var graph = void 0;
beforeEach(function () {
graph = new _Graph2.default();
});
it('is initialized empty', function () {
var keys = [].concat((0, _toConsumableArray3.default)(graph)).map(function (_ref3) {
var _ref4 = (0, _slicedToArray3.default)(_ref3, 1),
key = _ref4[0];
return key;
});
(0, _expect2.default)(keys.length).toBe(0);
});
it('returns the nodes when `toJSON` is called', function () {
var node = _Node2.default.create({ uid: 'unique id' });
node.merge({ data: 'intact' });
graph.merge((0, _defineProperty3.default)({}, node, node));
var string = (0, _stringify2.default)(graph);
(0, _expect2.default)(string).toContain('unique id');
(0, _expect2.default)(string).toContain('intact');
});
describe('iterator()', function () {
it('lists all the node indices', function () {
var _graph$merge3;
var first = _Node2.default.create({ uid: 'first' });
var second = _Node2.default.create({ uid: 'second' });
graph.merge((_graph$merge3 = {}, (0, _defineProperty3.default)(_graph$merge3, first, first), (0, _defineProperty3.default)(_graph$merge3, second, second), _graph$merge3));
var entries = [].concat((0, _toConsumableArray3.default)(graph));
(0, _expect2.default)(entries).toEqual([['first', first], ['second', second]]);
});
});
describe('read()', function () {
var node = void 0;
beforeEach(function () {
node = _Node2.default.create({ uid: 'dave' });
graph.merge((0, _defineProperty3.default)({}, node, node));
});
it('returns existing nodes', function () {
var result = graph.value(node.toString());
(0, _expect2.default)(result.meta()).toContain({
uid: String(node)
});
});
it('returns null for non-existent nodes', function () {
var result = graph.value('potato');
(0, _expect2.default)(result).toBe(null);
});
});
describe('merge()', function () {
var node1 = void 0,
node2 = void 0,
subgraph = void 0;
beforeEach(function () {
var _Graph$source;
node1 = _Node2.default.create();
node2 = _Node2.default.create();
subgraph = _Graph2.default.source((_Graph$source = {}, (0, _defineProperty3.default)(_Graph$source, node1, node1), (0, _defineProperty3.default)(_Graph$source, node2, node2), _Graph$source));
});
it('ensures the subgraph is a Graph instance', function () {
var _node1$meta = node1.meta(),
uid = _node1$meta.uid;
node1.merge({
value: 'preserved'
});
graph.merge((0, _defineProperty3.default)({}, uid, node1));
var result = graph.value(uid);
(0, _expect2.default)(result.snapshot()).toEqual(node1.snapshot());
});
it('adds all the items in the subgraph', function () {
graph.merge(subgraph);
var keys = [].concat((0, _toConsumableArray3.default)(graph)).map(function (_ref5) {
var _ref6 = (0, _slicedToArray3.default)(_ref5, 1),
key = _ref6[0];
return key;
});
(0, _expect2.default)(keys).toContain(node1.toString());
(0, _expect2.default)(keys).toContain(node2.toString());
});
it('assumes sub-objects are already formatted', function () {
node1.merge({ data: 'preserved' });
graph.merge((0, _defineProperty3.default)({}, node1, node1.toJSON()));
var result = graph.value(node1.toString());
(0, _expect2.default)(result.value('data')).toBe('preserved');
});
it('adds node copies, not originals', function () {
var _node1$meta2 = node1.meta(),
uid = _node1$meta2.uid;
node1.merge({ isNode1: true });
graph.merge((0, _defineProperty3.default)({}, node1, node1));
var copied = graph.value(uid);
(0, _expect2.default)(copied.snapshot()).toEqual({ isNode1: true });
(0, _expect2.default)(copied).toNotBe(node1);
});
it('returns the update delta', function () {
var _graph$merge8 = graph.merge((0, _defineProperty3.default)({}, node2, node2)),
update = _graph$merge8.update;
(0, _expect2.default)([].concat((0, _toConsumableArray3.default)(update))).toEqual([[String(node2), node2]]);
});
it('emits an `update` delta graph on change', function () {
var spy = (0, _expect.createSpy)();
graph.on('update', spy);
var _graph$merge10 = graph.merge((0, _defineProperty3.default)({}, node1, node1)),
update = _graph$merge10.update;
(0, _expect2.default)(spy).toHaveBeenCalledWith(update);
});
it('returns the history delta', function () {
var data = new _Node2.default({ uid: 'existing' });
var update = new _Node2.default({ uid: 'existing' });
data.merge({ old: true });
data.meta('old').state = 1;
update.merge({ old: false });
update.meta('old').state = 2;
graph.merge((0, _defineProperty3.default)({}, data, data));
var _graph$merge13 = graph.merge((0, _defineProperty3.default)({}, update, update)),
history = _graph$merge13.history;
var node = history.value('existing');
(0, _expect2.default)(node).toBeA(_Node2.default);
(0, _expect2.default)(node.snapshot()).toEqual({ old: true });
});
it('emits a `history` graph delta', function () {
var spy = (0, _expect.createSpy)();
graph.on('history', spy);
var node = new _Node2.default({ uid: 'node' });
var update = new _Node2.default({ uid: 'node' });
graph.merge((0, _defineProperty3.default)({}, node, node));
var _graph$merge16 = graph.merge((0, _defineProperty3.default)({}, update, update)),
history = _graph$merge16.history;
(0, _expect2.default)(spy).toHaveBeenCalledWith(history);
});
it('uses Graph#new to create deltas', function () {
var spy = (0, _expect.spyOn)(graph, 'new').andCall(function () {
var graph = new _Graph2.default();
graph.viaNew = true;
return graph;
});
var _graph$merge18 = graph.merge({}),
update = _graph$merge18.update,
history = _graph$merge18.history;
(0, _expect2.default)(update).toContain({ viaNew: true });
(0, _expect2.default)(history).toContain({ viaNew: true });
spy.restore();
});
});
describe('new()', function () {
var node = void 0;
beforeEach(function () {
node = new _Node2.default();
});
it('returns a new graph', function () {
graph.merge((0, _defineProperty3.default)({}, node, node));
var copy = graph.new();
(0, _expect2.default)(copy).toBeA(_Graph2.default);
// Must be a copy.
(0, _expect2.default)(copy).toNotBe(graph);
});
});
describe('rebase()', function () {
var target = void 0;
beforeEach(function () {
target = new _Graph2.default();
});
it('returns a graph', function () {
var result = graph.rebase(target);
(0, _expect2.default)(result).toBeA(_Graph2.default);
(0, _expect2.default)(result).toNotBe(graph);
(0, _expect2.default)(result).toNotBe(target);
});
it('contains all the state of the target', function () {
var node = _Node2.default.from({ existing: true });
target.merge((0, _defineProperty3.default)({}, node, node));
var result = graph.rebase(target);
(0, _expect2.default)([].concat((0, _toConsumableArray3.default)(result))).toEqual([].concat((0, _toConsumableArray3.default)(target)));
});
it('contains all the graph state', function () {
var node = _Node2.default.from({ existing: true });
graph.merge((0, _defineProperty3.default)({}, node, node));
var result = graph.rebase(target);
(0, _expect2.default)([].concat((0, _toConsumableArray3.default)(result))).toEqual([].concat((0, _toConsumableArray3.default)(graph)));
});
it('rebases every node', function () {
var node = _Node2.default.from({ existing: true });
graph.merge((0, _defineProperty3.default)({}, node, node));
target.merge((0, _defineProperty3.default)({}, node, node));
var spy = (0, _expect.spyOn)(graph.value(node), 'rebase').andCallThrough();
graph.rebase(target);
(0, _expect2.default)(spy).toHaveBeenCalled();
});
it('uses the rebased node in the new graph', function () {
var node = new _Node2.default();
var current = node.new();
var old = node.new();
old.merge({ old: true });
current.merge({ old: false });
graph.merge((0, _defineProperty3.default)({}, node, current));
target.merge((0, _defineProperty3.default)({}, node, old));
var result = graph.rebase(target).value(node.meta().uid);
(0, _expect2.default)(result.value('old')).toBe(false);
(0, _expect2.default)(result.state('old')).toBe(2);
});
});
describe('overlap()', function () {
var target = void 0;
beforeEach(function () {
target = new _Graph2.default();
});
it('returns a new graph', function () {
var result = graph.overlap(target);
(0, _expect2.default)(result).toBeA(_Graph2.default);
(0, _expect2.default)(result).toNotBe(graph);
(0, _expect2.default)(result).toNotBe(target);
});
it('contains no nodes if the target is empty', function () {
var node = new _Node2.default();
graph.merge((0, _defineProperty3.default)({}, node, node));
var result = graph.overlap(target);
(0, _expect2.default)([].concat((0, _toConsumableArray3.default)(result))).toEqual([]);
});
it('contains no nodes if the source is empty', function () {
var node = new _Node2.default();
target.merge((0, _defineProperty3.default)({}, node, node));
var result = graph.overlap(target);
(0, _expect2.default)([].concat((0, _toConsumableArray3.default)(result))).toEqual([]);
});
it('contains nodes shared by both graphs', function () {
var node1 = new _Node2.default();
var node2 = new _Node2.default({ uid: String(node1) });
graph.merge((0, _defineProperty3.default)({}, node1, node1));
target.merge((0, _defineProperty3.default)({}, node2, node2));
var result = graph.overlap(target);
(0, _expect2.default)([].concat((0, _toConsumableArray3.default)(result))).toEqual([].concat((0, _toConsumableArray3.default)(graph)));
});
it('only contains overlapping node fields', function () {
var node1 = new _Node2.default({ uid: 'tweets' });
var node2 = new _Node2.default({ uid: 'tweets' });
node1.merge({ node1: true, shared: true });
node2.merge({ node2: true, shared: true });
graph.merge((0, _defineProperty3.default)({}, node1, node1));
target.merge((0, _defineProperty3.default)({}, node2, node2));
var result = graph.overlap(target);
var _node1$meta3 = node1.meta(),
uid = _node1$meta3.uid;
(0, _expect2.default)([].concat((0, _toConsumableArray3.default)(result.value(uid)))).toEqual([['shared', true]]);
});
it('uses the values from the source graph', function () {
var node1 = new _Node2.default({ uid: 'node' });
var node2 = new _Node2.default({ uid: 'node' });
node1.merge({ shared: 'source' });
node2.merge({ shared: 'target' });
graph.merge((0, _defineProperty3.default)({}, node1, node1));
target.merge((0, _defineProperty3.default)({}, node2, node2));
var result = graph.overlap(target);
var _node1$meta4 = node1.meta(),
uid = _node1$meta4.uid;
(0, _expect2.default)(result.value(uid).value('shared')).toBe('source');
});
});
});