test('JSCheck Custom Specifier for SSN', function() { /** * Produces a valid social security string (with dashes) * @param param1 Area Number -> JSC.integer(100, 999) * @param param2 Group Number -> JSC.integer(10, 99) * @param param3 Serial Number -> JSC.integer(1000,9999) * @returns {Function} Specifier function */ JSC.SSN = function(param1, param2, param3) { return function generator() { const part1 = typeof param1 === 'function' ? param1() : param1 const part2 = typeof param2 === 'function' ? param2() : param2 const part3 = typeof param3 === 'function' ? param3() : param3 return [part1, part2, part3].join('-') } } // Functions to test const validLength = (len, str) => str.length === len const find = R.curry((db, id) => db.find(id)) const checkLengthSsn = ssn => { return Either.of(ssn).filter(R.partial(validLength, [9])) } JSC.clear() JSC.on_report(report => console.log('Report' + str)) JSC.on_pass(object => assert.ok(object.pass)) JSC.on_fail(object => expect( object.pass || object.args.length === 9, 'Test failed for: ' + object.args ).toEqual(true) ) JSC.test( 'Check Length SSN', function(verdict, ssn) { return verdict(checkLengthSsn(ssn)) }, [ JSC.SSN( JSC.integer(100, 999), JSC.integer(10, 99), JSC.integer(1000, 9999) ), ], function(ssn) { return 'Testing Custom SSN: ' + ssn } ) expect(0) })
it('check nested indexed sequences', function() { JSC.test('add when value added in nested sequence', function(veredict, obj, newInt) { var map1 = Immutable.fromJS(obj); var map2 = Immutable.fromJS(obj).updateIn(['b', 'c'], function(list) { return list.push(newInt); }); var result = diff(map1, map2); var expected = Immutable.fromJS([{ op: 'add', path: '/b/c/5', value: newInt }]); return veredict(Immutable.is(result, expected)); }, [JSC.object({ a: JSC.integer(), b: JSC.object({c: JSC.array(5, JSC.integer())}) }), JSC.integer()]); JSC.test('remove when value removed in nested sequence', function(veredict, obj, removeIdx) { var map1 = Immutable.fromJS(obj); var map2 = Immutable.fromJS(obj).updateIn(['b', 'c'], function(list) { return list.splice(removeIdx, 1); }); var result = diff(map1, map2); var expected = Immutable.fromJS([{ op: 'remove', path: '/b/c/' + removeIdx }]); return veredict(Immutable.is(result, expected)); }, [JSC.object({ a: JSC.integer(), b: JSC.object({c: JSC.array(10, JSC.integer())}) }), JSC.integer(0, 9)]); JSC.test('replace when values are replaced in nested sequence', function(veredict, obj, replaceIdx, newValue) { var map1 = Immutable.fromJS(obj); var map2 = Immutable.fromJS(obj).updateIn(['b', 'c'], function(list) { return list.set(replaceIdx, newValue); }); var result = diff(map1, map2); var expected = Immutable.fromJS([{ op: 'replace', path: '/b/c/' + replaceIdx, value: newValue }]); return veredict(Immutable.is(result, expected)); }, [JSC.object({ a: JSC.integer(), b: JSC.object({c: JSC.array(10, JSC.integer())}) }), JSC.integer(0, 9), JSC.integer()]); });
// Anything but an object, array, or undefined. function invalidMergeArgumentSpecifier() { return JSC.one_of([ (function() { return function() {}; }), JSC.integer(), JSC.number(), JSC.string(), true, Infinity, -Infinity ]); }
test('Run JSCHeck', function() { // Functions to test const fork = (join, func1, func2) => val => join(func1(val), func2(val)) const toLetterGrade = grade => { if (grade >= 90) return 'A' if (grade >= 80) return 'B' if (grade >= 70) return 'C' if (grade >= 60) return 'D' return 'F' } const computeAverageGrade = R.compose( toLetterGrade, fork(R.divide, R.sum, R.length) ) JSC.clear() JSC.on_report(str => console.log(str)) JSC.test( 'Compute Average Grade', function(verdict, grades, grade) { return verdict(computeAverageGrade(grades) === grade) }, [JSC.array(JSC.integer(20), JSC.number(90, 100)), 'A'], function(grades, grade) { return 'Testing for an ' + grade + ' on grades: ' + grades } ) expect(0) })
check(100, [ JSC.array([TestUtils.TraversableObjectSpecifier]) ], function(array) { var immutable = Immutable(array); var mutable = array.slice(); var idx = JSC.integer(0, array.length-1); var key = JSC.one_of(_.keys(immutable[idx]))(); var value; do { value = JSC.any()(); } while (TestUtils.isDeepEqual(value, immutable[idx][key])); TestUtils.assertJsonEqual(immutable, mutable); var resultImmutable = Immutable.setIn(immutable, [idx, key], value, {deep: true}); var resultMutable = mutable.slice(); resultMutable[idx][key] = value; TestUtils.assertJsonEqual( resultImmutable, resultMutable ); assert.notEqual( immutable, resultImmutable ); assert.equal( Immutable.setIn(resultImmutable, [idx, key], value, {deep: true}), resultImmutable ); });
check(100, [ JSC.array([TestUtils.TraversableObjectSpecifier]) ], function(array) { var immutable = Immutable(array); var mutable = array.slice(); var index = JSC.integer(0, array.length); var value; var newValue; do { value = JSC.any()(); } while (TestUtils.isDeepEqual(value, array[index])); var resultImmutable = Immutable.set(immutable, index, newValue, {deep: true}); var resultMutable = mutable.slice(); resultMutable[index] = newValue; TestUtils.assertJsonEqual( resultImmutable, resultMutable ); assert.notEqual( immutable, resultImmutable ); assert.equal( Immutable.set(resultImmutable, index, newValue, {deep: true}), resultImmutable ); });
it("cannot have its elements directly mutated", function() { checkImmutableMutable(function(immutable, mutable, randomIndex, randomData) { immutable[randomIndex] = randomData; assert.typeOf(randomIndex, "number"); assert.strictEqual(immutable.length, mutable.length); assert.deepEqual(immutable[randomIndex], mutable[randomIndex]); }, [JSC.integer(), JSC.any()]); });
check(100, [ JSC.array([TestUtils.TraversableObjectSpecifier]) ], function(array) { var immutable = Immutable(array); var idx = JSC.integer(0, array.length-1); var key = JSC.one_of(_.keys(immutable[idx]))(); var value = immutable[idx][key]; TestUtils.assertJsonEqual( Immutable.getIn(immutable, [idx, key]), value ); });
var obj = _.object(_.map(JSC.array()(), function() { var key = JSC.string()(); var value = JSC.one_of([JSC.array(), JSC.object(), JSC.falsy(), JSC.integer(), JSC.number(), JSC.string(), true, Infinity, -Infinity])(); if (typeof value === "object") { return [key, withoutItengerKeys(value)]; } return [key, value]; }));
check(100, [ JSC.array([TestUtils.TraversableObjectSpecifier, JSC.any()]) ], function(array) { var immutable = Immutable(array); var mutable = array.slice(); var index = JSC.integer(0, array.length); var newValue = JSC.any(); var resultImmutable = Immutable.set(immutable, index, newValue); var resultMutable = mutable.slice(); resultMutable[index] = newValue; TestUtils.assertJsonEqual(resultImmutable, resultMutable); });
it('check properties', function() { JSC.test('returns [] when equal', function(veredict, obj) { var map1 = Immutable.fromJS(obj); var map2 = Immutable.fromJS(obj); var result = diff(map1, map2); return veredict(result.count() === 0); }, [JSC.object(5)]); JSC.test('returns add op when missing attribute', function(veredict, obj, obj2) { var map1 = Immutable.fromJS(obj); var map2 = Immutable.fromJS(obj).set('key2', obj2.key2); var result = diff(map1, map2); var expected = Immutable.fromJS([{ op: 'add', path: '/key2', value: obj2.key2 }]); return veredict(Immutable.is(result, expected)); }, [JSC.object({key: JSC.integer()}), JSC.object({key2: JSC.integer()})]); JSC.test('returns replace op when same attribute with different values', function(veredict, obj, newValue) { var map1 = Immutable.fromJS(obj); var map2 = Immutable.fromJS(obj).set('key', newValue); var result = diff(map1, map2); var expected = Immutable.fromJS([{ op: 'replace', path: '/key', value: newValue }]); return veredict(Immutable.is(result, expected)); }, [JSC.object({key: JSC.integer(1, 100)}), JSC.integer(101, 200)]); JSC.test('returns remove op when attribute is missing', function(veredict, obj) { var map1 = Immutable.fromJS(obj); var map2 = Immutable.Map(); var result = diff(map1, map2); var expected = Immutable.fromJS([{ op: 'remove', path: '/key' }]); return veredict(Immutable.is(result, expected)); }, [JSC.object({key: JSC.integer()})]); });
check(100, [ JSC.array([TestUtils.TraversableObjectSpecifier]) ], function(array) { var immutable = Immutable(array); var mutable = array.slice(); var value = JSC.any()(); var idx = JSC.integer(0, array.length-1); var key = JSC.one_of(_.keys(immutable[idx]))(); var util = require('util'); function printArr(arr) { return '[\n\t>'+_.map(arr, util.inspect).join('\n\t>')+'\n]'; } TestUtils.assertJsonEqual(immutable, mutable); var resultMutable = mutable.slice(); resultMutable[idx][key] = value; TestUtils.assertJsonEqual( Immutable.setIn(immutable, [idx, key], value), resultMutable ); });
it("supports accessing elements by index via []", function() { checkImmutableMutable(function(immutable, mutable, index) { assert.typeOf(index, "number"); assert.deepEqual(immutable[index], mutable[index]); }, [JSC.integer()]); });
module.exports = function(config) { var Immutable = config.implementation; var TestUtils = getTestUtils(Immutable); var checkImmutableMutable = TestUtils.checkImmutableMutable(100, [JSC.array([TestUtils.ComplexObjectSpecifier()])]); describe("ImmutableArray", function() { describe("which is compatible with vanilla mutable arrays", function() { it("is an instance of Array", function() { checkImmutableMutable(function(immutable, mutable) { assert.instanceOf(immutable, Array); }); }); it("has the same length as its mutable equivalent", function() { checkImmutableMutable(function(immutable, mutable) { assert.strictEqual(immutable.length, mutable.length); }); }); it("supports accessing elements by index via []", function() { checkImmutableMutable(function(immutable, mutable, index) { assert.typeOf(index, "number"); TestUtils.assertJsonEqual(immutable[index], mutable[index]); }, [JSC.integer()]); }); it("works with for loops", function() { checkImmutableMutable(function(immutable, mutable) { for (var index=0; index < immutable.length; index++) { TestUtils.assertJsonEqual(immutable[index], mutable[index]); } }); }); it("works with for..in loops", function() { checkImmutableMutable(function(immutable, mutable) { for (var index in immutable) { TestUtils.assertJsonEqual(immutable[index], mutable[index]); } }); }); it("supports concat", function() { checkImmutableMutable(function(immutable, mutable, otherArray) { TestUtils.assertJsonEqual(immutable.concat(otherArray), mutable.concat(otherArray)); }, [JSC.array()]); }); it("supports being an argument to a normal immutable's concat", function() { checkImmutableMutable(function(immutable, mutable, otherArray) { TestUtils.assertJsonEqual(otherArray.concat(immutable), otherArray.concat(mutable)); }, [JSC.array()]); }); it("can be concatted to itself", function() { checkImmutableMutable(function(immutable, mutable) { TestUtils.assertJsonEqual(immutable.concat(immutable), mutable.concat(mutable)); }); }); it("has a toString() method that works like a regular immutable's toString()", function() { checkImmutableMutable(function(immutable, mutable) { assert.strictEqual(immutable.toString(), mutable.toString()); }); }); it("supports being passed to JSON.stringify", function() { checkImmutableMutable(function(immutable, mutable) { TestUtils.assertJsonEqual(JSON.stringify(immutable), JSON.stringify(mutable)); }); }); if (config.id === "dev") { it("is frozen", function() { checkImmutableMutable(function(immutable, mutable) { assert.isTrue(Object.isFrozen(immutable)); }); }); } if (config.id === "prod") { it("is not frozen", function() { checkImmutableMutable(function(immutable, mutable) { assert.isFalse(Object.isFrozen(immutable)); }); }); } it("is tagged as immutable", function() { checkImmutableMutable(function(immutable, mutable) { TestUtils.assertIsDeeplyImmutable(immutable); }) }); if (config.id === "dev") { it("cannot have its elements directly mutated", function () { checkImmutableMutable(function (immutable, mutable, randomIndex, randomData) { immutable[randomIndex] = randomData; assert.typeOf(randomIndex, "number"); assert.strictEqual(immutable.length, mutable.length); TestUtils.assertJsonEqual(immutable[randomIndex], mutable[randomIndex]); }, [JSC.integer(), JSC.any()]); }); } if (config.id === "prod") { it("can have its elements directly mutated", function () { var immutableArr = Immutable([1, 2, 3]); immutableArr[0] = 4; assert.equal(immutableArr[0], 4); immutableArr.sort(); TestUtils.assertJsonEqual(immutableArr, [2, 3, 4]); }); } it("makes nested content immutable as well", function() { checkImmutableMutable(function(immutable, mutable, innerArray, obj) { mutable.push(innerArray); // Make a nested immutable mutable.push(obj); // Get an object in there too immutable = Immutable(mutable); assert.strictEqual(immutable.length, mutable.length); for (var index in mutable) { TestUtils.assertIsDeeplyImmutable(immutable[index]); } TestUtils.assertIsDeeplyImmutable(immutable); }); }); // TODO this never fails under Node, even after removing Immutable.Array's // call to toImmutable(). Need to verify that it can fail in browsers. it("reuses existing immutables during construction", function() { checkImmutableMutable(function(immutable, mutable, innerArray, obj) { mutable.push(innerArray); // Make a nested immutable mutable.push(obj); // Get an object in there too immutable = Immutable(mutable); var copiedArray = Immutable(immutable); assert.strictEqual(copiedArray.length, immutable.length); for (var index in copiedArray) { TestUtils.assertJsonEqual(immutable[index], copiedArray[index]); } }, [JSC.array(), JSC.object()]); }); }); }); // Add a "returns immutable" claim for each non-mutating method on Array. nonMutatingArrayMethods = { map: [JSC.literal(identityFunction)], filter: [JSC.literal(identityFunction)], reduce: [JSC.literal(identityFunction), JSC.any()], reduceRight: [JSC.literal(identityFunction), JSC.any()], concat: [JSC.array()], slice: [JSC.integer(), JSC.integer()] } _.each(nonMutatingArrayMethods, function(specifiers, methodName) { it("returns only immutables when you call its " + methodName + "() method", function() { checkImmutableMutable(function(immutable, mutable) { var methodArgs = specifiers.map(function(generator) { return generator() }); TestUtils.assertImmutable(methodName, immutable, mutable, methodArgs); }); }); }); [ // Add a "reports banned" claim for each mutating method on Array. "setPrototypeOf", "push", "pop", "sort", "splice", "shift", "unshift", "reverse" ].forEach(function(methodName) { var description = "it throws an ImmutableError when you try to call its " + methodName + "() method"; checkImmutableMutable(function(immutable, mutable, innerArray, obj) { assert.throw(function() { array[methodName].apply(array, methodArgs); }); }, Immutable.ImmutableError); }); };
it('check properties', function() { JSC.test( 'returns [] when equal', function(veredict, int1){ var result = diff(int1, int1); var expected = Immutable.fromJS([]); return veredict(Immutable.is(result, expected)); }, [ JSC.integer() ] ); JSC.test( 'replaces numbers', function(veredict, int1, int2){ var result = diff(int1, int2); var expected = Immutable.fromJS([ {op: 'replace', path: '/', value: int2} ]); return veredict(Immutable.is(result, expected)); }, [ JSC.integer(), JSC.integer() ] ); JSC.test( 'replaces strings', function(veredict, str1, str2){ var result = diff(str1, str2); var expected = Immutable.fromJS([ {op: 'replace', path: '/', value: str2} ]); return veredict(Immutable.is(result, expected)); }, [ JSC.string(), JSC.string() ] ); JSC.test( 'replaces arrays', function(veredict, array1, array2){ var result = diff(array1, array2); var expected = Immutable.fromJS([ {op: 'replace', path: '/', value: array2} ]); return veredict(Immutable.is(result, expected)); }, [ JSC.array(5), JSC.array(5) ] ); JSC.test( 'replaces objects', function(veredict, object1, object2){ var result = diff(object1, object2); var expected = Immutable.fromJS([ {op: 'replace', path: '/', value: object2} ]); return veredict(Immutable.is(result, expected)); }, [ JSC.object(5), JSC.object(5) ] ); });
it('check nested structures', function() { JSC.test('returns add op when missing attribute in nested structure', function(veredict, obj, obj2) { var map1 = Immutable.fromJS(obj); var map2 = Immutable.fromJS(obj).setIn(['b', 'd'], obj2.d); var result = diff(map1, map2); var expected = Immutable.fromJS([{ op: 'add', path: '/b/d', value: obj2.d }]); return veredict(Immutable.is(result, expected)); }, [JSC.object({ a: JSC.integer(), b: JSC.object({c: JSC.integer()}) }), JSC.object({d: JSC.integer()})]); JSC.test('returns replace op when different value in nested structure', function(veredict, obj, obj2) { var map1 = Immutable.fromJS(obj); var map2 = Immutable.fromJS(obj).setIn(['b', 'c'], obj2.c); var result = diff(map1, map2); var expected = Immutable.fromJS([{ op: 'replace', path: '/b/c', value: obj2.c }]); return veredict(Immutable.is(result, expected)); }, [JSC.object({ a: JSC.integer(), b: JSC.object({c: JSC.integer(1, 100)}) }), JSC.object({c: JSC.integer(101, 200)})]); JSC.test('returns remove op when attribute removed in nested structure', function(veredict, obj, obj2) { var map1 = Immutable.fromJS(obj).setIn(['b', 'd'], obj2.d); var map2 = Immutable.fromJS(obj); var result = diff(map1, map2); var expected = Immutable.fromJS([{ op: 'remove', path: '/b/d' }]); return veredict(Immutable.is(result, expected)); }, [JSC.object({ a: JSC.integer(), b: JSC.object({c: JSC.integer()}) }), JSC.object({d: JSC.integer()})]); JSC.test('no replace in equal nested structure', function(veredict, obj, obj2) { var map1 = Immutable.fromJS(obj); var map2 = Immutable.fromJS(obj).set('a', obj2.a); var result = diff(map1, map2); var expected = Immutable.fromJS([{ op: 'replace', path: '/a', value: obj2.a }]); return veredict(Immutable.is(result, expected)); }, [JSC.object({ a: JSC.integer(), b: JSC.object({c: JSC.integer()}) }), JSC.object({a: JSC.integer()})]); JSC.test('add/remove when different nested structure', function(veredict, obj, obj2) { var map1 = Immutable.fromJS(obj); var map2 = Immutable.fromJS(obj).set('b', Immutable.fromJS(obj2)); var result = diff(map1, map2); var expected = Immutable.fromJS([{ op: 'remove', path: '/b/c' }, { op: 'add', path: '/b/e', value: obj2.e }]); return veredict(Immutable.is(result, expected)); }, [JSC.object({ a: JSC.integer(), b: JSC.object({c: JSC.integer()}) }), JSC.object({e: JSC.integer()})]); });