From 6539553df3ab0f9436286f7eb7b6df01807287a6 Mon Sep 17 00:00:00 2001 From: ben hockey Date: Thu, 29 Dec 2011 15:35:18 -0500 Subject: [PATCH 1/4] tests: get tests running (one test is failing) --- tests/data/TestStore | 6 +++++- tests/model.js | 6 +++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/data/TestStore b/tests/data/TestStore index 43d7742..d9e3bd4 100644 --- a/tests/data/TestStore +++ b/tests/data/TestStore @@ -1 +1,5 @@ -{1:{id:1, foo:2, rand:0.7013372050189607}, 2:{id:2, foo:1, bar:"hi"}, 3:{id:3, foo:1, bar:"hello"}} \ No newline at end of file +{id:1, foo:2, rand:0.7013372050189607}, +{id:2, foo:1, bar:"hi"}, +{id:3, foo:1, bar:"hello"}, +{"id":1,"foo":2,"rand":0.4593563524540514}, +{"id":1,"foo":2,"rand":0.05506560602225363}, diff --git a/tests/model.js b/tests/model.js index 0553828..2c4eb6f 100644 --- a/tests/model.js +++ b/tests/model.js @@ -1,7 +1,7 @@ var assert = require("assert"), - store = require("../stores").DefaultStore("TestStore"), + store = require("../stores").DefaultStore(), model = require("../model").Model(store, { - prototype: { + prototype: { testMethod: function(){ return this.foo; } @@ -41,7 +41,7 @@ exports.CreateTests = function(model){ }); assert.equal(count, 1); }, - + testSave: function(){ var object = model.get(1); var newRand = Math.random(); From bff4fa45c43a10e0d65f348235cc76b8746750ce Mon Sep 17 00:00:00 2001 From: ben hockey Date: Thu, 29 Dec 2011 15:57:01 -0500 Subject: [PATCH 2/4] facets: use prototype from appliesTo if schema does not provide one possibly fixes #21 --- facet.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/facet.js b/facet.js index cb1f10b..0ddb25e 100644 --- a/facet.js +++ b/facet.js @@ -694,7 +694,7 @@ exports.Permissive = function(appliesTo, schema){ schema = schema || {quality:0.5}; var appliesToPrototype = appliesTo.prototype; if(appliesToPrototype){ - var schemaPrototype = schema.prototype = schema.prototype || {}; + var schemaPrototype = schema.prototype = schema.prototype || appliesToPrototype; schemaPrototype.__noSuchMethod__ = function(name, source, args, onlyIfAvailable){ if(appliesToPrototype[name]){ return facet.wrap(appliesToPrototype[name].apply(source, args)); From 804904f87a9cd6986e04a700878895f8d79898f9 Mon Sep 17 00:00:00 2001 From: ben hockey Date: Thu, 29 Dec 2011 17:06:47 -0500 Subject: [PATCH 3/4] tests: added more tests for models and facets --- model.js | 13 +- tests/data/TestStore | 2 - tests/facet.js | 15 +- tests/model.js | 543 ++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 556 insertions(+), 17 deletions(-) diff --git a/model.js b/model.js index df301ab..164950a 100644 --- a/model.js +++ b/model.js @@ -1,11 +1,8 @@ var Permissive = require("./facet").Permissive; var DefaultStore = require("./stores").DefaultStore, - transaction = require("./transaction").transaction, - NotFoundError = require("./errors").NotFoundError, defineProperty = require("./util/es5-helper").defineProperty, - JSONExt = require("./util/json-ext"), - fs = require("promised-io/fs"); + JSONExt = require("./util/json-ext"); var Model = function(store, schema) { if(typeof store == "string"){ @@ -35,7 +32,7 @@ var Model = function(store, schema) { Model.Model = Model; Model.Store = function(store){ return Model(store, {});//(store.getSchema ? store.getSchema() : {}); -} +}; var modelPaths = {}; Model.initializeRoot = function(dataModel, addClass){ @@ -74,12 +71,12 @@ function setPath(model, path, name){ Model.createModelsFromModel = function(sourceModel, models, constructor){ // this allows you to create a set of models from another source model. This makes // it easy to have a RESTful interface for creating new models - constructor = constructor || Model; + constructor = constructor || Model; models = models || {}; sourceModel.query("").forEach(createSchema); if(sourceModel.subscribe){ sourceModel.subscribe("*").observe(function(events){ - createSchema(events.result); + createSchema(events.result); }); } function createSchema(schema){ @@ -88,7 +85,7 @@ Model.createModelsFromModel = function(sourceModel, models, constructor){ setPath(models[name] = constructor(schema), name, name); } return models; -} +}; Model.modelSchema = { maxLimit: Infinity, diff --git a/tests/data/TestStore b/tests/data/TestStore index d9e3bd4..5082b43 100644 --- a/tests/data/TestStore +++ b/tests/data/TestStore @@ -1,5 +1,3 @@ {id:1, foo:2, rand:0.7013372050189607}, {id:2, foo:1, bar:"hi"}, {id:3, foo:1, bar:"hello"}, -{"id":1,"foo":2,"rand":0.4593563524540514}, -{"id":1,"foo":2,"rand":0.05506560602225363}, diff --git a/tests/facet.js b/tests/facet.js index 93d895e..330627d 100644 --- a/tests/facet.js +++ b/tests/facet.js @@ -15,16 +15,19 @@ for(var i in permissiveTests){ } exports.testExtraStaticMethod = function(){ assert.equal(permissiveFacet.extraStaticMethod(), 4); -} +}; var restrictiveFacet = Restrictive(model); var restrictiveTests = CreateTests(restrictiveFacet); -exports.testGetRestrictive = restrictiveTests.testGet; -exports.testLoadRestrictive = restrictiveTests.testLoad; -exports.testSaveRestrictive = shouldFail(restrictiveTests.testSave); -exports.testMethodRestrictive = shouldFail(restrictiveTests.testMethod); -exports.testStaticMethodRestrictive = shouldFail(restrictiveTests.testStaticMethod); +restrictiveTests.testSave = shouldFail(restrictiveTests.testSave); +restrictiveTests.testMethod = shouldFail(restrictiveTests.testMethod); +restrictiveTests.testStaticMethod = shouldFail(restrictiveTests.testStaticMethod); +restrictiveTests.testQuery = shouldFail(restrictiveTests.testQuery); + +for(i in restrictiveTests){ + exports[i + "Restrictive"] = restrictiveTests[i]; +} function shouldFail(test){ return function(){ diff --git a/tests/model.js b/tests/model.js index 2c4eb6f..1c1a907 100644 --- a/tests/model.js +++ b/tests/model.js @@ -1,6 +1,8 @@ var assert = require("assert"), store = require("../stores").DefaultStore(), - model = require("../model").Model(store, { + Model = require("../model").Model, + errors = require("../errors"), + model = Model(store, { prototype: { testMethod: function(){ return this.foo; @@ -26,6 +28,56 @@ var assert = require("assert"), ] }); model.setPath("TestStore"); + +var baseTests = { + 'test can create a model from just a schema': function () { + var schema = {}; + + assert.doesNotThrow(function () { + Model(schema); + }); + + assert.doesNotThrow(function () { + Model(null, schema); + }); + }, + + 'test calls store.setSchema with schema if found on store': function () { + var arg, + store = { + setSchema: function (schema) { + arg = schema; + } + }, + schema = {}; + + Model(store, schema); + + assert.strictEqual(arg, schema); + }, + + 'test if schema is a function, it is the model': function () { + var expected = {}, + calledWith, + schema = function (source) { + calledWith = source; + return expected; + }, + store = {}, + obj = {}, + actual, + model; + + model = new Model(store, schema); + + assert.equal(model, schema); + + actual = model(obj); + assert.strictEqual(calledWith, obj); + assert.strictEqual(actual, expected); + } +}; + exports.model = model; exports.CreateTests = function(model){ return { @@ -73,12 +125,501 @@ exports.CreateTests = function(model){ testStaticMethod: function(){ var object = model.staticMethod(1); assert.equal(object.id, 1); + }, + + // TODO: model.put and propDef.set and more... + + 'test functions from the store become wrapped functions on the model': function () { + var calls = {}, + store = { + foo: function () { + calls.foo = true; + }, + bar: function () { + calls.bar = true; + } + }, + schema = {}, + model; + + model = new Model(store, schema); + + Object.keys(store).forEach(function (prop) { + assert.ok(prop in model); + assert.notEqual(model[prop], store[prop], prop + ' not wrapped'); + model[prop](); + assert.ok(calls[prop]); + }); + }, + + 'test model is a function/constructor': function () { + var store = {}, + schema = {}, + model; + + model = new Model(store, schema); + + assert.equal(typeof model, 'function'); + }, + + 'test model calls model.construct': function () { + var store = {}, + schema = {}, + model = new Model(store, schema), + called, + instance; + + model.construct = function () { + called = true; + }; + + instance = model(); + + assert.ok(called); + }, + + 'test empty prototype is added to model if none in schema': function () { + var store = {}, + schema = {}, + model; + + model = new Model(store, schema); + + assert.ok(model.prototype); + }, + + 'test prototype from schema is used as model prototype': function () { + var store = {}, + proto = { + foo: 'foo', + bar: {} + }, + schema = { + prototype: proto + }, + model = new Model(store, schema), + instance; + + instance = model(); + + Object.keys(instance).forEach(function (prop) { + assert.strictEqual(instance[prop], proto[prop], prop + ' should be copied to instance'); + }); + }, + + 'test properties from schema are static properties on model': function () { + var store = {}, + schema = { + foo: 'foo', + bar: {} + }, + model; + + model = new Model(store, schema); + + Object.keys(schema).forEach(function (prop) { + assert.strictEqual(model[prop], schema[prop], prop + ' should be a prop of model'); + }); + }, + + 'test model.instanceSchema is reference to schema': function () { + var store = {}, + schema = {}, + model; + + model = new Model(store, schema); + + assert.equal(model.instanceSchema, schema); + }, + + 'test model.query throws error if neither schema nor model define query': function () { + var store = {}, + schema = {}, + model; + + model = new Model(store, schema); + + assert.throws(function () { + model.query('foo'); + }, errors.MethodNotAllowedError); + }, + + 'test model.query delegates to store.query': function () { + var called = {}, + store = { + query: function () { + called.store = true; + } + }, + schema = {}, + model = new Model(store, schema); + + model.query('foo'); + + assert.ok(called.store); + }, + + 'test model.query prefers schema.query': function () { + var called = {}, + store = { + query: function () { + called.store = true; + } + }, + schema = { + query: function () { + called.schema = true; + } + }, + model = new Model(store, schema); + + model.query('foo'); + + assert.ok(called.schema); + assert.ok(!called.store); + }, + + 'test model.construct returns a wrapped instance': function () { + var store = {}, + schema = {}, + model = new Model(store, schema), + instance = { + foo: function () {}, + bar: 'bar' + }, + nonEnumerable = { + load: true, + schema: true, + save: true + }, + item; + + item = model(instance); + + Object.keys(item).forEach(function (prop) { + assert.strictEqual(item[prop], instance[prop]); + assert.ok(!nonEnumerable[prop]); + }); + + assert.equal(typeof item.load, 'function'); + assert.equal(typeof item.save, 'function'); + assert.ok('schema' in item); + }, + + 'test default properties from schema are set on new model instance': function () { + var foo = {}, + schema = { + properties: { + foo: { + 'default': foo + } + } + }, + model = Model(schema), + instance; + + instance = model(); + + assert.equal(instance.foo, foo); + + instance = model({ + foo: 5 + }); + + assert.equal(instance.foo, 5); + }, + + 'test default properties can be functions': function () { + var foo = {}, + schema = { + properties: { + foo: { + 'default': function () { + return foo; + } + } + } + }, + model = Model(schema), + instance; + + instance = model(); + + assert.strictEqual(instance.foo, foo); + }, + + 'test schema.construct is called with new model instance': function () { + var called, + args, + schema = { + construct: function () { + args = [].slice.call(arguments); + called = true; + } + }, + model = new Model(schema), + source = {}, + instance; + + instance = model(source); + + assert.ok(called); + assert.strictEqual(args[0], instance); + assert.ok(!args[1].overwrite); + }, + + 'test model.get delegates to store.get': function () { + var called = {}, + store = { + get: function () { + called.store = true; + } + }, + schema = {}, + model = new Model(store, schema); + + model.get(); + + assert.ok(called.store); + }, + + 'test model.get prefers schema.get': function () { + var called = {}, + store = { + get: function () { + called.store = true; + } + }, + schema = { + get: function () { + called.schema = true; + } + }, + model = new Model(store, schema); + + model.get(); + + assert.ok(called.schema); + assert.ok(!called.store); + }, + + 'test model.remove delegates to store.remove': function () { + var called = {}, + store = { + remove: function () { + called.store = true; + } + }, + schema = {}, + model = new Model(store, schema); + + model.remove(); + + assert.ok(called.store); + }, + + 'test model.remove prefers schema.remove': function () { + var called = {}, + store = { + remove: function () { + called.store = true; + } + }, + schema = { + remove: function () { + called.schema = true; + } + }, + model = new Model(store, schema); + + model.remove(); + + assert.ok(called.schema); + assert.ok(!called.store); + }, + + 'test model.add constructs and saves a new instance': function () { + var called = {}, + schema = {}, + model = Model(schema), + props = {}, + directives = {}, + instance = { + save: function (a) { + assert.strictEqual(a, directives); + assert.ok(called.construct); + called.save = true; + } + }; + + model.construct = function (p) { + assert.strictEqual(p, props); + called.construct = true; + return instance; + }; + + model.add(props, directives); + + assert.ok(called.construct); + assert.ok(called.save); + }, + + 'test property getters from schema property definitions get called': function () { + var called = {}, + schema = { + properties: { + foo: { + get: function (prop) { + assert.ok(prop, 'foo'); + called.foo = true; + } + } + } + }, + model = Model(schema), + source = { + foo: 5 + }, + instance; + + instance = model(source).load(); + + assert.ok(called.foo); + }, + + 'test schema.save is called when instance is saved': function () { + var called = {}, + schema = { + prototype: { + save: function () { + called.save = true; + assert.strictEqual(this, instance); + } + } + }, + source = { + id: 1 + }, + store = { + put: function () {} + }, + model = Model(store, schema), + instance; + + instance = model(source); + instance.save(); + + assert.ok(called.save); + }, + + 'test blocked properties are not included on instance': function () { + var schema = { + properties: { + _blocked: { + blocked: true + } + } + }, + source = { + id: 1, + _blocked: 'secret' + }, + store = { + get: function () { + return source; + } + }, + model = Model(store, schema), + instance; + + instance = model.get(source.id); + + assert.ok(!('_blocked' in instance)); + }, + + 'test blocked properties cannot be saved': function () { + var schema = { + properties: { + _blocked: { + blocked: true + } + } + }, + source = { + id: 1, + _blocked: 'secret' + }, + model = Model(schema), + instance; + + assert.throws(function () { + instance = model.add(source); + }); + }, + + 'test readonly properties are included on instance': function () { + var schema = { + properties: { + noWrite: { + readonly: true + } + } + }, + source = { + id: 1, + noWrite: 'secret' + }, + store = { + get: function () { + return source; + } + }, + model = Model(store, schema), + instance; + + instance = model.get(source.id); + + assert.ok('noWrite' in instance); + }, + + 'test readonly properties cannot be saved': function () { + var schema = { + properties: { + noWrite: { + readonly: true + } + } + }, + source = { + id: 1, + noWrite: 'secret' + }, + store = { + put: function () {}, + get: function () { + return source; + } + }, + model = Model(store, schema), + instance; + + assert.throws(function () { + instance = model.get(source.id); + instance.noWrite = 'touched'; + instance.save(); + }); } }; }; + +for(i in baseTests){ + exports[i] = baseTests[i]; +} + var modelTests = exports.CreateTests(model); for(var i in modelTests){ exports[i] = modelTests[i]; } + if (require.main === module) require("patr/runner").run(exports); \ No newline at end of file From a6c7b6104d0250616b70ed1cbb386a66aae07816 Mon Sep 17 00:00:00 2001 From: ben hockey Date: Tue, 3 Jan 2012 12:12:54 -0500 Subject: [PATCH 4/4] model: add stricter equality assertions in tests --- tests/model.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/model.js b/tests/model.js index 1c1a907..d7efecb 100644 --- a/tests/model.js +++ b/tests/model.js @@ -70,7 +70,7 @@ var baseTests = { model = new Model(store, schema); - assert.equal(model, schema); + assert.strictEqual(model, schema); actual = model(obj); assert.strictEqual(calledWith, obj); @@ -229,7 +229,7 @@ exports.CreateTests = function(model){ model = new Model(store, schema); - assert.equal(model.instanceSchema, schema); + assert.strictEqual(model.instanceSchema, schema); }, 'test model.query throws error if neither schema nor model define query': function () { @@ -320,13 +320,13 @@ exports.CreateTests = function(model){ instance = model(); - assert.equal(instance.foo, foo); + assert.strictEqual(instance.foo, foo); instance = model({ foo: 5 }); - assert.equal(instance.foo, 5); + assert.strictEqual(instance.foo, 5); }, 'test default properties can be functions': function () {