UNPKG

ts-enum-tools

Version:

Tools for evaluating TypeScript enum types

464 lines (391 loc) 18.5 kB
/* global describe it after should require */ declare var require: any; declare var should: any; declare var describe: any; declare var after: any; declare var it: any; var should = require('should'); import { EnumFlagsTest, EnumFlagsType, EnumFlagsTool, EnumStringsType, EnumStringsTool } from '../index'; var Stats = { }; function Timer() { return { start: new Date().getTime(), elapsed: function () { return new Date().getTime() - this.start } } } function PadValue(value, length) { var padded = " " + value; return (padded.length <= length) ? PadValue(padded, length) : padded; } function truthy(val) { if (!val) { var err = new Error("Invalid truth assertion"); // return (<any>err).stack; throw err; } } function falsey(val) { if (val) { var err = new Error("Invalid false assertion"); // (<any>err).stack; throw err; } } //////////////////////////////////////////////////////////// // Strings // Declare a TypeScript enum export enum AbStrings { None = <any> "none", Select = <any> "sel", Move = <any> "mov", Edit = <any> "edit", Sort = <any> "sort", Clone = <any> "clone" } // Create a map for string output and comparison export interface AbStringsMap { None: any; Select: any; Move: any; Edit: any; Sort: any; Clone: any; } // Declare a secondary closure validation enum export enum AbStringsChecked { Good = <any> "good", Better = <any> "better", } // Method 1: Get function that accepts a String and returns a set of tools // Filter truthys that valid keys are capitalized var abStrFunc = EnumStringsType<AbStrings, AbStringsMap>(AbStrings, "abStrProp", function(k) { return (k != k.toLowerCase()); }); // The tools function includes getters of type string, when map is provided truthy(abStrFunc.key.Clone === "Clone"); // Returns key truthy(abStrFunc.val.Clone === "clone"); // Returns value // Tools function works on enum types var abStrEnum: AbStrings = AbStrings.Clone; // console.log(abStrFunc(abStrEnum).equals(AbStrings.Move)); truthy(abStrFunc(abStrEnum).state.Clone); falsey(abStrFunc(abStrEnum).state.Select); truthy(abStrFunc(abStrEnum).equals(AbStrings.Clone)); falsey(abStrFunc(abStrEnum).equals(AbStrings.Move)); truthy(abStrFunc(abStrEnum).toStringKey() === "Clone"); truthy(abStrFunc(abStrEnum).toStringVal() === "clone"); // Method 2: Add Interface that extends a Number with a tools property export interface AbString extends String { abStrProp?: EnumStringsTool<AbStrings, AbStringsMap>; } // Tools properties are then accessible on extended string types var abStrVal: AbString = abStrFunc.val.Clone; truthy(abStrVal.abStrProp.state.Clone === true); falsey(abStrVal.abStrProp.state.Move); truthy(abStrFunc(abStrEnum).equals(AbStrings.Clone)); falsey(abStrVal.abStrProp.equals(AbStrings.Move)); truthy(abStrVal.abStrProp.toStringKey() === "Clone"); truthy(abStrVal.abStrProp.toStringVal() === "clone"); describe('EnumStringsType', function() { describe('Various logic tests', function() { it('should clone', function() { should(abStrVal.abStrProp.equals(AbStrings.Clone)).equal(true); }); it('should not move', function() { should(abStrVal.abStrProp.equals(AbStrings.Move)).equal(false); }); it('should not sort', function() { should(abStrVal.abStrProp.equals(AbStrings.Sort)).equal(false); }); it('should have property of Clone that is true', function() { should(abStrVal.abStrProp.state.Clone).equal(true); }); it('should have property of Move that is false', function() { should(abStrVal.abStrProp.state.Move).equal(false); }); it('should ouput a string', function() { should(abStrVal.abStrProp.toStringKey()).equal("Clone"); }); }); describe('Outlier checks', function() { it('should maintain closure integrity when re-used', function() { // Adding another enum using the factory method var AbCheck = EnumStringsType<AbStringsChecked, typeof AbStringsChecked>(AbStringsChecked, "abStringCheck"); var abValCheck: number = AbStringsChecked.Good should(AbCheck(abValCheck).state.Good).equal(true); // Double checking original enum integrity should(abStrVal.abStrProp.equals(AbStrings.Clone)).equal(true); should(abStrVal.abStrProp.state.Clone).equal(true); should(abStrVal.abStrProp.equals(AbStrings.Move)).equal(false); should(abStrVal.abStrProp.state.Move).equal(false); }); }); }); //////////////////////////////////////////////////////////// // Flags // Declare a TypeScript enum export enum AbFlags { isNone = 0, isMovable = 1 << 2, isSelectable = 1 << 3, isEditable = 1 << 28, isSortable = 1 << 30, isClonable = 1 << 31, // maximum! isSelectSort = isSelectable | isSortable // example combo flag } // Create a map for number output export interface AbFlagsMap { isNone: boolean; isSelectable: boolean; isMovable: boolean; isEditable: boolean; isSortable: boolean; isClonable: boolean; } // Declare a secondary closure validation enum export enum AbFlagsChecked { None = 0, isGood = 1 << 2, isBetter = 1 << 4, } // Method 1: Simple raw test methods var abFlgRaw = AbFlags.isClonable | AbFlags.isSortable; truthy(EnumFlagsTest.has(abFlgRaw, AbFlags.isClonable)); falsey(EnumFlagsTest.has(abFlgRaw, AbFlags.isMovable)); truthy(EnumFlagsTest.has(abFlgRaw, AbFlags.isClonable | AbFlags.isSortable)); falsey(EnumFlagsTest.has(abFlgRaw, AbFlags.isMovable | AbFlags.isSortable)); truthy(EnumFlagsTest.any(abFlgRaw, AbFlags.isMovable | AbFlags.isSortable)); truthy(EnumFlagsTest.any(abFlgRaw, AbFlags.isClonable)); falsey(EnumFlagsTest.any(abFlgRaw, AbFlags.isMovable)); truthy(EnumFlagsTest.eql(abFlgRaw, AbFlags.isClonable | AbFlags.isSortable)); falsey(EnumFlagsTest.eql(abFlgRaw, AbFlags.isClonable)); falsey(EnumFlagsTest.eql(abFlgRaw, AbFlags.isMovable)); // Method 2: Get enum wrapper function that binds a Number to a set of tools var abFlgFunc = EnumFlagsType<AbFlags, AbFlagsMap>(AbFlags, "abFlgProp"); // Tools function works on enum | number types var abFlgEnum: AbFlags = AbFlags.isClonable | AbFlags.isSortable; truthy(abFlgFunc(abFlgEnum).state.isClonable); falsey(abFlgFunc(abFlgEnum).state.isMovable); truthy(abFlgFunc(abFlgEnum).has(AbFlags.isClonable)); falsey(abFlgFunc(abFlgEnum).has(AbFlags.isMovable)); truthy(abFlgFunc(abFlgEnum).has(AbFlags.isClonable | AbFlags.isSortable)); falsey(abFlgFunc(abFlgEnum).has(AbFlags.isMovable | AbFlags.isSortable)); truthy(abFlgFunc(abFlgEnum).any(AbFlags.isMovable | AbFlags.isSortable)); truthy(abFlgFunc(abFlgEnum).any(AbFlags.isClonable)); falsey(abFlgFunc(abFlgEnum).any(AbFlags.isMovable)); truthy(abFlgFunc(abFlgEnum).eql(AbFlags.isClonable | AbFlags.isSortable)); falsey(abFlgFunc(abFlgEnum).eql(AbFlags.isClonable)); falsey(abFlgFunc(abFlgEnum).eql(AbFlags.isMovable)); truthy(abFlgFunc(abFlgEnum).toArray().length === 2); truthy(abFlgFunc(abFlgEnum).toString().indexOf("isSortable") + 1); // Method 3: Add Interface that extends a Number with a tools property export interface AbNumber extends Number { abFlgProp?: EnumFlagsTool<AbFlags, AbFlagsMap>; } // Tools properties are available on extended number types var abFlgVal: AbNumber = AbFlags.isClonable | AbFlags.isSortable; truthy(abFlgVal.abFlgProp.state.isClonable); falsey(abFlgVal.abFlgProp.state.isMovable); truthy(abFlgVal.abFlgProp.has(AbFlags.isClonable)); falsey(abFlgVal.abFlgProp.has(AbFlags.isMovable)); truthy(abFlgVal.abFlgProp.has(AbFlags.isClonable | AbFlags.isSortable)); falsey(abFlgVal.abFlgProp.has(AbFlags.isMovable | AbFlags.isSortable)); truthy(abFlgVal.abFlgProp.any(AbFlags.isMovable | AbFlags.isSortable)); truthy(abFlgVal.abFlgProp.any(AbFlags.isClonable)); falsey(abFlgVal.abFlgProp.any(AbFlags.isMovable)); truthy(abFlgVal.abFlgProp.eql(AbFlags.isClonable | AbFlags.isSortable)); falsey(abFlgVal.abFlgProp.eql(AbFlags.isClonable)); falsey(abFlgVal.abFlgProp.eql(AbFlags.isMovable)); truthy(abFlgVal.abFlgProp.toArray().length === 2); truthy(abFlgVal.abFlgProp.toString().indexOf("isSortable") + 1); describe('EnumFlagsType: Various tests', function() { this.timeout(8000); describe('Rigorous logic tests', function() { it('should handle case val(1000) > has(1000):t any(1000):t eql(1000):t state[1000]:t state[0100]:f ', function() { let val = (1 << 3); should(abFlgFunc(val).has(AbFlags.isSelectable)).equal(true); should(abFlgFunc(val).any(AbFlags.isSelectable)).equal(true); should(abFlgFunc(val).eql(AbFlags.isSelectable)).equal(true); should(abFlgFunc(val).state.isSelectable).equal(true); should(abFlgFunc(val).state.isMovable).equal(false); }); it('should handle case val(1100) > has(1100):t any(1100):t eql(1100):t state[1000]:t state[0100]:t ', function() { let val = ((1 << 3) | (1 << 2)); should(abFlgFunc(val).has(AbFlags.isSelectable | AbFlags.isMovable)).equal(true); should(abFlgFunc(val).any(AbFlags.isSelectable | AbFlags.isMovable)).equal(true); should(abFlgFunc(val).eql(AbFlags.isSelectable | AbFlags.isMovable)).equal(true); should(abFlgFunc(val).state.isSelectable).equal(true); should(abFlgFunc(val).state.isMovable).equal(true); }); it('should handle case val(0100) > has(1000):f any(1000):f eql(1000):f state[1000]:f state[0100]:t ', function() { let val = (1 << 2); should(abFlgFunc(val).has(AbFlags.isSelectable)).equal(false); should(abFlgFunc(val).any(AbFlags.isSelectable)).equal(false); should(abFlgFunc(val).eql(AbFlags.isSelectable)).equal(false); should(abFlgFunc(val).state.isSelectable).equal(false); should(abFlgFunc(val).state.isMovable).equal(true); }); it('should handle case val(1100) > has(0100):t any(0100):t eql(0100):f state[1000]:t state[0100]:t ', function() { let val = ((1 << 3) | (1 << 2)); should(abFlgFunc(val).has(AbFlags.isMovable)).equal(true); should(abFlgFunc(val).any(AbFlags.isMovable)).equal(true); should(abFlgFunc(val).eql(AbFlags.isMovable)).equal(false); should(abFlgFunc(val).state.isSelectable).equal(true); should(abFlgFunc(val).state.isMovable).equal(true); }); it('should handle case val(0100) > has(1100):f any(1100):t eql(1100):f state[1000]:f state[0100]:t ', function() { let val = (1 << 2); should(abFlgFunc(val).has(AbFlags.isSelectable | AbFlags.isMovable)).equal(false); should(abFlgFunc(val).any(AbFlags.isSelectable | AbFlags.isMovable)).equal(true); should(abFlgFunc(val).eql(AbFlags.isSelectable | AbFlags.isMovable)).equal(false); should(abFlgFunc(val).state.isSelectable).equal(false); should(abFlgFunc(val).state.isMovable).equal(true); }); it('should handle case val(0000) > has(1000):f any(1000):t eql(1000):f state[1000]:f state[0100]:f ', function() { let val = (0); should(abFlgFunc(val).has(AbFlags.isSelectable)).equal(false); should(abFlgFunc(val).any(AbFlags.isSelectable)).equal(false); should(abFlgFunc(val).eql(AbFlags.isSelectable)).equal(false); should(abFlgFunc(val).state.isSelectable).equal(false); should(abFlgFunc(val).state.isMovable).equal(false); }); it('should handle case val(0000) > has(0000):t any(0000):f eql(0000):t state[0000]:t state[0100]:f ', function() { let val = (0); should(abFlgFunc(val).has(0)).equal(true); should(abFlgFunc(val).any(0)).equal(false); should(abFlgFunc(val).eql(0)).equal(true); should(abFlgFunc(val).state.isNone).equal(true); should(abFlgFunc(val).state.isMovable).equal(false); }); }); describe('Miscellaneous logic tests', function() { it('should handle invalid bit combinations in values and arguments', function() { let val = ((1 << 5) | (1 << 10)); should(abFlgFunc(val).has(AbFlags.isSelectable)).equal(false); should(abFlgFunc(val).any(AbFlags.isSelectable)).equal(false); should(abFlgFunc(val).eql(AbFlags.isSelectable)).equal(false); should(abFlgFunc(val).has(val)).equal(true); should(abFlgFunc(val).any(val)).equal(true); should(abFlgFunc(val).eql(val)).equal(true); }); it('should support various flags', function() { should(abFlgVal.abFlgProp.has(AbFlags.isClonable)).equal(true); should(abFlgVal.abFlgProp.has(AbFlags.isMovable)).equal(false); should(abFlgVal.abFlgProp.has(AbFlags.isSortable)).equal(true); }); it('should support various properties', function() { should(abFlgVal.abFlgProp.state.isClonable).equal(true); should(abFlgVal.abFlgProp.state.isMovable).equal(false); }); it('should return an array consisting of (2) flags', function() { should(abFlgVal.abFlgProp.toArray().length).equal(2); }); it('should ouput a string similar to: "isSortable | isClonable"', function() { should(abFlgVal.abFlgProp.toString()).containEql("isClonable"); should(abFlgVal.abFlgProp.toString()).containEql("isSortable"); }); }); describe('Outlier checks', function() { it('should maintain closure integrity and support re-use', function() { // Adding another enum using the factory method var AbCheck = EnumFlagsType<AbFlagsChecked, typeof AbFlagsChecked>(AbFlagsChecked, "abChecked"); var abValCheck: number = AbFlagsChecked.isGood should(AbCheck(abValCheck).state.isGood).equal(true); // Double checking original enum integrity should(abFlgVal.abFlgProp.has(AbFlags.isClonable)).equal(true); should(abFlgVal.abFlgProp.state.isClonable).equal(true); should(abFlgVal.abFlgProp.toString()).containEql("isClonable"); should(abFlgVal.abFlgProp.toString()).containEql("isSortable"); }); it('should be immutable value when using function methods', function() { var changingValue: AbNumber = 0; // function binds to value passed in, so it gets captured changingValue = AbFlags.isClonable | AbFlags.isSortable; var toolsImmutable = abFlgFunc(changingValue); should(toolsImmutable.state.isClonable).be.true; should(!toolsImmutable.state.isEditable).be.true; changingValue = AbFlags.isMovable | AbFlags.isEditable; should(toolsImmutable.state.isClonable).be.true; should(!toolsImmutable.state.isEditable).be.true; }); it('should not be immutable value when using prototype properties', function() { var changingValue: AbNumber = 0; // prototype methods are created on the fly, so it acts on current value changingValue = AbFlags.isClonable | AbFlags.isSortable; should(changingValue.abFlgProp.state.isClonable).be.true; should(!changingValue.abFlgProp.state.isEditable).be.true; changingValue = AbFlags.isMovable | AbFlags.isEditable; should(changingValue.abFlgProp.state.isEditable).be.true; should(!changingValue.abFlgProp.state.isClonable).be.true; }); }); describe('Performance checks', function() { let iterationsFunc = 1000000; it(`should perform function(value) tools over (${iterationsFunc}) iterations`, function() { let timer = Timer(); let variousFlagValues = [ AbFlags.isSelectable, AbFlags.isMovable, AbFlags.isEditable, AbFlags.isSortable, AbFlags.isClonable, AbFlags.isSelectable | AbFlags.isClonable, AbFlags.isEditable | AbFlags.isMovable, AbFlags.isSortable | AbFlags.isClonable, AbFlags.isSelectable | AbFlags.isClonable | AbFlags.isEditable, AbFlags.isClonable | AbFlags.isMovable | AbFlags.isEditable | AbFlags.isSelectable ] let x = 0; let flag = false; for (let i = 0; i < iterationsFunc; i++) { // A function is resuseable, so it is typically faster when repeated use is likely let toolsPerInteration = abFlgFunc(variousFlagValues[x]); flag = toolsPerInteration.has(AbFlags.isClonable); flag = toolsPerInteration.any(AbFlags.isClonable); flag = toolsPerInteration.state.isClonable; x = (++x < 10) ? x : 0; } Stats["func"] = timer.elapsed(); }); let iterationsAll = 1000000; it(`should perform using value.has property over (${iterationsAll}) iterations`, function() { let timer = Timer(); let flag = false; for (let i = 0; i < iterationsAll; i++) { flag = abFlgVal.abFlgProp.has(AbFlags.isClonable); } should(flag).equal(true); Stats["prop"] = timer.elapsed(); }); let iterationsAny = 1000000; it(`should perform using value.any property over (${iterationsAny}) iterations`, function() { let flag = false; for (let i = 0; i < iterationsAny; i++) { flag = abFlgVal.abFlgProp.any(AbFlags.isSortable); } should(flag).equal(true); }); let iterationsMap = 1000000; it(`should perform using value.state property over (${iterationsMap}) iterations`, function() { let flag = false; for (let i = 0; i < iterationsMap; i++) { flag = abFlgVal.abFlgProp.state.isClonable; } should(flag).equal(true); }); let iterationsBase = 5000000; it(`inline logical operation comparison baseline (${iterationsBase}) iterations`, function() { let timer = Timer(); let valA: any = AbFlags.isMovable | AbFlags.isSortable; let valB: any = AbFlags.isClonable | AbFlags.isSortable; let flag = false; for (let i = 0; i < iterationsBase; i++) { flag = EnumFlagsTest.has(valA, AbFlags.isMovable); // t flag = EnumFlagsTest.any(valB, AbFlags.isMovable); // f } Stats["base"] = timer.elapsed(); }); }); after(function() { // console.log(`\n\n Stats Func/Prop/Base:${PadValue(Stats["func"], 6)}${PadValue(Stats["prop"], 6)}${PadValue(Stats["base"], 6)}`); }); });