diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..718cfca --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea/ +node_modules/ + diff --git a/enums.js b/enums.js index 560ae4b..987eed8 100644 --- a/enums.js +++ b/enums.js @@ -12,10 +12,12 @@ if (props) { copyOwnFrom(this, props); } - Object.freeze(this); + Object.seal(this); } /** We don’t want the mutable Object.prototype in the prototype chain */ - Symbol.prototype = Object.create(null); + // BG adding Object.prototype back to allow iteration, otherwise objects with + // symbols attached cannot be deep copied + Symbol.prototype = Object.create(Object.prototype); Symbol.prototype.constructor = Symbol; /** * Without Object.prototype in the prototype chain, we need toString() @@ -24,9 +26,9 @@ Symbol.prototype.toString = function () { return "|"+this.name+"|"; }; - Object.freeze(Symbol.prototype); + Object.seal(Symbol.prototype); - Enum = function (obj) { + var Enum = function (obj) { if (arguments.length === 1 && obj !== null && typeof obj === "object") { Object.keys(obj).forEach(function (name) { this[name] = new Symbol(name, obj[name]); @@ -36,19 +38,44 @@ this[name] = new Symbol(name); }, this); } - Object.freeze(this); - } + Object.seal(this); + }; Enum.prototype.symbols = function() { return Object.keys(this).map( function(key) { return this[key]; }, this ); - } + }; Enum.prototype.contains = function(sym) { - if (! sym instanceof Symbol) return false; + if (!sym instanceof Symbol) return false; return this[sym.name] === sym; - } + }; + Enum.prototype.fromName = function (name) { + if (!name instanceof String) return undefined; + return this[name]; + }; + + /** + * Get the enum based on a matching value + * @param valueMatcher an object literal with any subset of the properties passed into the original constructor + * @returns an array of matching enums or, if only one matches the enum or, if none match, undefined + */ + Enum.prototype.fromValue = function (valueMatcher) { + if (!valueMatcher) return undefined; + var matches = this.symbols().filter(function(symbol) { + for (var key in valueMatcher) { return valueMatcher[key] === symbol[key]; }; + }) + switch (matches.length) { + case 0: + return undefined; + case 1: + return matches[0]; + default: + return matches; + } + }; + exports.Enum = Enum; exports.Symbol = Symbol; }(typeof exports === "undefined" ? this.enums = {} : exports)); diff --git a/enums.spec.js b/enums.spec.js index 57d7ba8..b61c892 100644 --- a/enums.spec.js +++ b/enums.spec.js @@ -1,22 +1,97 @@ var enums = require("./enums.js"); describe("Enum", function() { + it("can create enums", function() { + var color = new enums.Enum("red", "green", "blue"); + expect(color.red).toBeDefined(); + expect(color.red.name).toEqual("red"); + }); + + it("can iterate over its properties", function() { + var color = new enums.Enum({ + red: { r: 255, g: 2, b: 0 }, + green: { r: 0, g: 255, b: 0 }, + blue: { r: 0, g: 0, b: 255 } + }); + var red = color.red; + var rValue; + for (var property in red) { + if (red.hasOwnProperty(property) && property === "r") { + rValue = red[property]; + } + } + + expect(rValue).toBeDefined(); + expect(rValue).toEqual(255); + }); + it("can have symbols with custom properties", function() { var color = new enums.Enum({ red: { de: "rot" }, green: { de: "grün" }, - blue: { de: "blau" }, + blue: { de: "blau" } }); function translate(c) { return c.de; } expect(translate(color.green)).toEqual("grün"); + expect(translate(color.green)).toEqual("grün"); }); + it("can check for symbol membership", function() { var color = new enums.Enum("red", "green", "blue"); var fruit = new enums.Enum("apple", "banana"); expect(color.contains(color.red)).toBeTruthy(); expect(color.contains(fruit.apple)).toBeFalsy(); }); + + describe("fromName", function() { + var color = new enums.Enum("red", "green", "blue"); + it("can find enums via their names", function() { + var red = color.fromName("red"); + expect(red).toBeDefined(); + expect(red.name).toEqual("red"); + }); + it("is undefined for unknown names", function() { + var foo = color.fromName("foo"); + expect(foo).toBeUndefined(); + }); + it("is undefined for a bogus parameter", function() { + var foo = color.fromName({}); + expect(foo).toBeUndefined(); + }); + }); + + describe("fromValue", function() { + var color = new enums.Enum({ + red: { r: 255, g: 2, b: 0 }, + green: { r: 0, g: 255, b: 0 }, + blue: { r: 0, g: 0, b: 255 } + }); + it("can find enums via a single matching values", function() { + var red = color.fromValue({ r: 255 }); + expect(red).toBeDefined(); + expect(red.name).toEqual("red"); + }); + it("can find enums via a pair of matching values", function() { + var red = color.fromValue({ r: 255, g: 2 }); + expect(red).toBeDefined(); + expect(red.name).toEqual("red"); + }); + it("can find a list of enums", function() { + var noReds = color.fromValue({ r: 0 }); + expect(noReds).toBeDefined(); + expect(noReds.length).toEqual(2); + }); + it("is undefined with no input", function() { + var value = color.fromValue(); + expect(value).toBeUndefined(); + }); + it("is undefined with input that does not match our enum values", function() { + var value = color.fromValue({ foo: "bar" }); + expect(value).toBeUndefined(); + }); + }); + }); diff --git a/package.json b/package.json new file mode 100644 index 0000000..0aa7c9d --- /dev/null +++ b/package.json @@ -0,0 +1,16 @@ +{ + "name": "enums", + "version": "0.0.1", + "description": "Enums for JavaScript", + "main": "enums.js", + "devDependencies": { + "jasmine": "^2.0.1" + }, + "scripts": { + "test": "./node_modules/.bin/jasmine" + }, + "repository": { + "type": "git", + "url": "git@github.com:rauschma/enums.git" + } +} diff --git a/spec/support/jasmine.json b/spec/support/jasmine.json new file mode 100644 index 0000000..f8a7214 --- /dev/null +++ b/spec/support/jasmine.json @@ -0,0 +1,6 @@ +{ + "spec_dir": "./", + "spec_files": [ + "enums.spec.js" + ] +} \ No newline at end of file