type-enforcer
Version:
Type enforcement library for javascript
865 lines (720 loc) • 22.9 kB
JavaScript
import { assert } from 'chai';
import { assign, each, join, map, startCase } from 'lodash';
import powerset from 'powerset';
import { method, Point } from '../../src';
import enforceString from '../../src/enforcer/types/enforceString';
import { processOutput } from '../../src/methods/variants/helper';
const TEST_METHOD = 'testMethod';
const variantSet = powerset(['get', 'other', 'before', 'set']);
const everyMethodVariant = map(variantSet, (combination) => {
return {
name: TEST_METHOD + join(map(combination, startCase), ''),
options: combination
};
});
export const testMethodType = (settings) => {
let testBefore = '';
let testSet = '';
const testBeforeCallback = function(oldValue) {
testBefore = processOutput(oldValue, settings.extraProps);
};
const testSetCallback = function(newValue) {
testSet = processOutput(newValue, settings.extraProps);
};
const testGetCallback = function() {
return settings.init;
};
const testGetCallbackWithTestItem = function() {
return settings.true[0];
};
const runTests = (TestConstructor, init, testItem, coerce) => {
each(everyMethodVariant, (methodData) => {
const methodName = methodData.name;
const hasGet = methodData.options.includes('get');
const hasOther = methodData.options.includes('other');
const hasBefore = methodData.options.includes('before');
const hasSet = methodData.options.includes('set');
describe('(' + methodName.replace('testMethod', '') + ')', () => {
it('should return the init value', () => {
const testConstructor = new TestConstructor();
assert.deepEqual(testConstructor[methodName](), init);
});
it('should return "this" after a value is set', () => {
const testConstructor = new TestConstructor();
const that = testConstructor[methodName](testItem);
assert.equal(that, testConstructor);
});
if (hasGet) {
it('should return whatever the "get" callback returns', () => {
const testConstructor = new TestConstructor();
testConstructor[methodName](testItem);
assert.deepEqual(testConstructor[methodName](), init);
});
}
else {
it('should return a set value', () => {
const testConstructor = new TestConstructor();
testConstructor[methodName](testItem);
assert.deepEqual(testConstructor[methodName](), testItem);
});
}
if (hasSet) {
it('should execute the "set" callback when the value is set', () => {
const testConstructor = new TestConstructor();
testSet = '';
testConstructor[methodName](testItem);
assert.deepEqual(testSet, testItem);
});
it('should execute the "set" callback when the value is set to the current value and a second parameter of "true" is provided', () => {
const testConstructor = new TestConstructor();
testSet = '';
testConstructor[methodName](testItem);
testSet = '';
testConstructor[methodName](testItem, true);
assert.deepEqual(testSet, testItem);
});
if (!hasGet) {
it('should NOT execute the "set" callback when the value is set to the current value', () => {
const testConstructor = new TestConstructor();
testSet = '';
testConstructor[methodName](testItem);
testSet = '';
testConstructor[methodName](testItem);
assert.deepEqual(testSet, '');
});
}
}
if (hasBefore) {
it('should call the "before" callback with the init value when a value is set', () => {
const testConstructor = new TestConstructor();
testBefore = '';
testConstructor[methodName](testItem);
assert.deepEqual(testBefore, init);
});
}
if (hasOther) {
if (!hasGet) {
it('should be able to be set to other', () => {
const testConstructor = new TestConstructor();
testConstructor[methodName](undefined);
assert.deepEqual(testConstructor[methodName](), undefined);
});
}
else if (hasSet && init) {
it('should be able to be set to other', () => {
const testConstructor = new TestConstructor();
testSet = '';
testConstructor[methodName](undefined);
assert.deepEqual(testSet, undefined);
});
}
else if (hasBefore && !hasGet && init) {
it('should be able to be set to other', () => {
const testConstructor = new TestConstructor();
testBefore = '';
testConstructor[methodName](undefined);
testConstructor[methodName](testItem);
assert.deepEqual(testBefore, undefined);
});
}
else if (hasBefore && hasGet && init) {
it('should be able to be set to other', () => {
const testConstructor = new TestConstructor();
testBefore = '';
testConstructor[methodName](undefined);
assert.deepEqual(testBefore, init);
});
}
}
each(coerce, (item) => {
if (!hasGet) {
it('should coerce ' + item.value + ' to ' + item.coerced, () => {
const testConstructor = new TestConstructor();
testConstructor[methodName](item.value);
assert.deepEqual(testConstructor[methodName](), item.coerced);
});
}
else if (hasSet) {
it('should coerce ' + item.value + ' to ' + item.coerced, () => {
const testConstructor = new TestConstructor();
testSet = '';
testConstructor[methodName](item.value);
assert.deepEqual(testSet, item.coerced);
});
}
});
});
});
it('should not set the value of another method with the same variant', () => {
const testConstructor = new TestConstructor();
testConstructor[TEST_METHOD](testItem);
assert.deepEqual(testConstructor[TEST_METHOD](), testItem);
assert.deepEqual(testConstructor[TEST_METHOD + '2'](), init);
});
};
const getOptionCallback = (option, withTestItem) => {
switch (option) {
case 'get':
return withTestItem ? testGetCallbackWithTestItem : testGetCallback;
case 'other':
return undefined;
case 'before':
return testBeforeCallback;
case 'set':
return testSetCallback;
}
};
const addMethodsTo = (applyTo, extraProps = {}) => {
each(everyMethodVariant, (methodData) => {
const options = assign({}, settings.extraProps, extraProps);
each(methodData.options, (option) => {
options[option] = getOptionCallback(option, extraProps.init);
});
applyTo[methodData.name] = method[settings.name](options);
});
applyTo[TEST_METHOD + '2'] = method[settings.name](assign({}, settings.extraProps, extraProps));
};
describe('(prototype)', () => {
class TestConstructor1 {
}
addMethodsTo(TestConstructor1.prototype);
runTests(TestConstructor1, settings.init, settings.true[0], settings.coerce);
});
describe('(prototype) (init)', () => {
class TestConstructor2 {
}
addMethodsTo(TestConstructor2.prototype, {
init: settings.true[0]
});
runTests(TestConstructor2, settings.true[0], settings.true[1], settings.coerce);
each(settings.false, (falseValue) => {
it(`should return ${settings.true[0]} after attempting to set to ${falseValue}`, () => {
const testConstructor = new TestConstructor2();
testConstructor.testMethod(falseValue);
assert.deepEqual(testConstructor.testMethod(), settings.true[0]);
});
});
});
describe('(property)', () => {
const TestConstructor3 = function() {
addMethodsTo(this);
};
runTests(TestConstructor3, settings.init, settings.true[0], settings.coerce);
});
describe('(property) (init)', () => {
const TestConstructor4 = function() {
addMethodsTo(this, {
init: settings.true[0]
});
};
runTests(TestConstructor4, settings.true[0], settings.true[1], settings.coerce);
});
if (settings.coerce !== false) {
describe('(prototype) (coerce=true)', () => {
class TestConstructor2 {
}
addMethodsTo(TestConstructor2.prototype, {
init: undefined,
coerce: true
});
each(settings.coerceTrue, (value) => {
it(`should return coerced ${value} after attempting to set to ${value}`, () => {
const testConstructor = new TestConstructor2();
testConstructor.testMethod(value);
assert.notEqual(testConstructor.testMethod(), undefined);
});
});
});
describe('(prototype) (coerce=false)', () => {
class TestConstructor2 {
}
addMethodsTo(TestConstructor2.prototype, {
init: undefined,
coerce: false
});
each(settings.coerceTrue, (value) => {
it(`should return coerced ${value} after attempting to set to ${value}`, () => {
const testConstructor = new TestConstructor2();
testConstructor.testMethod(value);
assert.equal(testConstructor.testMethod(), undefined);
});
});
});
}
};
export const testVariant = (settings) => {
const emptyFunction = () => {
};
const notEnforced = (newValue) => newValue;
const simpleCompare = (newValue, oldValue) => newValue !== oldValue;
const defaultOptions = {
enforce: notEnforced,
compare: simpleCompare,
set: emptyFunction,
get: emptyFunction,
before: emptyFunction,
other: []
};
it('should return the constructor after setting a value', () => {
const Constructor = function() {
this.testMethod = settings.variant(assign({}, defaultOptions));
};
const constructor = new Constructor();
constructor.testMethod('something');
assert.equal(constructor.testMethod('something'), constructor);
});
if (settings.options.includes('get')) {
if (settings.options.length !== 1) {
it('should call the "enforce" callback with newValue, oldValue, and options', () => {
let testVar = '';
const variantOptions = assign({}, defaultOptions, {
enforce: function(newValue, oldValue, options) {
if (newValue === '2' && oldValue === '1' && options === variantOptions) {
testVar = newValue;
}
return newValue;
},
get: () => '1'
});
const Constructor = function() {
this.testMethod = settings.variant(variantOptions);
};
const constructor = new Constructor();
constructor
.testMethod('1')
.testMethod('2');
assert.equal(testVar, '2');
});
it('should call the "compare" callback with the value returned from enforce and the oldValue', () => {
let testVar = '';
const variantOptions = assign({}, defaultOptions, {
enforce: function() {
return '3';
},
compare: (newValue, oldValue) => {
if (newValue === '3' && oldValue === '1') {
testVar = '4';
}
return true;
},
get: () => '1'
});
const Constructor = function() {
this.testMethod = settings.variant(variantOptions);
};
const constructor = new Constructor();
constructor
.testMethod('1');
assert.equal(testVar, '4');
});
}
describe('(get)', () => {
it('should call the "get" callback with context', () => {
let testVar = '';
const Constructor = function() {
this.testMethod = settings.variant(assign({}, defaultOptions, {
get: function() {
testVar = this;
return 'something';
}
}));
};
const constructor = new Constructor();
assert.equal(constructor.testMethod(), 'something');
assert.equal(testVar, constructor);
});
it('should run toString on the result of the "get" callback', () => {
const Constructor = function() {
this.testMethod = settings.variant(assign({}, defaultOptions, {
get: function() {
return new Point(1, 2);
},
stringify: true
}));
};
const constructor = new Constructor();
assert.equal(constructor.testMethod(), '1,2');
});
});
}
else {
it('should call the "enforce" callback with newValue, oldValue, and options', () => {
let testVar = '';
const variantOptions = assign({}, defaultOptions, {
enforce: function(newValue, oldValue, options) {
if (newValue === '2' && oldValue === '1' && options === variantOptions) {
testVar = newValue;
}
return newValue;
}
});
const Constructor = function() {
this.testMethod = settings.variant(variantOptions);
};
const constructor = new Constructor();
constructor
.testMethod('1')
.testMethod('2');
assert.equal(testVar, '2');
});
it('should call the "compare" callback with the value returned from enforce and the oldValue', () => {
let testVar = '';
const variantOptions = assign({}, defaultOptions, {
enforce: function(newValue, oldValue, options) {
if (newValue === '2' && oldValue === '1' && options === variantOptions) {
testVar = newValue;
}
return newValue;
},
compare: (newValue, oldValue) => {
if (newValue === '2' && oldValue === '1') {
testVar = '4';
}
return true;
}
});
const Constructor = function() {
this.testMethod = settings.variant(variantOptions);
};
const constructor = new Constructor();
constructor
.testMethod('1')
.testMethod('2');
assert.equal(testVar, '4');
});
it('should NOT set a property Symbol on the constructor before the value is set', () => {
const Constructor = function() {
this.testMethod = settings.variant(assign({}, defaultOptions));
};
const constructor = new Constructor();
assert.equal(Object.getOwnPropertySymbols(constructor).length, 0);
});
it('should set a property Symbol on the constructor after the value is set', () => {
const Constructor = function() {
this.testMethod = settings.variant(assign({}, defaultOptions));
};
const constructor = new Constructor();
constructor.testMethod('something');
assert.equal(Object.getOwnPropertySymbols(constructor).length, 1);
});
it('should save a value', () => {
const Constructor = function() {
this.testMethod = settings.variant(assign({}, defaultOptions));
};
const constructor = new Constructor();
constructor.testMethod('something');
assert.equal(constructor.testMethod(), 'something');
});
it('should run toString on returned value if stringify is true', () => {
const Constructor = function() {
this.testMethod = settings.variant(assign({}, defaultOptions, {
init: new Point(1, 2),
stringify: true
}));
};
const constructor = new Constructor();
assert.deepEqual(constructor.testMethod(), '1,2');
});
}
if (settings.options.includes('before')) {
if (settings.options.includes('get')) {
it('should call the "before" callback with the oldValue with context if "compare" returns true', () => {
let testVar = '';
const Constructor = function() {
this.testMethod = settings.variant(assign({}, defaultOptions, {
before: function(oldValue) {
if (oldValue === '1') {
testVar = this;
}
},
get: () => '1'
}));
};
const constructor = new Constructor();
constructor
.testMethod('1')
.testMethod('2');
assert.equal(testVar, constructor);
});
it('should NOT call the "before" callback if "compare" returns false', () => {
let testVar = '';
const Constructor = function() {
this.testMethod = settings.variant(assign({}, defaultOptions, {
before: function(oldValue) {
if (oldValue === '1') {
testVar = this;
}
},
get: () => '1'
}));
};
const constructor = new Constructor();
constructor
.testMethod('1')
.testMethod('1');
assert.equal(testVar, '');
});
it('should call the "before" callback if "compare" returns false and isForceSave is true', () => {
let testVar = '';
const Constructor = function() {
this.testMethod = settings.variant(assign({}, defaultOptions, {
before: function(oldValue) {
if (oldValue === '1') {
testVar = this;
}
},
get: () => '1'
}));
};
const constructor = new Constructor();
constructor
.testMethod('1')
.testMethod('1', true);
assert.equal(testVar, constructor);
});
}
else {
it('should call the "before" callback with the oldValue with context if "compare" returns true', () => {
let testVar = '';
const Constructor = function() {
this.testMethod = settings.variant(assign({}, defaultOptions, {
before: function(oldValue) {
if (oldValue === '1') {
testVar = this;
}
}
}));
};
const constructor = new Constructor();
constructor
.testMethod('1')
.testMethod('2');
assert.equal(testVar, constructor);
});
it('should NOT call the "before" callback if "compare" returns false', () => {
let testVar = '';
const Constructor = function() {
this.testMethod = settings.variant(assign({}, defaultOptions, {
before: function(oldValue) {
if (oldValue === '1') {
testVar = this;
}
}
}));
};
const constructor = new Constructor();
constructor
.testMethod('1')
.testMethod('1');
assert.equal(testVar, '');
});
it('should call the "before" callback if "compare" returns false and isForceSave is true', () => {
let testVar = '';
const Constructor = function() {
this.testMethod = settings.variant(assign({}, defaultOptions, {
before: function(oldValue) {
if (oldValue === '1') {
testVar = this;
}
}
}));
};
const constructor = new Constructor();
constructor
.testMethod('1')
.testMethod('1', true);
assert.equal(testVar, constructor);
});
}
}
if (settings.options.includes('set')) {
if (settings.options.includes('get')) {
it('should call the "set" callback with the newValue with context if "compare" returns true', () => {
let testVar = '';
const Constructor = function() {
this.testMethod = settings.variant(assign({}, defaultOptions, {
set: function(newValue) {
if (newValue === '2') {
testVar = this;
}
},
get: () => '1'
}));
};
const constructor = new Constructor();
constructor
.testMethod('2');
assert.equal(testVar, constructor);
});
it('should NOT call the "set" callback if "compare" returns false', () => {
let testVar = '';
const Constructor = function() {
this.testMethod = settings.variant(assign({}, defaultOptions, {
set: function(newValue) {
if (newValue === '1') {
testVar = this;
}
},
get: () => '1'
}));
};
const constructor = new Constructor();
constructor
.testMethod('1');
assert.equal(testVar, '');
});
it('should call the "set" callback if "compare" returns false and isForceSave is true', () => {
let testVar = '';
const Constructor = function() {
this.testMethod = settings.variant(assign({}, defaultOptions, {
set: function(newValue) {
if (newValue === '1') {
testVar = this;
}
},
get: () => '1'
}));
};
const constructor = new Constructor();
constructor
.testMethod('1', true);
assert.equal(testVar, constructor);
});
}
else {
it('should call the "set" callback with the newValue with context if "compare" returns true', () => {
let testVar = '';
const Constructor = function() {
this.testMethod = settings.variant(assign({}, defaultOptions, {
set: function(newValue) {
if (newValue === '2') {
testVar = this;
}
}
}));
};
const constructor = new Constructor();
constructor
.testMethod('1');
testVar = '';
constructor
.testMethod('2');
assert.equal(testVar, constructor);
});
it('should NOT call the "set" callback if "compare" returns false', () => {
let testVar = '';
const Constructor = function() {
this.testMethod = settings.variant(assign({}, defaultOptions, {
set: function(newValue) {
if (newValue === '2') {
testVar = this;
}
}
}));
};
const constructor = new Constructor();
constructor
.testMethod('1');
testVar = '';
constructor
.testMethod('1');
assert.equal(testVar, '');
});
it('should call the "set" callback if "compare" returns false and isForceSave is true', () => {
let testVar = '';
const Constructor = function() {
this.testMethod = settings.variant(assign({}, defaultOptions, {
set: function(newValue) {
if (newValue === '1') {
testVar = this;
}
}
}));
};
const constructor = new Constructor();
constructor
.testMethod('1');
testVar = '';
constructor
.testMethod('1', true);
assert.equal(testVar, constructor);
});
}
}
if (settings.options.includes('other')) {
if (settings.options.includes('get')) {
it('should call the "compare" callback with a different type if other has that type', () => {
let testVar = '';
const Constructor = function() {
this.testMethod = settings.variant(assign({}, defaultOptions, {
enforce: enforceString,
compare: function(newValue) {
testVar = newValue;
},
get: () => '1',
other: [RegExp, Boolean]
}));
};
const constructor = new Constructor();
constructor
.testMethod('1')
.testMethod(true);
assert.equal(testVar, true);
});
it('should call the "compare" callback with a different value if other has that value', () => {
let testVar = '';
const Constructor = function() {
this.testMethod = settings.variant(assign({}, defaultOptions, {
enforce: enforceString,
compare: function(newValue) {
testVar = newValue;
},
get: () => '1',
other: [RegExp, null]
}));
};
const constructor = new Constructor();
constructor
.testMethod('1')
.testMethod(null);
assert.equal(testVar, null);
});
}
else {
it('should set the value with a different type if other has that type', () => {
let testVar = '';
const Constructor = function() {
this.testMethod = settings.variant(assign({}, defaultOptions, {
enforce: enforceString,
compare: function(newValue) {
testVar = newValue;
},
other: [RegExp, Boolean]
}));
};
const constructor = new Constructor();
constructor
.testMethod('1')
.testMethod(true);
assert.equal(testVar, true);
});
it('should set the value with a different value if other has that value', () => {
let testVar = '';
const Constructor = function() {
this.testMethod = settings.variant(assign({}, defaultOptions, {
enforce: enforceString,
compare: function(newValue) {
testVar = newValue;
},
other: [RegExp, null]
}));
};
const constructor = new Constructor();
constructor
.testMethod('1')
.testMethod(null);
assert.equal(testVar, null);
});
}
}
};