From 143a5cd5231c374c626a34686058690f45979afb Mon Sep 17 00:00:00 2001 From: Lee Prosser Date: Mon, 15 Aug 2016 14:00:58 +0100 Subject: [PATCH] Added property intellisense to the ruleset creator, although its completely optional to use, it also should have been called `intellisense-properties` as there is no type safety. --- dist/commonjs/builders/ruleset-builder.js | 17 +++++++- dist/commonjs/exposer.js | 2 +- dist/commonjs/index.js | 6 +-- .../definitions/builders/ruleset-builder.d.ts | 25 ++++++------ dist/definitions/exposer.d.ts | 4 +- dist/definitions/index.d.ts | 12 +++--- docs/creating-rulesets.md | 14 ++++++- package.json | 3 +- readme.md | 12 ++++++ src/builders/ruleset-builder.ts | 40 +++++++++++++------ src/exposer.ts | 6 +-- src/index.ts | 12 +++--- tests/specs/ruleset-builder-tests.ts | 26 ++++++++++++ tests/specs/treacherous-sanity-tests.ts | 5 +-- 14 files changed, 130 insertions(+), 54 deletions(-) diff --git a/dist/commonjs/builders/ruleset-builder.js b/dist/commonjs/builders/ruleset-builder.js index 22f0a43..9c122c0 100644 --- a/dist/commonjs/builders/ruleset-builder.js +++ b/dist/commonjs/builders/ruleset-builder.js @@ -2,6 +2,7 @@ var ruleset_1 = require("../rulesets/ruleset"); var rule_link_1 = require("../rulesets/rule-link"); var for_each_rule_1 = require("../rulesets/for-each-rule"); +var type_helper_1 = require("../helpers/type-helper"); var RulesetBuilder = (function () { function RulesetBuilder(ruleRegistry) { var _this = this; @@ -11,8 +12,15 @@ var RulesetBuilder = (function () { _this.currentProperty = null; return _this; }; - this.forProperty = function (propertyName) { - _this.currentProperty = propertyName; + this.forProperty = function (propertyNameOrPredicate) { + var endProperty = propertyNameOrPredicate; + if (type_helper_1.TypeHelper.isFunctionType(endProperty)) { + endProperty = _this.extractPropertyName(propertyNameOrPredicate); + if (!endProperty) { + throw new Error("cannot resolve property from: " + propertyNameOrPredicate); + } + } + _this.currentProperty = endProperty; _this.currentRule = null; return _this; }; @@ -76,6 +84,11 @@ var RulesetBuilder = (function () { return _this.internalRuleset; }; } + RulesetBuilder.prototype.extractPropertyName = function (predicate) { + var regex = /.*\.([\w]*);/; + var predicateString = predicate.toString(); + return regex.exec(predicateString)[1]; + }; return RulesetBuilder; }()); exports.RulesetBuilder = RulesetBuilder; diff --git a/dist/commonjs/exposer.js b/dist/commonjs/exposer.js index 35c27fa..6145dc5 100644 --- a/dist/commonjs/exposer.js +++ b/dist/commonjs/exposer.js @@ -1,9 +1,9 @@ "use strict"; var field_error_processor_1 = require("./processors/field-error-processor"); -var ruleset_builder_1 = require("./builders/ruleset-builder"); var rule_resolver_1 = require("./rulesets/rule-resolver"); var validation_group_builder_1 = require("./builders/validation-group-builder"); var rule_registry_setup_1 = require("./rule-registry-setup"); +var ruleset_builder_1 = require("./builders/ruleset-builder"); var fieldErrorProcessor = new field_error_processor_1.FieldErrorProcessor(rule_registry_setup_1.ruleRegistry); var ruleResolver = new rule_resolver_1.RuleResolver(); function createRuleset(withRuleVerification) { diff --git a/dist/commonjs/index.js b/dist/commonjs/index.js index c60affe..14a9bdf 100644 --- a/dist/commonjs/index.js +++ b/dist/commonjs/index.js @@ -18,6 +18,7 @@ __export(require("./processors/field-error-processor")); __export(require("./processors/field-has-error")); __export(require("./processors/validation-error")); __export(require("./promises/promise-counter")); +__export(require("./resolvers/model-resolver")); __export(require("./rules/advanced-regex-rule")); __export(require("./rules/date-validation-rule")); __export(require("./rules/decimal-validation-rule")); @@ -35,12 +36,11 @@ __export(require("./rules/regex-validation-rule")); __export(require("./rules/required-validation-rule")); __export(require("./rules/rule-registry")); __export(require("./rules/step-validation-rule")); -__export(require("./resolvers/model-resolver")); -__export(require("./watcher/model-watcher")); -__export(require("./watcher/property-watcher")); __export(require("./rulesets/for-each-rule")); __export(require("./rulesets/rule-link")); __export(require("./rulesets/rule-resolver")); __export(require("./rulesets/ruleset")); __export(require("./validation-groups/reactive-validation-group")); __export(require("./validation-groups/validation-group")); +__export(require("./watcher/model-watcher")); +__export(require("./watcher/property-watcher")); diff --git a/dist/definitions/builders/ruleset-builder.d.ts b/dist/definitions/builders/ruleset-builder.d.ts index f373500..f4cf42a 100644 --- a/dist/definitions/builders/ruleset-builder.d.ts +++ b/dist/definitions/builders/ruleset-builder.d.ts @@ -1,19 +1,20 @@ import { Ruleset } from "../rulesets/ruleset"; import { RuleLink } from "../rulesets/rule-link"; import { RuleRegistry } from "../rules/rule-registry"; -export declare class RulesetBuilder { +export declare class RulesetBuilder { private ruleRegistry; - private internalRuleset; - currentProperty: string; - currentRule: RuleLink; + protected internalRuleset: Ruleset; + protected currentProperty: string; + protected currentRule: RuleLink; constructor(ruleRegistry?: RuleRegistry); - create: () => RulesetBuilder; - forProperty: (propertyName: string) => RulesetBuilder; - addRule: (rule: string, ruleOptions?: any) => RulesetBuilder; - withMessage: (messageOverride: ((value: any, ruleOptions?: any) => string) | string) => RulesetBuilder; - appliesIf: (appliesFunction: ((model: any, value: any, ruleOptions?: any) => boolean) | boolean) => RulesetBuilder; - addRuleForEach: (rule: string, ruleOptions?: any) => RulesetBuilder; - addRuleset: (ruleset: Ruleset) => RulesetBuilder; - addRulesetForEach: (ruleset: Ruleset) => RulesetBuilder; + protected extractPropertyName(predicate: (model: T) => any): string; + create: () => RulesetBuilder; + forProperty: (propertyNameOrPredicate: ((model: T) => any) | string) => RulesetBuilder; + addRule: (rule: string, ruleOptions?: any) => RulesetBuilder; + withMessage: (messageOverride: ((value: any, ruleOptions?: any) => string) | string) => RulesetBuilder; + appliesIf: (appliesFunction: ((model: any, value: any, ruleOptions?: any) => boolean) | boolean) => RulesetBuilder; + addRuleForEach: (rule: string, ruleOptions?: any) => RulesetBuilder; + addRuleset: (ruleset: Ruleset) => RulesetBuilder; + addRulesetForEach: (ruleset: Ruleset) => RulesetBuilder; build: () => Ruleset; } diff --git a/dist/definitions/exposer.d.ts b/dist/definitions/exposer.d.ts index ae191c1..a98244c 100644 --- a/dist/definitions/exposer.d.ts +++ b/dist/definitions/exposer.d.ts @@ -1,4 +1,4 @@ -import { RulesetBuilder } from "./builders/ruleset-builder"; import { ValidationGroupBuilder } from "./builders/validation-group-builder"; -export declare function createRuleset(withRuleVerification?: boolean): RulesetBuilder; +import { RulesetBuilder } from "./builders/ruleset-builder"; +export declare function createRuleset(withRuleVerification?: boolean): RulesetBuilder; export declare function createGroup(): ValidationGroupBuilder; diff --git a/dist/definitions/index.d.ts b/dist/definitions/index.d.ts index a43f460..a82a220 100644 --- a/dist/definitions/index.d.ts +++ b/dist/definitions/index.d.ts @@ -17,6 +17,9 @@ export * from "./processors/field-has-error"; export * from "./processors/ifield-error-processor"; export * from "./processors/validation-error"; export * from "./promises/promise-counter"; +export * from "./resolvers/imodel-resolver"; +export * from "./resolvers/iproperty-resolver"; +export * from "./resolvers/model-resolver"; export * from "./rules/advanced-regex-rule"; export * from "./rules/date-validation-rule"; export * from "./rules/decimal-validation-rule"; @@ -35,12 +38,6 @@ export * from "./rules/regex-validation-rule"; export * from "./rules/required-validation-rule"; export * from "./rules/rule-registry"; export * from "./rules/step-validation-rule"; -export * from "./resolvers/imodel-resolver"; -export * from "./resolvers/iproperty-resolver"; -export * from "./resolvers/model-resolver"; -export * from "./watcher/imodel-watcher"; -export * from "./watcher/model-watcher"; -export * from "./watcher/property-watcher"; export * from "./rulesets/for-each-rule"; export * from "./rulesets/irule-resolver"; export * from "./rulesets/rule-link"; @@ -50,3 +47,6 @@ export * from "./validation-groups/ireactive-validation-group"; export * from "./validation-groups/ivalidation-group"; export * from "./validation-groups/reactive-validation-group"; export * from "./validation-groups/validation-group"; +export * from "./watcher/imodel-watcher"; +export * from "./watcher/model-watcher"; +export * from "./watcher/property-watcher"; diff --git a/docs/creating-rulesets.md b/docs/creating-rulesets.md index a8c379f..22cf148 100644 --- a/docs/creating-rulesets.md +++ b/docs/creating-rulesets.md @@ -10,9 +10,10 @@ are very lightweight objects with no logic. When creating a ruleset there are the following methods: -### forProperty(propertyName: string) +### forProperty = (propertyNameOrPredicate: ((model: T) => any) | string): RulesetBuilder -This takes a string which should be the property name that you wish to apply rules to. +This takes a string which should be the property name that you wish to apply rules to or a predicate which +links to the property, although this is mainly just for typescript users. ### addRule(ruleType: string, ruleArgs?: any) @@ -66,6 +67,15 @@ So the above example is basically setting up a ruleset where it expects a proper should have a `maxLength` of 5. You can easily add multiple rules or nested rules to properties allowing for a very flexible and composite approach to ruleset building and management. +## Simplest Example With Typescript Intellisense + +```ts +var ruleset = Treacherous.createRuleset() + .addProperty(x => x.SomeProperty) + .addRule("required") + .build(); +``` + ## More Complex Example ```js diff --git a/package.json b/package.json index df4e676..8f371db 100644 --- a/package.json +++ b/package.json @@ -44,8 +44,9 @@ "gulp-uglify": "latest", "karma": "1.1.1", "karma-firefox-launcher": "latest", - "karma-mocha": "latest", + "karma-chrome-launcher": "latest", "karma-phantomjs-launcher": "^1.0.1", + "karma-mocha": "latest", "karma-webpack": "1.7.0", "merge2": "latest", "mkdirp": "latest", diff --git a/readme.md b/readme.md index f1df350..8ef69cb 100644 --- a/readme.md +++ b/readme.md @@ -128,6 +128,18 @@ var validationGroup = Treacherous.createGroup() validationGroup.propertyStateChangedEvent.subscribe(function(propertyValidationChangedEvent){...)); ``` +## Typescript users + +As typescript users you can get some nicer features and intellisense so you can create typed rules allowing +you to use lambda style property location like so: + +```ts +var ruleset = Treacherous.createRuleset() + .addProperty(x => x.SomeProperty) + .addRule("required") + .build(); +``` + --- ## Validation rules diff --git a/src/builders/ruleset-builder.ts b/src/builders/ruleset-builder.ts index 2c6f6fd..087cb13 100644 --- a/src/builders/ruleset-builder.ts +++ b/src/builders/ruleset-builder.ts @@ -2,30 +2,44 @@ import {Ruleset} from "../rulesets/ruleset"; import {RuleLink} from "../rulesets/rule-link"; import {ForEachRule} from "../rulesets/for-each-rule"; import {RuleRegistry} from "../rules/rule-registry"; +import {TypeHelper} from "../helpers/type-helper"; -export class RulesetBuilder +export class RulesetBuilder { - private internalRuleset: Ruleset; - public currentProperty: string; - public currentRule:RuleLink; + protected internalRuleset: Ruleset; + protected currentProperty: string; + protected currentRule:RuleLink; constructor(private ruleRegistry?: RuleRegistry) {} - public create = (): RulesetBuilder => + protected extractPropertyName(predicate: (model: T) => any) : string { + var regex = /.*\.([\w]*);/; + var predicateString = predicate.toString(); + return regex.exec(predicateString)[1]; + } + + public create = (): RulesetBuilder => { this.internalRuleset = new Ruleset(); this.currentProperty = null; return this; } - public forProperty = (propertyName: string): RulesetBuilder => + public forProperty = (propertyNameOrPredicate: ((model: T) => any) | string): RulesetBuilder => { - this.currentProperty = propertyName; + var endProperty = propertyNameOrPredicate; + if(TypeHelper.isFunctionType(endProperty)) + { + endProperty = this.extractPropertyName(propertyNameOrPredicate); + if(!endProperty) { throw new Error(`cannot resolve property from: ${propertyNameOrPredicate}`); } + } + + this.currentProperty = endProperty; this.currentRule = null; return this; } - public addRule = (rule: string, ruleOptions?: any): RulesetBuilder => + public addRule = (rule: string, ruleOptions?: any): RulesetBuilder => { if(rule == null || typeof(rule) == "undefined" || rule.length == 0) { throw new Error("A rule name is required"); } @@ -40,7 +54,7 @@ export class RulesetBuilder return this; } - public withMessage = (messageOverride: ((value: any, ruleOptions?: any) => string) | string): RulesetBuilder => + public withMessage = (messageOverride: ((value: any, ruleOptions?: any) => string) | string): RulesetBuilder => { if(!this.currentRule) { throw new Error("A message override must precede an addRule call in the chain"); } @@ -49,7 +63,7 @@ export class RulesetBuilder return this; } - public appliesIf = (appliesFunction: ((model: any, value: any, ruleOptions?: any) => boolean) | boolean): RulesetBuilder => + public appliesIf = (appliesFunction: ((model: any, value: any, ruleOptions?: any) => boolean) | boolean): RulesetBuilder => { if(!this.currentRule) { throw new Error("An appliesIf function must precede an addRule call in the chain"); } @@ -57,7 +71,7 @@ export class RulesetBuilder return this; } - public addRuleForEach = (rule: string, ruleOptions?: any): RulesetBuilder => + public addRuleForEach = (rule: string, ruleOptions?: any): RulesetBuilder => { if(rule == null || typeof(rule) == "undefined" || rule.length == 0) { throw new Error("A rule name is required"); } @@ -75,7 +89,7 @@ export class RulesetBuilder return this; } - public addRuleset = (ruleset: Ruleset): RulesetBuilder => + public addRuleset = (ruleset: Ruleset): RulesetBuilder => { if(!this.currentProperty) { throw new Error("A property must precede any rule calls in the chain"); } @@ -84,7 +98,7 @@ export class RulesetBuilder return this; } - public addRulesetForEach = (ruleset: Ruleset): RulesetBuilder => + public addRulesetForEach = (ruleset: Ruleset): RulesetBuilder => { if(!this.currentProperty) { throw new Error("A property must precede any rule calls in the chain"); } diff --git a/src/exposer.ts b/src/exposer.ts index f5fb3c8..74f3869 100644 --- a/src/exposer.ts +++ b/src/exposer.ts @@ -1,15 +1,15 @@ import {FieldErrorProcessor} from "./processors/field-error-processor"; -import {RulesetBuilder} from "./builders/ruleset-builder"; import {RuleResolver} from "./rulesets/rule-resolver"; import {ValidationGroupBuilder} from "./builders/validation-group-builder"; import {ruleRegistry} from "./rule-registry-setup"; +import {RulesetBuilder} from "./builders/ruleset-builder"; var fieldErrorProcessor = new FieldErrorProcessor(ruleRegistry); var ruleResolver = new RuleResolver(); -export function createRuleset(withRuleVerification = false): RulesetBuilder +export function createRuleset(withRuleVerification = false): RulesetBuilder { - var rulesetBuilder = withRuleVerification ? new RulesetBuilder(ruleRegistry) : new RulesetBuilder(); + var rulesetBuilder = withRuleVerification ? new RulesetBuilder(ruleRegistry) : new RulesetBuilder(); return rulesetBuilder.create(); } diff --git a/src/index.ts b/src/index.ts index 88d92ab..2e316eb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,6 +17,9 @@ export * from "./processors/field-has-error" export * from "./processors/ifield-error-processor" export * from "./processors/validation-error" export * from "./promises/promise-counter" +export * from "./resolvers/imodel-resolver" +export * from "./resolvers/iproperty-resolver" +export * from "./resolvers/model-resolver" export * from "./rules/advanced-regex-rule" export * from "./rules/date-validation-rule" export * from "./rules/decimal-validation-rule" @@ -35,12 +38,6 @@ export * from "./rules/regex-validation-rule" export * from "./rules/required-validation-rule" export * from "./rules/rule-registry" export * from "./rules/step-validation-rule" -export * from "./resolvers/imodel-resolver" -export * from "./resolvers/iproperty-resolver" -export * from "./resolvers/model-resolver" -export * from "./watcher/imodel-watcher" -export * from "./watcher/model-watcher" -export * from "./watcher/property-watcher" export * from "./rulesets/for-each-rule" export * from "./rulesets/irule-resolver" export * from "./rulesets/rule-link" @@ -50,3 +47,6 @@ export * from "./validation-groups/ireactive-validation-group" export * from "./validation-groups/ivalidation-group" export * from "./validation-groups/reactive-validation-group" export * from "./validation-groups/validation-group" +export * from "./watcher/imodel-watcher" +export * from "./watcher/model-watcher" +export * from "./watcher/property-watcher" diff --git a/tests/specs/ruleset-builder-tests.ts b/tests/specs/ruleset-builder-tests.ts index 485b31e..e4783c8 100644 --- a/tests/specs/ruleset-builder-tests.ts +++ b/tests/specs/ruleset-builder-tests.ts @@ -6,6 +6,32 @@ import {Ruleset} from "../../src/rulesets/ruleset"; describe('Ruleset Builder', function () { + it('should correctly resolve property predicates to property names', function () { + class DummyModel + { + public property1: string; + public property2: number; + } + + var dummyRuleRegistry = { hasRuleNamed: function(){ return true; }}; + var rulesetBuilder = new RulesetBuilder(dummyRuleRegistry); + + var ruleset: any = rulesetBuilder.create() + .forProperty(x => x.property1) + .addRule("required", true) + .forProperty(x => x.property2) + .addRule("required", true) + .build(); + + expect(ruleset).not.to.be.null; + expect(ruleset.rules).to.include.keys("property1"); + expect(ruleset.rules.property1.length).to.equal(1); + expect(ruleset.rules.property1[0]).to.eql(new RuleLink("required", true)); + expect(ruleset.rules).to.include.keys("property2"); + expect(ruleset.rules.property2.length).to.equal(1); + expect(ruleset.rules.property2[0]).to.eql(new RuleLink("required", true)); + }); + it('should correctly add rules to the ruleset', function () { var dummyRuleRegistry = { hasRuleNamed: function(){ return true; }}; var rulesetBuilder = new RulesetBuilder(dummyRuleRegistry); diff --git a/tests/specs/treacherous-sanity-tests.ts b/tests/specs/treacherous-sanity-tests.ts index 9cd16d7..dedad73 100644 --- a/tests/specs/treacherous-sanity-tests.ts +++ b/tests/specs/treacherous-sanity-tests.ts @@ -104,8 +104,7 @@ describe('Treacherous Sanity Checks', function () { }).catch(done); }); - // TODO: This needs fixing but needs discussion on purely state checks - it.skip("should correctly be invalid after changes when reactive", function(done){ + it("should correctly be invalid after changes when reactive and validate on startup", function(done){ var ruleSet = createRuleset() .forProperty("stringValue1").addRule("required") .forProperty("stringValue2").addRule("required") @@ -117,7 +116,7 @@ describe('Treacherous Sanity Checks', function () { }; var isValid; - var valGroup = createGroup().asReactiveGroup().build(model, ruleSet); + var valGroup = createGroup().asReactiveGroup().andValidateOnStart().build(model, ruleSet); valGroup.modelStateChangedEvent.subscribe(event => { console.log("changing state:", event); isValid = event.isValid