UNPKG

immutable-class-tester

Version:
144 lines (143 loc) 5.86 kB
import deepEqual from 'deep-equal'; import hasOwnProp from 'has-own-prop'; const PROPERTY_KEYS = [ 'name', 'defaultValue', 'possibleValues', 'validate', 'immutableClass', 'immutableClassArray', 'immutableClassLookup', 'equal', 'toJS', 'type', 'contextTransform', 'preserveUndefined', 'emptyArrayIsOk', ]; export function testImmutableClass(ClassFn, objects, options = {}) { if (typeof ClassFn !== 'function') throw new TypeError(`ClassFn must be a constructor function`); if (!Array.isArray(objects) || !objects.length) { throw new TypeError(`objects must be a non-empty array of js to test`); } const newThrows = options.newThrows; const context = options.context; const className = ClassFn.name; if (className.length < 1) throw new Error(`Class must have a name of at least 1 letter`); const instanceName = className[0].toLowerCase() + className.substring(1); if (typeof ClassFn.fromJS !== 'function') throw new Error(`${className}.fromJS should exist`); const instance = ClassFn.fromJS(objects[0], context); const objectProto = Object.prototype; if (instance.valueOf === objectProto.valueOf) { throw new Error(`Instance should implement valueOf`); } if (instance.toString === objectProto.toString) { throw new Error(`Instance should implement toString`); } if (typeof instance.toJS !== 'function') { throw new Error(`Instance should have a toJS function`); } if (typeof instance.toJSON !== 'function') { throw new Error(`Instance should have a toJSON function`); } if (typeof instance.equals !== 'function') { throw new Error(`Instance should have an equals function`); } if (ClassFn.PROPERTIES) { if (!Array.isArray(ClassFn.PROPERTIES)) { throw new Error('PROPERTIES should be an array'); } ClassFn.PROPERTIES.forEach((property, i) => { if (typeof property.name !== 'string') { throw new Error(`Property ${i} is missing a name`); } Object.keys(property).forEach(key => { if (!PROPERTY_KEYS.includes(key)) { throw new Error(`PROPERTIES should include ${key}`); } }); }); } for (let i = 0; i < objects.length; i++) { const where = `[in object ${i}]`; const objectJSON = JSON.stringify(objects[i]); const objectCopy1 = JSON.parse(objectJSON); const objectCopy2 = JSON.parse(objectJSON); const inst = ClassFn.fromJS(objectCopy1, context); if (!deepEqual(objectCopy1, objectCopy2)) { throw new Error(`${className}.fromJS function modified its input :-(`); } if (!(inst instanceof ClassFn)) { throw new Error(`${className}.fromJS did not return a ${className} instance ${where}`); } if (typeof inst.toString() !== 'string') { throw new Error(`${instanceName}.toString() must return a string ${where}`); } if (inst.equals(undefined) !== false) { throw new Error(`${instanceName}.equals(undefined) should be false ${where}`); } if (inst.equals(null) !== false) { throw new Error(`${instanceName}.equals(null) should be false ${where}`); } if (inst.equals([]) !== false) { throw new Error(`${instanceName}.equals([]) should be false ${where}`); } if (!deepEqual(inst.toJS(), objects[i])) { throw new Error(`${className}.fromJS(obj).toJS() was not a fixed point (did not deep equal obj) ${where}`); } const instValueOf = inst.valueOf(); if (inst.equals(instValueOf)) { throw new Error(`inst.equals(inst.valueOf()) ${where}`); } const instLazyCopy = {}; for (const key in inst) { if (!hasOwnProp(inst, key)) continue; instLazyCopy[key] = inst[key]; } if (inst.equals(instLazyCopy)) { throw new Error(`inst.equals(*an object with the same values*) ${where}`); } if (newThrows) { let badInst; let thrownError; try { badInst = new ClassFn(instValueOf); } catch (e) { thrownError = e; } if (!thrownError || badInst) { throw new Error(`new ${className} did not throw as indicated ${where}`); } } else { const instValueCopy = new ClassFn(instValueOf); if (!inst.equals(instValueCopy)) { throw new Error(`new ${className}().toJS() is not equal to the original ${where}`); } if (!deepEqual(instValueCopy.toJS(), inst.toJS())) { throw new Error(`new ${className}(${instanceName}.valueOf()).toJS() returned something bad ${where}`); } } const instJSONCopy = ClassFn.fromJS(JSON.parse(JSON.stringify(inst)), context); if (!inst.equals(instJSONCopy)) { throw new Error(`JS Copy does not equal original ${where}`); } if (!deepEqual(instJSONCopy.toJS(), inst.toJS())) { throw new Error(`${className}.fromJS(JSON.parse(JSON.stringify(${instanceName}))).toJS() returned something bad ${where}`); } } for (let j = 0; j < objects.length; j++) { const objectJ = ClassFn.fromJS(objects[j], context); for (let k = j; k < objects.length; k++) { const objectK = ClassFn.fromJS(objects[k], context); if (objectJ.equals(objectK) !== Boolean(j === k)) { throw new Error(`Equality of objects ${j} and ${k} was wrong`); } } } }