ee0444ba |
import StateContainer, {lensTransformer} from '../src/state_container';
|
272a0697 |
import sinon from 'sinon';
|
1383f6ba |
import * as R from 'ramda';
|
272a0697 |
test("initial state setting works", () => {
const container = new StateContainer({foo: 'bar'});
expect(container.get('foo')).toBe('bar')//, 'get works with initial data');
expect(container.getState().foo).toBe('bar')//, 'getState works with initial data');
});
test("set updates state", () => {
const container = new StateContainer({foo: 'bar'});
container.set('foo', 'baz');
expect(container.get('foo')).toBe('baz')//, 'set updated the data');
});
test("setState updates state", () => {
const container = new StateContainer({foo: 'bar', moo: 'cow'});
container.setState({foo: 'baz'});
expect(container.get('foo')).toBe('baz')//, 'setState updated the data');
expect(container.get('moo')).toBe('cow')//, 'setState did not lose data');
});
test("returned state cannot modify internal state", () => {
const container = new StateContainer({foo: 'bar'});
const outputState = container.getState();
expect(container.get('foo')).toBe(outputState.foo)//, "output state matches container initially");
outputState.foo = 'moo';
expect(container.get('foo')).toBe('bar')//, "container state was not modified by output state");
});
|
8cb41939 |
test("onUpdate listeners are fired when set is called", () => {
|
272a0697 |
let container, listener;
container = new StateContainer({foo: 'bar'});
listener = sinon.spy();
container.onUpdate(listener);
container.set('foo', 'hi');
expect(JSON.stringify(listener.args[0])).toBe(JSON.stringify([{foo: 'bar'}, {foo: 'hi'}]));
container = new StateContainer({foo: 'bar'});
const recorder = container.getRecorder();
listener = sinon.spy();
recorder.onUpdate(listener);
recorder.set('foo', 'hi');
expect(JSON.stringify(listener.args[0])).toBe(JSON.stringify([{foo: 'bar'}, {foo: 'hi'}]));
});
test("onUpdate listeners are fired when setState is called", () => {
|
7524a4a6 |
let container = new StateContainer({foo: 'bar'});
|
1abf40ca |
let listener1 = sinon.spy();
let listener2 = sinon.spy();
|
272a0697 |
|
1abf40ca |
let rfn1 = container.onUpdate(listener1);
let rfn2 = container.onUpdate(listener2);
|
272a0697 |
container.setState({'foo': 'hi'});
|
1abf40ca |
rfn1();
container.setState({'foo': 'unfoo'});
|
272a0697 |
|
1abf40ca |
expect(listener1.args.length).toBe(1);
expect(JSON.stringify(listener1.args[0]))
|
7524a4a6 |
.toEqual(JSON.stringify(
[{ foo: 'bar' },
{ foo: 'hi' }]
));
|
1abf40ca |
expect(listener2.args.length).toBe(2);
expect(JSON.stringify(listener2.args[0]))
.toEqual(JSON.stringify(
[{ foo: 'bar' },
{ foo: 'hi' }]
));
expect(JSON.stringify(listener2.args[1]))
.toEqual(JSON.stringify(
[{ foo: 'hi' },
{ foo: 'unfoo' }]
));
|
7524a4a6 |
container = new StateContainer({foo: 'bar'});
const recorder = container.getRecorder();
|
1abf40ca |
listener1 = sinon.spy();
listener2 = sinon.spy();
|
7524a4a6 |
|
1abf40ca |
rfn1 = recorder.onUpdate(listener1);
rfn2 = recorder.onUpdate(listener2);
|
7524a4a6 |
recorder.setState({'foo': 'hi'});
|
1abf40ca |
rfn1();
recorder.setState({'foo': 'unfoo'});
expect(listener1.args.length).toBe(1);
expect(JSON.stringify(listener1.args[0]))
.toEqual(JSON.stringify(
[{ foo: 'bar' },
{ foo: 'hi' }]
));
|
7524a4a6 |
|
1abf40ca |
expect(listener2.args.length).toBe(2);
expect(JSON.stringify(listener2.args[0]))
|
7524a4a6 |
.toEqual(JSON.stringify(
|
8cb41939 |
[{ foo: 'bar' },
{ foo: 'hi' }]
));
|
1abf40ca |
expect(JSON.stringify(listener2.args[1]))
.toEqual(JSON.stringify(
[{ foo: 'hi' },
{ foo: 'unfoo' }]
));
|
272a0697 |
});
test("recording works", () => {
const container = new StateContainer({foo: 'bar'});
const recorder = container.getRecorder();
// recorder mirrors container
container.set('foo', 3);
expect(recorder.get('foo')).toBe(3);
// copy on write - reads from local
recorder.set('foo', 4);
expect(recorder.get('foo')).toBe(4);
expect(container.get('foo')).toBe(3);
// copy on write - does not read from parent
container.set('foo', 5);
expect(recorder.get('foo')).toBe(4);
container.commit(recorder);
expect(container.get('foo')).toBe(4)//, 'changes commited affect parent');
// after committing, reflects parent again
container.set('foo', 5);
expect(recorder.get('foo')).toBe(5);
// getState works?
let theState = recorder.getState();
expect(theState).toEqual({foo: 5});
recorder.setState({bar: 7});
expect(recorder.getState()).toEqual({foo: 5, bar: 7});
expect(container.getState()).toEqual({foo: 5});
container.commit(recorder);
expect(container.getState()).toEqual({foo: 5, bar: 7});
// cannot commit a recorder into wrong container
const otherContainer = new StateContainer({foo: 'bar'});
let err = 42;
try {
otherContainer.commit(recorder);
} catch (e) {
err = e;
}
expect(err instanceof Error).toBe(true)//, 'throws an error on invalid commit');
});
test("recorder getState works as expected", () => {
let container = new StateContainer({foo: {bar: 1, baz: 1}});
let recorder = container.getRecorder();
let lens = recorder.lensFor('foo');
lens.set({bar: 2});
expect(recorder.getState()).toEqual({foo: {bar: 2}}, 'replaces when set with complex value');
container = new StateContainer({foo: {bar: 1, baz: 1}});
recorder = container.getRecorder();
lens = recorder.lensFor(['foo', 'bar']);
lens.set(2);
expect(recorder.getState()).toEqual({foo: {bar: 2, baz: 1}}, 'updates when set with simple value');
});
test("recorder playback works correctly", () => {
let container = new StateContainer({foo: {bar: 1, baz: 1}});
let recorder = container.getRecorder();
let lens = recorder.lensFor('foo');
lens.set({bar: 2});
container.commit(recorder);
expect(container.getState()).toEqual({foo: {bar: 2}}, 'replaces when set with complex value');
container = new StateContainer({foo: {bar: 1, baz: 1}});
recorder = container.getRecorder();
lens = recorder.lensFor(['foo', 'bar']);
lens.set(2);
container.commit(recorder);
expect(container.getState()).toEqual({foo: {bar: 2, baz: 1}}, 'updates when set with simple value');
});
test("recorder from recorder also records - infinite turtles", () => {
let container = new StateContainer({foo: {bar: 1, baz: 1}});
let recorder = container.getRecorder();
let subRecorder = recorder.getRecorder();
subRecorder.set(['foo', 'bar'], 2);
recorder.commit(subRecorder);
expect(subRecorder.get(['foo', 'bar'])).toBe(2);
recorder.set(['foo', 'bar'], 3);
expect(subRecorder.get(['foo', 'bar'])).toBe(3);
});
test("recorder from recorder also records - infinite turtles", () => {
let container = new StateContainer({foo: {bar: 1, baz: 1}});
let recorder = container.getRecorder();
let subRecorder = recorder.getRecorder();
expect(subRecorder.get(['foo', 'bar'])).toBe(1);
subRecorder.set(['foo', 'bar'], 2);
expect(subRecorder.get(['foo', 'bar'])).toBe(2);
expect(recorder.get(['foo', 'bar'])).toBe(1);
expect(container.get(['foo', 'bar'])).toBe(1);
recorder.commit(subRecorder);
expect(subRecorder.get(['foo', 'bar'])).toBe(2);
expect(recorder.get(['foo', 'bar'])).toBe(2);
expect(container.get(['foo', 'bar'])).toBe(1);
container.commit(recorder);
expect(subRecorder.get(['foo', 'bar'])).toBe(2);
expect(recorder.get(['foo', 'bar'])).toBe(2);
expect(container.get(['foo', 'bar'])).toBe(2);
container.set(['foo', 'bar'], 3);
expect(subRecorder.get(['foo', 'bar'])).toBe(3);
});
test("lenses work", () => {
|
1383f6ba |
let container, lens, sublens;
|
272a0697 |
|
1383f6ba |
container = new StateContainer({ foo: { bar: 1, baz: 2 } });
|
272a0697 |
lens = container.lensFor('foo');
expect(lens.get()).toEqual({bar:1, baz: 2});
sublens = lens.lensFor('bar');
expect(sublens.get()).toBe(1);
sublens.set(2);
expect(sublens.get()).toBe(2, 'changing a nested lens updates its value');
expect(lens.get()).toEqual({bar:2, baz: 2}, 'changing a nested lens updates its parent\'s value');
expect(container.getState()).toEqual({foo: {bar:2, baz: 2}}, 'changing a nested lens updates container state');
lens.set({bar: 3});
expect(sublens.get()).toBe(3, 'changing parent lens to complex value updates nested lens value');
expect(lens.get()).toEqual({bar:3}, 'changing parent lens to complex value updates itself');
expect(container.getState()).toEqual({foo: {bar:3}}, 'changing parent lens to complex value updates container');
lens.set(3);
expect(lens.get()).toBe(3);
|
1383f6ba |
expect(lens.withValue(R.identity)).toBe(3);
expect(lens.withValue((value,prop) => R.assoc(prop, value, {b: 2, a:1}), 'a')).toEqual({a:3, b:2});
|
272a0697 |
expect(container.getState()).toEqual({foo: 3});
|
1383f6ba |
expect(lens.swap(R.assoc('a', R.__, {b: 2, a:1}))).toEqual({a:3, b:2});
expect(lens.get()).toEqual({a:3, b:2});
container = new StateContainer({ foo: { bar: 1, baz: 2 } });
const recorder = container.getRecorder();
lens = recorder.lensFor('foo');
expect(lens.get()).toEqual({bar:1, baz: 2});
sublens = lens.lensFor('bar');
expect(sublens.get()).toBe(1);
sublens.set(2);
expect(sublens.get()).toBe(2, 'changing a nested lens updates its value');
expect(lens.get()).toEqual({bar:2, baz: 2}, 'changing a nested lens updates its parent\'s value');
expect(recorder.getState()).toEqual({foo: {bar:2, baz: 2}}, 'changing a nested lens updates recorder state');
lens.set({bar: 3});
expect(sublens.get()).toBe(3, 'changing parent lens to complex value updates nested lens value');
expect(lens.get()).toEqual({bar:3}, 'changing parent lens to complex value updates itself');
expect(recorder.getState()).toEqual({foo: {bar:3}}, 'changing parent lens to complex value updates recorder');
lens.set(3);
expect(lens.get()).toBe(3);
expect(lens.withValue(R.identity)).toBe(3);
expect(lens.withValue((value,prop) => R.assoc(prop, value, {b: 2, a:1}), 'a')).toEqual({a:3, b:2});
expect(recorder.getState()).toEqual({foo: 3});
expect(lens.swap(R.assoc('a', R.__, {b: 2, a:1}))).toEqual({a:3, b:2});
expect(lens.get()).toEqual({a:3, b:2});
|
272a0697 |
});
test("lenses treat undefined properly", () => {
let container = new StateContainer({foo: undefined});
let lens, sublens, recorder;
lens = container.lensFor('foo');
expect(lens.get()).toEqual(undefined);
sublens = lens.lensFor('bar');
expect(sublens.get()).toBe(undefined);
try {
sublens.set(2);
} catch (e) {
// nothing
}
expect(sublens.get()).toBe(2, 'changing a nested lens updates its value');
expect(lens.get()).toEqual({bar:2}, 'changing a nested lens updates its parent\'s value');
expect(container.getState()).toEqual({foo: {bar:2}}, 'changing a nested lens updates container state');
container = new StateContainer({foo: undefined});
recorder = container.getRecorder();
lens = recorder.lensFor(['foo', 'bar']);
try {
lens.set(2);
} catch (e) {
// nothing
}
expect(lens.get()).toBe(2, 'changing a lens updates its value');
container = new StateContainer({foo: {bar: undefined, qwerty: 2}});
recorder = container.getRecorder();
lens = recorder.lensFor(['foo', 'bar', 'baz']);
try {
lens.set(2);
} catch (e) {
// nothing
}
expect(lens.get()).toBe(2, 'changing a lens updates its value');
expect(recorder.getState()).toEqual({foo: {bar: {baz: 2}, qwerty: 2}});
container.commit(recorder);
expect(container.getState()).toEqual({foo: {bar: {baz: 2}, qwerty: 2}});
});
test('returned values are just Javascript objects', () => {
const container = new StateContainer({foo: {bar: {baz: 1}}});
const recorder = container.getRecorder();
expect(container.get('foo').bar).toEqual({baz: 1});
expect(recorder.get('foo').bar).toEqual({baz: 1});
});
test("lensFor accepts array paths", () => {
const container = new StateContainer({foo: {bar: 1, baz: {'qwerty': 2}}});
let lens, sublens;
lens = container.lensFor(['foo', 'bar']);
expect(lens.get()).toBe(1);
lens.set(2);
expect(lens.get()).toBe(2);
lens = container.lensFor('foo').lensFor(['baz', 'qwerty']);
expect(lens.get()).toBe(2);
});
test("lensFor accepts objects nested in array paths", () => {
const container = new StateContainer({foo: {bar: 1, baz: {}}});
let lens;
lens = container.lensFor('foo').lensFor(['baz', 'qwerty', 0, 'asdf']);
lens.set(2);
expect(lens.get()).toBe(2);
});
test("lensFor indexes into arrays", () => {
const container = new StateContainer({foo: {bar: [1], baz: {'qwerty': 2}}});
let lens, sublens;
lens = container.lensFor(['foo', 'bar']).lensFor(0);
expect(lens.get()).toBe(1);
lens.set(2);
expect(lens.get()).toBe(2);
lens = container.lensFor(['foo', 'bar']).lensFor(2);
lens.set(3);
expect(lens.get()).toBe(3);
});
test("lens transformers work", () => {
const container = new StateContainer({foo: {bar: 1, baz: {'qwerty': 2}}});
let lens, transformer;
lens = container.lensFor(['foo', 'bar']);
transformer = lensTransformer(
lens,
v => v * 2,
v => v / 2
);
expect(transformer.get()).toBe(2);
transformer.set(4);
expect(transformer.get()).toBe(4);
});
|