omniscient
Version:
A library providing an abstraction for React components for passing the same data structure through the entire component flow using cursors and immutable data structures.
533 lines (453 loc) • 14.4 kB
JavaScript
var Immutable = require('immutable');
var Cursor = require('immutable/contrib/cursor');
var component = require('../');
var shouldComponentUpdate = require('../shouldupdate');
var isCursor = shouldComponentUpdate.isCursor;
describe('shouldComponentUpdate', () => {
describe('should update', () => {
test('when cursors are different', () => {
var data = Immutable.fromJS({ foo: 'bar', bar: [1, 2, 3] });
shouldUpdate({
cursor: Cursor.from(data, ['foo']),
nextCursor: Cursor.from(data, ['bar'])
});
});
test('when a cursor changes to a non-cursor', () => {
var data = Immutable.fromJS({ foo: 'bar', bar: [1, 2, 3] });
var data2 = Immutable.fromJS({ foo: 'cat', bar: [1, 2, 3] });
shouldUpdate({
cursor: { one: Cursor.from(data, ['foo']) },
nextCursor: { one: data2 }
});
});
test("when there's suddenly a cursor", () => {
var data = Immutable.fromJS({ foo: 'bar', bar: [1, 2, 3] });
shouldUpdate({ nextCursor: Cursor.from(data, ['bar']) });
});
test("when there's no longer a cursor", () => {
var data = Immutable.fromJS({ foo: 'bar', bar: [1, 2, 3] });
shouldUpdate({ cursor: Cursor.from(data, ['bar']) });
});
test('when one of multiple cursors have changed', () => {
var data = Immutable.fromJS({ foo: 'bar', bar: [1, 2, 3] });
var data2 = Immutable.fromJS({ baz: [1, 2, 3] });
shouldUpdate({
cursor: { one: Cursor.from(data, ['foo']), two: Cursor.from(data2) },
nextCursor: {
one: Cursor.from(data, ['foo']).update(function() {
return 1;
}),
two: Cursor.from(data2)
}
});
});
test('when object literal has changed even if the cursor is the same', () => {
var data = Immutable.fromJS({ foo: 'bar' });
shouldUpdate({
cursor: { one: Cursor.from(data), two: { foo: 'hello' } },
nextCursor: { one: Cursor.from(data), two: { bar: 'good bye' } }
});
});
test('when same cursors change keys', () => {
var data = Immutable.fromJS({ foo: 'bar', bar: [1, 2, 3] });
shouldUpdate({
cursor: { one: Cursor.from(data, ['foo']) },
nextCursor: { changed: Cursor.from(data, ['foo']) }
});
});
test('when props has changed but not state', () => {
var data = Immutable.fromJS({ foo: 'bar', bar: [1, 2, 3] });
var state = { foo: 'hello' };
shouldUpdate({
cursor: { one: Cursor.from(data, ['foo']) },
nextCursor: { two: Cursor.from(data, ['foo']) },
state: state,
nextState: state
});
});
test('when state has changed but not props', () => {
var data = Immutable.fromJS({ foo: 'bar', bar: [1, 2, 3] });
var props = { one: Cursor.from(data, ['foo']) };
shouldUpdate({
cursor: props,
nextCursor: props,
state: { foo: 'hello' },
nextState: { foo: 'bar' }
});
});
test('when state has changed', () => {
shouldUpdate({
state: { foo: 'hello' },
nextState: { foo: 'bar' }
});
});
test('when deep state has changed', () => {
shouldUpdate({
state: { foo: { bar: 'hello' } },
nextState: { foo: { bar: 'bye' } }
});
});
test('when state have immutable structures', () => {
shouldUpdate({
state: { foo: Immutable.List.of(1) },
nextState: { foo: Immutable.List.of(2) }
});
});
test('when props have immutable structures', () => {
shouldUpdate({
cursor: { foo: Immutable.List.of(1) },
nextCursor: { foo: Immutable.List.of(2) }
});
});
test('when namespaced cursors changed', () => {
var data = Immutable.fromJS({ foo: 'bar', bar: [1, 2, 3] });
var one = Cursor.from(data, ['foo']);
var two = Cursor.from(data, ['bar']);
var three = one.update(function() {
return 'changed';
});
shouldUpdate({
cursor: {
ns: {
one: one,
two: two
}
},
nextCursor: {
ns: {
one: three,
two: two
}
}
});
});
test('when namespaced cursors changed to non cursor', () => {
var data = Immutable.fromJS({ foo: 'bar', bar: [1, 2, 3] });
var one = Cursor.from(data, ['foo']);
var two = Cursor.from(data, ['bar']);
shouldUpdate({
cursor: {
ns: {
one: one,
two: two
}
},
nextCursor: {
ns: {
one: 'foo',
two: two
}
}
});
});
});
describe('should not update', () => {
test('when no data is passed to component', () => {
shouldNotUpdate({});
});
test('when deep state is same', () => {
shouldNotUpdate({
state: { foo: { bar: 'hello' } },
nextState: { foo: { bar: 'hello' } }
});
});
test('when state have same immutable structures', () => {
var map = Immutable.List.of(1);
shouldNotUpdate({
state: { foo: map },
nextState: { foo: map }
});
});
test('when props have same immutable structures', () => {
var map = Immutable.List.of(1);
shouldNotUpdate({
props: { foo: map },
nextProps: { foo: map }
});
});
test('when children has changed', () => {
shouldNotUpdate({
children: { foo: 'hello' },
nextChildren: { bar: 'bye' }
});
});
test('when neither props nor state has changed', () => {
var data = Immutable.fromJS({ foo: 'bar', bar: [1, 2, 3] });
var props = { one: Cursor.from(data, ['foo']) };
var state = { foo: 'hello' };
shouldNotUpdate({
cursor: props,
nextCursor: props,
state: state,
nextState: state
});
});
test('when passing same cursors', () => {
var data = Immutable.fromJS({ foo: 'bar' });
shouldNotUpdate({
cursor: Cursor.from(data),
nextCursor: Cursor.from(data)
});
});
test('when passing same cursors and same data for multiple values', () => {
var data = Immutable.fromJS({ foo: 'bar' });
shouldNotUpdate({
cursor: { one: Cursor.from(data), two: { foo: 'hello' } },
nextCursor: { one: Cursor.from(data), two: { foo: 'hello' } }
});
});
test('when multiple cursors point to the same data', () => {
var data = Immutable.fromJS({ foo: 'bar', bar: [1, 2, 3] });
var data2 = Immutable.fromJS({ baz: [1, 2, 3] });
shouldNotUpdate({
cursor: { one: Cursor.from(data, ['foo']), two: Cursor.from(data2) },
nextCursor: { one: Cursor.from(data, ['foo']), two: Cursor.from(data2) }
});
});
test('when namespaced cursors is unchanged', () => {
var data = Immutable.fromJS({ foo: 'bar', bar: [1, 2, 3] });
var one = Cursor.from(data, ['foo']);
var two = Cursor.from(data, ['bar']);
shouldNotUpdate({
cursor: {
ns: {
one: one,
two: two
}
},
nextCursor: {
ns: {
one: one,
two: two
}
}
});
});
});
describe('overridables', () => {
describe('through main component', () => {
test('should have overridable isCursor', done => {
var data = Immutable.fromJS({ foo: 'bar', bar: [1, 2, 3] });
var called = 0;
var local = component.withDefaults({
isCursor: function() {
called++;
return true;
}
}).shouldComponentUpdate;
shouldUpdate(
{
cursor: Cursor.from(data, ['foo']),
nextCursor: Cursor.from(data, ['bar'])
},
local
);
expect(called).toBeGreaterThan(1);
done();
});
test('should have overridable isEqualCursor', done => {
var data = Immutable.fromJS({ foo: 'bar', bar: [1, 2, 3] });
var local = component.withDefaults({
isEqualCursor: function() {
done();
return true;
}
}).shouldComponentUpdate;
shouldNotUpdate(
{
cursor: Cursor.from(data, ['foo']),
nextCursor: Cursor.from(data, ['bar'])
},
local
);
});
test('should have overridable isEqualImmutable', done => {
var data = Immutable.fromJS({ foo: { value: 'bar' }, bar: [1, 2, 3] });
var local = component.withDefaults({
isEqualImmutable: function(a, b) {
expect(a.get('value')).toBe('bar');
expect(b.get(0)).toEqual(1);
done();
return true;
}
}).shouldComponentUpdate;
shouldNotUpdate(
{
cursor: { data: data.get('foo') },
nextCursor: { data: data.get('bar') }
},
local
);
});
test('should have overridable isEqualState', done => {
var local = component.withDefaults({
isEqualState: function() {
done();
}
}).shouldComponentUpdate;
shouldUpdate(
{
state: { foo: 'hello' },
nextState: { foo: 'bar' }
},
local
);
});
});
describe('internal', () => {
test('should create different instances', () => {
var localOne = shouldComponentUpdate.withDefaults();
var localTwo = shouldComponentUpdate.withDefaults();
expect(localOne).not.toBe(localTwo);
});
test('should have overridable isCursor', done => {
var data = Immutable.fromJS({ foo: 'bar', bar: [1, 2, 3] });
var called = 0;
var local = shouldComponentUpdate.withDefaults({
isCursor: function() {
called++;
return true;
}
});
shouldUpdate(
{
cursor: Cursor.from(data, ['foo']),
nextCursor: Cursor.from(data, ['bar'])
},
local
);
expect(called).toBeGreaterThan(1);
done();
});
test('should have overridable isIgnorable', () => {
var numCalls = 0;
var local = shouldComponentUpdate.withDefaults({
isIgnorable: function foobar(value, key) {
numCalls++;
return key === 'ignore';
}
});
shouldNotUpdate(
{
cursor: { ignore: 'hello' },
nextCursor: { ignore: 'bye' }
},
local
);
expect(numCalls).toBeGreaterThan(1);
});
test('should have debug on product of withDefaults', () => {
var localComponent = shouldComponentUpdate.withDefaults({
isCursor: function() {}
});
expect(typeof localComponent.debug).toBe('function');
});
test('should have overridable isEqualCursor', () => {
var data = Immutable.fromJS({ foo: 'bar', bar: [1, 2, 3] });
var local = shouldComponentUpdate.withDefaults({
isEqualCursor: function() {
return true;
}
});
shouldNotUpdate(
{
cursor: Cursor.from(data, ['foo']),
nextCursor: Cursor.from(data, ['bar'])
},
local
);
});
test('should have overridable isImmutable', done => {
var data = Immutable.fromJS({ foo: 'bar', bar: [1, 2, 3] });
var map = Immutable.List.of(1);
var i = 0;
var local = shouldComponentUpdate.withDefaults({
isImmutable: function() {
if (i++ === 1) {
done();
}
}
});
shouldNotUpdate(
{
cursor: { foo: map },
nextCursor: { foo: map }
},
local
);
});
test('should have overridable isEqualProps', done => {
var local = shouldComponentUpdate.withDefaults({
isEqualProps: function foobar() {
done();
}
});
shouldUpdate(
{
cursor: { foo: 1 },
nextCursor: { foo: 2 }
},
local
);
});
test('should have accessible helpers (isCursor, isEqualState, isEqualProps, isEqualCursor) to use externally', () => {
var local = shouldComponentUpdate.withDefaults();
expect(shouldComponentUpdate).toHaveProperty('isEqualState');
expect(shouldComponentUpdate).toHaveProperty('isCursor');
expect(shouldComponentUpdate).toHaveProperty('isEqualProps');
expect(shouldComponentUpdate).toHaveProperty('isEqualCursor');
expect(shouldComponentUpdate).toHaveProperty('isImmutable');
expect(local).toHaveProperty('isEqualState');
expect(local).toHaveProperty('isCursor');
expect(local).toHaveProperty('isEqualProps');
expect(local).toHaveProperty('isEqualCursor');
expect(local).toHaveProperty('isImmutable');
});
test('should have overridable isEqualState', done => {
var local = shouldComponentUpdate.withDefaults({
isEqualState: function() {
done();
}
});
shouldUpdate(
{
state: { foo: 'hello' },
nextState: { foo: 'bar' }
},
local
);
});
});
});
});
function shouldNotUpdate(opts, fn) {
expect(callShouldUpdate(opts, fn)).toBe(false);
}
function shouldUpdate(opts, fn) {
expect(callShouldUpdate(opts, fn)).toBe(true);
}
function callShouldUpdate(opts, fn) {
fn = fn || shouldComponentUpdate;
var props = isCursor(opts.cursor) ? { cursor: opts.cursor } : opts.cursor;
var nextProps = isCursor(opts.nextCursor)
? { cursor: opts.nextCursor }
: opts.nextCursor;
props = props || {};
nextProps = nextProps || {};
if (opts.statics || opts.nextStatics) {
props.statics = opts.statics;
nextProps.statics = opts.nextStatics;
}
if (opts.children || opts.nextChildren) {
props.children = opts.children;
nextProps.children = opts.nextChildren;
}
return fn.call(
{
props: props,
state: opts.state
},
nextProps,
opts.nextState
);
}