Permalink
Browse files

experimental value objects in javascript

  • Loading branch information...
0 parents commit ed24707aee600c247006b1eb349fe6d694146567 @leemuro committed Jul 19, 2012
Showing with 148 additions and 0 deletions.
  1. +84 −0 test/value_tests.js
  2. +64 −0 value.js
@@ -0,0 +1,84 @@
+"use strict";
+
+var assert = require('assert');
+var Value = require('../value');
+
+var Person = Value.define('firstName', 'lastName', 'age');
+var CoolPerson = Value.define('firstName', 'lastName', 'age');
+
+var Order = Value.define('customer', 'total');
+
+describe('value', function() {
+ it('can get attributes', function() {
+ var p = new Person({firstName:'John', lastName:'Smith', age:25});
+ assert.equal(p.firstName, 'John', 'gets first name');
+ assert.equal(p.lastName, 'Smith', 'gets last name');
+ assert.equal(p.age, 25, 'gets age');
+ });
+
+ it('requires all attributes', function() {
+ assert.throws(function() {
+ new Person({firstName:'John', lastName:'Smith'});
+ }, TypeError);
+ });
+
+ describe('immutability', function() {
+ it('cannot set attributes', function() {
+ var p = new Person({firstName:'John', lastName:'Smith', age:25});
+ assert.throws(function() { p.firstName = 'Bob'; }, TypeError);
+ });
+
+ it('cannot define new attributes', function() {
+ var p = new Person({firstName:'John', lastName:'Smith', age:25});
+ assert.throws(function() { p.eyeColor = 'Bob'; }, TypeError);
+ });
+ });
+
+ describe('custom attributes', function() {
+ var BetterPerson = Value.define('firstName', 'lastName', 'age', {
+ fullName: function() {
+ return this.firstName + ' ' + this.lastName;
+ }
+ });
+ var p1 = new BetterPerson({firstName:'John', lastName:'Smith', age:25});
+ assert.equal(p1.fullName, 'John Smith');
+ });
+
+ describe('equality', function() {
+ it('is equal if all attribute values are equal', function() {
+ var p1 = new Person({firstName:'John', lastName:'Smith', age:25});
+ var p2 = new Person({firstName:'John', lastName:'Smith', age:25});
+ assert.ok(p1.equals(p2));
+ });
+
+ it('is not equal if any attribute values are different', function() {
+ var p1 = new Person({firstName:'John', lastName:'Smith', age:25});
+ var p2 = new Person({firstName:'Bob', lastName:'Smith', age:25});
+ assert.ok(!p1.equals(p2));
+ });
+
+ it('is not equal if types are different', function() {
+ var p1 = new Person({firstName:'John', lastName:'Smith', age:25});
+ var p2 = new CoolPerson({firstName:'John', lastName:'Smith', age:25});
+ assert.ok(!p1.equals(p2));
+ });
+ });
+
+ describe('deep equality', function() {
+ it('is equal if nested values are equal', function() {
+ var p1 = new Person({firstName:'John', lastName:'Smith', age:25});
+ var p2 = new Person({firstName:'John', lastName:'Smith', age:25});
+ var o1 = new Order({customer: p1, total: 100.00});
+ var o2 = new Order({customer: p2, total: 100.00});
+ assert.ok(o1.equals(o2));
+ });
+
+ it('is not equal if nested values are not equal', function() {
+ var p1 = new Person({firstName:'John', lastName:'Smith', age:25});
+ var p2 = new Person({firstName:'Bob', lastName:'Smith', age:25});
+ var o1 = new Order({customer: p1, total: 100.00});
+ var o2 = new Order({customer: p2, total: 100.00});
+ assert.ok(!o1.equals(o2));
+ });
+ });
+});
@@ -0,0 +1,64 @@
+(function(exports) {
+ exports.define = function() {
+ // Build list of value's keys and custom properties
+ var keys = [], customProps;
+ for(var i in arguments) {
+ var arg = arguments[i];
+ if(typeof arg == "string")
+ keys.push(arg);
+ else if(typeof arg == "object")
+ customProps = arg;
+ }
+
+ // Define the value type
+ return function(obj) {
+
+ // Require exact number of values in the constructor
+ if(Object.keys(obj).length != keys.length)
+ throw new TypeError('Incorrect number of attribute values specified');
+
+ // Define getters for attributes
+ for(var i in keys) {
+ var attr = keys[i];
+ var val = obj[attr];
+ (function(obj, attr, val) {
+ obj.__defineGetter__(attr, function() {
+ return val;
+ });
+ })(this, attr, val);
+ }
+
+ // Define getters for custom property functions
+ for(var prop in customProps) {
+ var func = customProps[prop];
+ (function(obj, attr, attrFunc) {
+ obj.__defineGetter__(attr, function() {
+ return attrFunc.call(obj);
+ });
+ })(this, prop, func);
+ }
+
+ // Define equality for value objects
+ this.equals = function(otherValue) {
+ if(this.constructor != otherValue.constructor)
+ return false;
+
+ for(var i in keys) {
+ var attr = keys[i];
+ if(this[attr].equals) {
+ if(!this[attr].equals(otherValue[attr]))
+ return false;
+ }
+ else {
+ if(this[attr] !== otherValue[attr])
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // Freeze the object to prevent mutation or further extensibility
+ Object.freeze(this);
+ }
+ }
+}(typeof exports === 'undefined' ? this.Value = {} : exports));

0 comments on commit ed24707

Please sign in to comment.