graph-crdt
Version:
A Delta State Graph CRDT variant
287 lines (201 loc) • 8.14 kB
JavaScript
;
var _slicedToArray2 = require('babel-runtime/helpers/slicedToArray');
var _slicedToArray3 = _interopRequireDefault(_slicedToArray2);
var _toConsumableArray2 = require('babel-runtime/helpers/toConsumableArray');
var _toConsumableArray3 = _interopRequireDefault(_toConsumableArray2);
var _expect = require('expect');
var _expect2 = _interopRequireDefault(_expect);
var _index = require('./index');
var _index2 = _interopRequireDefault(_index);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/* eslint-env mocha */
describe('A node merge', function () {
var node = void 0;
beforeEach(function () {
node = new _index2.default();
});
it('namespaces to avoid conflicts', function () {
node.merge({ read: 'not a function' });
(0, _expect2.default)(node.value).toBeA(Function);
});
it('converts POJOs into Nodes', function () {
node.merge({ data: 'success' });
(0, _expect2.default)(node.value('data')).toBe('success');
});
it('uses the same node ID for new change nodes', function () {
var _node$merge = node.merge({}),
update = _node$merge.update,
history = _node$merge.history;
var _node$meta = node.meta(),
uid = _node$meta.uid;
(0, _expect2.default)(update.meta()).toContain({ uid: uid });
(0, _expect2.default)(history.meta()).toContain({ uid: uid });
});
describe('within operating state bounds', function () {
it('adds all new properties', function () {
var update = _index2.default.from({ data: true });
node.merge(update);
var keys = [].concat((0, _toConsumableArray3.default)(node)).map(function (_ref) {
var _ref2 = (0, _slicedToArray3.default)(_ref, 1),
key = _ref2[0];
return key;
});
(0, _expect2.default)(keys).toContain('data');
});
it('updates existing properties', function () {
var incoming = _index2.default.from({ data: false });
node.merge(incoming);
(0, _expect2.default)(node.value('data')).toBe(false);
incoming.merge({ data: true });
node.merge(incoming);
(0, _expect2.default)(node.value('data')).toBe(true);
});
it('emits `update` after updates', function () {
var spy = (0, _expect.createSpy)();
node.on('update', spy);
node.merge({ data: 'yep' });
var _spy$calls$0$argument = (0, _slicedToArray3.default)(spy.calls[0].arguments, 1),
value = _spy$calls$0$argument[0];
(0, _expect2.default)(value).toBeA(_index2.default);
var object = value.snapshot();
(0, _expect2.default)(object).toEqual({ data: 'yep' });
});
it('returns the updates', function () {
var _node$merge2 = node.merge({ data: 'yep' }),
update = _node$merge2.update;
(0, _expect2.default)(update).toBeA(_index2.default);
var object = update.snapshot();
(0, _expect2.default)(object).toEqual({
data: 'yep'
});
});
it('does not emit `update` without updates', function () {
var spy = (0, _expect.createSpy)();
node.on('update', spy);
// No properties.
node.merge({});
(0, _expect2.default)(spy).toNotHaveBeenCalled();
});
it('does not emit `update` for the same update', function () {
var spy = (0, _expect.createSpy)();
node.on('update', spy);
var update = _index2.default.from({ update: true });
node.merge(update);
node.merge(update);
(0, _expect2.default)(spy.calls.length).toBe(1);
});
it('merges other nodes', function () {
var weather = new _index2.default({ uid: 'weather' });
weather.merge({ condition: 'sunny', temp: 65 });
var change = new _index2.default({ uid: 'weather' });
change.merge(weather);
change.merge({ temp: 70 });
weather.merge(change);
(0, _expect2.default)(weather.state('temp')).toBe(2);
(0, _expect2.default)(weather.value('temp')).toBe(70);
});
});
describe('in historical state', function () {
var incoming = void 0;
beforeEach(function () {
incoming = _index2.default.create();
});
it('does not overwrite more recent properties', function () {
// Stale update.
incoming.merge({ hello: 'Mars' });
incoming.meta('hello').state = 1;
var update = _index2.default.from({ hello: 'World' });
update.meta('hello').state = 2;
node.merge(update);
node.merge(incoming);
(0, _expect2.default)(node.value('hello')).toBe('World');
});
it('adds new properties', function () {
// Really old state, but it's new to `node`.
incoming.merge({ success: true });
incoming.meta('success').state = 10;
node.merge(incoming);
(0, _expect2.default)(node.value('success')).toBe(true);
});
it('returns the history', function () {
incoming.merge({ old: true });
incoming.meta('old').state = 1;
node.merge({ old: false });
node.meta('old').state = 2;
var _node$merge3 = node.merge(incoming),
history = _node$merge3.history;
(0, _expect2.default)(history).toBeA(_index2.default);
var object = history.snapshot();
(0, _expect2.default)(object).toEqual({ old: true });
});
it('includes overwritten values in history', function () {
node.merge({ old: true });
var _node$merge4 = node.merge({ old: false }),
history = _node$merge4.history;
var object = history.snapshot();
(0, _expect2.default)(object).toEqual({ old: true });
});
it('emits `history` if updates are outdated', function () {
incoming.merge({ data: 'old state' });
incoming.meta('data').state = 1;
node.merge({ data: 'new state' });
node.meta('data').state = 2;
var spy = (0, _expect.createSpy)();
node.on('history', spy);
node.merge(incoming);
var _spy$calls$0$argument2 = (0, _slicedToArray3.default)(spy.calls[0].arguments, 1),
history = _spy$calls$0$argument2[0];
(0, _expect2.default)(history).toBeA(_index2.default);
var object = history.snapshot();
(0, _expect2.default)(object).toEqual({ data: 'old state' });
});
it('does not emit `history` without stale updates', function () {
var spy = (0, _expect.createSpy)();
node.on('history', spy);
incoming.merge({ 'new property': 'yeah' });
incoming.meta('new property').state = 1;
node.merge({ hello: 'Earth' });
node.merge(incoming);
(0, _expect2.default)(spy).toNotHaveBeenCalled();
});
});
describe('conflict', function () {
var conflict = void 0;
beforeEach(function () {
conflict = new _index2.default();
node.merge({ value: '9' });
conflict.merge({ value: '5' });
// Same state.
node.meta('value').state = conflict.meta('value').state;
});
it('is ignored if it loses', function () {
var _node$merge5 = node.merge(conflict),
update = _node$merge5.update,
history = _node$merge5.history;
(0, _expect2.default)(update.snapshot()).toEqual({});
(0, _expect2.default)(history.snapshot()).toEqual({});
});
it('triggers an update if it wins', function () {
node.meta('value').value = '1';
var _node$merge6 = node.merge(conflict),
update = _node$merge6.update;
(0, _expect2.default)(update.snapshot()).toEqual({ value: '5' });
});
it('emits `conflict` if it wins', function () {
var spy = (0, _expect.createSpy)();
node.on('conflict', spy);
var current = node.meta('value');
var update = conflict.meta('value');
current.value = '1';
node.merge(conflict);
(0, _expect2.default)(spy).toHaveBeenCalled();
(0, _expect2.default)(spy).toHaveBeenCalledWith(update, current);
});
it('does not emit `conflict` if it loses', function () {
var spy = (0, _expect.createSpy)();
node.on('conflict', spy);
node.merge(conflict);
(0, _expect2.default)(spy).toNotHaveBeenCalled();
});
});
});