Skip to content

Commit

Permalink
refactor(validators): implement addValidator and getValidator met…
Browse files Browse the repository at this point in the history
…hods for SmartFormValidator
  • Loading branch information
simplymichael committed Dec 14, 2023
1 parent e723032 commit 9fc7f93
Show file tree
Hide file tree
Showing 10 changed files with 325 additions and 57 deletions.
2 changes: 2 additions & 0 deletions __tests__/methods.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ module.exports = function methodsTest() {
addField: require("../src/methods/addField"),
addFields: require("../src/methods/addFields"),
addRule: require("../src/methods/addRule"),
addValidator: require("../src/methods/addValidator"),
getValidators: require("../src/methods/getValidators"),
getEffects: require("../src/methods/getEffects"),
getField: require("../src/methods/getField"),
getFields: require("../src/methods/getFields"),
Expand Down
182 changes: 182 additions & 0 deletions __tests__/methods/addValidator.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
const validators = require("../../src/validators");
const errorMessages = require("../../src/error-messages");
const { createListFromArray, getValidatorNames } = require("../../src/helpers");
const { wrapWithDOMFunctionality } = require("../test-helpers");


module.exports = {
arguments: ["validatorKey", "validatorFn", "[, validatorMeta]"],
test: addValidator,
};


function addValidator(it, expect) {
it("should throw an error if the `validatorKey` argument is not a string", function() {
const context = this.test.context;
const validatorFn = () => {};

expect(context.addValidator.bind(context, null, validatorFn)).to.throw(
errorMessages.functionParamExpectsType
.replace(":param:", "validatorKey")
.replace(":type:", "a string")
);

expect(context.addValidator.bind(context, undefined, validatorFn)).to.throw(
errorMessages.functionParamExpectsType
.replace(":param:", "validatorKey")
.replace(":type:", "a string")
);

expect(context.addValidator.bind(context, [], validatorFn)).to.throw(
errorMessages.functionParamExpectsType
.replace(":param:", "validatorKey")
.replace(":type:", "a string")
);

expect(context.addValidator.bind(context, {}, validatorFn)).to.throw(
errorMessages.functionParamExpectsType
.replace(":param:", "validatorKey")
.replace(":type:", "a string")
);
});

it("should throw an error if the `validatorKey` argument is an empty string", function() {
const context = this.test.context;
const validatorFn = () => {};

expect(context.addValidator.bind(context, "", validatorFn)).to.throw(
errorMessages.fieldCannotBeEmpty
.replace(":field:", "validatorKey")
.replace(":type:", "string")
);
});

it("should throw an error if the `validatorKey` argument is a default validator name", function() {
const context = this.test.context;
const validatorFn = () => {};
const defaultValidatorKeys = getValidatorNames(validators);

defaultValidatorKeys.forEach(function assertCannotUseDefaultValidatorName(validatorKey) {
expect(context.addValidator.bind(context, validatorKey, validatorFn)).to.throw(
errorMessages.argNamesAreReserved
.replace(":argNames:", "keys")
.replace(":argTypes:", "validator keys")
.replace(":argValues:", createListFromArray(defaultValidatorKeys))
);
});
});

it("should throw an error if the `validatorKey` has previously been registered", function() {
const context = this.test.context;
const validatorFn = () => {};
const validatorKey = "test-validator";

expect(context.addValidator.bind(context, validatorKey, validatorFn)).not.to.throw();
expect(context.addValidator.bind(context, validatorKey, validatorFn)).to.throw(
errorMessages.objectWithKeyExists
.replace(":object:", "A validator")
.replace(":key:", validatorKey)
);
});

it("should treat the combination of the `validatorKey` and `validatorMeta.namespace` as unique", function() {
const context = this.test.context;
const validatorKey = "test-validator";
const validatorFn = () => {};
const validatorMeta = { namespace: "test" };
const validatorMeta2 = { namespace: "" };

const defaultValidatorKeys = getValidatorNames(validators);

// Using default validator names, but with a different namespace
defaultValidatorKeys.forEach(validatorKey => {
expect(context.addValidator.bind(context, validatorKey, validatorFn, validatorMeta)).not.to.throw();
});

// Using addon validator names, but with different namespaces (one with, and one without, a namespace)
expect(context.addValidator.bind(context, validatorKey, validatorFn, validatorMeta2)).not.to.throw();
expect(context.addValidator.bind(context, validatorKey, validatorFn, validatorMeta2)).to.throw(
errorMessages.objectWithKeyExists
.replace(":object:", "A validator")
.replace(":key:", validatorKey)
);
expect(context.addValidator.bind(context, validatorKey, validatorFn, validatorMeta)).not.to.throw();
});

it("should throw an error if the `validatorFn` argument is not a function", function() {
const context = this.test.context;
const validatorKey = "test-validator";

expect(context.addValidator.bind(context, validatorKey, null)).to.throw(
errorMessages.functionParamExpectsType
.replace(":param:", "validatorFn")
.replace(":type:", "a function")
);

expect(context.addValidator.bind(context, validatorKey, undefined)).to.throw(
errorMessages.functionParamExpectsType
.replace(":param:", "validatorFn")
.replace(":type:", "a function")
);

expect(context.addValidator.bind(context, validatorKey, [])).to.throw(
errorMessages.functionParamExpectsType
.replace(":param:", "validatorFn")
.replace(":type:", "a function")
);

expect(context.addValidator.bind(context, validatorKey, {})).to.throw(
errorMessages.functionParamExpectsType
.replace(":param:", "validatorFn")
.replace(":type:", "a function")
);
});

it("should register the validator on the 'context' object", function() {
const context = this.test.context;
const validatorKey = "test-validator";
const validatorFn = () => {};

expect(context.addValidator.bind(context, validatorKey, validatorFn)).not.to.throw();

const validators = context.getValidators();

expect(validators).to.be.an("object");
expect(Object.keys(validators)).to.have.length(2);
expect(validators).to.have.property("default");
expect(validators).to.have.property("addon");
expect(validators.default).to.be.an("object");
expect(validators.addon).to.be.an("object");
expect(Object.keys(validators.addon)).to.have.length(1);
expect(Object.keys(validators.addon)[0]).to.equal(validatorKey);
expect(Object.values(validators.addon)[0]).to.deep.equal(validatorFn);
});

it("should register the validator on each field of the 'context' object, if any", function() {
const context = this.test.context;
const submitBtn = wrapWithDOMFunctionality({ type: "submit" });
const validatorKey = "test-validator";
const validatorFn = () => {};

context.addField({ id: "firstname-field" }, { fieldId: "firstname-field", required: true });
context.addField(submitBtn);

expect(context.addValidator.bind(context, validatorKey, validatorFn)).not.to.throw();

const validators = context.getValidators();

expect(validators).to.be.an("object");
expect(Object.keys(validators)).to.have.length(2);
expect(validators).to.have.property("default");
expect(validators).to.have.property("addon");
expect(validators.default).to.be.an("object");
expect(validators.addon).to.be.an("object");
expect(Object.keys(validators.addon)).to.have.length(1);
expect(Object.keys(validators.addon)[0]).to.equal(validatorKey);
expect(Object.values(validators.addon)[0]).to.deep.equal(validatorFn);

context.getFields().forEach(function validateFieldHasValidatorRegistered(field) {
expect(field.hasValidator(validatorKey)).to.equal(true);
});
});
}
2 changes: 2 additions & 0 deletions __tests__/methods/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ function index(it, expect) {
"addField",
"addFields",
"addRule",
"addValidator",
"getValidators",
"getEffects",
"getField",
"getFields",
Expand Down
4 changes: 2 additions & 2 deletions __tests__/methods/useEffect.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const effects = require("../../src/effects");
const errorMessages = require("../../src/error-messages");
const { getEffectNames } = require("../../src/helpers");
const { createListFromArray, getEffectNames } = require("../../src/helpers");
const { wrapWithDOMFunctionality } = require("../test-helpers");


Expand Down Expand Up @@ -142,7 +142,7 @@ function useEffect(it, expect) {
errorMessages.argNamesAreReserved
.replace(":argNames:", "names")
.replace(":argTypes:", "effect names")
.replace(":argValues:", defaultEffectNames.join("\n"))
.replace(":argValues:", createListFromArray(defaultEffectNames))
);
});
});
Expand Down
66 changes: 65 additions & 1 deletion src/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,11 @@ module.exports = {
createListFromArray,
generateEffectName,
getEffectNames,
getValidatorNames,
isSubmitBtn,
normalizeId,
preEffectRegistrationCheck,
preValidatorRegistrationCheck,
validateId,
APP_CLASSNAME,
DISABLED_FIELD_CLASSNAME,
Expand Down Expand Up @@ -106,6 +108,10 @@ function getEffectNames(effects) {
return effectNames;
}

function getValidatorNames(validators) {
return Object.keys(validators);
}

function isSubmitBtn(element) {
return element.type === "submit" || element.role === "submit-button";
}
Expand Down Expand Up @@ -171,13 +177,71 @@ function preEffectRegistrationCheck(effect, defaultEffectNames) {
errorMessages.argNamesAreReserved
.replace(":argNames:", "names")
.replace(":argTypes:", "effect names")
.replace(":argValues:", defaultEffectNames.join("\n"))
.replace(":argValues:", createListFromArray(defaultEffectNames))
);
}

return { ...effect, name: effectName };
}

function preValidatorRegistrationCheck(validatorKey, validatorFn, validatorMeta, defaultValidatorKeys) {
if(!(is.string(validatorKey))) {
throw new TypeError(
errorMessages.functionParamExpectsType
.replace(":param:", "validatorKey")
.replace(":type:", "a string")
);
}

let validatorNamespace = "";

validatorKey = validatorKey.trim();

if(!validatorKey) {
throw new TypeError(
errorMessages.fieldCannotBeEmpty
.replace(":field:", "validatorKey")
.replace(":type:", "string"));
}

if(is.object(validatorMeta) && is.string(validatorMeta.namespace)) {
validatorNamespace = validatorMeta.namespace.trim();
}

validatorKey = generateValidatorKey(validatorKey, validatorNamespace);

if(defaultValidatorKeys.includes(validatorKey)) {
throw new TypeError(
errorMessages.argNamesAreReserved
.replace(":argNames:", "keys")
.replace(":argTypes:", "validator keys")
.replace(":argValues:", createListFromArray(defaultValidatorKeys))
);
}

if(!(is.function(validatorFn))) {
throw new TypeError(
errorMessages.functionParamExpectsType
.replace(":param:", "validatorFn")
.replace(":type:", "a function")
);
}

return validatorKey;
}

function validateId(id) {
return ["number", "string"].includes(typeof id) && Boolean(id);
}


// Helpers
function generateValidatorKey(key, namespace) {
if(key.length > 0 && namespace.length > 0) {
return `${namespace}.${key}`;
} else if(key.length > 0) {
return key;
} else {
return "";
}
}
31 changes: 31 additions & 0 deletions src/methods/addValidator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
const errorMessages = require("../error-messages");
const { getValidatorNames, preValidatorRegistrationCheck } = require("../helpers");
const defaultValidators = require("../validators");

const defaultValidatorKeys = getValidatorNames(defaultValidators); //Object.keys(defaultValidators);


module.exports = function addValidator(validatorKey, validatorFn, validatorMeta) {
this.validators = this.validators || {};

validatorKey = preValidatorRegistrationCheck(validatorKey, validatorFn, validatorMeta, defaultValidatorKeys);

if(this.validators[validatorKey]) {
throw new TypeError(
errorMessages.objectWithKeyExists
.replace(":object:", "A validator")
.replace(":key:", validatorKey)
);
}

this.validators[validatorKey] = validatorFn;

// Add the effect to the elements attached to the instance.
if(typeof this.getFields === "function") {
this.getFields().forEach(field => {
if(!(field.hasValidator(validatorKey))) {
field.addValidator(validatorKey, validatorFn, validatorMeta);
}
});
}
};
30 changes: 30 additions & 0 deletions src/methods/getValidators.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
const validators = require("../validators");
const { is } = require("../helpers");

const defaultValidators = {};

for(const [key, validator] of Object.entries(validators)) {
defaultValidators[key] = validator;
}

/**
* @param {String} type (optional): "addon"|"default".
* @returns {Object} with members: `default` and/or `addon`.
*/
module.exports = function getValidator(type) {
type = is.string(type) ? type.trim().toLowerCase() : "";

const addonValidators = this.validators || null;

if(["addon", "default"].includes(type)) {
switch(type) {
case "default": return defaultValidators;
case "addon" : return addonValidators;
}
} else {
return {
default: defaultValidators,
addon: addonValidators,
};
}
};
2 changes: 2 additions & 0 deletions src/methods/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ const instanceMethodNames = [
"addField",
"addFields",
"addRule",
"addValidator",
"getValidators",
"getEffects",
"getField",
"getFields",
Expand Down
Loading

0 comments on commit 9fc7f93

Please sign in to comment.