UNPKG

react-forms

Version:
2,103 lines (1,823 loc) 52.1 kB
'use strict'; var assert = require('assert'); var sinon = require('sinon'); var Immutable = require('immutable'); var {Iterable, Map, Record, fromJS, is} = Immutable; var Value = require('../Value'); var {Scalar, ScalarNode, Mapping, MappingNode, List, Node, CompositeNode} = require('../schema'); var ValidationResult = require('../ValidationResult'); var emptyFunction = require('../emptyFunction'); function assertEquals(a, b) { a = fromJS(a); b = fromJS(b); assert.ok(is(a, b), `expected ${a} to be equal to ${b}`); } function assertNotEquals(a, b) { a = fromJS(a); b = fromJS(b); assert.ok(!is(a, b), 'should not be equal'); } describe('Value:', function() { describe('Value.create(schema, initial):', function() { function itGrows(schema, initial, expected) { initial = fromJS(initial); expected = { value: fromJS(expected.value), serialized: fromJS(expected.serialized), validation: expected.validation }; var onUpdate = emptyFunction; var root = () => grown; var grown = Value.create(schema, initial, onUpdate, root); assert.ok( is(grown.value, expected.value), `value equality: got "${grown.value}" instead of expected "${expected.value}"` ); assert.ok( is(grown.serialized, expected.serialized), `serialized equality: got "${grown.serialized}" instead of expected "${expected.serialized}"` ); if (expected.validation !== undefined) { assert.ok( is(grown.validation, expected.validation), `validation equality: got "${grown.validation}" instead of expected "${expected.validation}"` ); } } it('basic schema', function() { var schema = Mapping({ x: Scalar({type: 'number', defaultValue: 1}), w: Scalar(), }); itGrows( schema, {}, { value: {x: 1}, serialized: {x: 1}, validation: ValidationResult.success() } ); itGrows( schema, {x: 2}, { value: {x: 2}, serialized: {x: 2}, validation: ValidationResult.success() } ); itGrows( schema, {x: 2, w: 1}, { value: {x: 2, w: 1}, serialized: {x: 2, w: 1}, validation: ValidationResult.success() } ); itGrows( schema, {w: 1}, { value: {x: 1, w: 1}, serialized: {x: 1, w: 1}, validation: ValidationResult.success() } ); itGrows( schema, {x: 'x'}, { value: {x: 'x'}, serialized: {x: 'x'}, validation: ValidationResult.children({x: ValidationResult.error('invalid value')}) } ); }); it('schema with invalid default', function() { var schema = Mapping({ x: Scalar({type: 'number', defaultValue: 'x'}) }); itGrows( schema, {}, { value: {x: 'x'}, serialized: {x: 'x'}, validation: ValidationResult.children({x: ValidationResult.error('invalid value')}) } ); itGrows( schema, {x: 'y'}, { value: {x: 'y'}, serialized: {x: 'y'}, validation: ValidationResult.children({x: ValidationResult.error('invalid value')}) } ); }); it('nested schema with default', function() { var schema = Mapping({ x: Mapping({y: Scalar({defaultValue: 1})}) }); itGrows( schema, {}, { value: {}, serialized: {}, } ); itGrows( schema, {x: {}}, { value: {x: {y: 1}}, serialized: {x: {y: 1}}, } ); itGrows( schema, {x: {y: 2}}, { value: {x: {y: 2}}, serialized: {x: {y: 2}}, } ); }); it('nested schema with default on mapping', function() { var schema = Mapping({ x: Mapping({defaultValue: {}}, {y: Scalar()}) }); itGrows( schema, {}, { value: {x: {}}, serialized: {x: {}}, } ); itGrows( schema, {x: {y: 1}}, { value: {x: {y: 1}}, serialized: {x: {y: 1}}, } ); }); it('nested schema with rich default on mapping', function() { var schema = Mapping({ x: Mapping({defaultValue: {y: 1}}, {y: Scalar()}) }); itGrows( schema, {}, { value: {x: {y: 1}}, serialized: {x: {y: 1}}, } ); itGrows( schema, {x: {}}, { value: {x: {}}, serialized: {x: {}}, } ); itGrows( schema, {x: {y: 2}}, { value: {x: {y: 2}}, serialized: {x: {y: 2}}, } ); }); it('nested schema with default on mapping and scalar', function() { var schema = Mapping({ x: Mapping({defaultValue: {}}, { y: Scalar({defaultValue: 1}), z: Scalar() }) }); itGrows( schema, {}, { value: {x: {y: 1}}, serialized: {x: {y: 1}}, } ); itGrows( schema, {x: {}}, { value: {x: {y: 1}}, serialized: {x: {y: 1}}, } ); itGrows( schema, {x: {z: 2}}, { value: {x: {y: 1, z: 2}}, serialized: {x: {y: 1, z: 2}}, } ); itGrows( schema, {x: {y: 2}}, { value: {x: {y: 2}}, serialized: {x: {y: 2}}, } ); itGrows( schema, {x: {y: 2, z: 3}}, { value: {x: {y: 2, z: 3}}, serialized: {x: {y: 2, z: 3}}, } ); }); it('schema with default on mapping and scalar (pt. 2)', function() { var schema = Mapping({ x: Mapping({defaultValue: {z: 2}}, { y: Scalar({defaultValue: 1}), z: Scalar() }) }); itGrows( schema, {}, { value: {x: {y: 1, z: 2}}, serialized: {x: {y: 1, z: 2}}, } ); itGrows( schema, {x: {}}, { value: {x: {y: 1}}, serialized: {x: {y: 1}}, } ); itGrows( schema, {x: {z: 2}}, { value: {x: {y: 1, z: 2}}, serialized: {x: {y: 1, z: 2}}, } ); itGrows( schema, {x: {y: 2}}, { value: {x: {y: 2}}, serialized: {x: {y: 2}}, } ); itGrows( schema, {x: {y: 2, z: 3}}, { value: {x: {y: 2, z: 3}}, serialized: {x: {y: 2, z: 3}}, } ); }); it('mapping of list of mappings', function() { var schema = Mapping({ x: List(Mapping({y: Scalar({defaultValue: 1})})) }); itGrows( schema, {}, { value: {}, serialized: {}, } ); itGrows( schema, {x: []}, { value: {x: []}, serialized: {x: {}}, } ); itGrows( schema, {x: [{}]}, { value: {x: [{y: 1}]}, serialized: Map({x: Immutable.List([Map({y: 1})]).toMap()}), } ); itGrows( schema, {x: [{y: 2}]}, { value: {x: [{y: 2}]}, serialized: Map({x: Immutable.List([Map({y: 2})]).toMap()}), } ); itGrows( schema, {x: [{y: 2}, {}]}, { value: {x: [{y: 2}, {y: 1}]}, serialized: Map({x: Immutable.List([Map({y: 2}), Map({y: 1})]).toMap()}), } ); }); it('mapping of list of scalars', function() { var schema = Mapping({ x: List({defaultValue: []}, Scalar()) }); itGrows( schema, {}, { value: {x: []}, serialized: Map({x: Map()}) } ); itGrows( schema, {x: []}, { value: {x: []}, serialized: Map({x: Map()}), } ); itGrows( schema, {x: [1]}, { value: {x: [1]}, serialized: Map({x: Immutable.List([1]).toMap()}), } ); itGrows( schema, {x: [1, 2]}, { value: {x: [1, 2]}, serialized: Map({x: Immutable.List([1, 2]).toMap()}), } ); }); it('mapping of list of scalars with default', function() { var schema = Mapping({ x: List({defaultValue: [1]}, Scalar()) }); itGrows( schema, {}, { value: {x: [1]}, serialized: Map({x: Immutable.List([1]).toMap()}), } ); itGrows( schema, {x: []}, { value: {x: []}, serialized: Map({x: Map()}) } ); itGrows( schema, {x: [1]}, { value: {x: [1]}, serialized: Map({x: Immutable.List([1]).toMap()}), } ); itGrows( schema, {x: [1, 2]}, { value: {x: [1, 2]}, serialized: Map({x: Immutable.List([1, 2]).toMap()}), } ); }); it('mapping of list of scalars with default value', function() { var schema = Mapping({ x: List({defaultValue: []}, Scalar({defaultValue: 1})) }); itGrows( schema, {}, { value: {x: []}, serialized: Map({x: Map()}) } ); itGrows( schema, {x: []}, { value: {x: []}, serialized: Map({x: Map()}) } ); itGrows( schema, {x: [1]}, { value: {x: [1]}, serialized: Map({x: Immutable.List([1]).toMap()}), } ); itGrows( schema, {x: [1, 2]}, { value: {x: [1, 2]}, serialized: Map({x: Immutable.List([1, 2]).toMap()}), } ); }); it('mapping of list of mappings with default value on scalar', function() { var schema = Mapping({ x: List(Mapping({y: Scalar({defaultValue: 1})})) }); itGrows( schema, {}, { value: {}, serialized: {}, } ); itGrows( schema, {x: []}, { value: {x: []}, serialized: Map({x: Map()}) } ); itGrows( schema, {x: [{}]}, { value: {x: [{y: 1}]}, serialized: Map({x: Map().set(0, Map({y: 1}))}) } ); }); it('mapping of lust of mappings with default values on list and scalars', function() { var schema = Mapping({ x: List({defaultValue: []}, Mapping({y: Scalar({defaultValue: 1})})) }); itGrows( schema, {}, { value: {x: []}, serialized: Map({x: Map()}) } ); itGrows( schema, {x: []}, { value: {x: []}, serialized: Map({x: Map()}) } ); itGrows( schema, {x: [{}]}, { value: {x: [{y: 1}]}, serialized: Map({x: Map().set(0, Map({y: 1}))}) } ); }); it('preserves data which was not described by schema', function() { var value; var updated; var onUpdate = emptyFunction; var root = () => value; var schema = Mapping({ x: Scalar(), y: Scalar() }); value = Value.create(schema, {x: 1, y: 2, z: 3}, onUpdate, root); assertEquals(value.value, {x: 1, y: 2, z: 3}); var updated = value.get('x').set(42); assertEquals(updated.value, {x: 42, y: 2, z: 3}); value = Value.create(schema, {x: 1, y: 2, z: {a: 1}}, onUpdate, root); assertEquals(value.value, {x: 1, y: 2, z: {a: 1}}); var updated = value.get('x').set(42); assertEquals(updated.value, {x: 42, y: 2, z: {a: 1}}); }); }); describe('Value.prototype.get(key):', function() { function itGets(schema, key, initial, expected) { initial = fromJS(initial); expected = { value: fromJS(expected.value), serialized: fromJS(expected.serialized), validation: expected.validation }; var onUpdate = emptyFunction; var root = () => root; var value = Value.create(schema, initial, onUpdate, root); if (!Array.isArray(key)) { key = [key]; } var got = value; for (var i = 0, len = key.length; i < len; i++) { got = got.get(key[i]); } assert.ok( is(got.value, expected.value), `value equality: got "${got.value}" instead of expected "${expected.value}"` ); assert.ok( is(got.serialized, expected.serialized), `serialized equality: got "${got.serialized}" instead of expected "${expected.serialized}"` ); if (expected.validation !== undefined) { assert.ok( is(got.validation, expected.validation), `validation equality: got "${got.validation}" instead of expected "${expected.validation}"` ); } } it('throws informative error on trying to access field which does not exist', function() { var schema = Mapping({ x: Scalar(), y: Scalar() }); var value = Value.create(schema); assert.throws(function() { value.get('z'); }, /Access to key "z" which does not exist in schema/); }); it('schema1', function() { var schema = Mapping({ x: Scalar({defaultValue: 1}), w: Scalar() }); itGets( schema, 'x', {}, { value: 1, serialized: 1, validation: ValidationResult.success() } ); itGets( schema, 'w', {}, { value: undefined, serialized: undefined, validation: ValidationResult.success() } ); }); it('schema2', function() { var schema = Mapping({ x: Mapping({y: Scalar({defaultValue: 1})}) }); itGets( schema, 'x', {}, { value: {}, serialized: {}, validation: ValidationResult.success() } ); itGets( schema, ['x', 'y'], {}, { value: 1, serialized: 1, validation: ValidationResult.success() } ); }); it('schema3', function() { var schema = Mapping({ x: Mapping({defaultValue: {}}, {y: Scalar()}) }); itGets( schema, ['x'], {}, { value: {}, serialized: {}, validation: ValidationResult.success() } ); itGets( schema, ['x', 'y'], {}, { value: undefined, serialized: undefined, validation: ValidationResult.success() } ); itGets( schema, ['x'], {x: {y: 2}}, { value: {y: 2}, serialized: {y: 2}, validation: ValidationResult.success() } ); itGets( schema, ['x', 'y'], {x: {y: 2}}, { value: 2, serialized: 2, validation: ValidationResult.success() } ); }); it('validates', function() { var schema = Mapping({ x: Mapping({ y: Scalar({type: 'number', defaultValue: 'x'}) }) }); itGets( schema, ['x'], {}, { value: {}, serialized: {}, validation: ValidationResult.success() } ); itGets( schema, ['x', 'y'], {}, { value: 'x', serialized: 'x', validation: ValidationResult.error('invalid value') } ); }); it('schema2', function() { var schema = Mapping({ x: Mapping({defaultValue: {y: 1}}, {y: Scalar()}) }); itGets( schema, ['x'], {}, { value: {y: 1}, serialized: {y: 1}, validation: ValidationResult.success() } ); itGets( schema, ['x', 'y'], {}, { value: 1, serialized: 1, validation: ValidationResult.success() } ); itGets( schema, ['x'], {x: {y: 2}}, { value: {y: 2}, serialized: {y: 2}, validation: ValidationResult.success() } ); itGets( schema, ['x', 'y'], {x: {y: 2}}, { value: 2, serialized: 2, validation: ValidationResult.success() } ); }); it('schema3', function() { var schema = Mapping({ x: Mapping({defaultValue: {}}, { y: Scalar({defaultValue: 1}), z: Scalar() }) }); itGets( schema, ['x'], {}, { value: {y: 1}, serialized: {y: 1}, validation: ValidationResult.success() } ); itGets( schema, ['x', 'y'], {}, { value: 1, serialized: 1, validation: ValidationResult.success() } ); itGets( schema, ['x'], {x: {y: 2}}, { value: {y: 2}, serialized: {y: 2}, validation: ValidationResult.success() } ); itGets( schema, ['x', 'y'], {x: {y: 2}}, { value: 2, serialized: 2, validation: ValidationResult.success() } ); }); it('get schema3', function() { var schema = Mapping({ x: Mapping({defaultValue: {z: 2}}, { y: Scalar({defaultValue: 1}), z: Scalar() }) }); itGets( schema, ['x'], {}, { value: {z: 2, y: 1}, serialized: {z: 2, y: 1}, validation: ValidationResult.success() } ); itGets( schema, ['x', 'z'], {}, { value: 2, serialized: 2, validation: ValidationResult.success() } ); itGets( schema, ['x', 'y'], {}, { value: 1, serialized: 1, validation: ValidationResult.success() } ); itGets( schema, ['x'], {x: {y: 2}}, { value: {y: 2}, serialized: {y: 2}, validation: ValidationResult.success() } ); itGets( schema, ['x', 'y'], {x: {y: 2}}, { value: 2, serialized: 2, validation: ValidationResult.success() } ); itGets( schema, ['x'], {x: {z: 3}}, { value: {z: 3, y: 1}, serialized: {z: 3, y: 1}, validation: ValidationResult.success() } ); itGets( schema, ['x', 'z'], {x: {z: 3}}, { value: 3, serialized: 3, validation: ValidationResult.success() } ); itGets( schema, ['x', 'y'], {x: {z: 3}}, { value: 1, serialized: 1, validation: ValidationResult.success() } ); }); it('mapping of list of mappings (default on scalar inside mapping)', function() { var schema = Mapping({ x: List(Mapping({y: Scalar({defaultValue: 1})})) }); itGets( schema, ['x'], {}, { value: [], serialized: Map(), validation: ValidationResult.success() } ); itGets( schema, ['x'], {x: [{}]}, { value: [{y: 1}], serialized: Map().set(0, Map({y: 1})), validation: ValidationResult.success() } ); itGets( schema, ['x', 0], {x: [{}]}, { value: {y: 1}, serialized: {y: 1}, validation: ValidationResult.success() } ); itGets( schema, ['x', 0], {x: [{}]}, { value: {y: 1}, serialized: {y: 1}, validation: ValidationResult.success() } ); itGets( schema, ['x', 0], {x: [{y: 2}]}, { value: {y: 2}, serialized: {y: 2}, validation: ValidationResult.success() } ); itGets( schema, ['x', '0', 'y'], {x: [{}]}, { value: 1, serialized: 1, validation: ValidationResult.success() } ); itGets( schema, ['x', '0', 'y'], {x: [{y: 2}]}, { value: 2, serialized: 2, validation: ValidationResult.success() } ); }); it('schema3', function() { var schema = Mapping({ x: List({defaultValue: []}, Scalar()) }); itGets( schema, ['x'], {}, { value: [], serialized: Map(), validation: ValidationResult.success() } ); itGets( schema, ['x', '0'], {}, { value: undefined, serialized: undefined, validation: ValidationResult.success() } ); itGets( schema, ['x'], {x: ['1']}, { value: ['1'], serialized: Map([[0, '1']]), validation: ValidationResult.success() } ); itGets( schema, ['x', '0'], {x: ['1']}, { value: '1', serialized: '1', validation: ValidationResult.success() } ); }); it('mapping of list of scalars (defaults on list)', function() { var schema = Mapping({ x: List({defaultValue: [1]}, Scalar()) }); itGets( schema, ['x'], {}, { value: [1], serialized: Map([[0, 1]]), validation: ValidationResult.success() } ); itGets( schema, ['x', 0], {}, { value: 1, serialized: 1, validation: ValidationResult.success() } ); itGets( schema, ['x'], {x: [2]}, { value: [2], serialized: Map([[0, 2]]), validation: ValidationResult.success() } ); itGets( schema, ['x', '0'], {x: [2]}, { value: 2, serialized: 2, validation: ValidationResult.success() } ); }); it('mapping of list of scalars (defaults on list and scalar)', function() { var schema = Mapping({ x: List({defaultValue: []}, Scalar({defaultValue: 1})) }); itGets( schema, ['x'], {}, { value: [], serialized: {}, validation: ValidationResult.success() } ); itGets( schema, ['x', '0'], {}, { value: 1, serialized: 1, validation: ValidationResult.success() } ); itGets( schema, ['x'], {x: [2]}, { value: [2], serialized: Map().set(0, 2), validation: ValidationResult.success() } ); itGets( schema, ['x', '0'], {x: [2]}, { value: 2, serialized: 2, validation: ValidationResult.success() } ); }); it('mapping of list of mappings', function() { var schema = Mapping({ x: List(Mapping({y: Scalar({defaultValue: 1})})) }); itGets( schema, ['x'], {}, { value: [], serialized: {}, validation: ValidationResult.success() } ); itGets( schema, ['x', '0'], {}, { value: {}, serialized: {}, validation: ValidationResult.success() } ); itGets( schema, ['x', '0'], {x: []}, { value: {}, serialized: {}, validation: ValidationResult.success() } ); itGets( schema, ['x', '0'], {x: [{}]}, { value: {y: 1}, serialized: {}, validation: ValidationResult.success() } ); itGets( schema, ['x', '0', 'y'], {x: [{}]}, { value: 1, serialized: 1, validation: ValidationResult.success() } ); }); it('mapping of list of mappings (default on lust and scalar of mapping)', function() { var schema = Mapping({ x: List({defaultValue: [{}]}, Mapping({ y: Scalar({defaultValue: 1}) })) }); itGets( schema, ['x'], {}, { value: [{y: 1}], serialized: Map().set(0, Map({y: 1})), validation: ValidationResult.success() } ); itGets( schema, ['x', 0], {}, { value: {y: 1}, serialized: {y: 1}, validation: ValidationResult.success() } ); itGets( schema, ['x', 0], {x: []}, { value: {}, serialized: {}, validation: ValidationResult.success() } ); itGets( schema, ['x', 0], {x: [{}]}, { value: {y: 1}, serialized: {y: 1}, validation: ValidationResult.success() } ); itGets( schema, ['x', 0, 'y'], {x: [{}]}, { value: 1, serialized: 1, validation: ValidationResult.success() } ); }); }); describe('Value.prototype.keyPath:', function() { function assertKeyPath(value, keyPath) { assert.deepEqual(value.keyPath, keyPath); } it('reflects the path from the root to the value', function() { var schema = Mapping({x: Mapping({y: Mapping({z: Scalar()})})}); var onUpdate = emptyFunction; var root = () => value; var value = Value.create(schema, undefined, onUpdate, root); assertKeyPath(value, []); assertKeyPath(value.get('x'), ['x']); assertKeyPath(value.get('x').get('y'), ['x', 'y']); assertKeyPath(value.get('x').get('y').get('z'), ['x', 'y', 'z']); }); }); describe('Value.prototype.root:', function() { it('returns the root value for the value', function() { var schema = Mapping({x: Mapping({y: Mapping({z: Scalar()})})}); var root = () => value; var onUpdate = emptyFunction; var value = Value.create(schema, undefined, onUpdate, root); assertEquals(value.root, value); assertEquals(value.get('x').root, value); assertEquals(value.get('x').get('y').root, value); assertEquals(value.get('x').get('y').get('z').root, value); }); }); describe('Value.prototype.keys():', function() { it('returns an iterable of keys', function() { var schema = Mapping({ x: List(Scalar()), y: Scalar(), z: Scalar() }); var onUpdate = emptyFunction; var root = () => undefined; var value = Value.create(schema, {x: [1, 2, 3], y: 2, z: 3}, onUpdate, root); assertEquals(Iterable(value.keys()), ['x', 'y', 'z']); assertEquals(Iterable(value.get('x').keys()), [0, 1, 2]); }); }); describe('Value.prototype.map(func):', function() { it('maps value over a function to an array', function() { var schema = Mapping({ x: List(Scalar()), y: Scalar(), z: Scalar() }); var onUpdate = emptyFunction; var root = () => value; var value = Value.create(schema, {x: [1, 2, 3], y: 2, z: 3}, onUpdate, root); assertEquals(value.map(v => v.value), [[1, 2, 3], 2, 3]); assertEquals(value.get('x').map(v => v.value), [1, 2, 3]); }); }); describe('Value.prototype.set(value):', function() { function itUpdates(schema, initial, expected, updater) { initial = fromJS(initial); expected = { value: fromJS(expected.value), serialized: fromJS(expected.serialized), validation: expected.validation }; var onUpdate = emptyFunction; var root = () => value; var value = Value.create(schema, initial, onUpdate, root); var updated = updater(value); assert.ok( is(updated.value, expected.value), `value equality: expected "${expected.value}" got "${updated.value}"` ); assert.ok( is(updated.serialized, expected.serialized), `serialized equality: expected "${expected.serialized}" got "${updated.serialized}"` ); if (expected.validation !== undefined) { assert.ok( is(updated.validation, expected.validation), `validation equality: expected "${expected.validation}" got "${updated.validation}"` ); } } it('fires onUpdate callback', function() { var schema = Mapping({ x: Mapping({ y: Scalar({type: 'number', defaultValue: 1}) }) }); var onUpdate = sinon.spy(); var root = () => value; var value = Value.create(schema, undefined, onUpdate, root); var updated = value.get('x').get('y').set(42); assert.ok(onUpdate.callCount, 1); assertEquals(onUpdate.firstCall.args[0], updated); assert.deepEqual(onUpdate.firstCall.args[1], ['x', 'y']) assertEquals(onUpdate.firstCall.args[2], value); }); it('updates nested mapping', function() { var schema = Mapping({ x: Mapping({ y: Scalar({type: 'number', defaultValue: 1}) }) }); itUpdates( schema, {}, { value: {x: {y: 2}}, serialized: {x: {y: 2}}, validation: ValidationResult.success() }, (value) => value.getIn(['x', 'y']).set(2) ); return; itUpdates( schema, {}, { value: {x: {y: 'x'}}, serialized: {x: {y :'x'}}, validation: ValidationResult.children({ x: ValidationResult.children({ y: ValidationResult.error('invalid value') }) }) }, (value) => value.getIn(['x', 'y']).set('x') ); itUpdates( schema, {}, { value: {x: {y: 3}}, serialized: {x: {y: 3}}, validation: ValidationResult.success() }, (value) => value.getIn(['x']).set({y: 3}) ); itUpdates( schema, {}, { value: {x: {y: 'x'}}, serialized: {x: {y: 'x'}}, validation: ValidationResult.children({ x: ValidationResult.children({ y: ValidationResult.error('invalid value') }) }) }, (value) => value.getIn(['x']).set({y: 'x'}) ); itUpdates( schema, {}, { value: {x: {y: 4}}, serialized: {x: {y: 4}}, validation: ValidationResult.success() }, (value) => value.set({x: {y: 4}}) ); itUpdates( schema, {}, { value: {x: {y: 'x'}}, serialized: {x: {y: 'x'}}, validation: ValidationResult.children({ x: ValidationResult.children({ y: ValidationResult.error('invalid value') }) }) }, (value) => value.set({x: {y: 'x'}}) ); }); it('updates nested mapping (no default)', function() { var schema = Mapping({ x: List(Mapping({y: Scalar()})) }); itUpdates( schema, {}, { value: {x: []}, serialized: {x: {}}, validation: ValidationResult.success() }, (value) => value.getIn(['x']).set([]) ); itUpdates( schema, {}, { value: {x: [{y: 1}]}, serialized: Map({x: Map().set(0, Map({y: 1}))}), validation: ValidationResult.success() }, (value) => value.getIn(['x', 0]).set({y: 1}) ); itUpdates( schema, {}, { value: {x: [{y: 3}]}, serialized: Map({x: Map().set(0, Map({y: 3}))}), validation: ValidationResult.success() }, (value) => value.getIn(['x', 0, 'y']).set(3) ); }); it('can be updated multiple times', function() { var schema = Mapping({ x: Mapping({y: Scalar({defaultValue: 1})}), u: Mapping({v: Scalar({defaultValue: 2})}) }); var onUpdate = emptyFunction; var root = () => value; var value = Value.create(schema, undefined, onUpdate, root); value = value.getIn(['x', 'y']).set(3); assertEquals(value.value, {x: {y: 3}}); value = value.getIn(['u', 'v']).set(4); assertEquals(value.value, {x: {y: 3}, u: {v: 4}}); value = value.getIn(['u']).set({v: 12}); assertEquals(value.value, {x: {y: 3}, u: {v: 12}}); value = value.getIn(['x']).set({}); assertEquals(value.value, {x: {y: 1}, u: {v: 12}}); value = value.set({u: {}}); assertEquals(value.value, {u: {v: 2}}); }); it('preserves structural equality across untouched areas', function() { var schema = Mapping({ x: Mapping({y: Scalar({defaultValue: 1})}), v: Mapping({u: Scalar({defaultValue: 2})}) }); var onUpdate = emptyFunction; var root = () => value; var value = Value.create(schema, undefined, onUpdate, root); var updated = value.getIn(['x', 'y']).set(3); assertNotEquals(value, updated); assertNotEquals(value.get('x'), updated.get('x')); assertNotEquals(value.get('x').get('y'), updated.get('x').get('y')); assertEquals(value.get('v'), updated.get('v')); assertEquals(value.get('v').get('u'), updated.get('v').get('u')); }); }); }); describe('Value with dynamic schema:', function() { class TextType extends MappingNode { get defaultValue() { return fromJS({type: 'text', pattern: 'x'}); } getChildren() { return Map({ type: Scalar({defaultValue: 'text'}), shared: Scalar({defaultValue: 'shared'}), pattern: Scalar() }); } } class NumberType extends MappingNode { get defaultValue() { return fromJS({type: 'number'}); } getChildren() { return Map({ type: Scalar({defaultValue: 'number'}), shared: Scalar({defaultValue: 'shared'}) }); } } class Type extends Node { constructor(props) { super(props); this.textType = TextType.create(); this.numberType = NumberType.create(); } get defaultValue() { return this.textType.defaultValue; } instantiate(value) { var type = value ? value.get('type', 'text') : 'text'; switch (type) { case 'text': return {node: this.textType, value}; case 'number': return {node: this.numberType, value}; } } } it('processes input on updates', function() { var schema = Mapping({ value: Type.create() }); var onUpdate = emptyFunction; var root = () => value; var value = Value.create(schema, undefined, onUpdate, root); assertEquals(value.value, { value: { type: 'text', pattern: 'x', shared: 'shared' } }); var updated = value.get('value').get('type').set('number'); assertEquals(updated.value, { value: {type: 'number', shared: 'shared'} }); }); it('preserves structural equality across untouched areas', function() { var schema = Mapping({ value1: Type.create(), value2: Type.create() }); var onUpdate = emptyFunction; var root = () => value; var value = Value.create(schema, undefined, onUpdate, root); assertEquals(value.value, { value1: {type: 'text', shared: 'shared', pattern: 'x'}, value2: {type: 'text', shared: 'shared', pattern: 'x'} }); var updated = value.get('value1').get('type').set('number'); assertEquals(updated.value, { value1: {type: 'number', shared: 'shared'}, value2: {type: 'text', shared: 'shared', pattern: 'x'} }); assertNotEquals(value, updated); assertNotEquals(value.get('value1'), updated.get('value1')); assertNotEquals(value.get('value1').get('type'), updated.get('value1').get('type')); assertEquals(value.get('value1').get('shared'), updated.get('value1').get('shared')); assertEquals(value.get('value2'), updated.get('value2')); }); }); describe('Value with recursive schema:', function() { class TreeChildren extends CompositeNode { get defaultValue() { return Map(); } keys(value) { return value.keys(); } has(key) { return true; } get(key) { return treeNode; } } var treeNode = Mapping({ value: Scalar({defaultValue: 'v'}), children: TreeChildren.create() }); it('works', function() { var onUpdate = emptyFunction; var root = () => value; var value = Value.create(treeNode, undefined, onUpdate, root); assertEquals(value.value, {value: 'v', children: {}}); assertEquals(value.get('value').value, 'v'); assertEquals(value.get('children').get('x').value, {}); assertEquals(value.get('children').get('x').get('value').value, 'v'); assertEquals(value.get('children').get('x').get('children').value, {}); assertEquals(value.get('children').get('x').get('children').get('y').value, {}); value = value.get('children').get('x').get('children').get('y').get('value').set('x'); assertEquals(value.value, {value: 'v', children: {x: {children: {y: {value: 'x'}}}}}); }); }); describe('Value.prototype.validation', function() { var success = ValidationResult.success(); function assertValidation(value, expected) { assertNotValid(value); assertEquals(value.validation, expected) } function assertValid(value) { assert.ok(value.isValid, `value "${value.value}" is not valid`); } function assertNotValid(value) { assert.ok(!value.isValid, `value "${value.value}" is valid`); } function create(schema, initial, attributes) { if (initial === undefined) { initial = Map(); } if (attributes === undefined) { attributes = Map(); } var onUpdate = emptyFunction; var root = () => value; var value = Value.create(schema, initial, onUpdate, root); return value; } class NumericScalarNode extends ScalarNode { validate(value) { var numeric = parseInt(value, 10); if (isNaN(numeric)) { return new Error('is not a number'); } } } var NOT_NUMBER = new ValidationResult('is not a number'); it('defines getter for attribute', function() { var schema = Mapping({ x: NumericScalarNode.create({defaultValue: 1}), w: Scalar() }); var onUpdate = emptyFunction; var root = () => value; var value = Value.create(schema, {x: 'x'}, onUpdate, root); assertNotValid(value); assertEquals(value.validation, value.attributes.get('validation')); assertEquals(value.validation, new ValidationResult(null, {x: NOT_NUMBER})); }); describe('get', function() { it('propagates success', function() { var schema = Mapping({ x: NumericScalarNode.create({defaultValue: 1}), w: Scalar() }); var value = create(schema); assertValid(value); assertValid(value.get('x')); assertValid(value.get('w')); }); it('propagates failure', function() { var schema = Mapping({ x: NumericScalarNode.create({defaultValue: 1}), w: Scalar() }); var value = create(schema, {x: 's'}); assertValidation(value, new ValidationResult(null, Map({x: NOT_NUMBER}))); assertValidation(value.get('x'), NOT_NUMBER); assertValid(value.get('w')); }); it('propagates failure from default value', function() { var schema = Mapping({ x: NumericScalarNode.create({defaultValue: 'x'}), w: Scalar() }); var value = create(schema); assertValidation(value, new ValidationResult(null, Map({x: NOT_NUMBER}))); assertValidation(value.get('x'), NOT_NUMBER); assertValid(value.get('w')); }); it('validates on set', function() { var schema = Mapping({ x: NumericScalarNode.create({defaultValue: 1}), w: Scalar() }); var value = create(schema); var updated = value.get('x').set(2); assertValid(updated); assertValid(updated.get('x')); assertValid(updated.get('w')); }); it('validates on set (invalid value)', function() { var schema = Mapping({ x: NumericScalarNode.create({defaultValue: 1}), w: Scalar() }); var value = create(schema); var updated = value.get('x').set('a'); assertValidation(updated, new ValidationResult(null, Map({x: NOT_NUMBER}))); assertValidation(updated.get('x'), NOT_NUMBER); assertValid(updated.get('w')); var updated2 = updated.get('x').set(3); assertValid(updated2); assertValid(updated2.get('x')); assertValid(updated2.get('w')); }); it('validates on root set (invalid value)', function() { var schema = Mapping({ x: NumericScalarNode.create({defaultValue: 1}), w: Scalar() }); var value = create(schema); var updated = value.set({x: 'a'}); assertValidation(updated, new ValidationResult(null, Map({x: NOT_NUMBER}))); assertValidation(updated.get('x'), NOT_NUMBER); assertValid(updated.get('w')); var updated2 = updated.set({x: 2}); assertValid(updated2); assertValid(updated2.get('x')); assertValid(updated2.get('w')); }); it('propagates success (nested schemas)', function() { var schema = Mapping({ x: Mapping({defaultValue: {}}, { y: Scalar({defaultValue: 1}), z: Scalar() }) }); var value = create(schema); assertValid(value); assertValid(value.get('x')); assertValid(value.getIn(['x', 'y'])); }); it('propagates failure (nested schemas)', function() { var schema = Mapping({ x: Mapping({defaultValue: {}}, { y: NumericScalarNode.create({defaultValue: 1}), z: Scalar() }) }); var value = create(schema, {x: {y: 'x'}}); assertValidation(value, new ValidationResult(null, {x: new ValidationResult(null, {y: NOT_NUMBER})})); assertValidation(value.get('x'), new ValidationResult(null, {y: NOT_NUMBER})); assertValidation(value.getIn(['x', 'y']), NOT_NUMBER); }); it('propagates failure from default value (nested schemas)', function() { var schema = Mapping({ x: Mapping({defaultValue: {}}, { y: NumericScalarNode.create({defaultValue: 'x'}), z: Scalar() }) }); var value = create(schema); assertValidation(value, new ValidationResult(null, {x: new ValidationResult(null, {y: NOT_NUMBER})})); assertValidation(value.get('x'), new ValidationResult(null, {y: NOT_NUMBER})); assertValidation(value.getIn(['x', 'y']), NOT_NUMBER); }); it('validates on set (nested schema)', function() { var schema = Mapping({ x: Mapping({defaultValue: {}}, { y: Scalar({defaultValue: 1}), z: Scalar() }) }); var value = create(schema); var updated = value.getIn(['x', 'y']).set(2); assertValid(updated); assertValid(updated.get('x')); assertValid(updated.getIn(['x', 'y'])); }); it('validates on set (invalid value, nested schema)', function() { var schema = Mapping({ x: Mapping({defaultValue: {}}, { y: NumericScalarNode.create({defaultValue: 1}), z: Scalar() }) }); var value = create(schema); var updated = value.getIn(['x', 'y']).set('x'); assertValidation(updated, new ValidationResult(null, {x: new ValidationResult(null, {y: NOT_NUMBER})})); assertValidation(updated.get('x'), new ValidationResult(null, {y: NOT_NUMBER})); assertValidation(updated.getIn(['x', 'y']), NOT_NUMBER); var updated2 = updated.getIn(['x', 'y']).set(3); assertValid(updated2); assertValid(updated2.get('x')); assertValid(updated2.getIn(['x', 'y'])); }); it('validates on intermedate value set (invalid value, nested schema)', function() { var schema = Mapping({ x: Mapping({defaultValue: {}}, { y: NumericScalarNode.create({defaultValue: 1}), z: Scalar() }) }); var value = create(schema); var updated = value.get('x').set({y: 'x'}); assertValidation(updated, new ValidationResult(null, {x: new ValidationResult(null, {y: NOT_NUMBER})})); assertValidation(updated.get('x'), new ValidationResult(null, {y: NOT_NUMBER})); assertValidation(updated.getIn(['x', 'y']), NOT_NUMBER); var updated2 = updated.get('x').set({y: 3}); assertValid(updated2); assertValid(updated2.get('x')); assertValid(updated2.getIn(['x', 'y'])); }); it('does not validate undefined values', function() { function validate(node, value) { if (!/^aaa$/.exec(value)) { return new Error('invalid value'); } } var value; var schema = Mapping({x: Scalar({validate})}); var onUpdate = emptyFunction; var root = () => value; value = Value.create(schema, undefined, onUpdate, root); assertValid(value); assertValid(value.get('x')); value = Value.create(schema, {}, onUpdate, root); assertValid(value); assertValid(value.get('x')); value = Value.create(schema, {x: null}, onUpdate, root); assertValid(value); assertValid(value.get('x')); value = Value.create(schema, {x: undefined}, onUpdate, root); assertValid(value); assertValid(value.get('x')); value = Value.create(schema, {x: ''}, onUpdate, root); assertValid(value); assertValid(value.get('x')); value = Value.create(schema, {x: 'bbb'}, onUpdate, root); assertNotValid(value); assertNotValid(value.get('x')); value = Value.create(schema, {x: 'aaa'}, onUpdate, root); assertValid(value); assertValid(value.get('x')); }); it('validates mappings with required keys', function() { var value; var schema = Mapping({ x: Scalar({required: true}), y: Scalar({type: 'number'}) }); var onUpdate = emptyFunction; var root = () => value; value = Value.create(schema, undefined, onUpdate, root); assertNotValid(value); assertNotValid(value.get('x')); assertValid(value.get('y')); value = value.get('y').set('x'); assertNotValid(value); assertNotValid(value.get('x')); assertNotValid(value.get('y')); value = value.get('x').set('zzz'); assertNotValid(value); assertValid(value.get('x')); assertNotValid(value.get('y')); value = value.get('y').set(123); assertValid(value); assertValid(value.get('x')); assertValid(value.get('y')); value = value.get('x').set(undefined); assertNotValid(value); assertNotValid(value.get('x')); assertValid(value.get('y')); }); }); it('works with scalar schema', function() { var schema = NumericScalarNode.create(); var value; value = create(schema, 1); assertValid(value); value = create(schema, -1); assertValid(value); value = create(schema, 'a'); assertNotValid(value); }); }); describe('Value.prototype.dirty', function() { function assertNotDirty(value) { assert.ok(!value.isDirty, 'value is dirty'); } function assertDirty(value) { assert.ok(value.isDirty, 'value is not dirty'); } var schema = Mapping({ x: Mapping({y: Scalar()}), z: Scalar() }); it('is not dirty initially', function() { var value = Value.create(schema); assertNotDirty(value); }); it('can be made dirty/not-dirty', function() { var onUpdate = emptyFunction; var root = () => value; var value = Value.create(schema, undefined, onUpdate, root); value = value.makeDirty(); assertDirty(value); value = value.makeNotDirty(); assertNotDirty(value); }); it('propagates dirtyness for children', function() { var onUpdate = emptyFunction; var root = () => value; var value = Value.create(schema, undefined, onUpdate, root); value = value.makeDirty(); assertDirty(value); assertDirty(value.get('x')); assertDirty(value.get('x').get('y')); assertDirty(value.get('z')); value = value.makeNotDirty(); assertNotDirty(value); assertNotDirty(value.get('x')); assertNotDirty(value.get('x').get('